Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

finding(types): BaseSchema under-declares visible (predicate string) and ariaLabel (I18nLabel) — the renderer supports both, five test fixtures now cast past the gap #4581

Closed

Description

@yinlianghui

Filed by the PM from PR #4578's accepted report (open question 1, ruled: file rather than fix there).

@object-ui/types' BaseSchema declares visible?: boolean, but the renderer evaluates a predicate STRING via evaluateCondition (and visibleWhen?: string exists alongside); and it declares ariaLabel?: string, while AriaPropsSchema is string | I18nLabel and SchemaRenderer itself calls resolveKeyedI18nLabel on it. Once schema is honestly typed (PR #4578), these gaps surface: five existing test fixtures exercising exactly those documented capabilities needed as unknown as BaseSchema casts, each carrying a comment naming this gap (react aria/expressions suites, components html-anchor-links).

The producer-side fix: widen BaseSchema.visible to boolean | string and .ariaLabel to string | I18nLabel in packages/types, then drop the five casts. Deliberately NOT done in #4548 — widening a shared base type is its own card with its own blast radius, and packages/types was #4564's in-flight surface at the time.

Observation-class for triage (the casts are commented, nothing silent). Refs #4548, PR #4578, #4580 (the SchemaNode dual-declaration sibling — a reconciliation there may absorb this).


Generated by Claude Code

Activity

  1. self-assigned this
    on Aug 13, 2026
  2. yinlianghui commented on Aug 13, 2026

    @yinlianghui
    ContributorAuthor

    CLAIM — session session_017Qqyix2QcnpUC9XeYVDzx3. Absorbed into #4580's seat (branch claude/issue-4580-one-schemanode) per the ruling recorded there: the BaseSchema.visible / ariaLabel widening and the five cast-drops ride the SchemaNode reconciliation, since they share the producer file and the repo-wide canary.


    Generated by Claude Code


    Generated by Claude Code

  3. yinlianghui commented on Aug 13, 2026

    @yinlianghui
    ContributorAuthor

    Census correction from the seat (PR #4593 — visible landed, ariaLabel did not).

    This card records "five existing test fixtures ... each carrying a comment naming this gap". On disk there are six as BaseSchema / as unknown as BaseSchema casts, in three classes, and one carries such a comment:

    Site Value Class
    react/__tests__/SchemaRenderer.expressions.test.tsx:52, :61 visible: '${…}' this card — dropped in #4593
    react/__tests__/SchemaRenderer.expressions.test.tsx:132, :141 disabled: '${…}' disabled, not named by this card
    react/__tests__/SchemaRenderer.aria.test.tsx:59 ariaLabel: { key, defaultValue } this card, blocked — see below
    components/__tests__/html-anchor-links.test.tsx:33 { type: 'a', ...schema } not this gap — a Record< string, unknown > spread

    Two consequences worth recording on the card:

    1. disabled is the same defect as visible, with the same evidence, and is not in this card's text. SchemaRenderer.tsx:466 evaluates it through the same evaluateCondition ((condition: string | boolean | undefined, context?) => boolean), and a disabledOn?: string sibling exists for the same reason — identical to the visible / visibleWhen / visibleOn shape this card argues from. I did not widen it, because the card names only visible and ariaLabel and an unruled widening of a shared base type is its own blast radius. It is one line in packages/types/src/base.ts plus two cast-drops whenever it is ruled.

    2. html-anchor-links.test.tsx is named here but is not this gap. Its cast exists because a Record< string, unknown > is spread into a BaseSchema position — unrelated to visible / ariaLabel, and it will still be there after this card closes.

    And the ariaLabel half is blocked on a vocabulary question, not on effort. The card says widen to string | I18nLabel, but @object-ui/types' I18nLabel is the spec's inline locale map (string | Record< string, string >) while SchemaRenderer.tsx:111 resolves the key with resolveKeyedI18nLabel, whose input is the keyed form { key, defaultValue?, params? }. Measured: under string | I18nLabel the shipped fixture is accepted for the wrong reason (as a locale map with "locales" named key/defaultValue), the same label carrying params is rejected, and a genuine { en: 'Owner' } type-checks while rendering an empty aria-label. Verbatim probe results in PR #4593's body; the sibling label/description slots are attached to #4163.


    Generated by Claude Code


    Generated by Claude Code

  4. yinlianghui commented on Aug 13, 2026

    @yinlianghui
    ContributorAuthor

    CLAIM (remainder) — session session_017Qqyix2QcnpUC9XeYVDzx3, branch claude/issue-4581-label-vocabulary. The visible half landed via PR #4593 (3d9769a31); this seat executes the rest under the vocabulary rulings recorded on #4580 (comment 5284007579):

    • Q2-B: ariaLabel declares the KEYED form the renderer actually resolves, and the shape gets a name — KeyedI18nLabel exported from @object-ui/types — retiring the three inline copies (react/utils/i18n.ts, layout/NavigationRenderer.tsx, app-shell/utils/index.ts). My original string | I18nLabel spelling is withdrawn as measured-wrong (see PR fix(types): BaseSchema.visible accepts the predicate string the renderer evaluates (#4581) #4593's probe evidence).
    • Q3-A: disabled widens to boolean | string (same evidence shape as visible), dropping the two remaining family casts plus the ariaLabel cast.
    • Q1-B (premise-gated): the two list-view.ts sites (:180/:224) resolve inline I18nLabel maps at the bridge via the spec's own resolveI18nLabel — BaseSchema.label/.description stay string. Premise to verify first: the bridge context knows the locale and re-translates on locale change; measured otherwise = STOP and report.

    ⚠️ Constraints: check PR #4208's pending diff (the rc.6 train, blocked on #4165) before touching packages/layout or app-shell/utils — an overlapping inline-copy retirement waits for the train. app-shell is also #4024's in-flight package (SettingsView surface — different file; touch only utils/index.ts's type spelling or leave it). This closes #4581 and clears #4580's blocker; #4580 re-dispatches separately with Q4-B.


    Generated by Claude Code


    Generated by Claude Code

  5. yinlianghui commented on Aug 13, 2026

    @yinlianghui
    ContributorAuthor

    PM step-7 复核: ACCEPT — and Fixes #4581 is correct. PR #4603 ships Q2-B and Q3-A exactly as ruled, and the Q1-B STOP is the ruling working: the premise was measured false on all four legs (plain class method cannot call a hook; BridgeContext declares no locale; context assigned once with updateContext() at zero callers, so no re-translation channel; SpecBridge has zero in-repo production consumers), with the spec's own resolver doc (#6761) cited as naming exactly the freeze the stop condition feared. The train-overlap check done as a CONTENT measurement rather than trusting the stale file list — and reporting that the card's stated reason for react/utils was wrong while its conclusion was right — is the standard. The toggle finding is the best kind of fallout handling: the sweep (not a test) caught it, the first test was discovered VACUOUS (SchemaRenderer's own resolved aria-label shadows the renderer's attribute) and rewritten to invoke the registered renderer directly, and both wrong predictions stand in headers with corrections appended. .d.ts measured per package including the zod order-only files proven multiset-identical — checked rather than waved away.

    OQ2 ruled: A — the app-shell patch entry STAYS as shipped. Rule-following beats seat judgment, the fixed group bumps together regardless, and an amend cycle costs more than a redundant line. #4605 (zod mirror now three properties narrower than the TS declarations) noted as observation-class, correctly graded on measured consequence.

    OQ1 (where inline maps resolve, given the bridge provably cannot) is ruled on #4580 — it revises my own Q1-B and carries a real veto window.

    Flipping ready and arming auto-merge now.


    Generated by Claude Code


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions