Skip to content

[finding] Two label carriers in one spec accept different types — apps[].label may be localized, objects[].label may not, and nothing declares why #16282

Description

@os-litant

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[].labelz.string()apps[].labelI18nLabelSchema同一个 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:1621z.string().optional() no
objects[].fields.*.label packages/spec/src/data/field.zod.ts:299z.string() no
apps[].label packages/spec/src/ui/app.zod.ts:300I18nLabelSchema yes
views[].list.label packages/spec/src/ui/view.zod.ts:1819I18nLabelSchema.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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions