Skip to content

finding(types): 18 schemas narrow inherited disabled to boolean while 0 narrow its twin visible — no ruling says which is intended #7087

Description

@os-sam

Observation, filed unassigned while correcting the BaseSchema common-props table for #7079. This is a declaration question, not a docs question, so it is filed rather than decided. The docs correction went ahead on its own footing: the table documents BaseSchema, and BaseSchema is what a node gets when its own schema does not restate the key.

What was measured

Read off packages/types/src/** on origin/main at 592acafbe, excluding __tests__ and zod/. Each <key>?: declaration was attributed to its enclosing exported interface, and that interface checked for extends BaseSchema.

124 interfaces extend BaseSchema. Of those, the number that redeclare an inherited member more narrowly than the base union:

base member base declaration extenders that narrow it narrowed to
visible boolean | string 0
disabled boolean | string 18 boolean
label string | I18nLabel 29 string (4 of them required)
description string | I18nLabel 32 string
ariaLabel string | KeyedI18nLabel 1 string (PageNodeSchema)

The 18 narrowing disabled: ActionSchema (crud.ts:89), CollapsibleSchema (disclosure.ts:109), ToggleGroupSchema (disclosure.ts:173), and fifteen in form.tsButtonSchema, InputSchema, TextareaSchema, SelectSchema, CheckboxSchema, RadioGroupSchema, SwitchSchema, ToggleSchema, SliderSchema, FileUploadSchema, DatePickerSchema, CalendarSchema, InputOTPSchema, FormSchema, ComboboxSchema.

A further 13 declarations of disabled?: boolean sit on shapes that do not extend BaseSchema at all (ListItem, NavLink, ButtonGroupButton, SelectOption, RadioOption, FormField, ComboboxOption, SelectOptionMetadata, MenuCommandItem, AccordionItem, ToggleGroupItem, TabItem) plus ColorPalette.disabled?: string, an unrelated colour-token vocabulary. Those are independent declarations, not narrowings, and are counted separately on purpose.

Control: the census attributes BaseSchema's own disabled?: boolean \| string to BaseSchema and excludes it from the narrowing count.

Why the asymmetry is the finding, not the narrowing

visible and disabled are twins by construction. Both were widened to boolean | string by #4581 on the same evidence — the renderer does not read either key as a boolean, it passes both to evaluator.evaluateCondition, declared (condition: string | boolean | undefined, context?) => boolean. base.ts:320-325 records that the previous asymmetry between them was "accidental rather than deliberate" (ruling #4580 Q3-A).

Yet after the widening, every concrete schema left visible alone and 18 narrowed disabled back. So a predicate string on visible is accepted everywhere, and the same predicate on disabled is a type error on eighteen of the most commonly authored schemas — ButtonSchema, InputSchema, SelectSchema, FormSchema among them. Nothing found in the tree rules on this; it looks like the narrowings simply predate the widening and were never revisited.

Why it matters beyond tidiness

This is the shape that makes AI-authored metadata hard to get right. The published base type advertises a capability the renderer really implements, and eighteen of the most-used concrete schemas silently refuse it. An author — human or generated — who reads BaseSchema and writes disabled: "${data.status === 'locked'}" on an InputSchema gets a type error whose message points at the narrowed subtype, not at the ruling that widened the base. label (29) and description (32) carry the same shape at larger scale.

The corrected table on #7079 mitigates the reader-facing half with a note that a concrete schema may narrow an inherited member and its own declaration wins. That is honest, but it documents the inconsistency rather than resolving it.

The question, not the answer

Three coherent directions, deliberately not chosen here:

  1. Drop the narrowings — the 18 (and 29, and 32) redeclarations go, the base union stands everywhere, and evaluateCondition handles what it already handles. Widest capability, largest diff, and it grants a predicate on schemas nobody has asked for one on.
  2. Keep them and make it deliberate — record that narrowing to boolean is the house shape for concrete widgets and that the base union exists for the generic path. Then visible's 0-of-124 becomes the outlier to fix, not disabled's 18.
  3. Leave it and document it — what finding(docs): the canonical BaseSchema common-props table under-states five declared types and omits five declared members #7079 just did, at the docs layer only.

Picking between these is a contract decision about what the published type promises, and it wants a maintainer ruling rather than a dev's judgement.

Refs #7079 · #6347 · #4581 · #4580.

Generated by Claude Code

Activity

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

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpackage: typespriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions