Filed out-of-scope by the flight-⑧ sweep of skills/objectstack-i18n/** (#13815 / #13658) while establishing what translation-target-unknown actually reports. Not fixed there — that PR (#13833) is a documentation-only diff.
What is missing
packages/lint/src/validate-translation-references.ts walks the object branch of every bundle and reports an orphan key for fields, fields.*.options, _views, _sections, and _actions (plus params). It does not walk _tabs.
Section headers in the rule, in order:
711: // ── objects.<name>.…
830: // ── globalActions.<name>
865: // ── apps.<name>[.navigation.<id>]
895: // ── dashboards.<name>[.widgets.<id> | .actions.<url>]
938: // ── flows.<name>[.screens.<node_id>[.fields.<field_name>]]
and inside the object branch the sub-walks are fields / options / _views / _sections / _actions. _tabs appears nowhere in the file.
Why that is a hole and not a scoping choice
_tabs is a fully live authorable surface, not a planned one:
ObjectTranslationDataSchema declares it (packages/spec/src/system/translation.zod.ts), keyed by ViewTabSchema.name, resolved by resolveTabLabel;collectExpectedEntries harvests it — measured on a small fixture, objects.task._tabs.urgent.label is emitted (with source: 'view');- so
os i18n check already demands a translation for a tab key, while nothing tells an author that a _tabs key they wrote names a tab that no longer exists.
That is exactly the asymmetry #11608 identified for flows — "an authored key naming a flow, screen node or field that does not exist warns nobody" — which was accepted and closed by adding the flows leg. _tabs is the same shape, one group over, and was missed because it arrived after the object branch was written.
The failure is the quiet one this rule family exists to prevent: rename a filter preset, the bundle keeps its old _tabs key, the tab bar renders in the source locale above a fully localized grid, and every gate stays green.
Suggested fix
Add a _tabs sub-walk beside _sections in the object branch, reporting translation-target-unknown against the tab names the stack actually declares (page.interfaceConfig.userFilters.tabs[].name, de-duplicated per object the way walkObjectTabs in packages/cli/src/utils/i18n-extract.ts already does). The universe builder that the object branch consumes needs the tab names added alongside views and sections.
Also worth checking in the same pass: whether the rule's own coverage of the object branch should be pinned against ObjectTranslationDataSchema's key set, so the next group added to the shape cannot land without a leg here.
Generated by Claude Code
Filed out-of-scope by the flight-⑧ sweep of
skills/objectstack-i18n/**(#13815 / #13658) while establishing whattranslation-target-unknownactually reports. Not fixed there — that PR (#13833) is a documentation-only diff.What is missing
packages/lint/src/validate-translation-references.tswalks the object branch of every bundle and reports an orphan key forfields,fields.*.options,_views,_sections, and_actions(plusparams). It does not walk_tabs.Section headers in the rule, in order:
and inside the object branch the sub-walks are
fields/options/_views/_sections/_actions._tabsappears nowhere in the file.Why that is a hole and not a scoping choice
_tabsis a fully live authorable surface, not a planned one:ObjectTranslationDataSchemadeclares it (packages/spec/src/system/translation.zod.ts), keyed byViewTabSchema.name, resolved byresolveTabLabel;collectExpectedEntriesharvests it — measured on a small fixture,objects.task._tabs.urgent.labelis emitted (withsource: 'view');os i18n checkalready demands a translation for a tab key, while nothing tells an author that a_tabskey they wrote names a tab that no longer exists.That is exactly the asymmetry #11608 identified for
flows— "an authored key naming a flow, screen node or field that does not exist warns nobody" — which was accepted and closed by adding theflowsleg._tabsis the same shape, one group over, and was missed because it arrived after the object branch was written.The failure is the quiet one this rule family exists to prevent: rename a filter preset, the bundle keeps its old
_tabskey, the tab bar renders in the source locale above a fully localized grid, and every gate stays green.Suggested fix
Add a
_tabssub-walk beside_sectionsin the object branch, reportingtranslation-target-unknownagainst the tab names the stack actually declares (page.interfaceConfig.userFilters.tabs[].name, de-duplicated per object the waywalkObjectTabsinpackages/cli/src/utils/i18n-extract.tsalready does). The universe builder that the object branch consumes needs the tab names added alongside views and sections.Also worth checking in the same pass: whether the rule's own coverage of the object branch should be pinned against
ObjectTranslationDataSchema's key set, so the next group added to the shape cannot land without a leg here.Generated by Claude Code