Skip to content

Commit 9e14303

Browse files
committed
feat(spec,lint,cli): a page:tabs item label is keyed pages.PAGE.components.TABS.items.VALUE.label
The tabs component's own id, then the item's value (the stable ?tab= token). translatePage overlays the item label; walkAddressedPageComponents keeps a visitor's edit to a panel's own keys; objectstack validate judges the key; os i18n extract offers the slot and coverage demands it. Regenerated the api-surface, export-origins and strictness-ledger counts. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
1 parent 818b89e commit 9e14303

5 files changed

Lines changed: 22 additions & 2 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/lint': minor
4+
'@objectstack/cli': minor
5+
---
6+
7+
feat(spec): a `page:tabs` item label gains a translation key, `pages.<page>.components.<tabsId>.items.<value>.label`
8+
9+
Clause-②: yes (widening)
10+
11+
- **Before.** `pages.<page>.components` is keyed by component id, and a `page:tabs` item is not a component and carries no id, so a tab label had no key. `items` was refused as an unknown key, and `translatePage` never read `items[].label`. A localized page kept its tab strip in the authoring language.
12+
- **Now.** `pages.<page>.components.<tabsId>.items.<value>.label` is a declared key. It is addressed by the tabs component's own `id` and the item's `value` (the stable `?tab=` token), and `translatePage` overlays it onto that item's `label`, across the locale chain. The entry is closed: `title`, `name` and `text` are renames onto `label`, and `tabs` on a component entry is the rename onto `items`. An item without a `value` has no key, because its renderer token is index-derived and an array index is never a bundle key. Author a `value` on the item, or keep localizing that label with an inline locale map.
13+
- **`walkAddressedPageComponents`.** A visitor's edit to a panel's own keys (its `label`) now stands when the visitor keeps the panel list's authored shape. Panel children are still descended from the authored document.
14+
- **`objectstack validate`.** `@objectstack/lint`'s translation-reference check accepts the key over a declared `page:tabs` item `value`. It refuses it as `translation-target-unknown` over an unknown value (with "Did you mean"), over a component of another type (a `page:accordion` is pointed at the inline locale map, since its panels have no stable key), and under a component id the page does not declare.
15+
- **`os i18n extract` / coverage.** The extractor writes a slot for every valued tab item, and the coverage check demands it. An inline locale map on the item label is counted as authored, as before.
16+
- **Unchanged.** Every existing `pages` key, and an inline locale map on a tab label that the bundle does not key.

‎docs/audits/2026-07-unknown-key-strictness-ledger.counts/system.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ hand-patch a number here** — fix the code or the verdict and regenerate.
1919

2020
| Dir | Sites |
2121
|---|---|
22-
| `system/` | 376 |
22+
| `system/` | 377 |

‎packages/spec/api-surface/system.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@
500500
"OtelExporterType (const)",
501501
"OtelExporterType (type)",
502502
"PAGE_COMPONENT_COPY_KEYS (const)",
503+
"PAGE_TABS_COMPONENT_TYPE (const)",
503504
"PKG_CONVENTIONS (const)",
504505
"PLATFORM_OBJECTS_BY_PACKAGE (const)",
505506
"PLATFORM_OBJECT_PREFIXES (const)",
@@ -838,6 +839,7 @@
838839
"objectValidationMessageKey (function)",
839840
"operationMessageTranslationKey (function)",
840841
"organizationOwnershipShapeGenerationOf (function)",
842+
"pageTabItemKey (function)",
841843
"preferredLocaleFromHeader (function)",
842844
"renderOperationMessage (function)",
843845
"renderValidationMessage (function)",

‎packages/spec/export-origins/system.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,7 @@
479479
"OrganizationOwnershipSupplementSchema": "src/system/fate-supplement.zod.ts#OrganizationOwnershipSupplementSchema (const)",
480480
"OtelExporterType": "src/system/tracing.zod.ts#OtelExporterType (type)",
481481
"PAGE_COMPONENT_COPY_KEYS": "src/system/i18n-resolver.ts#PAGE_COMPONENT_COPY_KEYS (const)",
482+
"PAGE_TABS_COMPONENT_TYPE": "src/system/i18n-resolver.ts#PAGE_TABS_COMPONENT_TYPE (const)",
482483
"PKG_CONVENTIONS": "src/system/constants/paths.ts#PKG_CONVENTIONS (const)",
483484
"PLATFORM_OBJECTS_BY_PACKAGE": "src/system/constants/platform-object-names.ts#PLATFORM_OBJECTS_BY_PACKAGE (const)",
484485
"PLATFORM_OBJECT_PREFIXES": "src/system/constants/platform-object-names.ts#PLATFORM_OBJECT_PREFIXES (const)",
@@ -799,6 +800,7 @@
799800
"objectValidationMessageKey": "src/system/i18n-resolver.ts#objectValidationMessageKey (function)",
800801
"operationMessageTranslationKey": "src/system/operation-message.ts#operationMessageTranslationKey (function)",
801802
"organizationOwnershipShapeGenerationOf": "src/system/migration.zod.ts#organizationOwnershipShapeGenerationOf (function)",
803+
"pageTabItemKey": "src/system/i18n-resolver.ts#pageTabItemKey (function)",
802804
"preferredLocaleFromHeader": "src/system/i18n-resolver.ts#preferredLocaleFromHeader (function)",
803805
"renderOperationMessage": "src/system/operation-message.ts#renderOperationMessage (function)",
804806
"renderValidationMessage": "src/system/validation-message.ts#renderValidationMessage (function)",

‎packages/spec/src/system/i18n-resolver.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2524,7 +2524,7 @@ describe('walkAddressedPageComponents — the one addressed-component walk, shar
25242524
...component.properties,
25252525
// The visitor relabels each panel and drops its children; the walk
25262526
// owns the descent, so the authored children come back visited.
2527-
items: component.properties.items.map((item: any) => ({ label: `relabelled:${item.value}`, value: item.value })),
2527+
items: (component.properties?.items as any[]).map((item: any) => ({ label: `relabelled:${item.value}`, value: item.value })),
25282528
},
25292529
})) as any;
25302530
const items = regions[0].components[0].properties.items;

0 commit comments

Comments
 (0)