Skip to content

feat(Citation): expose the icon wrapper as the citation-icon theme target - #7780

Open
freddymeta wants to merge 1 commit into
mainfrom
feat/citation-icon-theme-target
Open

freddymeta wants to merge 1 commit into
mainfrom
feat/citation-icon-theme-target

Conversation

@freddymeta

@freddymeta freddymeta commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

User need

Theme authors who need to restyle Citation's source-icon ring. The decorative icon wrapper (styles.iconWrap) paints a 16px circle with a surface fill and a border, and it is the only Citation element without a theme target. A theme that wants a bare icon — or a different ring size, fill or radius — has no selector for it:

  • defineTheme emits one compound selector on one element, so a citation override cannot reach a child.
  • Re-scoping the wrapper's own vars on the root does not work: the ring reads --border-width / --color-border, which the label pill also uses, so neutralising them on the root strips the pill's border too.
  • Nothing wraps the element, so a consumer cannot supply a className.

The only remaining move is structural CSS keyed on the wrapper being the sole aria-hidden child: .astryx-citation > span[aria-hidden='true']. That selector depends on child order and an ARIA attribute, so an internal restructure or an a11y change breaks a shipped theme silently. This is the Citation row of #5417.

Problem and solution fit

The wrapper now renders astryx-citation-icon through the same mergeProps(themeProps(...), stylex.props(...)) path the root uses. One class, no new prop, no new API for a consumer to learn; the structural selector above becomes 'citation-icon': {base: {...}}.

No visual prop is reflected. The wrapper renders only in the label variant when a source icon is present and varies by nothing else, so a variant reflection would claim variation that does not exist.

Current specification

component:Citation (docs/specs/components/Citation/Citation.spec.md, now authority current, owner @cixzhang). Its FR6 reads "exposes no internal icon or text target without an approved theming-contract change" — this PR is that change, and the spec edit here is the proposed amendment for the owner to accept or rewrite: FR6 admits citation-icon on the decorative wrapper (and keeps the label text target-less), with matching updates to the contract table, AV1, the design-relationships row and the FR6 verification row, plus the checked ### Theming anatomy block (Container → citation, Icon → citation-icon, Label text / Number inherit). Same shape as the Banner frame (banner-frame) and FileInput upload-icon (file-input-icon) amendments.

Checked against the theming records and the wiki:

Rule How this change meets it
component-theming-surface INV2/INV3 — anatomy anchors targets citation-icon maps to the existing Icon anatomy entry; the anatomy block maps all four entries, check:knowledge enforces it (renaming the entry or the target fails the check — verified).
INV4 / Theming Infrastructure principle 1 / rubric T7 — target the element that paints The wrapper owns the ring: backgroundColor, borderWidth/Style/Color, borderRadius, width/height. It is not a positioning or event wrapper.
INV5 — composition preserves ownership The glyph inside stays caller-owned (<img> or a node); an Astryx <Icon> keeps astryx-icon.
INV6 / principle 2 / rubric T5, T26 — state on the owning target No state or variant reflected; the element never renders in the number variant, so no visualProps.
INV7–INV9 — guaranteed catalog The example above uses catalog keys only; no new guaranteed property is claimed.
Principle 3 — one name per concept citation-icon follows <component>-icon (banner-icon, file-input-icon, nav-icon — the last also sits on a wrapper span around a caller-supplied node).
Principle 4 — prefer inheritance Fill, border, radius and size do not inherit, so they cannot come from citation.
Principle 5 — no target to hide structure The wrapper stays in the DOM and keeps laying out the glyph; the override changes its paint.
Principle 6 — stated visual intent Theme the icon ring: fill, border, radius, size — a shipped theme's bare-icon treatment is the concrete case.
Principle 8 / API Conventions — naming citation + icon, kebab-cased.
Authoring Guide — sub-element has its own background/border distinct from the parent Yes; and re-scoping the shared tokens on the root is not possible because the label pill reads the same --border-width / --color-border.
Rubric T4 — every rendered class documented themingTargets.test.ts, with the negative control below.
Theming Infrastructure — targets are frozen once released Additive; citation untouched.

Public delta and caller burden

  • Theming: new target astryx-citation-icon (no visualProps), documented in Citation.doc.mjs theming.targets. Additive; citation unchanged.
  • Behavior / API / types / defaults / accessibility: none. The wrapper keeps aria-hidden="true" before the merged props, and its StyleX ring styles are preserved beside the class (tested).
  • Default rendering: unchanged — byte-identical screenshots against main (below).

Before (theme CSS a theme ships today):

.astryx-citation > span[aria-hidden='true'] { border: 0; border-radius: 0; background: none; }

After, with keys from the guaranteed-property catalog (architecture:component-theming-surface):

defineTheme({
  components: {
    'citation-icon': {base: {borderWidth: '0', borderRadius: '0', backgroundColor: 'transparent'}},
  },
});

width / height on the wrapper stay best-effort (outside the catalog, INV9); the wrapper is inline-flex, so a bare icon still gets its 16px box from the glyph.

Design and alternatives

  • Target on the wrapper, not the inner <img> / icon node: the ring is what paints and what themes need; the inner node is either the caller's or already astryx-icon (INV5).
  • No visualProps: ['variant']: the element never renders in the number variant, so the attribute would never vary.
  • Not a public CSS var: three independent properties are wanted (size, border, fill) and the generic styling pipeline already carries them once the element is reachable.

Evidence

Storybook core-citation--with-node-icon, Chromium (Playwright), main @ 381c0d272a vs this PR (re-measured after the rebase; numbers unchanged from the first run on 4a2f608f8e), same story loaded under the neutral and probe themes. The probe theme is the repo's generated fixture that styles every documented target with a distinct colour; regenerated here so it now carries citation-icon. The same regeneration also picks up command-palette-item's highlighted / selected / disabled states, which main documents but never regenerated into the fixture (pnpm visual:probe-theme:check fails on a clean main); the file is generated, so it is committed as the generator emits it rather than hand-trimmed.

Theme Element main PR
neutral wrapper background-color / border-color rgb(255,255,255) / rgba(0,0,0,.08) same
neutral wrapper border-width / border-radius / size 1px / 9999px / 16×16 same
neutral root background-color rgba(0,0,0,0) same
neutral screenshot md5 b72f819e5bf2… identical
probe wrapper background-color rgb(255,255,255) (no target) rgb(235,152,20) = probe citation-icon
probe wrapper border-color rgb(0,224,255) (token) rgb(110,34,17) = probe citation-icon
probe root background-color rgb(168,224,36) = probe citation variant:label same
probe .astryx-citation-icon count 0 1

core-citation--label (no icon): no .astryx-citation-icon on either build, screenshots identical under both themes.

EPS-shaped override through the real path (defineTheme + <Theme> in a scratch story, not committed): 'citation-icon': {width:'auto', height:'auto', border:'0', borderRadius:'0', background:'none'} → wrapper computed border-width: 0px, border-radius: 0px, background-color: rgba(0,0,0,0). Negative control: the same declarations on citation-glyph (a class Citation never renders) leave the wrapper at 1px / 9999px / rgb(255,255,255).

Citation icon target: main vs PR under neutral and probe themes; EPS-shaped override and its negative control

Image lives on the asset-only branch assets/pr-7780; delete it with the PR.

Guard negative controls:

  • Rename the class in Citation.doc.mjs to astryx-citation-glyph → themingTargets.test.ts fails: "core/Citation renders 1 astryx-* class(es) that … does not document: astryx-citation-icon".
  • Run the new tests against main's Citation.tsx → 4 of the 5 fail (the three icon sources and the StyleX-preserved check); the two placement guards pass on both, as they should.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Testing

  • pnpm build ✓, pnpm lint:strict ✓ (0 errors), pnpm -F @astryxdesign/core typecheck ✓, pnpm check:knowledge ✓, pnpm check:changesets ✓, pnpm visual:probe-theme:check ✓
  • pnpm exec vitest run packages/core/src/Citation packages/core/src/theme/themingTargets.test.ts → 462 passed (29 Citation, 433 guard)
  • Full pnpm test locally (shared, loaded box): every failure is a |node| CLI subprocess timeout at 30–120 s plus two |ui| files that pass in isolation (DropdownMenuLazySheet.test.tsx, Resizable/utils.test.ts); nothing in Citation, theme, or probe. Relying on CI for the full run.
  • Browser: Chromium via Playwright against Storybook dev builds of main and this branch; matrix above.

Writing rubric results

R1 narration: the compat row said the inert span "now uses" a naming role; "now" removed, every edited sentence is present tense.
R2 research: none in the record; the measurements live in this PR.
R3 evidence: FR6 verification now names the suites that check it, not a change; no cell names a pull request or commit.
R4 rejected alternatives: none added; the alternatives are in this description.
R5 change references: none (searched `#` + digits, `/pull/`, `/issues/`).
R6 open questions: none open; the "component-local theming surface" clause was removed because the record now answers it.

@vercel

vercel Bot commented Oct 21, 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 3, 2026 11:49am UTC

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 21, 2026
@astracat-bot

astracat-bot Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
❓ Needs maintainer input (for maintainers only) Nov 3, 2026, 12:03 PM UTC

@github-actions github-actions Bot added community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge needs:design-review Affects visuals — Design should review labels Oct 21, 2026
@freddymeta
freddymeta marked this pull request as ready for review October 21, 2026 02:47
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

🔎 Preview · ba77c5a

Storybook · Sandbox · built from this PR's head

Modified components

Component Size LoC Visual · A11y · RTL
Citation · Storybook 19.9 kB (+12 B) · 4.4 kB CSS (0) 239 (+5) — · — · —
lab/CommentThread 227.6 kB (+9 B) · 2.4 kB CSS (0) — — · — · —
Markdown 145.3 kB (+5 B) · 18.3 kB CSS (0) — — · — · —
1 more component
Component Size LoC Visual · A11y · RTL
lab/Comments 232.9 kB (+5 B) · 2.5 kB CSS (0) — — · — · —

Package size: @astryxdesign/core 6.11 MB packed (+66 B); no new dependencies.

API changes: core +1 · ~0 · −0

Committed API report behind this build: core 51 rows; run pnpm api:report.

1 API change (advisory)
Package Where Signature Usage
+ core Theme targets · target citation-icon (Citation) target citation-icon (Citation) defineTheme({components: {'citation-icon': {…}}})

Wide change (shared styles: packages/themes/probe/src/probeTheme.ts): add ci:run-visual, ci:run-a11y or ci:run-rtl to run them.


Generated by PR Enrichment workflow | View full report

@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 citation-icon target is implemented cleanly: the new tests and the theming-targets guard cover it, and unthemed Citation renders identically in light and dark mode. Approval now waits on a maintainer decision, not a code change. component:Citation is still a draft, and its FR6 says Citation exposes no icon target "without an approved theming-contract change", so a maintainer needs to approve or rewrite the proposed FR6, AV1 and ### Theming anatomy map, which settles whether citation-icon becomes a public theme target. Nothing else is needed from you for now.

[Automated review]

…rget

The decorative source-icon wrapper paints the icon ring (size, radius, fill, border) but carried no theme class, so a theme had to key on `.astryx-citation > span[aria-hidden='true']`. It now renders `astryx-citation-icon` through the same mergeProps(themeProps, stylex.props) path as the root. No visual prop is reflected: the wrapper renders only in the label variant when a source icon is present and varies by nothing else.

Default rendering is unchanged; the probe theme fixture is regenerated so the visual gate covers the new target.
@freddymeta
freddymeta force-pushed the feat/citation-icon-theme-target branch from 68d9528 to ba77c5a Compare November 3, 2026 11:39
@github-actions github-actions Bot added the needs:spec-owner-review Current knowledge records await owner approval label Nov 3, 2026
@freddymeta

Copy link
Copy Markdown
Contributor Author

Rebased onto main @ 381c0d272a (one commit, same seven files). Since this opened, #9132 promoted component:Citation to current with FR6 still excluding an icon target, so the spec amendment is re-applied at docs/specs/components/Citation/Citation.spec.md against the current record.

@cixzhang — the one open decision is yours: admit citation-icon on the decorative icon wrapper (FR6 / AV1 / Theming anatomy as proposed in the diff), or rule it out. Code, tests, probe fixture and browser evidence are unchanged on the new base; details in the description.

@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 pull request needs a maintainer check before review continues.

[Automated review]

This branch was successfully deployed

1 active deployment
Preview — ba77c5ac Deployed Nov 3, 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. community Authored by a community contributor (not on the eng/design team) needs:code-review High-risk change (new package/component/API) — needs human code review before merge needs:design-review Affects visuals — Design should review needs:spec-owner-review Current knowledge records await owner approval

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant