Skip to content

Commit 3dee220

Browse files
committed
docs(cli): i18n-extract's walk notes follow the shared slot list; changeset (#20940)
Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1 Co-authored-by: Claude <noreply@anthropic.com>
1 parent 41932a4 commit 3dee220

2 files changed

Lines changed: 43 additions & 14 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': patch
4+
'@objectstack/cli': patch
5+
---
6+
7+
feat(spec): one list of page-component slot positions, derived from the component rows and read by every page walk — `page:card`'s `footer` is now walked by all three (#20940)
8+
9+
The platform has three walks that descend into a page component's `properties` bag, and each kept its own list of where child components hang: the ADR-0087 conversion walker (`children`, `body`, `footer`, `items[].children`), `@objectstack/lint`'s `walkPageComponents` (the same four) and the exported `walkAddressedPageComponents` (`children`, `items[].children`). So a node in a card's `footer` — a declared, rendered slot ("Card footer components (slot)") — was judged by `os lint` and skipped by every consumer of the exported walk: `translatePage` left its copy untranslated, `os i18n extract` offered no key for it, and objectui's validator passed it unjudged.
10+
11+
**`@objectstack/spec` — new exports `pageComponentSlotPositions()` and `PageComponentSlotPosition` (`@objectstack/spec/ui`).** The component rows now mark each composition slot at its declaration, and `pageComponentSlotPositions()` derives the one list from `ComponentPropsMap`: `children`, `footer` and the panel position `items[].children`, plus the tombstoned `body` flagged `retired: true`. The marker changes nothing about the schema it marks — the parse, the JSON Schema and the authorable surface are unchanged. The list is derived on first call and memoized, never at import. `minor` because the package's public surface grows by these two exports.
12+
13+
**`walkAddressedPageComponents` descends `properties.footer`.** It reads the list's authorable entries, in the list's order (`children`, `footer`, then `items[].children`); signature and return shape are unchanged. What follows from it:
14+
15+
- `translatePage` translates the copy of a component in a card footer through `pages.<name>.components.<id>`, like any other nested component.
16+
- `os i18n extract` offers those keys, and `os i18n coverage` counts them, for a stack whose card footers hold components with an `id` and copy.
17+
- objectui's validator, which judges the nodes this walk visits, now judges a card footer's nodes.
18+
19+
`page:card.body` stays undescended, as #5775 ruled: it is not an authorable spelling.
20+
21+
**The conversion walker reads every entry, the retired one included.** Its reach does not change: it descends `children`, `body`, `footer` and `items[].children`, as before. Stored documents still carry `body`, the renderers still draw it, and a conversion that runs before `page-card-body-to-children` meets the sub-tree there. Within one component the visit order is now `children`, `body`, `footer`, then the panels. That order is observable only as the order of the notices for a component that carries both a direct slot and panels.
22+
23+
**`@objectstack/lint` — `walkPageComponents` reads the list's authorable entries.** It walks `footer` as before, and it stops walking the retired `page:card.body`. #5775 (maintainer ruling 2026-08-06, direction A) made `children` the one composition key. The renderers keep reading `body` only as a back-compat fallback for stored documents. An author who writes `body` is still refused, by the `page:card` tombstone's rename prescription (and by the thin containers' guidance), and the sub-tree is judged once it sits under `children`. So the rules built on this walk no longer report findings about nodes under a card's `body`. The conversion walker keeps reaching them for stored documents.
24+
25+
**`@objectstack/cli`:** no code change. `os i18n extract` and `os i18n coverage` pick up the `footer` component keys through the shared walk. The extractor's object-section pass stops reading `record:details` sections under a retired `body`, through lint's walk.
26+
27+
**Why no ADR-0087 ledger entry.** Nothing an author writes moves: no spec key is retired or renamed, no stored `sys_metadata` shape changes, and no conversion or migration id is touched. `objectstack migrate meta` has nothing to act on.

‎packages/cli/src/utils/i18n-extract.ts‎

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -932,9 +932,10 @@ function walkObjectSections(config: any, out: ExpectedEntry[]): void {
932932
// private copy. That walk exists precisely because duplicating it produced a
933933
// dead rule once already (#3583): components hang off `regions[].components`
934934
// AND `slots.<slot>` (which may be a bare component, not an array), sub-trees
935-
// live inside the untyped `properties` bag (`page:tabs` →
936-
// `properties.items[].children`, `page:card` → `properties.body`/`.footer`),
937-
// and source-authored pages (`kind: 'html' | 'react' | 'jsx'`) hold only a
935+
// live inside the untyped `properties` bag at the positions spec's
936+
// `pageComponentSlotPositions()` derives (`page:tabs` →
937+
// `properties.items[].children`, `page:card` → `properties.footer`, every
938+
// container → `properties.children`), and source-authored pages (`kind: 'html' | 'react' | 'jsx'`) hold only a
938939
// DERIVED region cache that the author never wrote — scaffolding translation
939940
// keys off that cache would invent an authoring surface.
940941
//
@@ -1099,8 +1100,10 @@ export function authorWarnedTranslationGroups(): ReadonlySet<string> {
10991100
* WALK — which components carry those keys — is `walkAddressedPageComponents`,
11001101
* the same traversal `translatePage` itself runs (commit c45d8e6b4, completing the key
11011102
* list's precedent). The walk owns the roots (`regions[].components[]` AND
1102-
* `slots.<slot>`), the descent (`properties.children` AND a panel's
1103-
* `properties.items[].children`, depth-capped, cycle-guarded) and the ruled
1103+
* `slots.<slot>`), the descent (the authorable slot positions spec derives
1104+
* from the component rows — `properties.children`, a card's
1105+
* `properties.footer`, a panel's `properties.items[].children` — depth-capped,
1106+
* cycle-guarded) and the ruled
11041107
* collision arbitration (commit 901355c3b: root level wins outright; among nested
11051108
* components, document-order first sighting) — this function used to
11061109
* hand-mirror all five and now owns none of them. What it still owns:
@@ -1113,15 +1116,14 @@ export function authorWarnedTranslationGroups(): ReadonlySet<string> {
11131116
* - the `label` either/or: `label` may be authored on the component itself
11141117
* or in its props — the same either/or `translatePage` resolves back onto.
11151118
*
1116-
* ⛔ Deliberately NOT `@objectstack/lint`'s `walkPageComponents`, which is
1117-
* WIDER than the resolver in two ways (`properties.body`, `properties.footer`
1118-
* — `page:card`'s slots, which the resolver leaves undescended as a renderer
1119-
* back-compat fallback rather than an authorable spelling; `slots.<slot>`
1120-
* roots and `properties.items[].children` were the other two until #16772
1121-
* brought both into the shared walk) and NARROWER in one (it skips
1122-
* `kind: 'html' | 'react' | 'jsx'` pages, which `translatePage` walks) —
1123-
* either direction of that mismatch is one half of the failure pair
1124-
* `PAGE_COMPONENT_COPY_KEYS`' own JSDoc names.
1119+
* ⛔ Deliberately NOT `@objectstack/lint`'s `walkPageComponents`. Its descent
1120+
* is no longer WIDER than the resolver's — since #20940 both read spec's one
1121+
* derived slot list, and `page:card`'s `properties.body` / `.footer` were the
1122+
* last positions lint walked and the resolver did not (`slots.<slot>` roots
1123+
* and `properties.items[].children` closed at #16772) — but it is still
1124+
* NARROWER in one way (it skips `kind: 'html' | 'react' | 'jsx'` pages, which
1125+
* `translatePage` walks), and a mismatch in either direction is one half of
1126+
* the failure pair `PAGE_COMPONENT_COPY_KEYS`' own JSDoc names.
11251127
*/
11261128
function emitPageComponentCopy(out: ExpectedEntry[], page: any, name: string): void {
11271129
walkAddressedPageComponents(page, (component, { id, nested, addressed }) => {

0 commit comments

Comments
 (0)