You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
[finding] convention/label-case reaches 5 of the 9 schemas that carry a label — page, dashboard, chart, report, action, bulk-action and component labels are never linted at all #16284
⛔ Recorded for triage; no severity asserted, no domain:*, no type — routing and grading are triage's. Filed by the domain:cli execution PM seat (#6024) from a measurement handed back on #15880, where the implementer was asked to enumerate the rule's call sites rather than trust the card's sweep. This is what the enumeration turned up, and it is not a defect PR #16280 introduces or should fix.
The gap
lintConfig walks five collections — objects, views, apps, flows, agents — and reaches checkLabelCase from four call sites covering five authoring paths. Located by symbol; line numbers are from head c8d31a3fe0e and will drift.
call site
authoring path
label type
lint.ts:202
objects[].label
z.string()
lint.ts:229
objects[].fields.*.label
z.string()
lint.ts:254 (via getViewLabel)
views[].list.labelorviews[].listViews.*.label
I18nLabelSchema
lint.ts:268
apps[].label
I18nLabelSchema
But I18nLabelSchema is imported by nineui/ schemas: app, view, page, dashboard, chart, report, action, bulk-action, component.
⇒ convention/label-case never reaches page, dashboard, chart, report, action, bulk-action or component labels — before this change or after it. The rule's coverage is narrower than the schema surface it nominally governs, and nothing says so.
Why it is worth a card rather than a shrug
⭐ The card that found the crash reported "exactly two hits" and that number was an artefact of its fixture, not a property of the class.#15880's mutation sweep (15,728 mutations, 3,064 schema-valid survivors) found exactly two crash sites — apps.0.label and views.0.list.label — and its second sweep over the four bundled eval-corpus fixtures found zero, correctly explained at the time as "those fixtures declare no apps and no views."
Both readings are honest and both are about the fixtures. The class is the call-site set, and the call-site set is four. A sweep can only find what its fixture reaches, so a sweep is evidence of presence and never of absence — which is exactly why the dispatch asked for the enumeration instead of taking the two.
That matters beyond this rule: a mutation sweep reporting a small hit count reads like a measured blast radius, and here it would have understated the class by more than half.
What is and is not claimed
Measured: the four call sites, the five collections lintConfig walks, and the nine ui/ schemas importing I18nLabelSchema.
⛔ Not measured: whether any of the seven unreached carriers is authored often enough for the silence to matter, and whether the rule was ever intended to reach them. This card records a coverage gap; it does not assert the gap is a bug.
⛔ Not a regression. The silence predates PR fix(cli): guard convention/label-case on a localized label #16280 and is unchanged by it. That PR's guard makes the rule silent on localized labels on the two carriers it does reach — a separate, deliberate, pinned silence.
Related, and deliberately separate
#16282 — the two label carriers accept different types (objects[].label is z.string(), apps[].label is I18nLabelSchema). That is a contract question about what may be authored. This card is a coverage question about what gets checked. They interact — widening the string carriers would enlarge this rule's blind area — but they are decided by different people on different surfaces.
Refs: #15880 (where the enumeration was made) · PR #16280 · #16282 · packages/spec/src/ui/i18n.zod.ts.
⛔ Recorded for triage; no severity asserted, no
domain:*, no type — routing and grading are triage's. Filed by thedomain:cliexecution PM seat (#6024) from a measurement handed back on #15880, where the implementer was asked to enumerate the rule's call sites rather than trust the card's sweep. This is what the enumeration turned up, and it is not a defect PR #16280 introduces or should fix.The gap
lintConfigwalks five collections —objects,views,apps,flows,agents— and reachescheckLabelCasefrom four call sites covering five authoring paths. Located by symbol; line numbers are from headc8d31a3fe0eand will drift.lint.ts:202objects[].labelz.string()lint.ts:229objects[].fields.*.labelz.string()lint.ts:254(viagetViewLabel)views[].list.labelorviews[].listViews.*.labelI18nLabelSchemalint.ts:268apps[].labelI18nLabelSchemaBut
I18nLabelSchemais imported by nineui/schemas:app,view,page,dashboard,chart,report,action,bulk-action,component.⇒
convention/label-casenever reachespage,dashboard,chart,report,action,bulk-actionorcomponentlabels — before this change or after it. The rule's coverage is narrower than the schema surface it nominally governs, and nothing says so.Why it is worth a card rather than a shrug
⭐ The card that found the crash reported "exactly two hits" and that number was an artefact of its fixture, not a property of the class. #15880's mutation sweep (15,728 mutations, 3,064 schema-valid survivors) found exactly two crash sites —
apps.0.labelandviews.0.list.label— and its second sweep over the four bundled eval-corpus fixtures found zero, correctly explained at the time as "those fixtures declare noappsand noviews."Both readings are honest and both are about the fixtures. The class is the call-site set, and the call-site set is four. A sweep can only find what its fixture reaches, so a sweep is evidence of presence and never of absence — which is exactly why the dispatch asked for the enumeration instead of taking the two.
That matters beyond this rule: a mutation sweep reporting a small hit count reads like a measured blast radius, and here it would have understated the class by more than half.
What is and is not claimed
lintConfigwalks, and the nineui/schemas importingI18nLabelSchema.convention/label-caseon a localized label #16280 and is unchanged by it. That PR's guard makes the rule silent on localized labels on the two carriers it does reach — a separate, deliberate, pinned silence.Related, and deliberately separate
#16282 — the two label carriers accept different types (
objects[].labelisz.string(),apps[].labelisI18nLabelSchema). That is a contract question about what may be authored. This card is a coverage question about what gets checked. They interact — widening the string carriers would enlarge this rule's blind area — but they are decided by different people on different surfaces.Refs: #15880 (where the enumeration was made) · PR #16280 · #16282 ·
packages/spec/src/ui/i18n.zod.ts.