Skip to content

docs(spec): SelectorOption records its shipped contract as current - #9144

Merged
cixzhang merged 2 commits into
mainfrom
spec-backfill/Selector
Nov 1, 2026
Merged

cixzhang merged 2 commits into
mainfrom
spec-backfill/Selector

Conversation

@cixzhang

@cixzhang cixzhang commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Exact decision needed

Add component:SelectorOption as a current record (it has its own .doc.mjs), recording its shipped behavior, and add one "Does not own" line to the current component:Selector record pointing at it. No behavior, API, default, or DOM change.

Existing authority searched

Decision

  • SelectorOption.spec.md (new, approved_by: cixzhang, approved_at: 2026-11-01): the Item row it renders, the host-pinned inline layout inside an InputGroup, the selector-option target and row reset, and root styling.
  • Selector.spec.md: one "Does not own" bullet naming component:SelectorOption. No requirement, decision, or map in Selector's record changes.

Rejected alternatives

None.

Claim scope and non-goals

Shipped behavior only, records live under docs/specs/components/Selector/; traced from SelectorOption.tsx, SelectorRowLayoutContext.ts, Selector.tsx, SelectorOption.doc.mjs, and Selector.test.tsx. No code is fixed.

Evidence and implementation relationship

  • Selector.test.tsx end-content, render-value, layout-fold, and narrow-list cases; each verification row lists its evidence gaps.

Current-state impact:

  1. SelectorOption accepts neither a ref nor other DOM props, against architecture:public-component-api INV5, INV8.

Writing rubric results

  • R1 narration: none; present tense.
  • R2 research in the record: none.
  • R3 evidence naming a change: none.
  • R4 rejected alternatives over one line or outside a decision: none.
  • R5 pull request or issue references remaining: none.
  • R6 ruled open questions still listing options: none.

Scope

  • This PR contains specification records only.
  • It records observable behavior and preserves equivalent internal implementations.
  • Any named internal mechanism is intentionally public, names its dependent caller/system, and explains why an equivalent implementation would not satisfy the contract: none named.
  • Verification states the evidence layer and failure signal without prescribing CI job/workflow topology.
  • It records one intentional durable decision, not a rescue for a separable tagalong.
  • The canonical knowledge owner, affected current claims, and overlapping open work are named; no ownership collision.
  • Adjacent API, visual, accessibility, composition, and implementation facts remain outside scope unless this exact decision depends on them.
  • Public text and artifacts contain no internal Meta context.

Validation

  • pnpm check:knowledge: aligned
  • Prettier: clean
  • git diff --check: clean

@astracat-bot

astracat-bot Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Nov 1, 2026, 7:07 AM UTC

@vercel

vercel Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Nov 1, 2026 7:01am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 31, 2026
@cixzhang
cixzhang marked this pull request as ready for review October 31, 2026 15:29
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 31, 2026

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on the new component:SelectorOption record and the component:Selector ownership bullet, plus two record fixes. An engineering owner records that decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so as an engineering owner.

  • FR1, AR1 and the icon concept row say every icon renders small, secondary and decorative, but SelectorOption passes a ReactNode icon through renderIconSlot unchanged (the test FR1 cites passes one), so please narrow them to IconType icons and list that evidence gap in the FR1 verification row.
  • The Design relationships rows cite component:Item for requirements Item's record does not state, so cite this record's own owner direction or mark them unsettled.

[Automated review]


| ID | Invariant | Basis |
| --- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| FR1 | SelectorOption renders one `component:Item` row: `icon` as a small secondary icon in the start slot when present, `label`, `description` when present, and `endContent` when present. Selector's default option renderer draws each option through SelectorOption too. | Current source and tests |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renderIconSlot makes only an IconType value (name, component or render object) a small secondary Icon. A ReactNode icon is returned unchanged, as the cited renderValue test shows with <span data-testid="lock-glyph" />. Suggest: "icon in the start slot when present: an IconType icon as a small secondary Icon, a ReactNode as given". Update the concept row at L78 the same way.

## Accessibility contract

- **AR1 — Content only.** SelectorOption adds no role, name, or state; the
Selector option row it sits in owns them. The icon is decorative.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only an IconType icon is decorative (Icon's aria-hidden default). A ReactNode icon keeps the semantics its caller gives it, so this should say that, and the FR1 verification row should list the missing icon-presentation test as a gap.

Comment on lines +124 to +128
| Row | Lays out the option content like a built-in option. | `component:Item` | Supporting | FR1, FR3 |
| Icon | Identifies the option at a glance. | `component:Item` | Supporting | FR1 |
| Label | Names the option. | `component:Item` | Prominent | FR1, FR3 |
| Description | Adds secondary detail. | `component:Item` | Supporting | FR1, FR2 |
| End content | Holds caller content after the text. | `component:Item` | Supporting | FR1 |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

component:Item's design table states only that start and end content annotate the label's first line, and that the label's first line is the reference line. These rows' requirements are this record's own; cite its owner direction or mark them unsettled.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on the new component:SelectorOption record and the component:Selector ownership bullet. An engineering owner records it by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Two record fixes are needed either way:

  • The non-goal "A ref or other DOM props; SelectorOption accepts neither" contradicts architecture:public-component-api INV8, so please record it as a requirement with an implementation gap, as other component records do, or have the architecture record amended.
  • IconType covers only icon components, but SelectorOption also draws semantic icon names such as icon="search" as Icon, so FR1, AR1 and the icon concept row should say that semantic names and icon components render through Icon, while other ReactNode content renders as given.

[Automated review]

owned by `component:Selector`.
- The trigger's sizing around a rendered value — owned by `component:Selector`.
- Item's layout, density, and targets — owned by `component:Item`.
- A `ref` or other DOM props; SelectorOption accepts neither.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

architecture:public-component-api INV8 says a public DOM component accepts ref and connects it to its contract element. SelectorOption is public and renders an Item <div>, so listing this as a non-goal turns the shipped gap into stated direction. Please record it as a requirement with an implementation gap (as Divider.spec.md FR6 and Citation.spec.md FR5 do), or have INV8 amended with an exception.


| ID | Invariant | Basis |
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| FR1 | SelectorOption renders one `component:Item` row: `icon` in the start slot when present (an `IconType` icon as a small secondary Icon, a ReactNode as given), `label`, `description` when present, and `endContent` when present. Selector's default option renderer draws each option through SelectorOption too. | Current source and tests |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IconType is only the component type, so a semantic name such as icon="search" falls under "a ReactNode as given" here. But renderIconSlot draws every string as a small, secondary, aria-hidden Icon, and an unregistered name renders nothing. component:ComplexSelector AV2's wording fits: semantic icon names and icon components render through Icon; other ReactNode content renders as given. The same applies to L78 and AR1.

| --- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| FR1 | SelectorOption renders one `component:Item` row: `icon` in the start slot when present (an `IconType` icon as a small secondary Icon, a ReactNode as given), `label`, `description` when present, and `endContent` when present. Selector's default option renderer draws each option through SelectorOption too. | Current source and tests |
| FR2 | The row uses the caller's `layout` (`stacked` by default). When its host pins the row height (a Selector trigger inside an InputGroup), the row is `inline` whatever the caller passed. | Current source and tests |
| FR3 | The row carries the `selector-option` target. Its own block and inline padding and corner radius are zero and its minimum width is zero, so a long label ellipsizes inside a narrow host. Caller `xstyle` merges after these base styles; `className` and `style` reach the row. | `architecture:component-theming-surface`; current source |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Item truncates only string labels (typeof label === 'string'), so "a long string label ellipsizes" is the shipped behaviour; a ReactNode label wraps.

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on the new component:SelectorOption record and the component:Selector ownership bullet; an engineering owner records it by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. The earlier ref and icon fixes are in. One wording fix is still needed either way:

  • The layout concept row (L76) and the Review checks row (L34) say SelectorOption folds inline anywhere inside an InputGroup, but only a Selector trigger value does (FR2), and the options in that Selector's list keep the caller's layout, so please scope both rows to the trigger value.
  • The End-user impact row (L31) should say a long string label ellipsizes, as FR3 does.

[Automated review]

| End-user impact | People see custom Selector options and the selected value in the same row shape as built-in options, and a long label ellipsizes instead of overflowing a narrow popover. |
| Builder impact | Builders return it from `renderOption` or `renderValue` to keep the `selector-option` target and the shared row layout while choosing the content. |
| Compatibility/readiness | Records shipped behavior; no API, default, or DOM change. The missing `ref` and DOM props are listed under Current-state impact. |
| Review checks | Reject a dropped icon, description, or end content, a layout that ignores the caller outside an InputGroup or stays stacked inside one, or a lost `selector-option` target. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a Selector trigger value folds inside an InputGroup (FR2; Selector.tsx provides the row-layout context around renderValue alone). The options in a grouped Selector's list keep the caller's layout, so "stays stacked inside one" would reject correct code. Suggest: "…or stays stacked as a Selector trigger value inside one".


| Concept | Closed values or states | Meaning | Availability by variant/orientation |
| ----------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------- |
| layout | `stacked`, `inline` | `stacked` puts the description on its own line; `inline` keeps label and description on one line. | Caller's choice, except inside an InputGroup (`inline`). |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This widens FR2: list options of a Selector inside an InputGroup, and a SelectorOption placed directly in one, stay the caller's layout. Suggest: "Caller's choice, except as a Selector trigger value inside an InputGroup (inline)."

| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Public contract | `SelectorOption` is exported beside Selector and takes `label` (required), `icon`, `description`, `layout` (`stacked` by default, or `inline`), `endContent`, `xstyle`, `className`, and `style`. |
| Behavior | Renders one Item row of an optional icon, the label, an optional description, and optional end content, with the row's own padding and corner radius removed so its host row lays it out. |
| End-user impact | People see custom Selector options and the selected value in the same row shape as built-in options, and a long label ellipsizes instead of overflowing a narrow popover. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Item truncates only string labels, so a ReactNode label wraps (as FR3 says). Suggest "a long string label ellipsizes".

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approval waits on an engineering owner's decision on the new component:SelectorOption record and the component:Selector ownership bullet, which state requirements the current specs do not yet hold; an engineering owner records it by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. The earlier wording fixes for the InputGroup fold and the string-label ellipsis are in, and the record now matches the shipped SelectorOption source and its cited tests, so no other change is needed.

[Automated review]

@cixzhang

cixzhang commented Nov 1, 2026

Copy link
Copy Markdown
Contributor Author

/approve-spec 117a4ff

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Nov 1, 2026
@cixzhang
cixzhang merged commit dc4dd03 into main Nov 1, 2026
51 checks passed

This branch was successfully deployed

1 active deployment
Preview — 117a4ffc Deployed Nov 1, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant