From 75b97af6fb65e60de4480e2075342ffd88ddcf8a Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 18:40:59 +0000 Subject: [PATCH 1/3] docs(cli): name the three readers of `_views..*` separately, per tail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `pushViewEntries` docblock attributed all three tails it emits to one browser-side reader: "the convention the runtime resolver reads (`viewLabel` / `viewDescription` / `viewEmptyState` in @object-ui/i18n)". Measured against `origin/main` and the objectui checkout, the three tails have three different readers and the sentence was wrong about two of them: - `label` is read on BOTH sides — `resolveViewLabel` (`packages/spec/src/system/i18n-resolver.ts:403`) via `translateView` (:907), registered as `view:` in `METADATA_DOCUMENT_TRANSLATORS` (:991) from which `TRANSLATABLE_METADATA_TYPES` is derived (:1008) and read by `packages/rest/src/rest-server.ts:363`; and `useObjectLabel().viewLabel` client-side. - `description` is read SERVER-SIDE ONLY, by `resolveViewDescription` (:424, called at :908). objectui#7219 removed the `useObjectLabel().viewDescription()` member, and the objectui source says so in as many words at `packages/i18n/src/useObjectLabel.ts:485`. The old prose therefore pointed the reader at a symbol that no longer exists, for a key that is still resolved and still reaches the screen — the inversion #15180 read as "declared, accepted, read by nothing". - `emptyState` is read CLIENT-SIDE ONLY. `emptyState` has zero occurrences in `i18n-resolver.ts` (positive control in the same file: `description`, 90 occurrences) and `resolveViewEmptyState` has zero occurrences tree-wide (positive control: `resolveViewLabel`, present), while `useObjectLabel().viewEmptyState` is live at `useObjectLabel.ts:512`. The second clause was re-checked rather than carried over and is TRUE: the platform bundle does carry `sys_user._views.all_users.label` (`packages/platform-objects/src/apps/translations/en.objects.generated.ts:124` `_views` → `:128` `all_users` → `label: "All Users"`). Across all nine shipped `en.objects.generated.ts` bundles, `_views` blocks carry `label` leaves and no `description` leaf, so naming `.label` there is exact. `viewLabel` and `viewEmptyState` are NOT retired: objectui kept the `viewSuffixes` helper all three shared and dropped only the `'description'` tail. The added warning says that, so the next reader does not over-apply objectui#7219 and delete two current citations. `pushViewEmptyState`'s own docblock (:453) names `viewEmptyState` in @object-ui/i18n and is left untouched — on this measurement it is accurate. Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude --- packages/cli/src/utils/i18n-extract.ts | 32 ++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/utils/i18n-extract.ts b/packages/cli/src/utils/i18n-extract.ts index 2bff68950c..84d478127d 100644 --- a/packages/cli/src/utils/i18n-extract.ts +++ b/packages/cli/src/utils/i18n-extract.ts @@ -360,10 +360,34 @@ function defaultListViewKey(object: string, container: any): string | undefined /** * Emit label / description / emptyState for ONE view under - * `objects.._views..*` — the convention the runtime resolver - * reads (`viewLabel` / `viewDescription` / `viewEmptyState` in - * @object-ui/i18n) and the one the shipped platform bundles already carry - * (`en.objects.generated.ts`: `sys_user._views.all_users.label`). + * `objects.._views..*` — the address the shipped platform + * bundles already carry (`en.objects.generated.ts`: + * `sys_user._views.all_users.label`). + * + * **The three tails do not share one reader**, and for `description` the + * reader is not in the browser at all. Getting that backwards reads as "this + * key is dead" — the inference #15180 drew and #17546 corrected. + * + * - **`label` — read on BOTH sides.** `resolveViewLabel` + * (`packages/spec/src/system/i18n-resolver.ts`) through `translateView`, + * which `@objectstack/rest` applies at the REST metadata boundary off + * `TRANSLATABLE_METADATA_TYPES`; and `useObjectLabel().viewLabel` in + * `@object-ui/i18n`, client-side. + * - **`description` — read SERVER-SIDE ONLY**, by `resolveViewDescription` + * in that same resolver and through that same `translateView`. objectui#7219 + * removed the `useObjectLabel().viewDescription()` member, so the server + * overlay is this key's ONE reader: the resolved value reaches the screen as + * the `description` the served view document already carries. ⛔ The + * absence of a browser-side helper is not evidence the key is inert. + * - **`emptyState` — read CLIENT-SIDE ONLY**, by + * `useObjectLabel().viewEmptyState` in `@object-ui/i18n`. The spec resolver + * declares no empty-state reader at all, which is why `pushViewEmptyState` + * below is the site that cites the client convention. + * + * ⚠️ `viewLabel` and `viewEmptyState` were NOT retired alongside + * `viewDescription`: objectui kept the `viewSuffixes` helper all three shared + * and dropped only the `'description'` tail passed to it. Naming those two + * here is current, not residue. */ function pushViewEntries(out: ExpectedEntry[], objectName: string, viewName: string, view: any): void { const root = ['objects', objectName, '_views', viewName]; From 72b23c0926948994288ce4aba558ae90944ed633 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 18:54:10 +0000 Subject: [PATCH 2/3] chore(changeset): patch @objectstack/cli for the `_views` reader-per-tail docblock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Measured rather than assumed: `@objectstack/cli` builds with plain `tsc -p tsconfig.build.json`, so JSDoc survives into `dist/utils/i18n-extract.js`, which is inside the package's `files[]`. A comment-only diff therefore moves published bytes here, and `skip-changeset` would be wrong — the false sentence is text an upgrading agent can grep in the published tarball. Positive control on the same probe: the docblock's `sys_user._views.all_users.label` phrase is likewise present in the built `dist/utils/i18n-extract.js`, and absent from the emitted `.d.ts`. Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude --- ...17546-i18n-extract-view-reader-per-tail.md | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .changeset/17546-i18n-extract-view-reader-per-tail.md diff --git a/.changeset/17546-i18n-extract-view-reader-per-tail.md b/.changeset/17546-i18n-extract-view-reader-per-tail.md new file mode 100644 index 0000000000..3c58c29129 --- /dev/null +++ b/.changeset/17546-i18n-extract-view-reader-per-tail.md @@ -0,0 +1,52 @@ +--- +'@objectstack/cli': patch +--- + +`pushViewEntries`' docblock names the reader of each `_views.` tail separately, instead of one browser-side symbol for all three + +The docblock above `pushViewEntries` (`packages/cli/src/utils/i18n-extract.ts`) +attributed every tail it emits to a single client-side reader, verbatim: *"the +convention the runtime resolver reads (`viewLabel` / `viewDescription` / +`viewEmptyState` in @object-ui/i18n)"*. Measured on the tree this changeset lands +on, the three tails have three different readers and the sentence was wrong about +two of them: + +- **`label` is read on BOTH sides.** `resolveViewLabel` + (`packages/spec/src/system/i18n-resolver.ts:403`) via `translateView` (`:907`), + registered as `view:` in `METADATA_DOCUMENT_TRANSLATORS` (`:991`) from which + `TRANSLATABLE_METADATA_TYPES` is derived (`:1008`) and read at the REST + metadata boundary (`packages/rest/src/rest-server.ts:363`); and + `useObjectLabel().viewLabel` client-side. +- **`description` is read SERVER-SIDE ONLY**, by `resolveViewDescription` + (`:424`, called at `:908`). objectui#7219 removed the + `useObjectLabel().viewDescription()` member. So the old sentence pointed a + reader at a symbol that no longer exists, for a key that is still resolved and + whose resolved value still reaches the screen as the `description` on the + served view document. +- **`emptyState` is read CLIENT-SIDE ONLY.** `emptyState` has zero occurrences in + `i18n-resolver.ts` — against 90 occurrences of `description` in the same file + on the same instrument — and `resolveViewEmptyState` has zero occurrences + tree-wide, with `resolveViewLabel` present as the control. + +**Why the inversion is worth a block rather than a word swap.** A reader who +checked the old prose for `description`, found no such client-side helper, and +concluded the key was inert would be exactly wrong — and that conclusion was +drawn once already. The block now states which side reads each tail, so the +answer does not have to be reconstructed from two other packages. + +The second clause was re-checked rather than carried over, and it is true: the +shipped platform bundle does carry `sys_user._views.all_users.label` +(`packages/platform-objects/src/apps/translations/en.objects.generated.ts`). +Across all nine shipped `en.objects.generated.ts` bundles, `_views` blocks carry +`label` leaves and no `description` leaf, so naming `.label` there is exact. + +`viewLabel` and `viewEmptyState` are **not** retired — objectui kept the +`viewSuffixes` helper all three shared and dropped only the `'description'` tail +passed to it — so the block says that too, to stop the next reader +over-applying objectui#7219 and deleting two current citations. + +No behaviour changes: the emitted key set, the walk and every exported signature +are byte-identical. This is a `patch` and not `skip-changeset` because +`@objectstack/cli` builds with plain `tsc`, so the JSDoc is emitted into +`dist/utils/i18n-extract.js` — inside the package's `files[]` — and the false +sentence is text an upgrading agent can grep in the published tarball. From 5fce933bf2fdcc9129768edc68359855d82a404b Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 11 Sep 2026 19:12:33 +0000 Subject: [PATCH 3/3] docs(cli): reconcile objectui's "inert" pin with the server reader of `_views..description` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Successor pass on the R73 draft. The draft's reader topology re-derived and CONFIRMED; one omission corrected and the changeset reversed. Corrected. The draft said "⛔ The absence of a browser-side helper is not evidence the key is inert" and stopped there. objectui does not merely lack a helper — it PINS this key as "an inert catalog entry" (`packages/i18n/src/__tests__/useObjectLabel-view.test.tsx`, a whole `describe`), and measured across all nine shipped `en.objects.generated.ts` bundles, `_views` blocks author `label` (104) and `emptyState` (9) leaves and ZERO `description` leaves. A reader who greps objectui hits "inert catalog entry" and deletes this emission — the same defect class as the sentence this card is about, aimed the other way. The block now names both facts, scopes them to the browser path objectui#7219 removed, and says neither reaches the server reader. The reach claim is now cited rather than asserted: the overlay lands on the served view document's `description`, which objectui relays (`packages/app-shell/src/views/ObjectView.tsx` `description: viewDef.description ?? listSchema.description`) and renders (`packages/plugin-list/src/ListView.tsx`, `pickLocalized(schema.description, displayLocale)`). Changeset REVERSED to `skip-changeset`. The draft argued `patch` because the JSDoc is emitted into `dist/utils/i18n-extract.js` inside `files[]`. That premise is true — re-measured — but it shows bytes moving, not a release. Measured with a positive control, the docblock reaches NO `.d.ts` (an exported symbol's JSDoc in the same file does), and `pushViewEntries` is not exported, so nothing a consumer can call or type against moves. House precedent is directly on point and went the other way: a comment-only edit to `packages/objectql/src/engine.ts` and `validation/rule-validator.ts` — a released package that also ships `dist` — took `skip-changeset` and no changeset file. Left standing deliberately, both re-measured as ACCURATE: the `pushViewEmptyState` docblock naming `viewEmptyState` in @object-ui/i18n (live), and the `formViews` comment naming `viewLabel` (live) — which `packages/lint` quotes verbatim, so editing it would dangle that citation. Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c Co-authored-by: Claude --- ...17546-i18n-extract-view-reader-per-tail.md | 52 ------------------- packages/cli/src/utils/i18n-extract.ts | 13 +++-- 2 files changed, 10 insertions(+), 55 deletions(-) delete mode 100644 .changeset/17546-i18n-extract-view-reader-per-tail.md diff --git a/.changeset/17546-i18n-extract-view-reader-per-tail.md b/.changeset/17546-i18n-extract-view-reader-per-tail.md deleted file mode 100644 index 3c58c29129..0000000000 --- a/.changeset/17546-i18n-extract-view-reader-per-tail.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -'@objectstack/cli': patch ---- - -`pushViewEntries`' docblock names the reader of each `_views.` tail separately, instead of one browser-side symbol for all three - -The docblock above `pushViewEntries` (`packages/cli/src/utils/i18n-extract.ts`) -attributed every tail it emits to a single client-side reader, verbatim: *"the -convention the runtime resolver reads (`viewLabel` / `viewDescription` / -`viewEmptyState` in @object-ui/i18n)"*. Measured on the tree this changeset lands -on, the three tails have three different readers and the sentence was wrong about -two of them: - -- **`label` is read on BOTH sides.** `resolveViewLabel` - (`packages/spec/src/system/i18n-resolver.ts:403`) via `translateView` (`:907`), - registered as `view:` in `METADATA_DOCUMENT_TRANSLATORS` (`:991`) from which - `TRANSLATABLE_METADATA_TYPES` is derived (`:1008`) and read at the REST - metadata boundary (`packages/rest/src/rest-server.ts:363`); and - `useObjectLabel().viewLabel` client-side. -- **`description` is read SERVER-SIDE ONLY**, by `resolveViewDescription` - (`:424`, called at `:908`). objectui#7219 removed the - `useObjectLabel().viewDescription()` member. So the old sentence pointed a - reader at a symbol that no longer exists, for a key that is still resolved and - whose resolved value still reaches the screen as the `description` on the - served view document. -- **`emptyState` is read CLIENT-SIDE ONLY.** `emptyState` has zero occurrences in - `i18n-resolver.ts` — against 90 occurrences of `description` in the same file - on the same instrument — and `resolveViewEmptyState` has zero occurrences - tree-wide, with `resolveViewLabel` present as the control. - -**Why the inversion is worth a block rather than a word swap.** A reader who -checked the old prose for `description`, found no such client-side helper, and -concluded the key was inert would be exactly wrong — and that conclusion was -drawn once already. The block now states which side reads each tail, so the -answer does not have to be reconstructed from two other packages. - -The second clause was re-checked rather than carried over, and it is true: the -shipped platform bundle does carry `sys_user._views.all_users.label` -(`packages/platform-objects/src/apps/translations/en.objects.generated.ts`). -Across all nine shipped `en.objects.generated.ts` bundles, `_views` blocks carry -`label` leaves and no `description` leaf, so naming `.label` there is exact. - -`viewLabel` and `viewEmptyState` are **not** retired — objectui kept the -`viewSuffixes` helper all three shared and dropped only the `'description'` tail -passed to it — so the block says that too, to stop the next reader -over-applying objectui#7219 and deleting two current citations. - -No behaviour changes: the emitted key set, the walk and every exported signature -are byte-identical. This is a `patch` and not `skip-changeset` because -`@objectstack/cli` builds with plain `tsc`, so the JSDoc is emitted into -`dist/utils/i18n-extract.js` — inside the package's `files[]` — and the false -sentence is text an upgrading agent can grep in the published tarball. diff --git a/packages/cli/src/utils/i18n-extract.ts b/packages/cli/src/utils/i18n-extract.ts index 84d478127d..2a59eede02 100644 --- a/packages/cli/src/utils/i18n-extract.ts +++ b/packages/cli/src/utils/i18n-extract.ts @@ -376,9 +376,16 @@ function defaultListViewKey(object: string, container: any): string | undefined * - **`description` — read SERVER-SIDE ONLY**, by `resolveViewDescription` * in that same resolver and through that same `translateView`. objectui#7219 * removed the `useObjectLabel().viewDescription()` member, so the server - * overlay is this key's ONE reader: the resolved value reaches the screen as - * the `description` the served view document already carries. ⛔ The - * absence of a browser-side helper is not evidence the key is inert. + * overlay is this key's ONE reader: it lands on the `description` of the + * served view document, which objectui relays (`ObjectView`) and renders + * (`ListView`, via `pickLocalized`). + * ⚠️ objectui pins this key as "an inert catalog entry", and no bundle in + * this repo authors a `description` leaf under `_views` (the nine shipped + * `en.objects.generated.ts` carry `label` and `emptyState` leaves only). + * ⛔ Neither fact licenses deleting this emission: both are scoped to the + * BROWSER path objectui#7219 removed, and the server reader above is + * untouched by them. Absence of a browser-side helper, and absence of an + * authored value, are not evidence the key is unread. * - **`emptyState` — read CLIENT-SIDE ONLY**, by * `useObjectLabel().viewEmptyState` in `@object-ui/i18n`. The spec resolver * declares no empty-state reader at all, which is why `pushViewEmptyState`