Skip to content

docs(spec): Badge records its shipped contract as current - #8263

Merged
cixzhang merged 3 commits into
mainfrom
spec-backfill/Badge
Oct 24, 2026
Merged

cixzhang merged 3 commits into
mainfrom
spec-backfill/Badge

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

Exact decision needed

Record Badge's shipped behavior as a current component contract (component:Badge). No behavior, API, or output change.

Existing authority searched

Decision

One record, packages/core/src/Badge/Badge.spec.md (authority: current, approved_by: cixzhang, approved_at: 2026-10-24). It owns:

  • the root, optional leading Icon slot, and Label container;
  • one-line geometry, typography, and the ellipsis overflow (FR1–FR3);
  • the title derived from a string or number label (FR4);
  • the built-in tones, their reflection on badge, and the unthemed fallback of a theme-added variant (FR5, FR6);
  • ref/passthrough/styling routing (FR7) and server-safe rendering (FR8, PR1);
  • static-text accessibility, meaning-in-text, delegated icon exposure, and full text in the DOM (AR1–AR4).

Rejected alternatives

None.

Claim scope and non-goals

Covers only Badge as shipped on main. Product choice of status/category and wording, the supplied icon's exposure (component:Icon), sibling presentations (component:Token, component:StatusDot), and theme token contrast stay with their owners. The theming-anatomy JSON block is omitted because Badge.doc.mjs anatomy lists no root part to carry the badge target; the record states the target mapping in prose. No new behavior is decided; OQ1 stays open.

Evidence and implementation relationship

  • FR1, FR3: Badge.tsx base and label styles (inline-flex, --spacing-5 height, --spacing-2 inline padding, --radius-full, supporting type, medium weight, nowrap, maxWidth: 100%, minWidth: 0, label overflow: hidden + text-overflow: ellipsis); Badge.stories.tsx "Long labels in tight space" fixture.
  • FR2: Badge.tsx render order (icon, then label span, gap: --spacing-1); Badge.test.tsx "renders with icon".
  • FR4: Badge.tsx labelTitle; Badge.test.tsx full-label suite (string, number, rich, empty).
  • FR5, FR6: Badge.tsx variant styles and themeProps('badge', {variant}); index.ts BadgeVariantMap; Badge.test.tsx data-variant assertions; extensibleAxes.test.ts and themingTargets.test.ts structural checks. A theme-added value indexes no entry in the variant style map, so only the base styles apply.
  • FR7: Badge.tsx mergeProps(themeProps, stylex.props(base, variant, xstyle), className, style) then title, then ...props; Badge.test.tsx ref and passthrough tests.
  • FR8, PR1: no hooks or client imports in Badge.tsx; serverSafeComponents.test.ts lists Badge as server-safe by import graph.
  • AR1–AR3: root is a plain span with no role, tabIndex, or live region; the icon is rendered as supplied.

Current-state impact

Writing rubric results

  • R1 narration: none; every row is present tense and states shipped behavior.
  • R2 research in the record: none.
  • R3 evidence naming a change: none; evidence names source, tests, and the story fixture.
  • R4 rejected alternatives over one line or outside a decision: none.
  • R5 pull request or issue references remaining: none (searched # + digits, /pull/, /issues/).
  • R6 ruled open questions still listing options: none; OQ1 is unanswered and keeps its options.

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 beyond the public title attribute and badge target.
  • 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

@vercel

vercel Bot commented Oct 24, 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 Oct 24, 2026 6:42pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Oct 24, 2026, 7:20 PM UTC

@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Oct 24, 2026
@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 24, 2026
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Current run: The current change has no visual or preview-producing scope. Visual checks are not applicable; preview links are not shown.

The exact-head preview is not available yet.


Generated by PR Enrichment workflow | View current CI run

@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.

This record matches Badge's shipped behavior for default usage, but as a new current contract it needs an engineering owner's decision before it governs: 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. Independently of that decision, three corrections are needed:

  • AR2 says tone color is never the only carrier of status, but the record allows an empty label, which renders a color-only badge with nothing in the accessibility tree; describe what Badge itself does and leave a non-empty label to the callsite.
  • AR1 says the root carries no role and is not focusable, while FR7 passes a caller's role and tabIndex through; say instead that Badge adds none of its own.
  • The verification map maps FR5 (the neutral default and tone paint) and FR6's unthemed fallback to tests that would not fail if those changed; narrow those rows and name the gaps.

[Automated review]

Comment thread packages/core/src/Badge/Badge.spec.md Outdated

## Accessibility contract

- **AR1 — Static text.** The root carries no role, is not focusable, and exposes

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AR1 says the root carries no role and is not focusable, but FR7 sends supported ARIA props to the root and BaseProps keeps role and tabIndex, so <Badge role="status" tabIndex={0} label="Syncing" /> is a focusable live region. Suggested wording: "Badge adds no role, tabIndex, or live region of its own; caller ARIA reaches the root under FR7."

Comment thread packages/core/src/Badge/Badge.spec.md Outdated
- **AR1 — Static text.** The root carries no role, is not focusable, and exposes
its label as ordinary text in reading order. Badge creates no live region, so
a changed label is not announced.
- **AR2 — Meaning in text.** The required label carries the badge's meaning;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

As written, AR2 promises the tone is never the only carrier of status, but the Label row allows an empty string, and <Badge variant="error" label="" /> renders a color-only pill with nothing in the accessibility tree. Consider stating what Badge does and leaving a non-empty label to the callsite (Badge.doc.mjs already says to always include a text label), or listing the empty-label case under Current-state impact.

Comment thread packages/core/src/Badge/Badge.spec.md Outdated

| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
| ------------- | --------------------------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- | ---------------------- |
| FR2, FR5, FR7 | `Badge.test.tsx` | Default, semantic and color tones, icon, ref | Dropping the icon, the ref, a passthrough prop, or variant reflection fails DOM assertions. | `audit:Badge/behavior` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No assertion in Badge.test.tsx fails if the default stops being neutral or a tone's paint changes: the tone tests only call getByText, and data-variant is checked only for purple and success. FR2's icon-before-label order isn't asserted either. Suggest narrowing this row to what the tests prove and naming FR5 and FR2's order as verification gaps.

Comment thread packages/core/src/Badge/Badge.spec.md Outdated
| FR2, FR5, FR7 | `Badge.test.tsx` | Default, semantic and color tones, icon, ref | Dropping the icon, the ref, a passthrough prop, or variant reflection fails DOM assertions. | `audit:Badge/behavior` |
| FR4, AR4 | `Badge.test.tsx` full-label suite | String, number, rich, and empty labels | Setting `title` from a rich or empty label, or dropping it for a string or number, fails. | `audit:Badge/a11y` |
| FR1, FR3 | `Badge.stories.tsx` long-label fixture in a browser | Long labels in tight space | A label that wraps or escapes the badge is visible in the rendered fixture. | `audit:Badge/layout` |
| FR6 | `extensibleAxes.test.ts`, `themingTargets.test.ts` | `BadgeVariantMap`, `badge` target | Removing `variant` from the target reflection or the doc's `visualProps` fails the structural checks. | `audit:Badge/theming` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

These structural tests say in their own header that they can't judge a fallback, and the verification for architecture:component-theming-surface INV14 also names a focused no-match fallback test, which Badge doesn't have yet. Suggest naming FR6's fallback sentence as a verification gap in this row.

@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.

The AR1, AR2 and verification-map updates resolve the earlier corrections, and the record matches Badge's shipped behavior, but as a new current contract it still needs an engineering owner's decision before it governs: 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. Independently of that decision, two record corrections remain:

  • The verification map has no row for AR1–AR3 and covers only the title half of AR4; name their evidence or the gap (none of the listed checks fails if Badge adds its own role, tab stop, live region or icon semantics), and note that FR2's icon-before-label order is untested.
  • The Review checks cell still says to reject "an interactive or focusable badge", while AR1 now lets a caller role or tabIndex through; reword it to "a badge that adds its own role, tab stop, or live region".

[Automated review]

Comment thread packages/core/src/Badge/Badge.spec.md Outdated
| End-user impact | Readers see a compact status or category label that stays inside its container; assistive technology reads the full label text. |
| Builder impact | None; the record describes shipped behavior. |
| Compatibility/readiness | Released behavior recorded as current; no API, default, or output changes. One implementation gap is listed under Current-state impact. |
| Review checks | Reject a badge that wraps or escapes its container, a `title` derived from a rich label, an interactive or focusable badge, or a variant that is not reflected on `badge`. |

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 projection still says to reject "an interactive or focusable badge", but AR1 (lines 146–148) now lets a caller role or tabIndex through under FR7, so the cell adds a rule the body does not have. Suggested wording: "a badge that adds its own role, tab stop, or live region".

Comment thread packages/core/src/Badge/Badge.spec.md Outdated
Comment on lines +185 to +190
| FR2, FR7 | `Badge.test.tsx` | Icon, ref, passthrough | Dropping the icon, the ref, or a passthrough prop fails DOM assertions. | `audit:Badge/behavior` |
| FR5 | `Badge.test.tsx` `data-variant` assertions; `Badge.stories.tsx` variant fixtures in a browser | `success` and `purple` reflection; every tone rendered | A lost `data-variant` for those tones fails. Gap: no automated check covers the `neutral` default's reflection or any tone's paint; a paint change is visible only in the rendered fixtures. | `audit:Badge/theming` |
| FR4, AR4 | `Badge.test.tsx` full-label suite | String, number, rich, and empty labels | Setting `title` from a rich or empty label, or dropping it for a string or number, fails. | `audit:Badge/a11y` |
| FR1, FR3 | `Badge.stories.tsx` long-label fixture in a browser | Long labels in tight space | A label that wraps or escapes the badge is visible in the rendered fixture. | `audit:Badge/layout` |
| FR6 | `extensibleAxes.test.ts`, `themingTargets.test.ts` | `BadgeVariantMap`, `badge` target | Removing `variant` from the target reflection or the doc's `visualProps` fails the structural checks. Gap: no test renders a theme-added value, so the unthemed fallback is verified by source review only. | `audit:Badge/theming` |
| FR8, PR1 | `serverSafeComponents.test.ts` | Import graph | Adding a hook or a client-only import to Badge fails the server-safety invariant. | `audit:Badge/behavior` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AR1–AR3 have no row in this map, and the FR4/AR4 row fails only when the title changes. None of the listed files fails if Badge adds its own role, tab stop, live region or icon semantics, or truncates the label text, so consider a row such as "AR1–AR3, AR4 DOM text | source review | Gap: no listed check fails if Badge adds its own role, tab stop, live region or icon semantics", and name FR2's icon-before-label order as a gap in the first row.

@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.

This record matches Badge's shipped behavior and the corrections from the earlier rounds are in, so nothing else needs to change in it. As a new current contract it still needs an engineering owner's decision before it governs: 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. Open #8236 changes the badge height and minimum width that FR1 and FR3 pin, so whichever of the two lands second should reconcile them.

[Automated review]

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 7c16ab1

@github-actions github-actions Bot added spec-auto-merge Auto-merge was enabled by the spec owner gate and removed needs:spec-owner-review Current knowledge records await owner approval labels Oct 24, 2026
@cixzhang
cixzhang merged commit 1eb6cce into main Oct 24, 2026
53 checks passed
@github-actions
github-actions Bot deleted the spec-backfill/Badge branch October 25, 2026 06:20

This branch was successfully deployed

1 active deployment
Preview — 7c16ab1c Deployed Oct 24, 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. spec-auto-merge Auto-merge was enabled by the spec owner gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant