Skip to content

docs(spec): Citation records its shipped contract as current - #9132

Merged
cixzhang merged 1 commit into
mainfrom
spec-backfill/Citation
Oct 31, 2026
Merged

cixzhang merged 1 commit into
mainfrom
spec-backfill/Citation

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

Exact decision needed

Promote component:Citation from draft to current, recording Citation's shipped behavior on main today. No behavior, API, or output change.

Existing authority searched

  • component:Citation on main (draft, template 6): FR1–FR7, AV1–AV3, ORD1–ORD3, PR1–PR2, AR1–AR4, and DEC-1 (inert references use role="group") were re-verified against Citation.tsx and kept, rewritten in the present tense in template 7. FR5 now records that a caller's explicit role or aria-label reaches the root, following the INV5 caller-wins conformance ruling that component:Avatar records; today's replacement, which a root-contract test pins, is an implementation gap. This is a conformance ruling, not a new decision.
  • family:navigation-destinations and spec:AST-005 (current) own the destination decision.
  • pnpm specs:for packages/core/src/Citation/Citation.tsx: directory-wide architecture records.
  • Overlapping open work: docs(knowledge): every component spec lives in docs/specs/components, one folder per component #9007 and feat(knowledge): check every specification in the repository as a knowledge record, and convert the native specs #8288 rebase on top of this record (the docs/specs/components/ move and the schema-5 migration). This record keeps the colocated, schema-3 path main uses today.
  • Open issues: none for Citation.

Decision

packages/core/src/Citation/Citation.spec.md: authority: current, approved_by: cixzhang, approved_at: 2026-10-31, owners unchanged, template 7, behavioral table headed "Invariant | Basis". DEC-1 is unchanged.

Rejected alternatives

None.

Claim scope and non-goals

Covers only Citation as shipped on main. Destination policy, image resource policy, ordering, and surrounding prose stay with their owners. No new behavior is decided.

Evidence and implementation relationship

  • FR1, FR2, AR1, AR2: destination suites in Citation.test.tsx and safeUrl.test.ts.
  • FR3, FR4, AR3: variant and icon suites (truncation untested, named as a gap).
  • FR5: root-contract suite.
  • FR6: class-name tests and themingTargets.test.ts (variant reflection untested).
  • FR7: accessible-name assertions (no non-English catalog run).
  • Stable visual baseline core-citation--label.

Current-state impact

  • FR5: a caller's explicit role or aria-label is replaced by the component's role and translated name (architecture:public-component-api INV5).

Writing rubric results

  • R1 narration: the draft's "restorative accessibility semantics plus observational documentation", "authoritative only after owner approval", "MUST" rows, and "verify" review-state cells are rewritten in the 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; DEC-1 keeps its one reconsideration line.
  • R5 pull request or issue references remaining: none (searched # + digits, /pull/, /issues/).
  • R6 ruled open questions still listing options: none; no open questions.

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.
  • 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 31, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
astryx Error Error Oct 31, 2026 1:53pm 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
@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) Oct 31, 2026, 2:02 PM UTC

@cixzhang
cixzhang marked this pull request as ready for review October 31, 2026 12:26
@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 of this record waits on an engineering owner's decision. FR5 now requires a caller's explicit role or aria-label to reach the Citation root, which reverses the draft's component-owned precedence, and current component records read architecture:public-component-api INV5 both ways. An engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request; the author may do so if they are an engineering owner. Whichever precedence is chosen, two fixes are needed:

  • FR5 conflicts with FR1, FR2, AR1, AR2, ORD3 and the Review checks row, which state Citation's role and name with no caller exception; scope those rows as component:Divider FR2 does, or restore the draft's FR5.
  • The FR3–FR4 verification row names truncation as the only gap, but no test covers src winning over a legacy string icon.

[Automated review]

| FR2 | A missing or rejected destination renders the selected citation presentation in a noninteractive span with no navigation or reference role. The span uses a supported naming role so its translated label reaches assistive technology. Rejection derives no navigation attributes from `source.url`, invents no disabled semantics, and does not hide the citation. | shipped source and tests; `family:navigation-destinations/FR4,FR7`; WAI-ARIA naming rules |
| FR3 | The label variant presents the source title, an optional decorative source icon, and truncation; the number variant presents the supplied number as the compact visible reference. | shipped source, docs, tests, and stories |
| FR4 | A node icon takes precedence over `src`, and `src` over a legacy string `icon`. Every icon path remains decorative inside an `aria-hidden` wrapper. | shipped compatibility behavior and tests |
| FR5 | Both root branches preserve supported DOM props, `className`, `style`, `xstyle`, `data-*` attributes, and the declared `HTMLElement` ref, and a caller's explicit `role` or `aria-label` reaches the root (`architecture:public-component-api` INV5). Implementation gap: the component's role and translated name replace a caller's values (Current-state impact). | `architecture:public-component-api/INV5,INV6,INV8`; regression 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.

FR5 now lets a caller's role or aria-label reach the root. FR1 and AR1 (doc-noteref and the translated name), FR2 and AR2 (role="group", no interactive role), ORD3 and the Review checks row still state Citation's own role and name with no caller exception. Scope those rows to apply absent a caller override, as component:Divider FR2 does, or keep the draft's component-owned precedence.

| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
| ---------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| FR1–FR2, AR1–AR2 | `Citation.test.tsx` destination suites and `safeUrl.test.ts` matrix | accepted, missing, blocked schemes, normalized blocked text | A blocked URL creates navigation, an accepted URL loses its destination or native affordances, or an inert reference loses its supported naming role. | `audit:Citation/navigation` |
| FR3–FR4, AR3 | `Citation.test.tsx` variant and icon suites | label, number, no icon, node icon, `src`, legacy string icon | The wrong visible variant renders, icon precedence changes, or decorative source paint reaches the accessibility tree. Gap: no test covers the label's truncation. | `audit:Citation/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 test renders src together with a string icon (Citation.test.tsx:267-344), so reversing that order in Citation.tsx:203-204 passes every test. Name it here as a gap beside truncation, or add the test.

@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 of this record waits on an engineering owner's decision. FR5 now lets a caller's explicit role or aria-label reach the Citation root, reversing the draft's component-owned precedence, and current component records read architecture:public-component-api INV5 both ways; an engineering owner records the decision by commenting /approve-spec <full head SHA> on this pull request, and the author may do so as an engineering owner. Either way, one fix remains:

  • FR2, AR2, the missing-or-rejected Representative states row and the Review checks row still forbid an interactive or reference role on an inert citation, which FR5 now lets a caller set; scope those clauses to what Citation itself adds, or restore the draft's FR5.

[Automated review]

- **AR1 — Linked reference.** An accepted destination exposes one native link with `doc-noteref` and the translated `Citation {number}: {title}` accessible name.
- **AR2 — Inert reference.** Missing or rejected destinations expose no interactive or navigation role or attributes. The inert span uses `role="group"` so its translated citation label remains an accessible name, including when the visible number has no source text.
- **AR1 — Linked reference.** An accepted destination exposes one native link with, absent a caller `role` or `aria-label` (FR5), `doc-noteref` and the translated `Citation {number}: {title}` accessible name.
- **AR2 — Inert reference.** Missing or rejected destinations expose no interactive or navigation role or attributes. Absent a caller `role` or `aria-label` (FR5), the inert span uses `role="group"` so its translated citation label remains an accessible name, including when the visible number has no source text.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AR2 still says an inert citation exposes "no interactive or navigation role or attributes", but FR5 (line 106) now lets a caller's role reach the span, so the root-contract test's own role: 'button' input would be required by FR5 and rejected here. FR2's first sentence (line 103), the missing-or-rejected Representative states row (line 122) and the Review checks row (line 48) have the same gap. Scope them to what Citation itself adds, or restore the draft's FR5.

@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 of this record waits on an engineering owner's decision: FR5 now lets a caller's explicit role or aria-label reach the Citation root, reversing the draft's component-owned precedence, and current component specs apply the public-component-api INV5 rule both ways. 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), or FR5 returns to component-owned precedence. Either way the record needs one more commit: reverting FR5 removes the caller exceptions, and keeping it needs these two fixes.

  • FR1, FR2, AR1, AR2 and the missing-or-rejected state row drop Citation's role whenever a caller passes role or aria-label, so a caller aria-label alone no longer keeps the role="group" that DEC-1 needs to name the span; condition each value on its own prop.
  • The FR1–FR2 verification row still counts a lost naming role as a failure even when the caller supplies its own role.

[Automated review]

| ID | Invariant | Basis |
| --- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| FR1 | An accepted `source.url` renders a native anchor with the unchanged destination, `target="_blank"`, `rel="noopener noreferrer"`, and, unless a caller supplies an explicit `role` or `aria-label` (FR5), `role="doc-noteref"` and the translated accessible name; its `title` is the source title, or the number when there is none. | shipped source and tests; `family:navigation-destinations/FR1,FR3–FR5` |
| FR2 | A missing or rejected destination renders the selected citation presentation in a noninteractive span; Citation adds no navigation or reference role to it. Unless a caller supplies an explicit `role` or `aria-label` (FR5), the span uses a supported naming role so its translated label reaches assistive technology. Rejection derives no navigation attributes from `source.url`, invents no disabled semantics, and does not hide the citation. | shipped source and tests; `family:navigation-destinations/FR4,FR7`; WAI-ARIA naming rules |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

"Unless a caller supplies an explicit role or aria-label" releases the naming role even when the caller passes only aria-label, but DEC-1 says a roleless span cannot be named, so that label would be lost. Condition the role on a caller role and the translated name on a caller aria-label (the same applies to FR1, AR1, AR2 and the missing-or-rejected state row).

| All | `Citation.stories.tsx`, component tests, typecheck, lint, and exact-head CI | all documented variants and representative source states | A required state becomes unreachable or gains a build, accessibility, visual, or test regression. | `audit:Citation/testing` |
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
| ---------------- | ----------------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- |
| FR1–FR2, AR1–AR2 | `Citation.test.tsx` destination suites and `safeUrl.test.ts` matrix | accepted, missing, blocked schemes, normalized blocked text | A blocked URL creates navigation, an accepted URL loses its destination or native affordances, or an inert reference loses its supported naming role. | `audit:Citation/navigation` |

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 failure signal still counts "an inert reference loses its supported naming role" with no caller exception, while FR5 and AR2 now require a caller role (the root-contract test's role: 'button') to replace group. Suggest "…when the caller supplies no role".

@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 of this record waits on an engineering owner's decision: FR5 now lets a caller's explicit role or aria-label reach the Citation root, reversing the draft's component-owned precedence, and current component specs apply the public-component-api INV5 rule both ways. 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), or FR5 returns to component-owned precedence. Each caller prop now replaces only its own value; if caller-wins stays, two rows still need that scoping:

  • The linked label and linked number Representative states rows still require the translated accessible name with no exception for a caller aria-label.

[Automated review]

Comment on lines +120 to +121
| linked label | Native anchor, source title, optional decorative icon, translated accessible name. | Source content and root theme paint. |
| linked number | Native anchor, numeric visible reference, translated accessible name. | Number and root theme paint. |

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 two rows still require the "translated accessible name" with no caller exception, while FR1, AR1 and FR5 now let a caller's aria-label replace it. If caller-wins stays, "translated accessible name absent a caller aria-label" keeps them consistent with the missing-or-rejected row below.

@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 of this record waits on an engineering owner's decision: FR5 lets a caller's explicit role or aria-label reach the Citation root, reversing the draft's component-owned precedence, and current component specs apply the public-component-api INV5 rule both ways. 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), or FR5 returns to component-owned precedence before approval. The caller exceptions are now scoped consistently across FR1, FR2, AR1, AR2, ORD3, the state rows and the Review checks row, and no other change is needed.

[Automated review]

@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec a921db6

@github-actions github-actions Bot removed the needs:spec-owner-review Current knowledge records await owner approval label Oct 31, 2026
@cixzhang
cixzhang merged commit 8f9fce1 into main Oct 31, 2026
50 of 51 checks passed
@github-actions
github-actions Bot deleted the spec-backfill/Citation branch November 1, 2026 06:05

This branch had an error being deployed

1 failed deployment
Preview — a921db6b Deployed Oct 31, 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