You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit fb1ab89
Browse filesBrowse the repository at this point in the historyBrowse files
fix(objectql,rest)!: serve an object an extension renames without the base's plural (#30391) (#30459)
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wg1JP2CcGNs5hTdBvvidGk1x)_
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
## 维护者速读
- 改了什么:对象扩展(`.custom` 配对包里的 `object_extension`,或代码包的
`objectExtensions`)只改了 `label`、没写 `pluralLabel` 时,`/meta/object`
不再返回基础包的复数名,也不再返回基础包对旧复数的译文;对象按“没声明复数”的样子返回,列表标题就显示新名字。配对包自己保存了该复数的译文时,返回配对包的译文。
- 为什么改:客户改了对象名,侧栏和列表标题(先读复数)还是旧名字,一个对象两个名字;中文下尤其明显,因为旧复数还被翻成了“任务”。
- 风险与回滚:这是一次已返回值的收窄(BREAKING):所有只改 label 的扩展都会少返回 pluralLabel,包括
showcase 自带的 `showcase_account`(列表标题从 “Accounts” 变成 “Account (Success
Overlay)”)。没有数据迁移,回滚即撤本 PR。
- 先看:`packages/objectql/src/registry.ts` 的
`mergeObjectDefinitions`(标量覆盖那一段),再看
`packages/rest/src/meta-item-read-gate.ts` 的 `withoutStaleObjectPlural`
和它在 `translateMetaList` / `translateMetaDocument` 里的两处调用。
- 两分钟验证:`pnpm --filter @objectstack/rest exec vitest run
src/meta-object-extension-plural-label.test.ts`,预期 11 条全绿;修复前的代码上有 4
条是红的。
- 需要你做的:无,席位落地(非受治理面,narrowing 已过达档合同复核 PASS 6658126879)。
Fixes#30391
Clause-②: no (narrowing) — `/meta/object` (item and list reads, both
transports) and every registry read of an object
(`SchemaRegistry.getObject`, the extension fold) stop serving
`pluralLabel` for an object an extension renames without a plural of its
own, where they served the base's plural (and, in a translated locale,
the base's translation of it). The shipped showcase's `showcase_account`
is one. Nothing an author writes is refused, and no exported symbol
moves.
Metadata-protocol: semantic
Skills-docs: follow-up #30466 — `skills/objectstack-data/SKILL.md`
"Object Extension Model" says an extension "can add but never remove"; a
renaming `label` now takes the base's `pluralLabel` off. The sentence
wants that exception (`skills/**` is outside this change's surface).
## What changes
`pluralLabel` is the plural of `label`. The seat's route (served side,
under ruling record 6631250894's label order): an extension that changes
`label` without `pluralLabel` serves what an object with no
`pluralLabel` serves, the pair's own translation first. The new pin
measures that reference on the same boot (`crm_note`, which declares no
plural): it is served with no `pluralLabel` key in `en` and in `zh-CN`,
and the Console then titles a list by the label (`useNavTargetLabel`'s
`objectPluralLabel`: served plural, else served label).
Two halves, and each one alone leaves a locale red (Ablations below):
- **The fold** (`mergeObjectDefinitions`, `@objectstack/objectql`). When
an extension's `label` is applied (it does not yield to a
tenant-authored label, ADR-0029 D9.2a), differs from the running label,
and the extension sets no `pluralLabel`, the running `pluralLabel` is
deleted. A plural the tenant authored stays (the same D9.2a yield). The
fold stays idempotent over an already-folded base: the second pass
yields on the label and leaves the plural absent.
`getPackagedObjectBase` is untouched and keeps the owner's plural, which
the translators compare against.
- **The `/meta` read** (`withoutStaleObjectPlural`,
`@objectstack/rest`). The spec's `translateObject` answers a missing
`pluralLabel` with the catalog's `objects.NAME.pluralLabel`, and that
entry is the base package's translation of the plural the extension left
behind (the `en` catalog repeats the source text, so `en` is affected
too). Where the served object carries no plural, the packaged base
declares one, and the served label diverged from the packaged label, the
catalog's plural is replaced by the owner package's `.custom` pair's own
translation of `objects.NAME.pluralLabel`
(`getPairTranslationBundle(_packageId of the packaged base)`, read once
per base per request, translated through `translateMetadataDocument`
over the pair's bundle alone), or dropped when the pair has none. Every
other object is returned unchanged, and the pair is read only when the
rule applies. It runs on both `translateMetaList` and
`translateMetaDocument`, which both transports use.
No spec change: the catalog lookup stays the spec's; the server decides
what it serves. No ADR text changes: ADR-0146 D10 says an object is
extended, never replaced; the base's declaration is untouched and only
the served fold changes.
## Tests
- `packages/rest/src/meta-object-extension-plural-label.test.ts` (new,
real `SchemaRegistry` + real `ObjectStackProtocolImplementation` +
`RestServer`, i18n service over a deployment catalog with the pair's row
merged in, both reads, `en` and `zh-CN`): the reference (`crm_note`, no
plural); a label-only rename serves what the reference serves; the
pair's `zh-CN` plural is served and `en` gets none; a pair row
translating only `label` does not let the base's plural back; controls:
label + plural served as set, plural only, neither (base plural and `任务`
kept), repeated label, an untouched object. 4 red on main `09720e2c` (en
and zh-CN rename, pair, non-pair), 11/11 after.
- `packages/objectql/src/registry-extension-plural-label.test.ts` (new,
the fold): rename drops the plural; packaged base keeps `Tasks` and
`_packageId`; idempotent refold; plural set, plural only, fields only,
repeated label; tenant-authored plural stays; tenant-authored label
makes the rename yield; two extenders in priority order both ways.
11/11.
- Suites at `f71f7bdc` (after merging `origin/main`; the two later
commits touch only the changeset and the engine-double ledger):
`@objectstack/objectql` 551 files, 10983 passed; `@objectstack/rest` 387
files, 7346 passed / 332 skipped; `typecheck` for both exit 0 (including
`check:test-typecheck`); eslint on the four changed source/test files
exit 0.
## Ablations
Through `scripts/ablation-replace.mjs`, each restored to blob == HEAD
with `git diff HEAD` empty.
| | Mutation | Red |
|:--|:--|:--|
| A | the fold's `delete merged.pluralLabel` guarded off (rebuilt;
`ablation-dist-preflight.mjs` exit 0, then `--absent` exit 0 after the
restore rebuild) | objectql 2/11, rest 4/11 |
| B | `withoutStaleObjectPlural` returns the translator's answer
unchanged (rest tests import `src`) | rest 4/11 (both locales: the `en`
catalog repeats the source plural) |
## Acceptance notes
- **Two names on objects still.** For objects the served `label` reads
no pair translation (PR #30353 left objects out of `pairBundle`, seat
ruling: an object is extended, never overridden). The plural now reads
the pair's translation per this card's route, so a pair that translates
both strings gets its plural translated and its label served as written.
Carrier: seat (`domain:server#5`).
- **Studio's rename.** A tenant overlay that renames an object and keeps
the old `pluralLabel` is untouched (the tenant authored that body, and
#8284's pin keeps its plural translated). Carrier: seat, not filed.
- `form-routes.ts` (public form views) translates the form's object
schema through `translateMetadataDocument` on its own path, outside this
change's surface; for such an object that schema can still carry the
catalog's plural. Not measured. Carrier: seat.
## Verification
<details><summary>Derived gates, dogfood, lint — final head
a1107d3</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` at `f71f7bdc`: 91 commands; the ledger
commit added 8 (99). Each run with its exit code captured before any
pipe. 94 exit 0 on the first pass. Five did not:
- `check-adr-0087-registration --base origin/main` exit 1: a `FROM → TO`
block contradicted `not-required (no-migration-prescription)`. Nothing
has to be rewritten, so the block became a sentence saying so; re-run
exit 0.
- `check:engine-double-contract` exit 1: the new pin's `findOne` double
was not in the ledger; `node scripts/check-engine-double-contract.mjs
--write` added one row (`scripts/engine-double-contract.pinned.json`,
+5); re-run exit 0.
- `check-plugin-teardown-shape --self-test`, `check:skill-examples`,
`check:dual-build-cjs-loads` exit 3 (prerequisite): exit 0 after
fetching the pinned commit and a whole-workspace build.
- `dispatch-gates --ran` at `a1107d3c6`: 99 derived, 99 run, 0
NOT-MEASURED, 0 UNRUN, exit 0.
- `pnpm lint` (`eslint . --no-inline-config`) at `a1107d3c6`: exit 0.
- Full dogfood (`Clause-②` carries the narrowing arm), `pnpm --filter
@objectstack/dogfood test` under `os-verify-lock.sh` at `f71f7bdc` (same
code as the final head): `VERDICT command-exit 0`, Test Files 419 passed
/ 3 skipped (422), Tests 3492 passed / 36 skipped (3528), 0 failures.
- Ablation legs: see Ablations. CI check-runs: not read at the time of
writing.
</details>
## Docs
`content/docs/data-modeling/object-extensions.mdx`: the merge table's
`label` row now says a new label without a plural takes the target's
plural off. `content/docs/ui/translations.mdx`: the object paragraph of
"An edit beats the packaged catalog" now says such an object is served
with no plural unless the owning package's `.custom` pair translates it.
`releases/**` untouched.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01Utp6CYaDMYnWxbm95qon7G)_
<!-- signed: domain:server#5 · os-bill ·
session_01Utp6CYVV6fcAQrNTDQRvPb -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(objectql,rest)!: an object extension that renames `label` and sets no `pluralLabel` is served without the base's plural, so list titles stop showing the old name (#30391)
7
+
8
+
Clause-②: no (narrowing) — `/meta/object` (item and list reads, both transports) and every registry read of an object (`SchemaRegistry.getObject`, the fold) stop serving `pluralLabel` for an object an extension renames without a plural of its own, where they served the base's plural (and, in a translated locale, the base's translation of it). The shipped showcase's `showcase_account` is one: its extension renames it `Account (Success Overlay)` and declares no plural. Nothing an author writes is refused.
9
+
Metadata-protocol: semantic
10
+
Skills-docs: follow-up — `skills/objectstack-data/SKILL.md` "Object Extension Model" says an extension "can add but never remove"; a renaming `label` now takes the base's `pluralLabel` off. The sentence wants that exception (outside this change's surface).
11
+
12
+
<!-- adr-0087: not-required (no-migration-prescription) No key of any schema is added, removed, renamed or re-shaped: an object extension declares `label` and `pluralLabel` exactly as before. What moves is the plural an object is served with when an extension renames it without one, and nothing at rest records that answer, so there is nothing for a migration to rewrite. -->
13
+
14
+
**BREAKING** served-value narrowing for an object an extension renames.
15
+
16
+
-**Before.** An `objectExtensions` entry or a `.custom``object_extension` item that set `label` and no `pluralLabel` was folded with the new label and the base's `pluralLabel`. A translated read served the base package's translation of that plural (`objects.NAME.pluralLabel`), so a list title, read plural first, kept the old name in every locale.
17
+
-**Now.**`pluralLabel` is the plural of `label`: such an extension is served as an object that declares no `pluralLabel`, in every locale, so a list titles it by its label. Where the owning package's `.custom` pair saves its own translation of `objects.NAME.pluralLabel`, that translation is served (ruling record 6631250894: a translation in the pair, then the override's own text, then the base's translation).
18
+
-**Unchanged.** An extension that sets `pluralLabel` is served with it. One that sets no `label`, or repeats the label it extends, keeps the base's plural and its translation. A plural the tenant set stays, and a tenant's own rename yields as before (ADR-0029 D9.2a). An object no extension renames is served as before. The packaged base the translators compare against keeps its plural.
19
+
20
+
Nothing has to be rewritten: an extension that wants a plural for its new name declares `pluralLabel` beside `label`, as it always could, and a `.custom` pair can save `objects.NAME.pluralLabel` in its own `translation` item for a translated locale.
Copy file name to clipboardExpand all lines: content/docs/data-modeling/object-extensions.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -66,7 +66,7 @@ check that spelling first.
66
66
|`fieldPatches`|**Applied, field by field.** Changes the *display* of one of the target's own fields — never one an extension adds — without replacing it — [below](#relabelling-a-vendor-field). |
67
67
|`validations`|**Concatenated.** Your rules run alongside the owner's; neither replaces the other. |
68
68
|`indexes`|**Concatenated**, same as validations. |
69
-
|`label`| Replaces the target's label — with one exception, [below](#extensions-and-tenant-customization). |
69
+
|`label`| Replaces the target's label — with one exception, [below](#extensions-and-tenant-customization). A new `label` with no `pluralLabel` beside it also takes the target's plural off, since that plural names the old label: the object is served as one that declares no plural, and lists title it by its label. Set `pluralLabel` too to serve one. A plural the tenant set stays. |
70
70
|`pluralLabel`| Same as `label`. |
71
71
|`description`| Same as `label`. |
72
72
|`priority`| Not merged — it *orders* the merge. `0`–`999`, default `200`. |
0 commit comments