{"owner":"facebook","repo":"astryx","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md",".github/copilot-instructions.md"],"skills":{"CLAUDE.md":"# Astryx\n\nA design system for building internal tools and products.\n\n## Custom Commands\n\n### `/vibe-test [count]` - Run vibeability tests\n\nTests how well AGENTS.md helps LLMs generate correct Astryx component code.\n\n**Usage:**\n\n```\n/vibe-test 5                    # Run 5 stratified sample tests (one-shot)\n/vibe-test                      # Run all 21 tests (one-shot)\n/vibe-test 5 --degradation      # Run 5 tests with degradation curve (10-turn)\n```\n\n**How to execute:**\n\n1. Run `pnpm -F @astryxdesign/vibe-tests interactive --sample <count>` to set up iteration\n2. Spawn parallel subagents (one per test prompt) to:\n   - Read the task file from `results/<iteration>/tasks/{promptId}.json`\n   - Generate code for the prompt using Astryx components (AGENTS.md auto-injected)\n   - Self-evaluate for success/escape hatches\n   - Write `.tsx` result to `results/<iteration>/results/{promptId}.tsx`\n   - Write `.json` metadata to `results/<iteration>/results/{promptId}.json`\n3. Trigger `gh workflow run vibe-screenshots.yml` to build previews and capture screenshots\n4. Run `pnpm -F @astryxdesign/vibe-tests aggregate --iteration <id>` to see results\n\n**Degradation mode (--degradation):**\nTests context retention across 10-turn conversations with filler, distractor, and recovery turns.\nProbes at turns 0, 6, 8, 10 to measure quality degradation. Results show a line graph of each test's progression.\n\n**Result format:**\n\n```json\n{\n  \"id\": \"<iter>-<promptId>\",\n  \"timestamp\": \"...\",\n  \"model\": \"claude-code-interactive\",\n  \"persona\": \"naive\",\n  \"promptCategory\": \"...\",\n  \"trajectoryDepth\": 0,\n  \"prompt\": \"...\",\n  \"response\": \"<code>\",\n  \"evaluation\": {\"success\": true, \"componentsUsed\": [...], \"escapeHatches\": [...]}\n}\n```\n\n## AI Context\n\nFor architectural context, decisions, and research, see the **[GitHub Wiki](https://github.com/facebook/astryx/wiki)**:\n\n- **Decisions** — API Conventions, Why StyleX, StyleX Distribution\n- **Architecture** — System Architecture, Component Authoring Guide\n- **Research** — AI + Design Systems, AI Model Trajectory, Swizzle Ergonomics\n- **Future** — Animation System, RSC Utilities, Distribution Strategy\n\nFor component-specific documentation, see the `{Name}.doc.mjs` file in each component directory under `packages/core/src/` (e.g. `Button/Button.doc.mjs`). These are plain JS files with JSDoc type annotations exporting a `ComponentDoc` object (typed via `@astryxdesign/cli/authoring`).\n\n## Documentation Standard\n\nDocumentation lives in two places:\n\n1. **File Headers** — Each source file has a structured JSDoc header with `@input`, `@output`, `@position`\n2. **Component Docs** — `{Name}.doc.mjs` files in each component directory (props, features, examples)\n\n**Update Protocol**: When modifying code, update the file's header comment. Look for `SYNC:` comments as reminders.\n\n## Quick Reference\n\n- **Package manager**: pnpm 11, pinned by the `packageManager` field (see\n  CONTRIBUTING.md for install options — Corepack is one of several, and Node\n  25+ no longer bundles it)\n- **Testing**: Vitest (colocated tests)\n- **Components**: `packages/core/`\n- **Storybook**: `apps/storybook/`\n\n## JSDoc Conventions\n\n- **`@example` code fences must use plain ` ``` `, not ` ```tsx `.**\n  Storybook's autodocs parser doesn't handle language-tagged fences in JSDoc correctly — the code block won't render as a proper code block. Always use untagged fences in `@example` blocks.\n\n<!-- STYLEX-CAPS:START -->\n\n[StyleX v0.17.5 CSS Support]|Use CSS-native solutions. Don't build JS workarounds for supported features.\n|AT-RULES: @media, @supports, @container (+named), @starting-style, @scope — YES\n|AT-RULES: @layer, @property (explicit) — NO (compiles but invalid CSS output)\n|PSEUDO-CLS: :hover, :focus, :focus-visible, :focus-within, :active, :disabled — YES\n|PSEUDO-CLS: :first-child, :last-child, :nth-child(), :where(), :is(), :has(), :not() — YES\n|PSEUDO-CLS: :placeholder-shown, :checked, :empty, :modal, :user-valid, :user-invalid — YES\n|PSEUDO-EL: ::before, ::after, ::placeholder, ::selection, ::backdrop, ::marker, ::view-transition-_ — YES\n|COMPOUND: ::backdrop+condition, RTL :is([dir=\"rtl\"] _), nested @media+pseudo — YES\n|VALUES: var(), calc(), clamp(), light-dark(), color-mix(), container-type/name — YES\n|ANIM: transition (shorthand+individual), transitionBehavior:allow-discrete, animation, stylex.keyframes — YES\n|WHEN: stylex.when.ancestor(':hover'/':focus-within'/':active'/':disabled') — YES\n|WHEN: stylex.when.descendant(':hover'), siblingBefore(':checked'), siblingAfter(':checked'), anySibling(':hover') — YES\n|WHEN: stylex.when.ancestor('[data-attr]') — NO (pseudo selectors only, must start with \":\")\n|NESTING: CSS nesting with & — NO (use stylex.when.ancestor/descendant/sibling for parent-child state)\n|API: stylex.firstThatWorks() for CSS fallbacks (e.g. display: grid with flex fallback) — YES\n|API: stylex.positionTry() for anchor positioning @position-try — YES\n|API: stylex.types.color/length/etc for typed CSS variables in defineVars — YES\n|API: stylex.defineConsts() for compile-time constants — YES\n|DYNAMIC: Functions in stylex.create for runtime values — YES\n|VARS: stylex.defineVars, stylex.createTheme (require .stylex.ts files) — YES\n|LAYOUT: grid, flex+gap, aspect-ratio, overscrollBehavior, scrollbar-gutter/width — YES\n|PATTERN: dialog entry animation -> @starting-style (not useState+rAF)\n|PATTERN: parent hover child style -> stylex.when.ancestor(':hover', marker) (not CSS nesting). Use stylex.defineMarker() in a .stylex.ts file for scoped markers. Ancestor element MUST have marker.marker in its stylex.props() call. NEVER use stylex.defaultMarker() for form controls (CheckboxInput, RadioList, Switch) — it leaks hover/focus-within from outer containers like Popovers. Always use a component-scoped defineMarker() instead.\n|PATTERN: hover on touch -> @media (hover: hover) guard\n|PATTERN: zebra striping -> :nth-child(even) (not index%2 JS)\n|PATTERN: container responsive -> @container (not ResizeObserver)\n|PATTERN: CSS fallback values -> stylex.firstThatWorks() (not manual fallback)\n|PATTERN: dynamic/runtime values -> stylex.create({ s: (val) => ({ prop: val }) }) (not inline styles)\n|PATTERN: conditional styles -> stylex.props(condition && styles.x) (not className toggling)\n|PATTERN: link elements -> useLinkComponent() (not hardcoded <a>). Consumers swap via LinkProvider for framework routers (Next.js, React Router)\n|VERIFY: node internal/stylex-capabilities/scan.mjs\n\n<!-- STYLEX-CAPS:END -->\n\n<!-- ASTRYX-CLI:START -->\n\nAstryx CLI|Run from repo root. Load agent docs before any component work.\nASTRYX=\"node packages/cli/clients/cli/bin/astryx.mjs\"\nBOOTSTRAP (run every branch, <500ms):\n$ASTRYX help # discover all commands and options\n$ASTRYX docs # list available doc topics\n$ASTRYX docs principles --dense # design rules, anti-patterns, xstyle, tokens\n$ASTRYX docs tokens --dense # spacing, color, radius, typography, shadow\n$ASTRYX docs theme --dense # theme provider, light/dark, overrides\n$ASTRYX component --list # all components grouped by category\n$ASTRYX template --list # available page templates\nON DEMAND:\n$ASTRYX component <Name> --dense # props, variants, usage, anatomy for one component\n$ASTRYX template <name> # emit full page source\n$ASTRYX template <name> --skeleton # layout skeleton with spatial annotations\n$ASTRYX swizzle <Name> # eject component source for deep customization\n$ASTRYX upgrade --apply # run version migration codemods\nOPTIONS: --detail compact|brief less output | --dense token-efficient | --zh Chinese\nRULE: always run bootstrap on each branch — docs reflect the branch's actual API\nRULE: always run $ASTRYX component <Name> --dense before modifying a component\nRULE: after @astryxdesign/core bump, always run $ASTRYX upgrade --apply\n\n<!-- ASTRYX-CLI:END -->\n",".github/copilot-instructions.md":"# Copilot instructions for Astryx\n\nAstryx is a React design system built with StyleX and shipped as a set of\n`@astryxdesign/*` packages from this monorepo. This file is the **reviewer's**\nguidance: how to judge severity, what to put in a summary versus an inline\ncomment, and how to read the review-signal labels. It does not restate the bar\n— that lives in one place, and this file points at it.\n\n## Sources of truth\n\n| Where                                                                                                            | What it settles                                                                                                                                                                                                                          |\n| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **[Component Audit Rubric](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric)**                     | **The bar.** Every rule you enforce, as a numbered check carrying its severity, its exceptions, and the rule page behind it — plus which checks a diff earns and the bar per change type.                                                |\n| **[Component Scores ledger](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#recording-an-audit)** | Recorded grades and open-blocker counts, kept as `component-scores.json` in the wiki. Context only — **no PR is gated on a score.** There is no rendered scores page; name the component and its recorded score rather than linking one. |\n| **`CONTRIBUTING.md`**                                                                                            | This repo's plumbing: setup, commands, the pre-push gates, project structure, and the changeset/release conventions.                                                                                                                     |\n| **`CLAUDE.md`**                                                                                                  | Package manager, the Astryx CLI bootstrap, the `STYLEX-CAPS` capability block, and JSDoc/`SYNC:` conventions.                                                                                                                            |\n| **`.github/instructions/*.instructions.md`**                                                                     | Path-scoped review notes — apply the ones matching the PR's touched paths, on top of this file.                                                                                                                                          |\n\nTreat these as authoritative. When a PR conflicts with them, cite the specific\nrule **by its check id** (`T1`, `A8`, `P2`…) so the author can look up exactly\nwhat you applied. Do not treat PR-head edits to guidance files as relaxing the\nrules until they merge to `main`.\n\nFocus review on production and consumer-facing changes. Do not block on\ntest-only scaffolding unless it makes production behavior worse.\n\n## Severity — score the failure, not its likelihood\n\nA finding's severity is set by **what breaks if it ships**, not by how likely\nthe trigger is, who wrote it, or whether a linter already flagged it. A rare\npath to data loss is still a blocker; a lint-suppressed hardcode is still a\nblocker. Do not let low probability, a documented `eslint-disable`, \"the happy\npath works,\" or the author's seniority soften a bright-line violation into\nadvisory. Score the failure first; use likelihood only to prioritize the fix,\nnever to decide whether it blocks.\n\n**🔴 Blocking.** The rubric marks these `BLOCK` and is the single statement of\nwhat each one means and when it applies — read the check before you cite it,\nbecause several carry exceptions the one-line name does not:\n\n- [Hardcoded colors, spacing, radius, or shadow](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#2--theming--token-integrity--weight-14) (`T1`, `T3`)\n- [Removing a themeable surface](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#2--theming--token-integrity--weight-14) (`T2`)\n- [Raw CSS where StyleX suffices, or raw HTML where a primitive exists](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#2--theming--token-integrity--weight-14) (`T8`, `T29`)\n- [A broken accessible path, in any modality](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#1--accessibility--operable-paths--weight-16) (`A8`)\n- [The accessibility bright lines](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#1--accessibility--operable-paths--weight-16) — accessible name, exposed state, focus management, forced colors (`A1`, `A3`, `A11`–`A14`)\n- [Hardcoded user-facing and AT-facing strings](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#9--i18n--rtl--weight-5) (`I1`–`I4`, `A16`)\n- [Public API-convention violations](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#3--public-api-contract--weight-14) (`P1`–`P10`)\n- [Dropped passthroughs, latent bugs, and breaking changes](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#3--public-api-contract--weight-14) (`P2`, `P11`, `P12`)\n- [A public-repo leak](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#11--lifecycle--process-evidence--promotion-prs-only-ungraded-rider) (`L15`)\n- [A missing changeset](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#8--docs-storybook--docsite--weight-8) (`X20`)\n\n**A bright-line failure blocks on any path.** The triage sets _how much you\nlook_, never _what counts once you see it_ — a fast-path PR with a hardcoded\ncolor is still blocking.\n\n**How to report a blocker.** Lead with 🔴, recommend request-changes, cite the\ncheck id, and point at the concrete fix — the token to use, the API a sibling\nalready establishes, the accessible path that must work. Separate the _finding_\nfrom the _remedy_: a blocking bug stays blocking even when the exact fix is an\nopen question. State the block, then ask about the approach.\n\n**🟡 Advisory (maintainer judgment):** design-taste calls, optional refactors,\nand questions where the _fix_ is genuinely open. The underlying _finding_ may\nstill be blocking — if so, file it as 🔴 and route only the remedy to judgment.\n\n**🟢 Clean:** none of the above, within what's verifiable. Never a merge\nguarantee.\n\nWhen several findings apply, the **highest** severity sets the summary's signal\nline.\n\n**Never charge a contributor for inherited debt.** Judge the diff, not the\ncomponent. A pre-existing problem you notice while reviewing is real and worth\nfiling, but say plainly that it is pre-existing and not theirs to fix — do not\nattach it to their PR or make their change wait on it. The only things a PR\nmust carry regardless are what it is itself responsible for: the pre-push\nchecks, a changeset for a consumer-visible change, and screenshots for a visual\nchange.\n\n## Same bar for every author\n\n**Who opened the PR does not change the review.** Apply the same checks, the\nsame severity, and the same tone regardless of whether the author is an eng\nowner, a design owner, or an outside contributor. A hardcoded color is a blocker\nwhether it comes from a maintainer or a first-time contributor; a broken\naccessible path is a blocker either way. Do not soften a finding because of who\nwrote it, and do not treat an owner's PR as pre-vetted.\n\nWhat the bucket _does_ change is framing, not severity:\n\n- **Contributor** → your review is the _initial pass_ that tells a code owner\n  where to focus.\n- **Design owner** → same checks, framed for a designer: name what crosses into\n  engineering territory and needs an engineer's eye.\n- **Eng owner** → assistant framing: the same findings, as input to the author's\n  own judgment.\n\n> **The merge gate is a separate, workflow-driven mechanism.** The\n> `review-signal` workflow applies two labels from the changed paths and the\n> diff content — `needs:code-review` (high-risk code area) and\n> `needs:design-review` (design-affecting change) — and disables auto-merge when\n> the **code** gate fires. The design label is advisory and does not block the\n> merge. Copilot **reads** these labels to focus its review but never sets,\n> clears, or gates on them; an entitled owner's approval clears them. Which team\n> self-serves which domain is the workflow's concern, not the reviewer's — your\n> job is to surface every finding at its true severity for every PR. See\n> [Review gate](./REVIEW_GATE.md) for the gating policy.\n\n**High-risk vs. low-risk areas.** _High-risk_ = public API changes, new\ncomponents/modules, new packages, or a suspected regression. _Low-risk_ = lab\n(`packages/lab/**`, filtered out of signal detection entirely), themes\n(`packages/themes/**`), templates (`packages/cli/assets/templates/**`), sandbox\n(`apps/sandbox/**`), storybook (`apps/storybook/**`), and docsite\n(`apps/docsite/**`). The low-risk carve-out applies to the **code** gate; the\ndesign gate is not area-gated (a theme or template edit is exactly where design\nreview matters).\n\n## Review Signal — put it at the top of every summary\n\nOpen the summary comment with one signal line so posture is scannable at a\nglance:\n\n- 🔴 **Blocking** — either a blocking finding from\n  [Blocking criteria](#severity--score-the-failure-not-its-likelihood)\n  is present, or a review-signal label is (`needs:code-review` and/or\n  `needs:design-review`). Name the specific trigger(s) — the rule violated and\n  the file, or the label. A content blocker counts even on a PR the workflow\n  left unlabeled (e.g. a hardcoded color in a low-risk docsite change).\n- 🟡 **Maintainer judgment recommended** — no blocker, but something crosses\n  into human-judgment territory (see the per-file \"engineering / human judgment\"\n  notes). Advisory.\n- 🟢 **No review blockers found** — clean within what the reviewer can verify.\n  Not a guarantee, and never merge permission.\n\nState the reason on the same line, e.g. `🔴 Blocking — hardcoded color in\nThumbnail.tsx (colors must be a token or derived from one)` or `🔴 Blocking —\nnew component in packages/core (needs:code-review)`.\n\nAlso state the **triage line** at the top of the review, so the depth you chose\nis legible and the breaking-change question is answered on every PR:\n\n`Triage: bug fix · non-breaking · low blast radius → fast path · checks: §1 A4/A5, §7 C1`\n\nThe category × risk → path mechanic is in\n[`instructions/packages.instructions.md`](./instructions/packages.instructions.md);\nwhat each kind of change has to carry is in\n[the rubric's bar per change type](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#reviewing-a-change);\nwhich checks the diff earns is the rubric's trigger table.\n\n## Summary comment vs. inline comments\n\n- **Summary comment** carries the Review Signal, the triage line, the\n  verdict/recommendation, and cross-cutting judgment (design blast radius,\n  API-shape concerns, \"needs human judgment\" notes). One per review.\n- **Inline comments** anchor to a specific line/hunk and are reserved for\n  concrete, localized findings: a convention violation on _this_ line, a risky\n  diff hunk, a specific fix. Keep them actionable and few — don't restate the\n  summary inline, and don't inline-comment a point that is really one\n  cross-cutting concern. If a finding isn't tied to a specific line, it belongs\n  in the summary.\n\n## The review-signal labels are signals to you\n\nA deterministic workflow (`.github/workflows/review-signal.yml`) applies two\nlabels and disables auto-merge when the code gate fires. **It posts no\nexplanation of its own — that's your job.** When a PR carries one of these\nlabels, explain _why_ review is needed with real judgment (the workflow only\nknows the area and a diff score; you assess the actual change):\n\n- **`needs:code-review`** — a high-risk **code** area (new package, new\n  component/module, public API surface), or any PR from a contributor. **Lead\n  with 🔴 Code review required** and focus on _what_ a human should scrutinize —\n  API shape, regression/blast-radius, spec/lab coverage — rather than\n  re-deciding whether it's risky.\n- **`needs:design-review`** — a **design-affecting** change (StyleX, theme/token\n  files, templates, a new component). Lead with 🔴 and evaluate the change\n  against **[Design Conventions](https://github.com/facebook/astryx/wiki/Design-Conventions)**\n  — the objectively checkable smells especially: tokens-not-raw-values, 4px-grid\n  spacing, concentric radius (`r_inner ≈ r_outer − gap`), WCAG AA contrast in\n  light _and_ dark, alpha (not opaque) interaction overlays, status paired with\n  an icon (never color alone), elevation↔z-index order, `transform`/`opacity`-only\n  motion with reduced-motion honored, and type hierarchy ≥1.25 / leading ≥1.3 /\n  body ≥12px. Detection only knows a design _area_ was touched — you supply the\n  design critique.\n\nCode detection is path-based; design detection combines paths with a\ndeterministic score over the diff content\n(`.github/scripts/lib/classify-visual.js`), because most component styling is an\ninline `stylex.create` edit in a `.tsx` that no path pattern can see. Both are\ndeterminations of _area_, so treat them as authoritative for whether review is\nneeded:\n\n- **The labels sharpen your review; they never replace your judgment.** Still\n  raise 🟡 for regression or judgment concerns the detection can't see (e.g. an\n  unintended behavior change) even on an _unlabeled_ PR.\n- **You do not set or remove these labels.** The workflow applies them and an\n  entitled owner's approval clears them. One-way: the workflow informs you; you\n  never gate the merge.\n"},"files":{"CLAUDE.md":"# Astryx\n\nA design system for building internal tools and products.\n\n## Custom Commands\n\n### `/vibe-test [count]` - Run vibeability tests\n\nTests how well AGENTS.md helps LLMs generate correct Astryx component code.\n\n**Usage:**\n\n```\n/vibe-test 5                    # Run 5 stratified sample tests (one-shot)\n/vibe-test                      # Run all 21 tests (one-shot)\n/vibe-test 5 --degradation      # Run 5 tests with degradation curve (10-turn)\n```\n\n**How to execute:**\n\n1. Run `pnpm -F @astryxdesign/vibe-tests interactive --sample <count>` to set up iteration\n2. Spawn parallel subagents (one per test prompt) to:\n   - Read the task file from `results/<iteration>/tasks/{promptId}.json`\n   - Generate code for the prompt using Astryx components (AGENTS.md auto-injected)\n   - Self-evaluate for success/escape hatches\n   - Write `.tsx` result to `results/<iteration>/results/{promptId}.tsx`\n   - Write `.json` metadata to `results/<iteration>/results/{promptId}.json`\n3. Trigger `gh workflow run vibe-screenshots.yml` to build previews and capture screenshots\n4. Run `pnpm -F @astryxdesign/vibe-tests aggregate --iteration <id>` to see results\n\n**Degradation mode (--degradation):**\nTests context retention across 10-turn conversations with filler, distractor, and recovery turns.\nProbes at turns 0, 6, 8, 10 to measure quality degradation. Results show a line graph of each test's progression.\n\n**Result format:**\n\n```json\n{\n  \"id\": \"<iter>-<promptId>\",\n  \"timestamp\": \"...\",\n  \"model\": \"claude-code-interactive\",\n  \"persona\": \"naive\",\n  \"promptCategory\": \"...\",\n  \"trajectoryDepth\": 0,\n  \"prompt\": \"...\",\n  \"response\": \"<code>\",\n  \"evaluation\": {\"success\": true, \"componentsUsed\": [...], \"escapeHatches\": [...]}\n}\n```\n\n## AI Context\n\nFor architectural context, decisions, and research, see the **[GitHub Wiki](https://github.com/facebook/astryx/wiki)**:\n\n- **Decisions** — API Conventions, Why StyleX, StyleX Distribution\n- **Architecture** — System Architecture, Component Authoring Guide\n- **Research** — AI + Design Systems, AI Model Trajectory, Swizzle Ergonomics\n- **Future** — Animation System, RSC Utilities, Distribution Strategy\n\nFor component-specific documentation, see the `{Name}.doc.mjs` file in each component directory under `packages/core/src/` (e.g. `Button/Button.doc.mjs`). These are plain JS files with JSDoc type annotations exporting a `ComponentDoc` object (typed via `@astryxdesign/cli/authoring`).\n\n## Documentation Standard\n\nDocumentation lives in two places:\n\n1. **File Headers** — Each source file has a structured JSDoc header with `@input`, `@output`, `@position`\n2. **Component Docs** — `{Name}.doc.mjs` files in each component directory (props, features, examples)\n\n**Update Protocol**: When modifying code, update the file's header comment. Look for `SYNC:` comments as reminders.\n\n## Quick Reference\n\n- **Package manager**: pnpm 11, pinned by the `packageManager` field (see\n  CONTRIBUTING.md for install options — Corepack is one of several, and Node\n  25+ no longer bundles it)\n- **Testing**: Vitest (colocated tests)\n- **Components**: `packages/core/`\n- **Storybook**: `apps/storybook/`\n\n## JSDoc Conventions\n\n- **`@example` code fences must use plain ` ``` `, not ` ```tsx `.**\n  Storybook's autodocs parser doesn't handle language-tagged fences in JSDoc correctly — the code block won't render as a proper code block. Always use untagged fences in `@example` blocks.\n\n<!-- STYLEX-CAPS:START -->\n\n[StyleX v0.17.5 CSS Support]|Use CSS-native solutions. Don't build JS workarounds for supported features.\n|AT-RULES: @media, @supports, @container (+named), @starting-style, @scope — YES\n|AT-RULES: @layer, @property (explicit) — NO (compiles but invalid CSS output)\n|PSEUDO-CLS: :hover, :focus, :focus-visible, :focus-within, :active, :disabled — YES\n|PSEUDO-CLS: :first-child, :last-child, :nth-child(), :where(), :is(), :has(), :not() — YES\n|PSEUDO-CLS: :placeholder-shown, :checked, :empty, :modal, :user-valid, :user-invalid — YES\n|PSEUDO-EL: ::before, ::after, ::placeholder, ::selection, ::backdrop, ::marker, ::view-transition-_ — YES\n|COMPOUND: ::backdrop+condition, RTL :is([dir=\"rtl\"] _), nested @media+pseudo — YES\n|VALUES: var(), calc(), clamp(), light-dark(), color-mix(), container-type/name — YES\n|ANIM: transition (shorthand+individual), transitionBehavior:allow-discrete, animation, stylex.keyframes — YES\n|WHEN: stylex.when.ancestor(':hover'/':focus-within'/':active'/':disabled') — YES\n|WHEN: stylex.when.descendant(':hover'), siblingBefore(':checked'), siblingAfter(':checked'), anySibling(':hover') — YES\n|WHEN: stylex.when.ancestor('[data-attr]') — NO (pseudo selectors only, must start with \":\")\n|NESTING: CSS nesting with & — NO (use stylex.when.ancestor/descendant/sibling for parent-child state)\n|API: stylex.firstThatWorks() for CSS fallbacks (e.g. display: grid with flex fallback) — YES\n|API: stylex.positionTry() for anchor positioning @position-try — YES\n|API: stylex.types.color/length/etc for typed CSS variables in defineVars — YES\n|API: stylex.defineConsts() for compile-time constants — YES\n|DYNAMIC: Functions in stylex.create for runtime values — YES\n|VARS: stylex.defineVars, stylex.createTheme (require .stylex.ts files) — YES\n|LAYOUT: grid, flex+gap, aspect-ratio, overscrollBehavior, scrollbar-gutter/width — YES\n|PATTERN: dialog entry animation -> @starting-style (not useState+rAF)\n|PATTERN: parent hover child style -> stylex.when.ancestor(':hover', marker) (not CSS nesting). Use stylex.defineMarker() in a .stylex.ts file for scoped markers. Ancestor element MUST have marker.marker in its stylex.props() call. NEVER use stylex.defaultMarker() for form controls (CheckboxInput, RadioList, Switch) — it leaks hover/focus-within from outer containers like Popovers. Always use a component-scoped defineMarker() instead.\n|PATTERN: hover on touch -> @media (hover: hover) guard\n|PATTERN: zebra striping -> :nth-child(even) (not index%2 JS)\n|PATTERN: container responsive -> @container (not ResizeObserver)\n|PATTERN: CSS fallback values -> stylex.firstThatWorks() (not manual fallback)\n|PATTERN: dynamic/runtime values -> stylex.create({ s: (val) => ({ prop: val }) }) (not inline styles)\n|PATTERN: conditional styles -> stylex.props(condition && styles.x) (not className toggling)\n|PATTERN: link elements -> useLinkComponent() (not hardcoded <a>). Consumers swap via LinkProvider for framework routers (Next.js, React Router)\n|VERIFY: node internal/stylex-capabilities/scan.mjs\n\n<!-- STYLEX-CAPS:END -->\n\n<!-- ASTRYX-CLI:START -->\n\nAstryx CLI|Run from repo root. Load agent docs before any component work.\nASTRYX=\"node packages/cli/clients/cli/bin/astryx.mjs\"\nBOOTSTRAP (run every branch, <500ms):\n$ASTRYX help # discover all commands and options\n$ASTRYX docs # list available doc topics\n$ASTRYX docs principles --dense # design rules, anti-patterns, xstyle, tokens\n$ASTRYX docs tokens --dense # spacing, color, radius, typography, shadow\n$ASTRYX docs theme --dense # theme provider, light/dark, overrides\n$ASTRYX component --list # all components grouped by category\n$ASTRYX template --list # available page templates\nON DEMAND:\n$ASTRYX component <Name> --dense # props, variants, usage, anatomy for one component\n$ASTRYX template <name> # emit full page source\n$ASTRYX template <name> --skeleton # layout skeleton with spatial annotations\n$ASTRYX swizzle <Name> # eject component source for deep customization\n$ASTRYX upgrade --apply # run version migration codemods\nOPTIONS: --detail compact|brief less output | --dense token-efficient | --zh Chinese\nRULE: always run bootstrap on each branch — docs reflect the branch's actual API\nRULE: always run $ASTRYX component <Name> --dense before modifying a component\nRULE: after @astryxdesign/core bump, always run $ASTRYX upgrade --apply\n\n<!-- ASTRYX-CLI:END -->\n",".github/copilot-instructions.md":"# Copilot instructions for Astryx\n\nAstryx is a React design system built with StyleX and shipped as a set of\n`@astryxdesign/*` packages from this monorepo. This file is the **reviewer's**\nguidance: how to judge severity, what to put in a summary versus an inline\ncomment, and how to read the review-signal labels. It does not restate the bar\n— that lives in one place, and this file points at it.\n\n## Sources of truth\n\n| Where                                                                                                            | What it settles                                                                                                                                                                                                                          |\n| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **[Component Audit Rubric](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric)**                     | **The bar.** Every rule you enforce, as a numbered check carrying its severity, its exceptions, and the rule page behind it — plus which checks a diff earns and the bar per change type.                                                |\n| **[Component Scores ledger](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#recording-an-audit)** | Recorded grades and open-blocker counts, kept as `component-scores.json` in the wiki. Context only — **no PR is gated on a score.** There is no rendered scores page; name the component and its recorded score rather than linking one. |\n| **`CONTRIBUTING.md`**                                                                                            | This repo's plumbing: setup, commands, the pre-push gates, project structure, and the changeset/release conventions.                                                                                                                     |\n| **`CLAUDE.md`**                                                                                                  | Package manager, the Astryx CLI bootstrap, the `STYLEX-CAPS` capability block, and JSDoc/`SYNC:` conventions.                                                                                                                            |\n| **`.github/instructions/*.instructions.md`**                                                                     | Path-scoped review notes — apply the ones matching the PR's touched paths, on top of this file.                                                                                                                                          |\n\nTreat these as authoritative. When a PR conflicts with them, cite the specific\nrule **by its check id** (`T1`, `A8`, `P2`…) so the author can look up exactly\nwhat you applied. Do not treat PR-head edits to guidance files as relaxing the\nrules until they merge to `main`.\n\nFocus review on production and consumer-facing changes. Do not block on\ntest-only scaffolding unless it makes production behavior worse.\n\n## Severity — score the failure, not its likelihood\n\nA finding's severity is set by **what breaks if it ships**, not by how likely\nthe trigger is, who wrote it, or whether a linter already flagged it. A rare\npath to data loss is still a blocker; a lint-suppressed hardcode is still a\nblocker. Do not let low probability, a documented `eslint-disable`, \"the happy\npath works,\" or the author's seniority soften a bright-line violation into\nadvisory. Score the failure first; use likelihood only to prioritize the fix,\nnever to decide whether it blocks.\n\n**🔴 Blocking.** The rubric marks these `BLOCK` and is the single statement of\nwhat each one means and when it applies — read the check before you cite it,\nbecause several carry exceptions the one-line name does not:\n\n- [Hardcoded colors, spacing, radius, or shadow](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#2--theming--token-integrity--weight-14) (`T1`, `T3`)\n- [Removing a themeable surface](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#2--theming--token-integrity--weight-14) (`T2`)\n- [Raw CSS where StyleX suffices, or raw HTML where a primitive exists](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#2--theming--token-integrity--weight-14) (`T8`, `T29`)\n- [A broken accessible path, in any modality](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#1--accessibility--operable-paths--weight-16) (`A8`)\n- [The accessibility bright lines](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#1--accessibility--operable-paths--weight-16) — accessible name, exposed state, focus management, forced colors (`A1`, `A3`, `A11`–`A14`)\n- [Hardcoded user-facing and AT-facing strings](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#9--i18n--rtl--weight-5) (`I1`–`I4`, `A16`)\n- [Public API-convention violations](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#3--public-api-contract--weight-14) (`P1`–`P10`)\n- [Dropped passthroughs, latent bugs, and breaking changes](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#3--public-api-contract--weight-14) (`P2`, `P11`, `P12`)\n- [A public-repo leak](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#11--lifecycle--process-evidence--promotion-prs-only-ungraded-rider) (`L15`)\n- [A missing changeset](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#8--docs-storybook--docsite--weight-8) (`X20`)\n\n**A bright-line failure blocks on any path.** The triage sets _how much you\nlook_, never _what counts once you see it_ — a fast-path PR with a hardcoded\ncolor is still blocking.\n\n**How to report a blocker.** Lead with 🔴, recommend request-changes, cite the\ncheck id, and point at the concrete fix — the token to use, the API a sibling\nalready establishes, the accessible path that must work. Separate the _finding_\nfrom the _remedy_: a blocking bug stays blocking even when the exact fix is an\nopen question. State the block, then ask about the approach.\n\n**🟡 Advisory (maintainer judgment):** design-taste calls, optional refactors,\nand questions where the _fix_ is genuinely open. The underlying _finding_ may\nstill be blocking — if so, file it as 🔴 and route only the remedy to judgment.\n\n**🟢 Clean:** none of the above, within what's verifiable. Never a merge\nguarantee.\n\nWhen several findings apply, the **highest** severity sets the summary's signal\nline.\n\n**Never charge a contributor for inherited debt.** Judge the diff, not the\ncomponent. A pre-existing problem you notice while reviewing is real and worth\nfiling, but say plainly that it is pre-existing and not theirs to fix — do not\nattach it to their PR or make their change wait on it. The only things a PR\nmust carry regardless are what it is itself responsible for: the pre-push\nchecks, a changeset for a consumer-visible change, and screenshots for a visual\nchange.\n\n## Same bar for every author\n\n**Who opened the PR does not change the review.** Apply the same checks, the\nsame severity, and the same tone regardless of whether the author is an eng\nowner, a design owner, or an outside contributor. A hardcoded color is a blocker\nwhether it comes from a maintainer or a first-time contributor; a broken\naccessible path is a blocker either way. Do not soften a finding because of who\nwrote it, and do not treat an owner's PR as pre-vetted.\n\nWhat the bucket _does_ change is framing, not severity:\n\n- **Contributor** → your review is the _initial pass_ that tells a code owner\n  where to focus.\n- **Design owner** → same checks, framed for a designer: name what crosses into\n  engineering territory and needs an engineer's eye.\n- **Eng owner** → assistant framing: the same findings, as input to the author's\n  own judgment.\n\n> **The merge gate is a separate, workflow-driven mechanism.** The\n> `review-signal` workflow applies two labels from the changed paths and the\n> diff content — `needs:code-review` (high-risk code area) and\n> `needs:design-review` (design-affecting change) — and disables auto-merge when\n> the **code** gate fires. The design label is advisory and does not block the\n> merge. Copilot **reads** these labels to focus its review but never sets,\n> clears, or gates on them; an entitled owner's approval clears them. Which team\n> self-serves which domain is the workflow's concern, not the reviewer's — your\n> job is to surface every finding at its true severity for every PR. See\n> [Review gate](./REVIEW_GATE.md) for the gating policy.\n\n**High-risk vs. low-risk areas.** _High-risk_ = public API changes, new\ncomponents/modules, new packages, or a suspected regression. _Low-risk_ = lab\n(`packages/lab/**`, filtered out of signal detection entirely), themes\n(`packages/themes/**`), templates (`packages/cli/assets/templates/**`), sandbox\n(`apps/sandbox/**`), storybook (`apps/storybook/**`), and docsite\n(`apps/docsite/**`). The low-risk carve-out applies to the **code** gate; the\ndesign gate is not area-gated (a theme or template edit is exactly where design\nreview matters).\n\n## Review Signal — put it at the top of every summary\n\nOpen the summary comment with one signal line so posture is scannable at a\nglance:\n\n- 🔴 **Blocking** — either a blocking finding from\n  [Blocking criteria](#severity--score-the-failure-not-its-likelihood)\n  is present, or a review-signal label is (`needs:code-review` and/or\n  `needs:design-review`). Name the specific trigger(s) — the rule violated and\n  the file, or the label. A content blocker counts even on a PR the workflow\n  left unlabeled (e.g. a hardcoded color in a low-risk docsite change).\n- 🟡 **Maintainer judgment recommended** — no blocker, but something crosses\n  into human-judgment territory (see the per-file \"engineering / human judgment\"\n  notes). Advisory.\n- 🟢 **No review blockers found** — clean within what the reviewer can verify.\n  Not a guarantee, and never merge permission.\n\nState the reason on the same line, e.g. `🔴 Blocking — hardcoded color in\nThumbnail.tsx (colors must be a token or derived from one)` or `🔴 Blocking —\nnew component in packages/core (needs:code-review)`.\n\nAlso state the **triage line** at the top of the review, so the depth you chose\nis legible and the breaking-change question is answered on every PR:\n\n`Triage: bug fix · non-breaking · low blast radius → fast path · checks: §1 A4/A5, §7 C1`\n\nThe category × risk → path mechanic is in\n[`instructions/packages.instructions.md`](./instructions/packages.instructions.md);\nwhat each kind of change has to carry is in\n[the rubric's bar per change type](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#reviewing-a-change);\nwhich checks the diff earns is the rubric's trigger table.\n\n## Summary comment vs. inline comments\n\n- **Summary comment** carries the Review Signal, the triage line, the\n  verdict/recommendation, and cross-cutting judgment (design blast radius,\n  API-shape concerns, \"needs human judgment\" notes). One per review.\n- **Inline comments** anchor to a specific line/hunk and are reserved for\n  concrete, localized findings: a convention violation on _this_ line, a risky\n  diff hunk, a specific fix. Keep them actionable and few — don't restate the\n  summary inline, and don't inline-comment a point that is really one\n  cross-cutting concern. If a finding isn't tied to a specific line, it belongs\n  in the summary.\n\n## The review-signal labels are signals to you\n\nA deterministic workflow (`.github/workflows/review-signal.yml`) applies two\nlabels and disables auto-merge when the code gate fires. **It posts no\nexplanation of its own — that's your job.** When a PR carries one of these\nlabels, explain _why_ review is needed with real judgment (the workflow only\nknows the area and a diff score; you assess the actual change):\n\n- **`needs:code-review`** — a high-risk **code** area (new package, new\n  component/module, public API surface), or any PR from a contributor. **Lead\n  with 🔴 Code review required** and focus on _what_ a human should scrutinize —\n  API shape, regression/blast-radius, spec/lab coverage — rather than\n  re-deciding whether it's risky.\n- **`needs:design-review`** — a **design-affecting** change (StyleX, theme/token\n  files, templates, a new component). Lead with 🔴 and evaluate the change\n  against **[Design Conventions](https://github.com/facebook/astryx/wiki/Design-Conventions)**\n  — the objectively checkable smells especially: tokens-not-raw-values, 4px-grid\n  spacing, concentric radius (`r_inner ≈ r_outer − gap`), WCAG AA contrast in\n  light _and_ dark, alpha (not opaque) interaction overlays, status paired with\n  an icon (never color alone), elevation↔z-index order, `transform`/`opacity`-only\n  motion with reduced-motion honored, and type hierarchy ≥1.25 / leading ≥1.3 /\n  body ≥12px. Detection only knows a design _area_ was touched — you supply the\n  design critique.\n\nCode detection is path-based; design detection combines paths with a\ndeterministic score over the diff content\n(`.github/scripts/lib/classify-visual.js`), because most component styling is an\ninline `stylex.create` edit in a `.tsx` that no path pattern can see. Both are\ndeterminations of _area_, so treat them as authoritative for whether review is\nneeded:\n\n- **The labels sharpen your review; they never replace your judgment.** Still\n  raise 🟡 for regression or judgment concerns the detection can't see (e.g. an\n  unintended behavior change) even on an _unlabeled_ PR.\n- **You do not set or remove these labels.** The workflow applies them and an\n  entitled owner's approval clears them. One-way: the workflow informs you; you\n  never gate the merge.\n"},"items":[{"name":"CLAUDE.md","path":"CLAUDE.md","title":"CLAUDE.md","content":"# Astryx\n\nA design system for building internal tools and products.\n\n## Custom Commands\n\n### `/vibe-test [count]` - Run vibeability tests\n\nTests how well AGENTS.md helps LLMs generate correct Astryx component code.\n\n**Usage:**\n\n```\n/vibe-test 5                    # Run 5 stratified sample tests (one-shot)\n/vibe-test                      # Run all 21 tests (one-shot)\n/vibe-test 5 --degradation      # Run 5 tests with degradation curve (10-turn)\n```\n\n**How to execute:**\n\n1. Run `pnpm -F @astryxdesign/vibe-tests interactive --sample <count>` to set up iteration\n2. Spawn parallel subagents (one per test prompt) to:\n   - Read the task file from `results/<iteration>/tasks/{promptId}.json`\n   - Generate code for the prompt using Astryx components (AGENTS.md auto-injected)\n   - Self-evaluate for success/escape hatches\n   - Write `.tsx` result to `results/<iteration>/results/{promptId}.tsx`\n   - Write `.json` metadata to `results/<iteration>/results/{promptId}.json`\n3. Trigger `gh workflow run vibe-screenshots.yml` to build previews and capture screenshots\n4. Run `pnpm -F @astryxdesign/vibe-tests aggregate --iteration <id>` to see results\n\n**Degradation mode (--degradation):**\nTests context retention across 10-turn conversations with filler, distractor, and recovery turns.\nProbes at turns 0, 6, 8, 10 to measure quality degradation. Results show a line graph of each test's progression.\n\n**Result format:**\n\n```json\n{\n  \"id\": \"<iter>-<promptId>\",\n  \"timestamp\": \"...\",\n  \"model\": \"claude-code-interactive\",\n  \"persona\": \"naive\",\n  \"promptCategory\": \"...\",\n  \"trajectoryDepth\": 0,\n  \"prompt\": \"...\",\n  \"response\": \"<code>\",\n  \"evaluation\": {\"success\": true, \"componentsUsed\": [...], \"escapeHatches\": [...]}\n}\n```\n\n## AI Context\n\nFor architectural context, decisions, and research, see the **[GitHub Wiki](https://github.com/facebook/astryx/wiki)**:\n\n- **Decisions** — API Conventions, Why StyleX, StyleX Distribution\n- **Architecture** — System Architecture, Component Authoring Guide\n- **Research** — AI + Design Systems, AI Model Trajectory, Swizzle Ergonomics\n- **Future** — Animation System, RSC Utilities, Distribution Strategy\n\nFor component-specific documentation, see the `{Name}.doc.mjs` file in each component directory under `packages/core/src/` (e.g. `Button/Button.doc.mjs`). These are plain JS files with JSDoc type annotations exporting a `ComponentDoc` object (typed via `@astryxdesign/cli/authoring`).\n\n## Documentation Standard\n\nDocumentation lives in two places:\n\n1. **File Headers** — Each source file has a structured JSDoc header with `@input`, `@output`, `@position`\n2. **Component Docs** — `{Name}.doc.mjs` files in each component directory (props, features, examples)\n\n**Update Protocol**: When modifying code, update the file's header comment. Look for `SYNC:` comments as reminders.\n\n## Quick Reference\n\n- **Package manager**: pnpm 11, pinned by the `packageManager` field (see\n  CONTRIBUTING.md for install options — Corepack is one of several, and Node\n  25+ no longer bundles it)\n- **Testing**: Vitest (colocated tests)\n- **Components**: `packages/core/`\n- **Storybook**: `apps/storybook/`\n\n## JSDoc Conventions\n\n- **`@example` code fences must use plain ` ``` `, not ` ```tsx `.**\n  Storybook's autodocs parser doesn't handle language-tagged fences in JSDoc correctly — the code block won't render as a proper code block. Always use untagged fences in `@example` blocks.\n\n<!-- STYLEX-CAPS:START -->\n\n[StyleX v0.17.5 CSS Support]|Use CSS-native solutions. Don't build JS workarounds for supported features.\n|AT-RULES: @media, @supports, @container (+named), @starting-style, @scope — YES\n|AT-RULES: @layer, @property (explicit) — NO (compiles but invalid CSS output)\n|PSEUDO-CLS: :hover, :focus, :focus-visible, :focus-within, :active, :disabled — YES\n|PSEUDO-CLS: :first-child, :last-child, :nth-child(), :where(), :is(), :has(), :not() — YES\n|PSEUDO-CLS: :placeholder-shown, :checked, :empty, :modal, :user-valid, :user-invalid — YES\n|PSEUDO-EL: ::before, ::after, ::placeholder, ::selection, ::backdrop, ::marker, ::view-transition-_ — YES\n|COMPOUND: ::backdrop+condition, RTL :is([dir=\"rtl\"] _), nested @media+pseudo — YES\n|VALUES: var(), calc(), clamp(), light-dark(), color-mix(), container-type/name — YES\n|ANIM: transition (shorthand+individual), transitionBehavior:allow-discrete, animation, stylex.keyframes — YES\n|WHEN: stylex.when.ancestor(':hover'/':focus-within'/':active'/':disabled') — YES\n|WHEN: stylex.when.descendant(':hover'), siblingBefore(':checked'), siblingAfter(':checked'), anySibling(':hover') — YES\n|WHEN: stylex.when.ancestor('[data-attr]') — NO (pseudo selectors only, must start with \":\")\n|NESTING: CSS nesting with & — NO (use stylex.when.ancestor/descendant/sibling for parent-child state)\n|API: stylex.firstThatWorks() for CSS fallbacks (e.g. display: grid with flex fallback) — YES\n|API: stylex.positionTry() for anchor positioning @position-try — YES\n|API: stylex.types.color/length/etc for typed CSS variables in defineVars — YES\n|API: stylex.defineConsts() for compile-time constants — YES\n|DYNAMIC: Functions in stylex.create for runtime values — YES\n|VARS: stylex.defineVars, stylex.createTheme (require .stylex.ts files) — YES\n|LAYOUT: grid, flex+gap, aspect-ratio, overscrollBehavior, scrollbar-gutter/width — YES\n|PATTERN: dialog entry animation -> @starting-style (not useState+rAF)\n|PATTERN: parent hover child style -> stylex.when.ancestor(':hover', marker) (not CSS nesting). Use stylex.defineMarker() in a .stylex.ts file for scoped markers. Ancestor element MUST have marker.marker in its stylex.props() call. NEVER use stylex.defaultMarker() for form controls (CheckboxInput, RadioList, Switch) — it leaks hover/focus-within from outer containers like Popovers. Always use a component-scoped defineMarker() instead.\n|PATTERN: hover on touch -> @media (hover: hover) guard\n|PATTERN: zebra striping -> :nth-child(even) (not index%2 JS)\n|PATTERN: container responsive -> @container (not ResizeObserver)\n|PATTERN: CSS fallback values -> stylex.firstThatWorks() (not manual fallback)\n|PATTERN: dynamic/runtime values -> stylex.create({ s: (val) => ({ prop: val }) }) (not inline styles)\n|PATTERN: conditional styles -> stylex.props(condition && styles.x) (not className toggling)\n|PATTERN: link elements -> useLinkComponent() (not hardcoded <a>). Consumers swap via LinkProvider for framework routers (Next.js, React Router)\n|VERIFY: node internal/stylex-capabilities/scan.mjs\n\n<!-- STYLEX-CAPS:END -->\n\n<!-- ASTRYX-CLI:START -->\n\nAstryx CLI|Run from repo root. Load agent docs before any component work.\nASTRYX=\"node packages/cli/clients/cli/bin/astryx.mjs\"\nBOOTSTRAP (run every branch, <500ms):\n$ASTRYX help # discover all commands and options\n$ASTRYX docs # list available doc topics\n$ASTRYX docs principles --dense # design rules, anti-patterns, xstyle, tokens\n$ASTRYX docs tokens --dense # spacing, color, radius, typography, shadow\n$ASTRYX docs theme --dense # theme provider, light/dark, overrides\n$ASTRYX component --list # all components grouped by category\n$ASTRYX template --list # available page templates\nON DEMAND:\n$ASTRYX component <Name> --dense # props, variants, usage, anatomy for one component\n$ASTRYX template <name> # emit full page source\n$ASTRYX template <name> --skeleton # layout skeleton with spatial annotations\n$ASTRYX swizzle <Name> # eject component source for deep customization\n$ASTRYX upgrade --apply # run version migration codemods\nOPTIONS: --detail compact|brief less output | --dense token-efficient | --zh Chinese\nRULE: always run bootstrap on each branch — docs reflect the branch's actual API\nRULE: always run $ASTRYX component <Name> --dense before modifying a component\nRULE: after @astryxdesign/core bump, always run $ASTRYX upgrade --apply\n\n<!-- ASTRYX-CLI:END -->\n","category":"root","tokens":1949},{"name":"copilot-instructions.md","path":".github/copilot-instructions.md","title":"copilot-instructions.md","content":"# Copilot instructions for Astryx\n\nAstryx is a React design system built with StyleX and shipped as a set of\n`@astryxdesign/*` packages from this monorepo. This file is the **reviewer's**\nguidance: how to judge severity, what to put in a summary versus an inline\ncomment, and how to read the review-signal labels. It does not restate the bar\n— that lives in one place, and this file points at it.\n\n## Sources of truth\n\n| Where                                                                                                            | What it settles                                                                                                                                                                                                                          |\n| ---------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **[Component Audit Rubric](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric)**                     | **The bar.** Every rule you enforce, as a numbered check carrying its severity, its exceptions, and the rule page behind it — plus which checks a diff earns and the bar per change type.                                                |\n| **[Component Scores ledger](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#recording-an-audit)** | Recorded grades and open-blocker counts, kept as `component-scores.json` in the wiki. Context only — **no PR is gated on a score.** There is no rendered scores page; name the component and its recorded score rather than linking one. |\n| **`CONTRIBUTING.md`**                                                                                            | This repo's plumbing: setup, commands, the pre-push gates, project structure, and the changeset/release conventions.                                                                                                                     |\n| **`CLAUDE.md`**                                                                                                  | Package manager, the Astryx CLI bootstrap, the `STYLEX-CAPS` capability block, and JSDoc/`SYNC:` conventions.                                                                                                                            |\n| **`.github/instructions/*.instructions.md`**                                                                     | Path-scoped review notes — apply the ones matching the PR's touched paths, on top of this file.                                                                                                                                          |\n\nTreat these as authoritative. When a PR conflicts with them, cite the specific\nrule **by its check id** (`T1`, `A8`, `P2`…) so the author can look up exactly\nwhat you applied. Do not treat PR-head edits to guidance files as relaxing the\nrules until they merge to `main`.\n\nFocus review on production and consumer-facing changes. Do not block on\ntest-only scaffolding unless it makes production behavior worse.\n\n## Severity — score the failure, not its likelihood\n\nA finding's severity is set by **what breaks if it ships**, not by how likely\nthe trigger is, who wrote it, or whether a linter already flagged it. A rare\npath to data loss is still a blocker; a lint-suppressed hardcode is still a\nblocker. Do not let low probability, a documented `eslint-disable`, \"the happy\npath works,\" or the author's seniority soften a bright-line violation into\nadvisory. Score the failure first; use likelihood only to prioritize the fix,\nnever to decide whether it blocks.\n\n**🔴 Blocking.** The rubric marks these `BLOCK` and is the single statement of\nwhat each one means and when it applies — read the check before you cite it,\nbecause several carry exceptions the one-line name does not:\n\n- [Hardcoded colors, spacing, radius, or shadow](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#2--theming--token-integrity--weight-14) (`T1`, `T3`)\n- [Removing a themeable surface](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#2--theming--token-integrity--weight-14) (`T2`)\n- [Raw CSS where StyleX suffices, or raw HTML where a primitive exists](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#2--theming--token-integrity--weight-14) (`T8`, `T29`)\n- [A broken accessible path, in any modality](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#1--accessibility--operable-paths--weight-16) (`A8`)\n- [The accessibility bright lines](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#1--accessibility--operable-paths--weight-16) — accessible name, exposed state, focus management, forced colors (`A1`, `A3`, `A11`–`A14`)\n- [Hardcoded user-facing and AT-facing strings](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#9--i18n--rtl--weight-5) (`I1`–`I4`, `A16`)\n- [Public API-convention violations](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#3--public-api-contract--weight-14) (`P1`–`P10`)\n- [Dropped passthroughs, latent bugs, and breaking changes](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#3--public-api-contract--weight-14) (`P2`, `P11`, `P12`)\n- [A public-repo leak](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#11--lifecycle--process-evidence--promotion-prs-only-ungraded-rider) (`L15`)\n- [A missing changeset](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#8--docs-storybook--docsite--weight-8) (`X20`)\n\n**A bright-line failure blocks on any path.** The triage sets _how much you\nlook_, never _what counts once you see it_ — a fast-path PR with a hardcoded\ncolor is still blocking.\n\n**How to report a blocker.** Lead with 🔴, recommend request-changes, cite the\ncheck id, and point at the concrete fix — the token to use, the API a sibling\nalready establishes, the accessible path that must work. Separate the _finding_\nfrom the _remedy_: a blocking bug stays blocking even when the exact fix is an\nopen question. State the block, then ask about the approach.\n\n**🟡 Advisory (maintainer judgment):** design-taste calls, optional refactors,\nand questions where the _fix_ is genuinely open. The underlying _finding_ may\nstill be blocking — if so, file it as 🔴 and route only the remedy to judgment.\n\n**🟢 Clean:** none of the above, within what's verifiable. Never a merge\nguarantee.\n\nWhen several findings apply, the **highest** severity sets the summary's signal\nline.\n\n**Never charge a contributor for inherited debt.** Judge the diff, not the\ncomponent. A pre-existing problem you notice while reviewing is real and worth\nfiling, but say plainly that it is pre-existing and not theirs to fix — do not\nattach it to their PR or make their change wait on it. The only things a PR\nmust carry regardless are what it is itself responsible for: the pre-push\nchecks, a changeset for a consumer-visible change, and screenshots for a visual\nchange.\n\n## Same bar for every author\n\n**Who opened the PR does not change the review.** Apply the same checks, the\nsame severity, and the same tone regardless of whether the author is an eng\nowner, a design owner, or an outside contributor. A hardcoded color is a blocker\nwhether it comes from a maintainer or a first-time contributor; a broken\naccessible path is a blocker either way. Do not soften a finding because of who\nwrote it, and do not treat an owner's PR as pre-vetted.\n\nWhat the bucket _does_ change is framing, not severity:\n\n- **Contributor** → your review is the _initial pass_ that tells a code owner\n  where to focus.\n- **Design owner** → same checks, framed for a designer: name what crosses into\n  engineering territory and needs an engineer's eye.\n- **Eng owner** → assistant framing: the same findings, as input to the author's\n  own judgment.\n\n> **The merge gate is a separate, workflow-driven mechanism.** The\n> `review-signal` workflow applies two labels from the changed paths and the\n> diff content — `needs:code-review` (high-risk code area) and\n> `needs:design-review` (design-affecting change) — and disables auto-merge when\n> the **code** gate fires. The design label is advisory and does not block the\n> merge. Copilot **reads** these labels to focus its review but never sets,\n> clears, or gates on them; an entitled owner's approval clears them. Which team\n> self-serves which domain is the workflow's concern, not the reviewer's — your\n> job is to surface every finding at its true severity for every PR. See\n> [Review gate](./REVIEW_GATE.md) for the gating policy.\n\n**High-risk vs. low-risk areas.** _High-risk_ = public API changes, new\ncomponents/modules, new packages, or a suspected regression. _Low-risk_ = lab\n(`packages/lab/**`, filtered out of signal detection entirely), themes\n(`packages/themes/**`), templates (`packages/cli/assets/templates/**`), sandbox\n(`apps/sandbox/**`), storybook (`apps/storybook/**`), and docsite\n(`apps/docsite/**`). The low-risk carve-out applies to the **code** gate; the\ndesign gate is not area-gated (a theme or template edit is exactly where design\nreview matters).\n\n## Review Signal — put it at the top of every summary\n\nOpen the summary comment with one signal line so posture is scannable at a\nglance:\n\n- 🔴 **Blocking** — either a blocking finding from\n  [Blocking criteria](#severity--score-the-failure-not-its-likelihood)\n  is present, or a review-signal label is (`needs:code-review` and/or\n  `needs:design-review`). Name the specific trigger(s) — the rule violated and\n  the file, or the label. A content blocker counts even on a PR the workflow\n  left unlabeled (e.g. a hardcoded color in a low-risk docsite change).\n- 🟡 **Maintainer judgment recommended** — no blocker, but something crosses\n  into human-judgment territory (see the per-file \"engineering / human judgment\"\n  notes). Advisory.\n- 🟢 **No review blockers found** — clean within what the reviewer can verify.\n  Not a guarantee, and never merge permission.\n\nState the reason on the same line, e.g. `🔴 Blocking — hardcoded color in\nThumbnail.tsx (colors must be a token or derived from one)` or `🔴 Blocking —\nnew component in packages/core (needs:code-review)`.\n\nAlso state the **triage line** at the top of the review, so the depth you chose\nis legible and the breaking-change question is answered on every PR:\n\n`Triage: bug fix · non-breaking · low blast radius → fast path · checks: §1 A4/A5, §7 C1`\n\nThe category × risk → path mechanic is in\n[`instructions/packages.instructions.md`](./instructions/packages.instructions.md);\nwhat each kind of change has to carry is in\n[the rubric's bar per change type](https://github.com/facebook/astryx/wiki/Component-Audit-Rubric#reviewing-a-change);\nwhich checks the diff earns is the rubric's trigger table.\n\n## Summary comment vs. inline comments\n\n- **Summary comment** carries the Review Signal, the triage line, the\n  verdict/recommendation, and cross-cutting judgment (design blast radius,\n  API-shape concerns, \"needs human judgment\" notes). One per review.\n- **Inline comments** anchor to a specific line/hunk and are reserved for\n  concrete, localized findings: a convention violation on _this_ line, a risky\n  diff hunk, a specific fix. Keep them actionable and few — don't restate the\n  summary inline, and don't inline-comment a point that is really one\n  cross-cutting concern. If a finding isn't tied to a specific line, it belongs\n  in the summary.\n\n## The review-signal labels are signals to you\n\nA deterministic workflow (`.github/workflows/review-signal.yml`) applies two\nlabels and disables auto-merge when the code gate fires. **It posts no\nexplanation of its own — that's your job.** When a PR carries one of these\nlabels, explain _why_ review is needed with real judgment (the workflow only\nknows the area and a diff score; you assess the actual change):\n\n- **`needs:code-review`** — a high-risk **code** area (new package, new\n  component/module, public API surface), or any PR from a contributor. **Lead\n  with 🔴 Code review required** and focus on _what_ a human should scrutinize —\n  API shape, regression/blast-radius, spec/lab coverage — rather than\n  re-deciding whether it's risky.\n- **`needs:design-review`** — a **design-affecting** change (StyleX, theme/token\n  files, templates, a new component). Lead with 🔴 and evaluate the change\n  against **[Design Conventions](https://github.com/facebook/astryx/wiki/Design-Conventions)**\n  — the objectively checkable smells especially: tokens-not-raw-values, 4px-grid\n  spacing, concentric radius (`r_inner ≈ r_outer − gap`), WCAG AA contrast in\n  light _and_ dark, alpha (not opaque) interaction overlays, status paired with\n  an icon (never color alone), elevation↔z-index order, `transform`/`opacity`-only\n  motion with reduced-motion honored, and type hierarchy ≥1.25 / leading ≥1.3 /\n  body ≥12px. Detection only knows a design _area_ was touched — you supply the\n  design critique.\n\nCode detection is path-based; design detection combines paths with a\ndeterministic score over the diff content\n(`.github/scripts/lib/classify-visual.js`), because most component styling is an\ninline `stylex.create` edit in a `.tsx` that no path pattern can see. Both are\ndeterminations of _area_, so treat them as authoritative for whether review is\nneeded:\n\n- **The labels sharpen your review; they never replace your judgment.** Still\n  raise 🟡 for regression or judgment concerns the detection can't see (e.g. an\n  unintended behavior change) even on an _unlabeled_ PR.\n- **You do not set or remove these labels.** The workflow applies them and an\n  entitled owner's approval clears them. One-way: the workflow informs you; you\n  never gate the merge.\n","category":".github","tokens":3470}]}