Skip to content

Commit f3a3afc

Browse files
docs(i18n): restate the orphan-key table from the emitter, sibling packages included (#19839)
Fixes #19626 Clause-②: no ## What changed The orphan-key table under **Orphan Keys and Option Keys** in `content/docs/protocol/kernel/i18n-standard.mdx` is rewritten as one unit, row by row from the emitter of `translation-target-unknown` on `origin/main` (`1cacfe4a`). It no longer uses the old rule. - **Row 1 was false and is corrected.** `objects.{object}` said "an object this stack defines, or a platform object". On the per-package pass, an object that a **sibling package of the same artifact** declares also resolves, and its subtree is judged against that sibling's declaration. The row now says so. It also names rung 2b, which was missing: an object an `objectExtensions[]` entry extends but no package of the artifact declares is skipped whole. - **"Declared" is now defined once, above the table.** It means declared by the stack being checked or by a sibling package of the same artifact. The six vague rows (`_views`, `_sections`, `_actions`, `apps.*`, `dashboards.*`, `globalActions`) now inherit that meaning instead of each implying "this stack". A short rule also covers what happens when the package and a sibling declare the same name: the package's own record is used to judge options, params, widgets and header actions, and flow screens. - **Rows the emitter judges but the table never listed are added:** `_tabs`, `_validations`, `flows.{flow}`, `.screens.{node}` and `.fields.{field}`. The contributed-into app level (`navigationContributions`) is also added. - **Rows that were vague but not false are made exact:** `_views` (the key the registry assigns, `default` for an unnamed default list), `_sections` (all three anchors), `fields` (extension-merged fields and system columns), and `globalActions` (a bound action gets a redirect, not a match). - `content/docs/deployment/validating-metadata.mdx`: the per-package sentence ("so a sibling package's object still resolves") now also names the collections a translation key resolves the same way, and links to the table. The emitter covers more than the card listed. The card named six understated rungs. `artifactProvidedRecords` is also called for `flows` and `pages`, and `pages` feeds `_tabs` and `record:details` `_sections`. So a sibling's flow, filter-preset tab or page section resolves too. Where the emitter and the card disagree, the table follows the emitter. ## Code sites each row is backed by All in `packages/lint/src/validate-translation-references.ts` at `1cacfe4a`: | Row | Site | |:---|:---| | sibling reach (all rows) | `artifactProvidedRecords` :919 (reads `packages[].manifest.COLLECTION`); calls for `objects` :1019, `views` :1081, `pages` :1114, `actions` :1152, `apps` :1211, `dashboards` :1282, `flows` :1331 | | own-record precedence | `ownDeclaration` in `collectObjectRecord` :950, `collectActionRecord` :1137, `collectDashboardRecord` :1261, `collectFlowRecord` :1306 | | `objects.{object}` | :1379-1421: universe hit, `isPlatformProvidedObjectName` skip, `universe.extended` skip (rung 2b), otherwise orphan; `objectExtensionsByTarget` :834 reads stack and `packages[]` | | `.fields.{field}` | `IMPLICIT_FIELDS` :284 (`SYSTEM_FIELDS` + `_id`/`name`/`space`); extension fold :1042-1070; check :1423 | | `.options.{key}` | `checkOptionKeys` :1778 (warning, `translation-option-key-unknown`) | | `._views.{view}` | `collectViewRecord` :421, `defaultListViewKey` :574, `namedViewKeys` :612 (composer-derived keys) | | `._sections.{section}` | `fieldGroups[].key` in `collectObjectRecord`; `addSections` for `formViews`, the default `form` and the view record in `collectViewRecord`; `record:details` `properties.sections` in `collectPageRecord` :1093 | | `._tabs.{tab}` | `collectPageTabs` :525 (`interfaceConfig.source`, else `page.object`; `userFilters.tabs[].name`); check :1484 | | `._validations.{rule}` | `collectValidationRuleNames` :238 (conditional branches), extension fold; check :1513 | | `._actions.{action}` / `.params.{param}` | `collectActionRecord` (owner = `objectName` / `object`); `checkActionParams` :1834 | | `globalActions.{action}` | :1564-1597, bound-action redirect via `actionOwners` | | `apps.{app}` / `.navigation.{id}` | `collectAppRecord` (declared + sibling), `contributedNavItemsByApp` :738 (stack `manifest` + `packages[]`), `contributedOnlyApps` pass; check :1599-1652 | | `dashboards.*` | `collectDashboardRecord`; check :1653-1695 | | `flows.*` | `collectFlowRecord` (`walkFlowNodes`, `type: 'screen'`, `config.fields[].name`, object-form diagnosis); check :1696-1760 | The sibling behaviour for each rung is pinned in `validate-translation-references.test.ts`, in the two describe blocks for objects a sibling package of the artifact declares (from :1253) and for collection rungs a sibling package declares (from :1463). ## Scope and changeset Docs only: `content/docs/**`, which is not a governed surface and publishes no package. No `.changeset` file is needed; by the `files[]` criterion this is `skip-changeset` territory, and the label is left to the PM seat. `skills/objectstack-i18n/SKILL.md` is deliberately **not** touched, per the triage ruling. ## Verification - Derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at head `c61ae6d4`: 40 commands. All 40 ran with exit 0. Four of them (`check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples`, `check:docs-transcript-drift`) first exited 3 (PREREQUISITE NOT MET, nothing measured). I built their named prerequisites (the lint, formula and client-react closures, plus spec) and re-ran them: all exit 0. - `--ran` reconciliation: "40 derived, 40 run, 0 NOT-MEASURED, 0 UNRUN", which it calls a derived zero because every line recorded an exit code. - Relevant verdicts: `check-doc-anchors: 368 internal #fragment link(s) across 410 source file(s) all resolve to a real heading` (this covers the two new links); `check-nul-bytes: OK`; `check-doc-frontmatter` OK. - NOT MEASURED locally, left to CI: the `Build Docs` job (the Next.js docs build), the type-check lanes and the other families listed outside the derived set. ## Acceptance notes - The ladder comment in the emitter's own header (`validate-translation-references.ts:122-123`) still says rungs 3 and 4 "warn on the object key only", but both go through `orphan()` at `TRANSLATION_TARGET_UNKNOWN_SEVERITY = 'error'`. This is comment drift inside the code, not a defect under class a, b or c, and it was not changed here (docs-only scope). No carrier. - `collectViewRecord` also registers a view record's root `name` as a `_views` key. The table describes the composer's keys (map key, or `default`) as the canonical spelling and does not list this edge. --- _Generated by [Claude Code](https://claude.ai/code/session_01VDtqoecgES7ScQYGbFVDRv)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 08b213e commit f3a3afc

2 files changed

Lines changed: 36 additions & 13 deletions

File tree

‎content/docs/deployment/validating-metadata.mdx‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,10 @@ matrix answers the first question only.
623623
2. **The per-package walk.** The same table again, once per entry of `packages[]`:
624624
each package's body re-read as the stack it was assembled from, handed the
625625
artifact's own `packages[]` as **resolution context** so a sibling package's
626-
object still resolves, while the collections being judged stay that package's
626+
object still resolves — and so does a translation key naming a sibling's view,
627+
page, action, app, dashboard or flow (the
628+
[orphan-key table](/docs/protocol/kernel/i18n-standard#orphan-keys-and-option-keys)
629+
lists every rung) — while the collections being judged stay that package's
627630
alone. It is de-duplicated against the union run, so what it reports is the
628631
set of per-package findings no union finding already carried under the same
629632
rule, `where`, message and non-top-level position — the leading

‎content/docs/protocol/kernel/i18n-standard.mdx‎

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1019,20 +1019,40 @@ write for it: no bundle key exists for a map.
10191019
nothing supplies text for, whether that text would come from a bundle or from
10201020
an inline locale map. The reverse direction (keys a bundle carries that no
10211021
metadata claims) is checked by `os validate`, `os lint`, and `os compile`, which walk
1022-
every bundle against the stack it ships with:
1023-
1024-
| Key | Must name |
1022+
every bundle against the stack it ships with.
1023+
1024+
"Declared" below has one meaning in every row: declared by the stack being
1025+
checked **or by a sibling package of the same artifact**. On a single
1026+
`defineStack` project, and on the union pass over an artifact, those are the same
1027+
thing. On the per-package pass over an artifact's `packages[]` (see
1028+
[Validating metadata](/docs/deployment/validating-metadata)) each package is
1029+
checked alone, but with the artifact's `packages[]` as resolution context — so a
1030+
package may translate an object, view, page, action, app, dashboard or flow a
1031+
sibling package declares. The keys beneath it are then judged against the
1032+
sibling's own declaration, not waved through: a typo under a sibling's object is
1033+
still an error. Where the package being checked and a sibling declare the same
1034+
name, the package's own record is the one a field's options, an action's params,
1035+
a dashboard's widgets and header actions, or a flow's screens are judged against;
1036+
every other name the two declare adds up.
1037+
1038+
| Key | Resolves when it names |
10251039
|:---|:---|
1026-
| `objects.{object}` | an object this stack defines, or a platform object |
1027-
| `objects.{object}.fields.{field}` | a field that object declares |
1040+
| `objects.{object}` | an object declared by this stack **or a sibling package of the same artifact** — its keys are then judged row by row below; **or** a platform object, or an object an `objectExtensions[]` entry extends that no package of the artifact declares — both skipped whole, because the owner's fields are not visible from here |
1041+
| `objects.{object}.fields.{field}` | a field that object declares, a field an `objectExtensions[]` entry merges into it, or a system column the registry injects (`id`, `owner_id`, the audit and tenant columns; `_id`, `name` and `space` are accepted too) |
10281042
| `objects.{object}.fields.{field}.options.{key}` | an option's stored **`value`** |
1029-
| `objects.{object}._views.{view}` | a view's `name` |
1030-
| `objects.{object}._actions.{action}` | an action bound to that object |
1031-
| `objects.{object}._actions.{action}.params.{param}` | a param's `name` |
1032-
| `objects.{object}._sections.{section}` | a `fieldGroups[].key`, or a section with a `name` |
1033-
| `apps.{app}` / `apps.{app}.navigation.{id}` | an app's `name` / a navigation item's `id` |
1034-
| `dashboards.{dash}` / `.widgets.{id}` / `.actions.{actionUrl}` | the dashboard's `name` / a widget `id` / a header `actionUrl` |
1035-
| `globalActions.{action}` | an action with **no** `objectName` |
1043+
| `objects.{object}._views.{view}` | the key the view registry assigns a view over that object: a `listViews` / `formViews` map key, or the default `list`'s `name` — `default` when it has none (see [ObjectUI Integration](#objectui-integration)) |
1044+
| `objects.{object}._sections.{section}` | a `fieldGroups[].key`, a named section of a form view over that object (a `formViews` entry or the default `form`), or a named section of a `record:details` component on a page bound to it |
1045+
| `objects.{object}._tabs.{tab}` | a filter-preset tab's `name` in `interfaceConfig.userFilters.tabs` on a page over that object (`interfaceConfig.source`, else the page's `object`) — a list view's own `tabs` has no renderer and is not resolvable |
1046+
| `objects.{object}._validations.{rule}` | a validation rule's `name` in that object's `validations[]` or in an `objectExtensions[]` entry aimed at it — a `conditional` rule's `then` / `otherwise` branch included |
1047+
| `objects.{object}._actions.{action}` | an action bound to that object: one in its `actions`, or a stack-level action whose `objectName` is that object |
1048+
| `objects.{object}._actions.{action}.params.{param}` | a param's `name` on that action |
1049+
| `globalActions.{action}` | an action with **no** `objectName` — a key naming a bound action is reported with the `objects.{owner}._actions` key the resolver reads instead |
1050+
| `apps.{app}` | an app declared, or one a `navigationContributions` entry anywhere in the artifact contributes into — including an app owned outside the artifact |
1051+
| `apps.{app}.navigation.{id}` | a navigation item `id` the app declares (nested `children` and `areas[]` included) or one contributed into it; under an app that is only contributed into, the contributed ids alone |
1052+
| `dashboards.{dash}` / `.widgets.{id}` / `.actions.{actionUrl}` | a dashboard declared / a widget `id` on it / a header action's `actionUrl` on it |
1053+
| `flows.{flow}` | a flow's machine `name`, declared |
1054+
| `flows.{flow}.screens.{node}` | the `id` of a `type: 'screen'` node in that flow, nodes nested in loop, parallel and try/catch regions included |
1055+
| `flows.{flow}.screens.{node}.fields.{field}` | a `config.fields[].name` on that screen — an object-form screen (`config.objectName`) declares none, and its input labels live under `objects.{objectName}.fields` |
10361056

10371057
An unresolvable key is an **error**, not a warning: `os validate`, `os lint` and
10381058
`os compile` each report it as `translation-target-unknown` and exit non-zero, so

0 commit comments

Comments
 (0)