⛔ 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), which reserved filing when dispatching #15880 so the implementer would back-link rather than widen its PR.
Provenance, and why this is a separate card
Triage said so on #15880, and the words are theirs rather than this seat's:
⚠️ 但卡片提出的那条更深的问题,本席认为它说得对,且值得单独立卡:
two label carriers in one spec accepting different types is a contract question, not a lint question.
⇒ objects[].label 是 z.string(),apps[].label 是 I18nLabelSchema。同一个 spec 里两个标签载体接受不同类型,这既是本缺陷的根,也会以别的形状再冒出来。⛔ 本席不代为开卡(本轮纪律是先清完既有裸卡)
They also fenced it out of that card's PR in as many words: ⛔ 不要在本卡的 PR 里顺手统一那两个 schema——那是收窄或拓宽已发布声明,需要人工地板. That fence was honoured — PR #16280 touches neither schema.
The measurement
Located by symbol; line numbers are from head c8d31a3fe0e and will drift.
| carrier |
declaration |
accepts an inline locale map? |
objects[].label |
packages/spec/src/data/object.zod.ts:1621 — z.string().optional() |
no |
objects[].fields.*.label |
packages/spec/src/data/field.zod.ts:299 — z.string() |
no |
apps[].label |
packages/spec/src/ui/app.zod.ts:300 — I18nLabelSchema |
yes |
views[].list.label |
packages/spec/src/ui/view.zod.ts:1819 — I18nLabelSchema.optional() |
yes |
I18nLabelSchema (packages/spec/src/ui/i18n.zod.ts) is z.union([z.string(), InlineLocaleMapSchema]), and its own header says of the two forms: "Both are real; neither is deprecated by this schema."
The consequence an author actually meets
From #15880's own driven measurement:
an author who localizes an object label is told so; one who localizes an app label crashes the linter.
objects.0.label refuses the map at the schema door with Invalid input: expected string, received object — a clear, immediate, correct diagnostic. apps[].label accepts it, and the config is schema-valid (ObjectStackDefinitionSchema.safeParse returns success: true).
⚠️ The crash half is fixed and is not what this card is about. PR #16280 guards checkLabelCase, so os lint no longer dies. What remains is the split itself: an author who wants a localized label gets it on an app and a view list, and is refused on an object or a field, with no document stating that this is intentional or where the line falls.
What a decision would have to weigh — ⛔ costs only, not a recommendation
- Leave the split. ⛔ Cost: the boundary stays undiscoverable. An author learns it by hitting a parse error on one carrier after it worked on another, and nothing in the spec says which carriers are which.
- Widen the
z.string() carriers to I18nLabelSchema. ⛔ Cost: widening a published declaration — a human floor. Every consumer that today may assume objects[].label is a string (renderers, extractors, the lint rule, generated docs) has to be re-checked; the os lint rule this came from would go silent on object labels exactly as it now does on app labels.
- Narrow the
I18nLabelSchema carriers to z.string(). ⛔ Cost: a breaking removal of an authoring form the schema currently calls real and undeprecated, against authored configs that already use it.
- Leave the types and declare the boundary. ⛔ Cost: pays nothing structural, but it is the only option that does not touch a published declaration.
⚠️ Whoever rules this should also say what the answer implies for the lint rule's silence: with the guard in place, convention/label-case now says nothing at all about a localized label, and that is deliberate — the localized tests in PR #16280 assert the absence of an issue, so a later widening has to rewrite them on purpose rather than drift into them.
Dedupe
One targeted search, with a live control in the same call: the query returned #16251 (the normative i18n page's locale-map omission — a docs question, different subject) and #15880 itself, so the channel provably reaches this neighbourhood rather than being silently empty. Nothing open covers the schema split.
Refs: #15880 (where it was measured) · PR #16280 (the guard, which deliberately does not touch this) · 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), which reserved filing when dispatching #15880 so the implementer would back-link rather than widen its PR.Provenance, and why this is a separate card
Triage said so on #15880, and the words are theirs rather than this seat's:
They also fenced it out of that card's PR in as many words: ⛔ 不要在本卡的 PR 里顺手统一那两个 schema——那是收窄或拓宽已发布声明,需要人工地板. That fence was honoured — PR #16280 touches neither schema.
The measurement
Located by symbol; line numbers are from head
c8d31a3fe0eand will drift.objects[].labelpackages/spec/src/data/object.zod.ts:1621—z.string().optional()objects[].fields.*.labelpackages/spec/src/data/field.zod.ts:299—z.string()apps[].labelpackages/spec/src/ui/app.zod.ts:300—I18nLabelSchemaviews[].list.labelpackages/spec/src/ui/view.zod.ts:1819—I18nLabelSchema.optional()I18nLabelSchema(packages/spec/src/ui/i18n.zod.ts) isz.union([z.string(), InlineLocaleMapSchema]), and its own header says of the two forms: "Both are real; neither is deprecated by this schema."The consequence an author actually meets
From #15880's own driven measurement:
objects.0.labelrefuses the map at the schema door withInvalid input: expected string, received object— a clear, immediate, correct diagnostic.apps[].labelaccepts it, and the config is schema-valid (ObjectStackDefinitionSchema.safeParsereturnssuccess: true).checkLabelCase, soos lintno longer dies. What remains is the split itself: an author who wants a localized label gets it on an app and a view list, and is refused on an object or a field, with no document stating that this is intentional or where the line falls.What a decision would have to weigh — ⛔ costs only, not a recommendation
z.string()carriers toI18nLabelSchema. ⛔ Cost: widening a published declaration — a human floor. Every consumer that today may assumeobjects[].labelis a string (renderers, extractors, the lint rule, generated docs) has to be re-checked; theos lintrule this came from would go silent on object labels exactly as it now does on app labels.I18nLabelSchemacarriers toz.string(). ⛔ Cost: a breaking removal of an authoring form the schema currently calls real and undeprecated, against authored configs that already use it.convention/label-casenow says nothing at all about a localized label, and that is deliberate — the localized tests in PR #16280 assert the absence of an issue, so a later widening has to rewrite them on purpose rather than drift into them.Dedupe
One targeted search, with a live control in the same call: the query returned #16251 (the normative i18n page's locale-map omission — a docs question, different subject) and #15880 itself, so the channel provably reaches this neighbourhood rather than being silently empty. Nothing open covers the schema split.
Refs: #15880 (where it was measured) · PR #16280 (the guard, which deliberately does not touch this) ·
packages/spec/src/ui/i18n.zod.ts.