@adia-ai/adia-ui-forge 0.8.62 → 0.8.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/.claude-plugin/plugin.json +2 -2
  2. package/.codex-plugin/plugin.json +3 -3
  3. package/CHANGELOG.md +317 -275
  4. package/README.md +54 -18
  5. package/__init__.py +8 -8
  6. package/agents/a2ui-maintenance-agent.corpus.json +7 -7
  7. package/agents/a2ui-maintenance-agent.md +6 -6
  8. package/agents/a2ui-planner-agent.md +11 -11
  9. package/agents/demo-audit-agent.corpus.json +4 -4
  10. package/agents/demo-audit-agent.md +8 -9
  11. package/agents/framework-checker.md +10 -11
  12. package/agents/framework-planner-agent.md +9 -10
  13. package/agents/gen-ui-review-agent.corpus.json +4 -4
  14. package/agents/gen-ui-review-agent.md +8 -8
  15. package/agents/package-release-agent.corpus.json +4 -4
  16. package/agents/package-release-agent.md +9 -10
  17. package/agents/primitive-authoring-agent.corpus.json +1 -1
  18. package/agents/primitive-authoring-agent.md +5 -5
  19. package/agents/routing-corpus.json +46 -46
  20. package/codex.interface.json +1 -1
  21. package/commands/gen-ui-review.md +1 -1
  22. package/commands/package-release.md +2 -2
  23. package/package.json +1 -1
  24. package/plugin.yaml +2 -2
  25. package/prompts/gen-ui-review.md +1 -1
  26. package/prompts/package-release.md +2 -2
  27. package/references/contracts/a2ui-mcp-surface.md +7 -7
  28. package/references/contracts/migration-guide-format.md +6 -7
  29. package/references/shared/content-trust.md +3 -5
  30. package/skills/a2ui-maintenance/SKILL.md +13 -16
  31. package/skills/a2ui-maintenance/evals/evals.json +2 -2
  32. package/skills/a2ui-maintenance/evals/routing-corpus.json +1 -1
  33. package/skills/a2ui-maintenance/references/anti-patterns.md +10 -10
  34. package/skills/a2ui-maintenance/references/chunk-authoring.md +11 -12
  35. package/skills/a2ui-maintenance/references/corpus-discipline.md +13 -14
  36. package/skills/a2ui-maintenance/references/data-model-reactivity.md +22 -25
  37. package/skills/a2ui-maintenance/references/eval-diagnostics.md +21 -21
  38. package/skills/a2ui-maintenance/references/format-extension-decisions.md +12 -13
  39. package/skills/a2ui-maintenance/references/leverage-rules.md +6 -6
  40. package/skills/a2ui-maintenance/references/mcp-pipeline-ops.md +9 -10
  41. package/skills/a2ui-maintenance/references/mcp-tool-reference.md +11 -12
  42. package/skills/a2ui-maintenance/references/pipeline-overview.md +33 -38
  43. package/skills/a2ui-maintenance/references/semantic-fail-lifting.md +13 -14
  44. package/skills/a2ui-maintenance/references/strategy-engines.md +14 -16
  45. package/skills/a2ui-maintenance/references/surface-lifecycle.md +10 -11
  46. package/skills/a2ui-maintenance/references/zettel-calibration.md +14 -16
  47. package/skills/component-md-authoring/SKILL.md +22 -22
  48. package/skills/component-md-authoring/agents/openai.yaml +1 -1
  49. package/skills/cross-harness-authoring-standards/SKILL.md +19 -21
  50. package/skills/cross-harness-authoring-standards/agents/openai.yaml +1 -1
  51. package/skills/cross-harness-compatibility-standards/SKILL.md +35 -34
  52. package/skills/cross-harness-compatibility-standards/agents/openai.yaml +1 -1
  53. package/skills/demo-audit/SKILL.md +38 -38
  54. package/skills/demo-audit/evals/routing-corpus.json +1 -1
  55. package/skills/demo-audit/references/admin-shell-anatomy.md +21 -25
  56. package/skills/demo-audit/references/app-shell-pitfalls.md +11 -13
  57. package/skills/demo-audit/references/auto-fix-allowlist.md +3 -3
  58. package/skills/demo-audit/references/card-anatomy-sweep.md +10 -11
  59. package/skills/demo-audit/references/chat-shell-anatomy.md +25 -26
  60. package/skills/demo-audit/references/editor-shell-anatomy.md +25 -28
  61. package/skills/demo-audit/references/html-attr-sweep.md +12 -13
  62. package/skills/demo-audit/references/mode7-status-battery.md +4 -4
  63. package/skills/demo-audit/references/native-leak-annotations.md +8 -8
  64. package/skills/demo-audit/references/output-contract-worked-example.md +8 -8
  65. package/skills/demo-audit/references/probe-discipline-and-escalation.md +2 -2
  66. package/skills/demo-audit/references/visual-probe-triage.md +21 -23
  67. package/skills/gen-ui-review/SKILL.md +28 -28
  68. package/skills/gen-ui-review/agents/openai.yaml +1 -1
  69. package/skills/gen-ui-review/evals/evals.json +1 -1
  70. package/skills/gen-ui-review/evals/routing-corpus.json +1 -1
  71. package/skills/gen-ui-review/references/corpus-html-patterns.md +28 -29
  72. package/skills/gen-ui-review/references/exit-gate-mechanics.md +5 -5
  73. package/skills/gen-ui-review/references/lookup-maintenance.md +2 -2
  74. package/skills/gen-ui-review/references/loop-protocol.md +25 -25
  75. package/skills/gen-ui-review/references/rubric-cosmetic.md +10 -10
  76. package/skills/gen-ui-review/references/rubric-decompose.md +15 -16
  77. package/skills/gen-ui-review/references/rubric-score.md +23 -24
  78. package/skills/gen-ui-review/references/scorecard-worked-examples.md +12 -12
  79. package/skills/gen-ui-review/references/scores.schema.json +2 -2
  80. package/skills/llm-client-maintenance/SKILL.md +12 -12
  81. package/skills/llm-client-maintenance/evals/routing-corpus.json +1 -1
  82. package/skills/llm-client-maintenance/references/adapter-contract.md +27 -27
  83. package/skills/llm-client-maintenance/references/add-a-provider.md +25 -25
  84. package/skills/llm-client-maintenance/references/bridge-facade.md +31 -31
  85. package/skills/llm-client-maintenance/references/browser-proxy-boundary.md +17 -17
  86. package/skills/llm-client-maintenance/references/model-registry.md +14 -14
  87. package/skills/llm-client-maintenance/references/streaming-sse.md +21 -21
  88. package/skills/package-release/SKILL.md +21 -23
  89. package/skills/package-release/evals/evals.json +2 -2
  90. package/skills/package-release/evals/routing-corpus.json +2 -2
  91. package/skills/package-release/references/authorization-model.md +6 -8
  92. package/skills/package-release/references/changelog-discipline.md +25 -27
  93. package/skills/package-release/references/cut-procedure.md +129 -136
  94. package/skills/package-release/references/gates-catalog.md +42 -43
  95. package/skills/package-release/references/independent-package-release.md +14 -15
  96. package/skills/package-release/references/invariants-detail.md +9 -9
  97. package/skills/package-release/references/mechanization.md +11 -12
  98. package/skills/package-release/references/migration-guide-authoring.md +20 -20
  99. package/skills/package-release/references/notes-authoring.md +20 -20
  100. package/skills/package-release/references/recovery-paths.md +28 -28
  101. package/skills/primitive-authoring/SKILL.md +19 -19
  102. package/skills/primitive-authoring/agents/openai.yaml +1 -1
  103. package/skills/primitive-authoring/evals/evals.json +2 -2
  104. package/skills/primitive-authoring/evals/routing-corpus.json +2 -2
  105. package/skills/primitive-authoring/references/INDEX.md +22 -22
  106. package/skills/primitive-authoring/references/anti-patterns.md +76 -77
  107. package/skills/primitive-authoring/references/api-contract.md +63 -65
  108. package/skills/primitive-authoring/references/authoring-cycle.md +47 -50
  109. package/skills/primitive-authoring/references/canonical-pattern-index.md +118 -118
  110. package/skills/primitive-authoring/references/code-style.md +50 -52
  111. package/skills/primitive-authoring/references/common-gotchas.md +23 -23
  112. package/skills/primitive-authoring/references/composite-demo-protocol.md +42 -45
  113. package/skills/primitive-authoring/references/css-patterns.md +97 -97
  114. package/skills/primitive-authoring/references/for-attribute-event-contract.md +12 -12
  115. package/skills/primitive-authoring/references/form-control-sizing.md +16 -18
  116. package/skills/primitive-authoring/references/lifecycle-patterns.md +42 -42
  117. package/skills/primitive-authoring/references/llm-bridge.md +21 -21
  118. package/skills/primitive-authoring/references/module-promotion.md +51 -51
  119. package/skills/primitive-authoring/references/primitive-audit.md +16 -16
  120. package/skills/primitive-authoring/references/shell-patterns.md +81 -81
  121. package/skills/primitive-authoring/references/svg-authoring.md +62 -66
  122. package/skills/primitive-authoring/references/token-contract.md +45 -49
  123. package/skills/primitive-authoring/references/trait-pages.md +8 -8
  124. package/skills/primitive-authoring/references/worked-example.md +39 -39
  125. package/skills/primitive-authoring/references/yaml-contract.md +139 -143
  126. package/skills/site-deployment/SKILL.md +25 -26
  127. package/skills/site-deployment/agents/openai.yaml +1 -1
  128. package/skills/site-deployment/evals/routing-corpus.json +1 -1
  129. package/skills/site-deployment/references/deploy-playbooks.md +51 -53
  130. package/skills/site-docs-authoring/SKILL.md +14 -14
  131. package/skills/site-docs-authoring/agents/openai.yaml +1 -1
  132. package/skills/site-docs-authoring/evals/evals.json +1 -1
  133. package/skills/site-docs-authoring/evals/routing-corpus.json +1 -1
  134. package/skills/site-docs-authoring/intent.md +18 -18
  135. package/skills/ssr-compatibility/SKILL.md +24 -25
  136. package/skills/ssr-compatibility/agents/openai.yaml +1 -1
  137. package/skills/ssr-compatibility/evals/evals.json +1 -1
  138. package/skills/ssr-compatibility/evals/routing-corpus.json +1 -1
  139. package/skills/ssr-compatibility/references/consumer-workarounds.md +22 -23
  140. package/skills/ssr-compatibility/references/failure-shapes.md +84 -91
  141. package/skills/ssr-compatibility/references/guard-patterns.md +54 -58
  142. package/skills/ssr-compatibility/references/status-ledger.md +17 -17
  143. package/skills/ssr-compatibility/references/test-without-linkedom.md +26 -27
@@ -1,4 +1,4 @@
1
- # CSS Patterns Two-Block `@scope`, Variants, Modes, Tokens
1
+ # CSS Patterns, Two-Block `@scope`, Variants, Modes, Tokens
2
2
 
3
3
  Deep dive on AdiaUI's CSS architecture. Read when authoring a new component stylesheet, adding a variant that feels layout-shaped, or editing the token block of an existing component.
4
4
 
@@ -18,7 +18,7 @@ Every component CSS file has exactly this shape:
18
18
  }
19
19
 
20
20
  :scope {
21
- /* ── Base styles consume component tokens only ── */
21
+ /* ── Base styles, consume component tokens only ── */
22
22
  box-sizing: border-box;
23
23
  display: inline-flex;
24
24
  align-items: center;
@@ -46,13 +46,13 @@ Every component CSS file has exactly this shape:
46
46
  **Why two blocks?**
47
47
 
48
48
  - `:where(:scope)` has specificity `(0,0,0)`. Consumer overrides, theme providers, and nested surface rules all beat it cleanly.
49
- - `:scope` has specificity `(0,1,0)` enough to beat `:where()` inside the same file, but low enough to compose across components without `!important`.
49
+ - `:scope` has specificity `(0,1,0)`, enough to beat `:where()` inside the same file, but low enough to compose across components without `!important`.
50
50
  - The separation enforces the rule visually: tokens in the first block, styles in the second. A reviewer can spot a violation at a glance.
51
51
 
52
52
  **Common mistake:** collapsing both into one `:scope` block.
53
53
 
54
54
  ```css
55
- /* WRONG tokens and styles interleaved */
55
+ /* WRONG, tokens and styles interleaved */
56
56
  :scope {
57
57
  --component-bg: var(--a-bg);
58
58
  background: var(--component-bg);
@@ -64,17 +64,17 @@ When a parent tries to override `--component-bg`, specificity beats them. The ze
64
64
 
65
65
  ## Region elements never self-style (2026-09-01, ADR-0105)
66
66
 
67
- Native `<header>`/`<section>`/`<footer>` and `<header-ui>`/`<section-ui>`/`<footer-ui>` (a CSS-only slot-routing anatomy stub no JS, no events, no `.css` file of its own) are **region elements**, not components in their own right. They carry zero layout CSS. Every grid, gap, sticky rule, and slot-vocabulary variant a region renders is written in the **closest host pattern's** `@scope (host-name)` block `page-ui`, `card-ui`, `drawer-ui`, `modal-ui`, `anchor-bar`, the shells matching the region by tag + attribute selector (`:scope > :where(header, header-ui)`, `:has(> [slot="action"])`, etc.), never in a region-owned stylesheet.
67
+ Native `<header>`/`<section>`/`<footer>` and `<header-ui>`/`<section-ui>`/`<footer-ui>` (a CSS-only slot-routing anatomy stub, no JS, no events, no `.css` file of its own) are **region elements**, not components in their own right. They carry zero layout CSS. Every grid, gap, sticky rule, and slot-vocabulary variant a region renders is written in the **closest host pattern's** `@scope (host-name)` block, `page-ui`, `card-ui`, `drawer-ui`, `modal-ui`, `anchor-bar`, the shells, matching the region by tag + attribute selector (`:scope > :where(header, header-ui)`, `:has(> [slot="action"])`, etc.), never in a region-owned stylesheet.
68
68
 
69
- **If you're authoring a new host pattern that accepts region children, you MUST ship its `@scope` treatment for every region shape it accepts before it ships.** A host that accepts `<header-ui>` without a matching grid/sticky/slot-vocabulary block is not a lesser-but-valid host the region element structurally cannot supply the missing layout itself, so the host renders wrong (usually unstyled block flow, or the observed failure mode a stale layout that packs content into the wrong shape). gh#2760 is the live example: `page-ui`'s header-band `@scope` block didn't reproduce the retired `admin-page-header`'s action-cluster grid for a multi-item `[slot="action"]` containing a block-level `<tabs-ui>`, so tabs overlapped buttons instead of getting their own row same markup, incomplete host coverage.
69
+ **If you're authoring a new host pattern that accepts region children, you MUST ship its `@scope` treatment for every region shape it accepts before it ships.** A host that accepts `<header-ui>` without a matching grid/sticky/slot-vocabulary block is not a lesser-but-valid host, the region element structurally cannot supply the missing layout itself, so the host renders wrong (usually unstyled block flow, or, the observed failure mode, a stale layout that packs content into the wrong shape). gh#2760 is the live example: `page-ui`'s header-band `@scope` block didn't reproduce the retired `admin-page-header`'s action-cluster grid for a multi-item `[slot="action"]` containing a block-level `<tabs-ui>`, so tabs overlapped buttons instead of getting their own row, same markup, incomplete host coverage.
70
70
 
71
- **Bare `<header>` vs `<header-ui>`** both reach a host's tag-matched rules identically (`:where(header, header-ui, …)` gives them equal zero specificity). They diverge only on rules keyed off a named-slot attribute selector (`:has(> [slot="icon"])`, etc.) and there the divergence isn't mechanical (`slot=` is decorative everywhere, ADR-0033), it's documentation: `header-ui`'s own yaml is the only place the icon/heading/description/action vocabulary is written down and steered toward by `a2ui.rules`. Use bare `<header>`/`<section>`/`<footer>` only for unstructured default-slot content; use the `-ui` stub whenever content decomposes into the slot vocabulary that's the only path an author or the A2UI generator discovers it exists.
71
+ **Bare `<header>` vs `<header-ui>`**, both reach a host's tag-matched rules identically (`:where(header, header-ui, …)` gives them equal zero specificity). They diverge only on rules keyed off a named-slot attribute selector (`:has(> [slot="icon"])`, etc.), and there the divergence isn't mechanical (`slot=` is decorative everywhere, ADR-0033), it's documentation: `header-ui`'s own yaml is the only place the icon/heading/description/action vocabulary is written down and steered toward by `a2ui.rules`. Use bare `<header>`/`<section>`/`<footer>` only for unstructured default-slot content; use the `-ui` stub whenever content decomposes into the slot vocabulary, that's the only path an author or the A2UI generator discovers it exists.
72
72
 
73
- A region element acquiring its own `.css` file (layout rules scoped to `header-ui` itself, not the host) is the anti-pattern this section exists to name see ADR-0105 for the full rationale and the corollary above.
73
+ A region element acquiring its own `.css` file (layout rules scoped to `header-ui` itself, not the host) is the anti-pattern this section exists to name, see ADR-0105 for the full rationale and the corollary above.
74
74
 
75
75
  Source: [ADR-0105](../../../../../../docs/ops/adr/adr-0105-region-elements-never-self-style.md).
76
76
 
77
- ## Variants vs modes the decision tree
77
+ ## Variants vs modes, the decision tree
78
78
 
79
79
  The rule: **variants change tokens; modes change layout.**
80
80
 
@@ -94,28 +94,28 @@ Does your [attribute=value] need to change any of:
94
94
 
95
95
  **Approved mode attributes** (as of the most recent contract update):
96
96
 
97
- - `code-ui[inline]` inline vs block display
98
- - `divider-ui[vertical]` flex-direction, width ↔ height swap
99
- - `tabs-ui[orientation="vertical"]` flex-direction swap
100
- - `input-ui / textarea-ui / select-ui / slider-ui[data-direction="row"]` grid rewrite
101
- - `toast-ui[position="..."]` fixed positioning corner
102
- - `nav-ui / pane-ui / cot-ui[collapsed]` width/height collapse
103
- - `list-ui[divider]` gap:0 for visual seam
104
- - `description-list-ui[layout="inline"]` grid-template change
105
- - `chart-ui[type="sparkline"|"segments"]` strip vs full layout
106
- - `chat-ui[data-role="user"|"assistant"]` bubble alignment
107
- - `timeline-ui[orientation="horizontal"]` flex-direction swap
108
- - `timeline-ui[mode="steps"]` flex-direction + step-counter layout
109
- - `button-ui[block]` block-fill with `display: flex; width: 100%`
110
- - `pagination-ui[variant="button"]` square 1:1 page buttons
97
+ - `code-ui[inline]`, inline vs block display
98
+ - `divider-ui[vertical]`, flex-direction, width ↔ height swap
99
+ - `tabs-ui[orientation="vertical"]`, flex-direction swap
100
+ - `input-ui / textarea-ui / select-ui / slider-ui[data-direction="row"]`, grid rewrite
101
+ - `toast-ui[position="..."]`, fixed positioning corner
102
+ - `nav-ui / pane-ui / cot-ui[collapsed]`, width/height collapse
103
+ - `list-ui[divider]`, gap:0 for visual seam
104
+ - `description-list-ui[layout="inline"]`, grid-template change
105
+ - `chart-ui[type="sparkline"|"segments"]`, strip vs full layout
106
+ - `chat-ui[data-role="user"|"assistant"]`, bubble alignment
107
+ - `timeline-ui[orientation="horizontal"]`, flex-direction swap
108
+ - `timeline-ui[mode="steps"]`, flex-direction + step-counter layout
109
+ - `button-ui[block]`, block-fill with `display: flex; width: 100%`
110
+ - `pagination-ui[variant="button"]`, square 1:1 page buttons
111
111
 
112
- This list is the single source of truth. If your mode isn't here, add it. If adding would feel weird, that's a signal the "mode" is actually a **sibling component** prefer `code-inline-ui` over `code-ui[inline]` unless the attribute genuinely toggles one surface between two states of the same thing.
112
+ This list is the single source of truth. If your mode isn't here, add it. If adding would feel weird, that's a signal the "mode" is actually a **sibling component**, prefer `code-inline-ui` over `code-ui[inline]` unless the attribute genuinely toggles one surface between two states of the same thing.
113
113
 
114
- **Orthogonal boolean combinations need every combination defined, not left as an undefined hybrid.** Two independently-toggleable boolean attributes on the same component (e.g. stat-ui's `band` and `bleed`) form an N² space; shipping three of the four combinations and leaving the fourth undefined means a consumer who reaches it gets whatever the cascade happens to produce, not a designed layout. ADR-0083 gave stat-ui's `band bleed` combination a real contract the band bleeds inline-start/inline-end/block-end via the `--card-inset` negative-extent technique, with `[slot="change"]` overlaid as a z-ordered, `pointer-events: none` chip anchored block-start/inline-end. When you add a second orthogonal boolean to an existing single-boolean variant, audit all four quadrants before shipping an unstyled hybrid is a defect, not a follow-up.
114
+ **Orthogonal boolean combinations need every combination defined, not left as an undefined hybrid.** Two independently-toggleable boolean attributes on the same component (e.g. stat-ui's `band` and `bleed`) form an N² space; shipping three of the four combinations and leaving the fourth undefined means a consumer who reaches it gets whatever the cascade happens to produce, not a designed layout. ADR-0083 gave stat-ui's `band bleed` combination a real contract, the band bleeds inline-start/inline-end/block-end via the `--card-inset` negative-extent technique, with `[slot="change"]` overlaid as a z-ordered, `pointer-events: none` chip anchored block-start/inline-end. When you add a second orthogonal boolean to an existing single-boolean variant, audit all four quadrants before shipping, an unstyled hybrid is a defect, not a follow-up.
115
115
 
116
- ## Font-family floor text-bearing primitives must anchor to a token
116
+ ## Font-family floor, text-bearing primitives must anchor to a token
117
117
 
118
- A primitive that renders text must NOT rely on `font: inherit` / `font-family: inherit` alone. Those carry **no default** the primitive inherits whatever the host page sets, so a consumer page with a broken or serif `font-family` (a dead token, a missing `--a-font-family`, a serif host document) makes the primitive's labels render in UA serif while token-anchored siblings (`text-ui`) stay correct. A confusing same-page split the exact bug behind an embedded-app `<segmented-ui>` serif regression (25 primitives shared the flaw).
118
+ A primitive that renders text must NOT rely on `font: inherit` / `font-family: inherit` alone. Those carry **no default**, the primitive inherits whatever the host page sets, so a consumer page with a broken or serif `font-family` (a dead token, a missing `--a-font-family`, a serif host document) makes the primitive's labels render in UA serif while token-anchored siblings (`text-ui`) stay correct. A confusing same-page split, the exact bug behind an embedded-app `<segmented-ui>` serif regression (25 primitives shared the flaw).
119
119
 
120
120
  **Rule:** anchor `font-family` to a token, the way `text-ui` does:
121
121
 
@@ -125,7 +125,7 @@ A primitive that renders text must NOT rely on `font: inherit` / `font-family: i
125
125
  --foo-font-family-default: var(--a-font-family-ui); /* UI-control font */
126
126
  }
127
127
  :scope {
128
- font: inherit; /* keep resets style/variant/leading */
128
+ font: inherit; /* keep, resets style/variant/leading */
129
129
  font-family: var(--foo-font-family, var(--foo-font-family-default));
130
130
  }
131
131
  }
@@ -133,24 +133,24 @@ A primitive that renders text must NOT rely on `font: inherit` / `font-family: i
133
133
 
134
134
  - `font: inherit` may stay (it still resets `font-style` / `font-variant` / `line-height`); the `font-family` longhand AFTER it is the floor.
135
135
  - Floor to `--a-font-family-ui` for chrome/controls (or `--a-body-family` for prose-like text, as `text-ui` does). The `var(--foo-font-family, …)` first arg is the per-component override hook.
136
- - For composite controls (`select` / `combobox` / `tags-input` / `table`), floor the **host `:scope`** internal fields/options/cells inherit it.
137
- - **Exception:** a contextual editor (`inline-edit`) or an optional centered label (`spinner`) SHOULD inherit to match surrounding content do NOT floor those (they're allowlisted in the audit).
136
+ - For composite controls (`select` / `combobox` / `tags-input` / `table`), floor the **host `:scope`**, internal fields/options/cells inherit it.
137
+ - **Exception:** a contextual editor (`inline-edit`) or an optional centered label (`spinner`) SHOULD inherit to match surrounding content, do NOT floor those (they're allowlisted in the audit).
138
138
 
139
139
  **Enforced by** `npm run audit:font-family-floor:strict` (`scripts/dev/audit-font-family-floor.mjs`): flags any component CSS with `font: inherit` / `font-family: inherit` and no `font-family: var(…)` floor, plus dead `var(--a-font)` usage.
140
140
 
141
- ## Token consumption L3 over L2
141
+ ## Token consumption, L3 over L2
142
142
 
143
143
  The token stack has four layers:
144
144
 
145
- - **L1 (primitives):** raw scale values `--a-blue-500`, `--a-gray-100`.
146
- - **L2 (family semantics):** role tokens per family `--a-primary`, `--a-danger`, `--a-success`, `--a-info`, `--a-warning`.
147
- - **L3 (state × role matrix):** `--a-<family>-{bg,fg,border}-{rest,hover,active,selected,disabled,invalid}` every family has a full matrix.
145
+ - **L1 (primitives):** raw scale values, `--a-blue-500`, `--a-gray-100`.
146
+ - **L2 (family semantics):** role tokens per family, `--a-primary`, `--a-danger`, `--a-success`, `--a-info`, `--a-warning`.
147
+ - **L3 (state × role matrix):** `--a-<family>-{bg,fg,border}-{rest,hover,active,selected,disabled,invalid}`, every family has a full matrix.
148
148
  - **L4 (component tokens):** `--component-*`, defined in `:where(:scope)`.
149
149
 
150
150
  **Rule:** L4 aliases L3, not L2.
151
151
 
152
152
  ```css
153
- /* RIGHT component token aliases from L3 */
153
+ /* RIGHT, component token aliases from L3 */
154
154
  :where(:scope) {
155
155
  --button-bg: var(--a-primary-bg);
156
156
  --button-bg-hover: var(--a-primary-bg-hover);
@@ -163,7 +163,7 @@ The token stack has four layers:
163
163
  --button-bg-hover: var(--a-danger-bg-hover);
164
164
  }
165
165
 
166
- /* WRONG variant body consumes L2 directly */
166
+ /* WRONG, variant body consumes L2 directly */
167
167
  :scope[variant="danger"]:not([disabled]):hover {
168
168
  --button-fg: var(--a-danger); /* ← L2 */
169
169
  --button-border: var(--a-danger); /* ← L2 */
@@ -174,23 +174,23 @@ The token stack has four layers:
174
174
 
175
175
  This was a real bug in `button.css` caught in a final audit pass.
176
176
 
177
- ## Raw values what's allowed
177
+ ## Raw values, what's allowed
178
178
 
179
179
  - **Colors:** zero raw values in component CSS files.
180
180
  - No `#hex`, `rgb()`, `rgba()`, `oklch()`, `hsl()`, named colors (`red`, `white`). Every color goes through a token.
181
- - Exception: `styles/colors/semantics.css` and `styles/tokens.css` those ARE the raw values.
181
+ - Exception: `styles/colors/semantics.css` and `styles/tokens.css`, those ARE the raw values.
182
182
 
183
183
  - **px values:**
184
184
  - ≤ 2px: allowed for `stroke-width`, `border-width`, hairline details. Comment not required.
185
185
  - ≥ 3px: forbidden in component base styles. Use `var(--a-space-*)`.
186
186
  - Exception: component-intrinsic constants (e.g. a port-dot diameter, an icon size that must match a specific SVG coordinate). Each such literal needs a one-line comment justifying why.
187
187
 
188
- - **Typography leading, tracking, weight (ADR-0052, ratified 2026-08-15, gh#1298):** zero bare literals in `packages/web-components/components/*/*.css`; every value goes through the one scale per property in `styles/type/scale.css`.
189
- - **Leading:** `--a-font-leading-*` is the single canonical scale `none: 1` · `tight: 1.05` · `snug: 1.2` · `normal: 1.35` · `relaxed: 1.5` · `loose: 1.6` (`scale.css:67-72`). The legacy flat `--a-leading-*` family (`none/tight/snug/normal` = 1/1.2/**1.3**/**1.5**) is **retired** removed from `scale.css` with no value-preserving aliases, because `snug` and `normal` named different values in each family and an alias would silently restyle. Two families under shared step names was the root cause of the literal epidemic (55/131 component files hardcoding `line-height`): no token choice was obviously correct, so authors reached for a number.
190
- - **Mapping for the literals you will find in older code:** `1.3` and `1.4` both → `--a-font-leading-normal` (1.35); at the 12–14 px sizes where they occur, ±0.05 is ≤ 0.7 px below visual significance, and no new step is minted to ratify drift. Escape valve: genuinely multi-line body copy may map up to `--a-font-leading-relaxed` (1.5), case-by-case. Legacy `--a-leading-snug` (1.3) → `--a-font-leading-normal`; legacy `--a-leading-normal` (1.5) → `--a-font-leading-relaxed` value-nearest, not name-preserving.
188
+ - **Typography, leading, tracking, weight (ADR-0052, ratified 2026-08-15, gh#1298):** zero bare literals in `packages/web-components/components/*/*.css`; every value goes through the one scale per property in `styles/type/scale.css`.
189
+ - **Leading:** `--a-font-leading-*` is the single canonical scale, `none: 1` · `tight: 1.05` · `snug: 1.2` · `normal: 1.35` · `relaxed: 1.5` · `loose: 1.6` (`scale.css:67-72`). The legacy flat `--a-leading-*` family (`none/tight/snug/normal` = 1/1.2/**1.3**/**1.5**) is **retired**, removed from `scale.css` with no value-preserving aliases, because `snug` and `normal` named different values in each family and an alias would silently restyle. Two families under shared step names was the root cause of the literal epidemic (55/131 component files hardcoding `line-height`): no token choice was obviously correct, so authors reached for a number.
190
+ - **Mapping for the literals you will find in older code:** `1.3` and `1.4` both → `--a-font-leading-normal` (1.35); at the 12–14 px sizes where they occur, ±0.05 is ≤ 0.7 px, below visual significance, and no new step is minted to ratify drift. Escape valve: genuinely multi-line body copy may map up to `--a-font-leading-relaxed` (1.5), case-by-case. Legacy `--a-leading-snug` (1.3) → `--a-font-leading-normal`; legacy `--a-leading-normal` (1.5) → `--a-font-leading-relaxed`, value-nearest, not name-preserving.
191
191
  - **Tracking:** `0.05em` → `--a-font-tracking-wide` (0.04em); everything else was already on-scale (`--a-font-tracking-tight/snug/normal/wide/wider`, `scale.css:75-79`). **Weight:** numeric weights tokenize to `--a-weight-*` (`scale.css:47-52`).
192
- - **Gate:** `npm run check:typography-tokens` (`scripts/audit/check-typography-tokens.mjs --strict`, in the `npm run check` aggregate) fails any bare numeric `line-height` (except `0`, the icon line-box-collapse idiom), any `em`-literal `letter-spacing`, any numeric `font-weight`, **and (gh#1496) any numeric literal used as a `var()` fallback** on one of those three properties or on a custom property whose own name names the role (`--alert-line-height`, `--foo-weight`) `line-height: var(--slider-hint-lh, 1.4)` trips it exactly like a bare literal, because the fallback still computes to the raw number whenever the custom property is unset. When you author a fallback, fall back to a token (`var(--x, var(--a-font-leading-normal))`), not a number the gate now enforces that rather than merely recommending it. Comments are stripped before scanning.
193
- - Shipped as a breaking change on the `0.8.40` wave (`--a-leading-*` were published stylesheet symbols; the ADR text names the wave `0.9.0` it shipped as `0.8.40`), with its migration-guide entry in the same cycle. Source: ADR-0052.
192
+ - **Gate:** `npm run check:typography-tokens` (`scripts/audit/check-typography-tokens.mjs --strict`, in the `npm run check` aggregate) fails any bare numeric `line-height` (except `0`, the icon line-box-collapse idiom), any `em`-literal `letter-spacing`, any numeric `font-weight`, **and (gh#1496) any numeric literal used as a `var()` fallback** on one of those three properties or on a custom property whose own name names the role (`--alert-line-height`, `--foo-weight`), `line-height: var(--slider-hint-lh, 1.4)` trips it exactly like a bare literal, because the fallback still computes to the raw number whenever the custom property is unset. When you author a fallback, fall back to a token (`var(--x, var(--a-font-leading-normal))`), not a number, the gate now enforces that rather than merely recommending it. Comments are stripped before scanning.
193
+ - Shipped as a breaking change on the `0.8.40` wave (`--a-leading-*` were published stylesheet symbols; the ADR text names the wave `0.9.0`, it shipped as `0.8.40`), with its migration-guide entry in the same cycle. Source: ADR-0052.
194
194
 
195
195
  Example carve-out:
196
196
 
@@ -230,9 +230,9 @@ Example carve-out:
230
230
  }
231
231
  ```
232
232
 
233
- A cursory check might flag `--col-*` as "wrong" because the file is named `layout.css`. It's not wrong the **scope tag** determines the stem, not the filename.
233
+ A cursory check might flag `--col-*` as "wrong" because the file is named `layout.css`. It's not wrong, the **scope tag** determines the stem, not the filename.
234
234
 
235
- **Cross-component token-fallback aliasing.** A new component's tokens can alias a sibling component's existing token ladder as their `var()` fallback instead of minting an independent scale when the two components share a visual role closely enough that re-theming one should re-theme the other in the same stroke. ADR-0083's stat-ui `[slot="change"]` chip mints six stat-scoped tokens (`--stat-change-*`) that fall back to chart.css's existing `--chart-chip-*` rungs:
235
+ **Cross-component token-fallback aliasing.** A new component's tokens can alias a sibling component's existing token ladder as their `var()` fallback instead of minting an independent scale, when the two components share a visual role closely enough that re-theming one should re-theme the other in the same stroke. ADR-0083's stat-ui `[slot="change"]` chip mints six stat-scoped tokens (`--stat-change-*`) that fall back to chart.css's existing `--chart-chip-*` rungs:
236
236
 
237
237
  ```css
238
238
  :where(:scope) {
@@ -241,11 +241,11 @@ A cursory check might flag `--col-*` as "wrong" because the file is named `layou
241
241
  }
242
242
  ```
243
243
 
244
- A consumer who re-themes chart chips re-themes the stat delta chip too, with no separate override required. Reach for this only when the aliasing component is a genuine visual sibling of the aliased one (same role, same page context) otherwise it's spooky action at a distance when the aliased component's tokens change for an unrelated reason.
244
+ A consumer who re-themes chart chips re-themes the stat delta chip too, with no separate override required. Reach for this only when the aliasing component is a genuine visual sibling of the aliased one (same role, same page context), otherwise it's spooky action at a distance when the aliased component's tokens change for an unrelated reason.
245
245
 
246
- ## Concentric-corner radius deriving an item's radius from its container
246
+ ## Concentric-corner radius, deriving an item's radius from its container
247
247
 
248
- When a rounded container pads a rounded-corner item flush against its own edge a popover listbox around `[role="option"]` rows, a menu popover around `menu-item-ui` a flat item-radius token only reads as concentric with the container's own corner at the one padding value it happened to be tuned against. Change the density scale or the radius scale independently (a consumer re-theme, a `--a-density` step) and the two arcs drift apart.
248
+ When a rounded container pads a rounded-corner item flush against its own edge, a popover listbox around `[role="option"]` rows, a menu popover around `menu-item-ui`, a flat item-radius token only reads as concentric with the container's own corner at the one padding value it happened to be tuned against. Change the density scale or the radius scale independently (a consumer re-theme, a `--a-density` step) and the two arcs drift apart.
249
249
 
250
250
  **The formula.** Solve for the ITEM's radius; keep the container's padding fixed:
251
251
 
@@ -253,37 +253,37 @@ When a rounded container pads a rounded-corner item flush against its own edge
253
253
  Ir = max(0, Cr − Cp)
254
254
  ```
255
255
 
256
- - `Cr` the container's own `border-radius` (an existing fixed radius-scale token, untouched).
257
- - `Cp` the container's own padding/inset (an existing fixed spacing-scale token, untouched this pattern never derives padding).
258
- - `Ir` the item's `border-radius`, the only new derived value.
256
+ - `Cr`, the container's own `border-radius` (an existing fixed radius-scale token, untouched).
257
+ - `Cp`, the container's own padding/inset (an existing fixed spacing-scale token, untouched, this pattern never derives padding).
258
+ - `Ir`, the item's `border-radius`, the only new derived value.
259
259
 
260
- **Why solve for `Ir`, not `Cp`.** The tempting reverse direction hold the item's radius fixed and derive the container's padding from it needs a second term, `min(Ir, Ih/2)` (`Ih` = item height), to keep the derived padding from going negative once the item is short enough that its own radius would pill-clamp. That's the browser's own `border-radius` clamp (CSS Backgrounds §5.5 a radius past half an element's shorter side reduces to a pill) reimplemented by hand inside the padding formula. Solving for `Ir` instead gets that clamp for free: `max(0px, calc(Cr - Cp))` alone is correct at every item height, because the browser applies its pill-clamp to the declared `Ir` automatically no `min()` term needed anywhere in the CSS.
260
+ **Why solve for `Ir`, not `Cp`.** The tempting reverse direction, hold the item's radius fixed and derive the container's padding from it, needs a second term, `min(Ir, Ih/2)` (`Ih` = item height), to keep the derived padding from going negative once the item is short enough that its own radius would pill-clamp. That's the browser's own `border-radius` clamp (CSS Backgrounds §5.5, a radius past half an element's shorter side reduces to a pill) reimplemented by hand inside the padding formula. Solving for `Ir` instead gets that clamp for free: `max(0px, calc(Cr - Cp))` alone is correct at every item height, because the browser applies its pill-clamp to the declared `Ir` automatically, no `min()` term needed anywhere in the CSS.
261
261
 
262
- **Companion rule the `min-height` floor is load-bearing, not optional.** The browser's pill-clamp cuts both ways: if the item is shorter than `2 × Ir`, the *effective* rendered radius clamps down even though the *declared* `Ir` is correct the item silently stops reading as concentric, with no error and no visual-eval regression to catch it (a live-browser-only defect class, the same shape as this file's `display:contents` entry below). Pair every derived radius with a matching height floor:
262
+ **Companion rule: the `min-height` floor is load-bearing, not optional.** The browser's pill-clamp cuts both ways: if the item is shorter than `2 × Ir`, the *effective* rendered radius clamps down even though the *declared* `Ir` is correct, the item silently stops reading as concentric, with no error and no visual-eval regression to catch it (a live-browser-only defect class, the same shape as this file's `display:contents` entry below). Pair every derived radius with a matching height floor:
263
263
 
264
264
  ```css
265
265
  min-height: calc(2 * <item-radius-var>);
266
266
  ```
267
267
 
268
- Ruled (Kim, 2026-08-24): enforce this floor rather than let it silently degrade items get taller at large radius/density scales as the accepted tradeoff for exact concentricity, not a bug to route around.
268
+ Ruled (Kim, 2026-08-24): enforce this floor rather than let it silently degrade, items get taller at large radius/density scales as the accepted tradeoff for exact concentricity, not a bug to route around.
269
269
 
270
- **Worked example** (`select-ui`'s listbox the reported surface, gh#1956):
270
+ **Worked example** (`select-ui`'s listbox, the reported surface, gh#1956):
271
271
 
272
272
  ```css
273
- /* Container Cr and Cp declared as LOCAL custom properties on the
273
+ /* Container, Cr and Cp declared as LOCAL custom properties on the
274
274
  popover's own top-layer rule. A top-layer popover usually can't inherit
275
275
  the host element's @scope'd component tokens (a different, often
276
- detached DOM subtree once popover-open) declaring them locally here
276
+ detached DOM subtree once popover-open), declaring them locally here
277
277
  sidesteps that; a local declaration DOES inherit down to a genuine DOM
278
278
  child. */
279
279
  select-ui [slot="listbox"] {
280
- --select-listbox-padding: var(--a-space-1); /* Cp fixed */
281
- --select-listbox-radius: var(--a-radius); /* Cr fixed */
280
+ --select-listbox-padding: var(--a-space-1); /* Cp, fixed */
281
+ --select-listbox-radius: var(--a-radius); /* Cr, fixed */
282
282
  padding: var(--select-listbox-padding);
283
283
  border-radius: var(--select-listbox-radius);
284
284
  }
285
285
 
286
- /* Item Ir derived; Cr/Cp inherited from the listbox above because
286
+ /* Item, Ir derived; Cr/Cp inherited from the listbox above because
287
287
  [role="option"] is a genuine DOM child of it (an appended option row). */
288
288
  select-ui [slot="listbox"] [role="option"] {
289
289
  --select-option-radius:
@@ -295,19 +295,19 @@ select-ui [slot="listbox"] [role="option"] {
295
295
 
296
296
  (`packages/web-components/components/select/select.css:330-343` and `:378-386`.)
297
297
 
298
- Express `Ir` as a live `calc()`/`max()` referencing the radius/space custom properties never a baked pixel value so it holds across every `--a-radius-k` / `--a-density` scale change.
298
+ Express `Ir` as a live `calc()`/`max()` referencing the radius/space custom properties, never a baked pixel value, so it holds across every `--a-radius-k` / `--a-density` scale change.
299
299
 
300
- **Shipped in** (gh#1956, PR #1959 the reference implementation for this pattern):
300
+ **Shipped in** (gh#1956, PR #1959, the reference implementation for this pattern):
301
301
 
302
- - `select.css:330-343,378-386` `[slot="listbox"]` vs. `[role="option"]`.
303
- - `combobox.css:209-221,254-264` `[data-listbox]`.
304
- - `tags-input.css:166-184,200-209` `[data-suggestions]`.
305
- - `menu.css:51-65,90-92` `[data-menu-popover]` vs. `menu-item-ui`.
306
- - `context-menu.css:26-36,56-58` `[data-context-menu-surface]`, which shares `menu-item-ui` rows with `menu.css`.
307
- - `nav-group.css:328-340,358-365` `[slot="popover"]` vs. `[role="option"]`. Previously gave option rows the container's own flat radius a design inconsistency next to every sibling popover's distinct smaller item radius. Deriving `Ir` resolves it with no special-casing: under this direction (unlike the reverse "solve for `Cp`" direction, which floors padding to a hard 0px here and was reverted) the row's radius just comes out smaller than the container's automatically.
308
- - `calendar-picker.css:87` / `date-range-picker.css:267` `calendar-grid-ui`'s day cells (gh#1966), a variant shape: the item primitive is a *shared substrate* consumed by two different popover containers with different `Cr`/`Cp`, not a single component owning both container and item, so `Ir` is derived once per consumer (not once in the shared `calendar-grid.css`) via a `--calendar-grid-day-radius` override cascaded down. `date-range-picker`'s popover padding is asymmetric (`--date-range-picker-px` ≠ `-py`) a single circular radius can't be exactly concentric on both axes, so it derives from `min(px, py)`, the tighter constraint; harmless on the corner (bottom-left, with a preset rail present) that isn't actually flush. Also the first shipped case to pair the derived radius with the `min-height: calc(2 * Ir)` companion floor (`calendar-grid.css:199`), per the ruling above.
302
+ - `select.css:330-343,378-386`, `[slot="listbox"]` vs. `[role="option"]`.
303
+ - `combobox.css:209-221,254-264`, `[data-listbox]`.
304
+ - `tags-input.css:166-184,200-209`, `[data-suggestions]`.
305
+ - `menu.css:51-65,90-92`, `[data-menu-popover]` vs. `menu-item-ui`.
306
+ - `context-menu.css:26-36,56-58`, `[data-context-menu-surface]`, which shares `menu-item-ui` rows with `menu.css`.
307
+ - `nav-group.css:328-340,358-365`, `[slot="popover"]` vs. `[role="option"]`. Previously gave option rows the container's own flat radius, a design inconsistency next to every sibling popover's distinct smaller item radius. Deriving `Ir` resolves it with no special-casing: under this direction (unlike the reverse "solve for `Cp`" direction, which floors padding to a hard 0px here and was reverted) the row's radius just comes out smaller than the container's automatically.
308
+ - `calendar-picker.css:87` / `date-range-picker.css:267`, `calendar-grid-ui`'s day cells (gh#1966), a variant shape: the item primitive is a *shared substrate* consumed by two different popover containers with different `Cr`/`Cp`, not a single component owning both container and item, so `Ir` is derived once per consumer (not once in the shared `calendar-grid.css`) via a `--calendar-grid-day-radius` override cascaded down. `date-range-picker`'s popover padding is asymmetric (`--date-range-picker-px` ≠ `-py`), a single circular radius can't be exactly concentric on both axes, so it derives from `min(px, py)`, the tighter constraint; harmless on the corner (bottom-left, with a preset rail present) that isn't actually flush. Also the first shipped case to pair the derived radius with the `min-height: calc(2 * Ir)` companion floor (`calendar-grid.css:199`), per the ruling above.
309
309
 
310
- Audited, not applicable: `command-ui`, `drilldown-ui` item list sits inside a padded region behind a header, not flush against the container's own rounded corner, or the host carries no radius at all.
310
+ Audited, not applicable: `command-ui`, `drilldown-ui`, item list sits inside a padded region behind a header, not flush against the container's own rounded corner, or the host carries no radius at all.
311
311
 
312
312
  ## Slot styling without `::slotted()`
313
313
 
@@ -323,7 +323,7 @@ AdiaUI is light-DOM. Slotted children are just children. Style them with attribu
323
323
  font-weight: var(--a-weight-semibold);
324
324
  }
325
325
 
326
- /* WRONG ::slotted() is for shadow DOM */
326
+ /* WRONG, ::slotted() is for shadow DOM */
327
327
  ::slotted([slot="icon"]) { ... }
328
328
  ```
329
329
 
@@ -360,24 +360,24 @@ When a component can contain itself (cards inside cards), step the background up
360
360
 
361
361
  The `:scope card-ui` specificity `(0,1,1)` beats the inner scope's `:where(:scope)` initializer `(0,0,0)`, so the nested card picks up the bumped canvas. No JavaScript required.
362
362
 
363
- ## `:has()` constrain to direct children when gating on slots
363
+ ## `:has()`, constrain to direct children when gating on slots
364
364
 
365
- When a component uses `:has([slot="X"])` to toggle a layout (e.g. activate a grid when a slotted child is present), the selector matches any descendant. That collides with composite children like `<avatar-ui>` which owns an internal `<icon-ui slot="icon">` dropping an avatar into a header you intended to render without an icon column will falsely activate it.
365
+ When a component uses `:has([slot="X"])` to toggle a layout (e.g. activate a grid when a slotted child is present), the selector matches any descendant. That collides with composite children like `<avatar-ui>` which owns an internal `<icon-ui slot="icon">`, dropping an avatar into a header you intended to render without an icon column will falsely activate it.
366
366
 
367
367
  **Rule:** gate layout on `:has(> [slot="X"])`, not `:has([slot="X"])`.
368
368
 
369
369
  ```css
370
- /* WRONG matches nested <icon-ui slot="icon"> inside an <avatar-ui> */
370
+ /* WRONG, matches nested <icon-ui slot="icon"> inside an <avatar-ui> */
371
371
  > header:has([slot="icon"]) { grid-template-columns: max-content 1fr; }
372
372
 
373
- /* RIGHT only activates for a direct-child [slot="icon"] */
373
+ /* RIGHT, only activates for a direct-child [slot="icon"] */
374
374
  > header:has(> [slot="icon"]) { grid-template-columns: max-content 1fr; }
375
375
  ```
376
376
 
377
377
  This applies everywhere layout flips on slot presence:
378
378
 
379
379
  ```css
380
- /* card-ui / drawer-ui header grid all :has() clauses are direct-child */
380
+ /* card-ui / drawer-ui header grid, all :has() clauses are direct-child */
381
381
  > header:has(> [slot="icon"]):has(> :is([slot="action"], [slot="close"])) {
382
382
  grid-template-columns: max-content 1fr max-content;
383
383
  }
@@ -392,23 +392,23 @@ This applies everywhere layout flips on slot presence:
392
392
 
393
393
  ## Conditional-render parts defeat `:scope >` (the `display:contents` wrapper)
394
394
 
395
- The template engine wraps every **conditional render branch** a `${cond ? … : null}` (or `?` / `.map()`) expression in a `<span style="display:contents">`. The span generates no box (invisible in layout) but is a real DOM node, so a conditionally-rendered part is a **grandchild** of `:scope`, not a direct child. A `:scope > [data-part="X"]` rule on that part silently matches nothing no error, passes `components --verify`, and renders un-styled only in a live browser (happy-dom won't catch it).
395
+ The template engine wraps every **conditional render branch**, a `${cond ? … : null}` (or `?` / `.map()`) expression, in a `<span style="display:contents">`. The span generates no box (invisible in layout) but is a real DOM node, so a conditionally-rendered part is a **grandchild** of `:scope`, not a direct child. A `:scope > [data-part="X"]` rule on that part silently matches nothing, no error, passes `components --verify`, and renders un-styled only in a live browser (happy-dom won't catch it).
396
396
 
397
397
  **Rule:** use a **descendant** combinator for any conditionally-rendered part; keep `:scope >` only for parts that render unconditionally.
398
398
 
399
399
  ```css
400
- /* WRONG empty-state lives behind a `${isEmpty ? … : null}` branch,
400
+ /* WRONG, empty-state lives behind a `${isEmpty ? … : null}` branch,
401
401
  so it's wrapped in <span style="display:contents"> and never matched */
402
402
  :scope > [data-part="empty"] { display: grid; place-items: center; }
403
403
 
404
- /* RIGHT descendant combinator survives the display:contents wrapper */
404
+ /* RIGHT, descendant combinator survives the display:contents wrapper */
405
405
  :scope [data-part="empty"] { display: grid; place-items: center; }
406
406
 
407
407
  /* static parts (always rendered) stay direct children */
408
408
  :scope > [data-part="header"] { … }
409
409
  ```
410
410
 
411
- Recurring class (integrations-page empty-state, onboarding-checklist complete CTA bug-51 / bug-53). Full failure entry + recognition heuristic: [anti-patterns.md](anti-patterns.md) AP-S6.
411
+ Recurring class (integrations-page empty-state, onboarding-checklist complete CTA, bug-51 / bug-53). Full failure entry + recognition heuristic: [anti-patterns.md](anti-patterns.md) AP-S6.
412
412
 
413
413
  ## Sticky header/footer inside a flex-column scroll container
414
414
 
@@ -447,12 +447,12 @@ When a card-like container (drawer-ui, pane-ui, full-height cards) needs a heade
447
447
  **Why this over a dedicated scroll wrapper:**
448
448
 
449
449
  - No extra DOM element required.
450
- - Multiple `[slot="body"]` siblings stack naturally the author can put dividers between sections, include sub-headers, etc.
450
+ - Multiple `[slot="body"]` siblings stack naturally, the author can put dividers between sections, include sub-headers, etc.
451
451
  - Sticky works inside `display: flex` flex-columns in all modern browsers.
452
452
 
453
453
  **Gotcha:** the sticky background must be opaque. If the header is transparent, content scrolls visibly underneath. Match the sticky element's `background` to the panel's `--*-bg` token.
454
454
 
455
- ## Cascade layers precedence is declared once, never fought per-rule
455
+ ## Cascade layers, precedence is declared once, never fought per-rule
456
456
 
457
457
  `styles/index.css` declares the single ordered list, before any layered rule:
458
458
 
@@ -460,12 +460,12 @@ When a card-like container (drawer-ui, pane-ui, full-height cards) needs a heade
460
460
  @layer reset, tokens, elements, components, utilities, context, overrides;
461
461
  ```
462
462
 
463
- The precedence law: a later layer beats an earlier layer **regardless of selector specificity**; within a layer, normal specificity applies. Component `@scope` rules live inside `@layer components` `@scope` sets proximity *inside* the layer, `@layer` sets inter-group order; they compose, they are not substitutes.
463
+ The precedence law: a later layer beats an earlier layer **regardless of selector specificity**; within a layer, normal specificity applies. Component `@scope` rules live inside `@layer components`, `@scope` sets proximity *inside* the layer, `@layer` sets inter-group order; they compose, they are not substitutes.
464
464
 
465
465
  Consequences for component authors:
466
466
 
467
- - **Consumer overrides always win** unlayered consumer CSS, or `@layer overrides`, beats everything by layer order. Never add `:where()` wraps, matched-specificity selectors, or `!important` to "let the consumer win"; the layer order already guarantees it.
468
- - **Utilities beat component defaults by layer order.** The global attribute API (`api/*`) sits in `utilities`, so a component cannot out-specific it a component repurposing a global attribute (e.g. `color=` on a filled control) must opt out explicitly (see the `[color]`/`[weight]` trap below).
467
+ - **Consumer overrides always win**, unlayered consumer CSS, or `@layer overrides`, beats everything by layer order. Never add `:where()` wraps, matched-specificity selectors, or `!important` to "let the consumer win"; the layer order already guarantees it.
468
+ - **Utilities beat component defaults by layer order.** The global attribute API (`api/*`) sits in `utilities`, so a component cannot out-specific it, a component repurposing a global attribute (e.g. `color=` on a filled control) must opt out explicitly (see the `[color]`/`[weight]` trap below).
469
469
  - **`@layer` governs rule precedence only.** The `var()` token chains (token indirection), genuine element-default `:where()`s, and allowlisted a11y `!important`s serve other mechanisms and are not retired by layers.
470
470
  - `npm run check:cascade-layers` gates the model: canonical order declared once, `!important` ≤ allowlist, no precedence-`:where()` in layered files.
471
471
 
@@ -475,19 +475,19 @@ Source: ADR-0038.
475
475
 
476
476
  One line each; every entry is a shipped bug.
477
477
 
478
- - **`background: <color>` shorthand silently resets `background-clip`/`origin`/`position`/`size`.** For a state that changes only the color, use the `background-color` longhand a base `background-clip: content-box` otherwise flips to `border-box` and the fill balloons (swiper-ui dots grew 6px → 16px pills).
479
- - **Modern `translate` / `scale` / `rotate` are independent properties, NOT `transform` aliases.** Writing `style.translate` and reading `getComputedStyle().transform` (or vice-versa) silently no-ops they are computed separately (spring-animate wrote translate, read the transform matrix, saw 0, never animated).
478
+ - **`background: <color>` shorthand silently resets `background-clip`/`origin`/`position`/`size`.** For a state that changes only the color, use the `background-color` longhand, a base `background-clip: content-box` otherwise flips to `border-box` and the fill balloons (swiper-ui dots grew 6px → 16px pills).
479
+ - **Modern `translate` / `scale` / `rotate` are independent properties, NOT `transform` aliases.** Writing `style.translate` and reading `getComputedStyle().transform` (or vice-versa) silently no-ops, they are computed separately (spring-animate wrote translate, read the transform matrix, saw 0, never animated).
480
480
  - **An OFFSETTING ancestor transform (`translate(-50%,-50%)`) breaks CSS anchor-positioning for top-layer popovers**; an identity `translateX(0)` does not.
481
- - **A `@media` override with an equal-specificity selector must come AFTER its base rule in source order** earlier placement is silently ignored.
482
- - **One un-suffixed component token per property (`--card-bg`), read directly (`var(--card-bg)`).** The `-default` token-shadowing layer was reverted do not declare `--card-bg-default` fallback chains, and do not rely on ancestor surfaces overriding component-named tokens (that inheritance no longer works).
483
- - **Global `[color]`/`[weight]` presentational utilities override component color by `@layer` order** a filled control repurposing `color=` must opt out; conversely the global `[weight]` attribute does NOT override component-scoped font-weight (use a variant).
484
- - **A grid `auto`/`max-content` track collapses to ~1px around a flex wrapper whose child carries the explicit width** the child's width doesn't propagate through the wrapper's intrinsic size; set the width on the wrapper (CSS, or JS-mirrored via ResizeObserver).
485
- - **Square/1:1 cells inheriting `--a-radius-md` render as circles** use `--a-radius-sm` for small square cells.
481
+ - **A `@media` override with an equal-specificity selector must come AFTER its base rule in source order**, earlier placement is silently ignored.
482
+ - **One un-suffixed component token per property (`--card-bg`), read directly (`var(--card-bg)`).** The `-default` token-shadowing layer was reverted, do not declare `--card-bg-default` fallback chains, and do not rely on ancestor surfaces overriding component-named tokens (that inheritance no longer works).
483
+ - **Global `[color]`/`[weight]` presentational utilities override component color by `@layer` order**, a filled control repurposing `color=` must opt out; conversely the global `[weight]` attribute does NOT override component-scoped font-weight (use a variant).
484
+ - **A grid `auto`/`max-content` track collapses to ~1px around a flex wrapper whose child carries the explicit width**, the child's width doesn't propagate through the wrapper's intrinsic size; set the width on the wrapper (CSS, or JS-mirrored via ResizeObserver).
485
+ - **Square/1:1 cells inheriting `--a-radius-md` render as circles**, use `--a-radius-sm` for small square cells.
486
486
 
487
487
  ## Anti-patterns specific to CSS
488
488
 
489
- - **BEM class syntax** `.component--variant__element`. Not allowed. Slot attribute selectors replace this pattern.
490
- - **`::part()` / `::slotted()`** shadow DOM syntax. AdiaUI is light DOM.
491
- - **Global selectors inside `@scope`** `body { ... }`, `html { ... }`, `* { ... }`. The scope is the component; don't reach outside.
492
- - **`!important`** ever. If you need it, the specificity layering is wrong. Fix the layering.
493
- - **Setting tokens at `:root`** tokens scoped to a component belong in `:where(:scope)`. Only cross-component semantic tokens live in `:root` / `styles/colors/semantics.css`.
489
+ - **BEM class syntax**, `.component--variant__element`. Not allowed. Slot attribute selectors replace this pattern.
490
+ - **`::part()` / `::slotted()`**, shadow DOM syntax. AdiaUI is light DOM.
491
+ - **Global selectors inside `@scope`**, `body { ... }`, `html { ... }`, `* { ... }`. The scope is the component; don't reach outside.
492
+ - **`!important`**, ever. If you need it, the specificity layering is wrong. Fix the layering.
493
+ - **Setting tokens at `:root`**, tokens scoped to a component belong in `:where(:scope)`. Only cross-component semantic tokens live in `:root` / `styles/colors/semantics.css`.
@@ -3,15 +3,15 @@
3
3
  Source of truth: [ADR-0079](../../../../../../docs/ops/adr/adr-0079-for-attribute-id-ref-and-events-only-interaction-contract.md)
4
4
  (ratified 2026-08-20, executed by gh#1780). Read the ADR before adding a new
5
5
  `for=`-bound pair or touching `table-toolbar-ui`/`chart-legend-ui`/
6
- `tooltip-ui`/`context-menu-ui` this file is the routing pointer + the
6
+ `tooltip-ui`/`context-menu-ui`: this file is the routing pointer + the
7
7
  fix-now checklist, not a restatement of the ruling.
8
8
 
9
9
  ## The one-line rule
10
10
 
11
- `for=` means id-ref, everywhere in AdiaUI no component may give it a
11
+ `for=` means id-ref, everywhere in AdiaUI, no component may give it a
12
12
  different resolution contract (`context-menu-ui`'s prior CSS-selector
13
13
  reading was the violation this ADR ends). The interaction contract over any
14
- `for=` binding is bubbling `CustomEvent`s in **both** directions never a
14
+ `for=` binding is bubbling `CustomEvent`s in **both** directions, never a
15
15
  direct property write or method call across the binding.
16
16
  `chart-legend-ui`↔`chart-ui` is the ratified model: the bound element
17
17
  dispatches a bubbling event, the target listens on `document` filtered by
@@ -19,21 +19,21 @@ dispatches a bubbling event, the target listens on `document` filtered by
19
19
 
20
20
  ## Fix-now checklist for a `for=`-bound primitive
21
21
 
22
- - `for=` resolves via `getElementById` (id-ref) never
22
+ - `for=` resolves via `getElementById` (id-ref), never
23
23
  `querySelectorAll`/a CSS-selector reading. A component needing
24
24
  selector-flavored targeting names a DIFFERENT attribute (see
25
- `context-menu-ui`'s `target-selector` below) it never overloads `for`.
25
+ `context-menu-ui`'s `target-selector` below), it never overloads `for`.
26
26
  - Every write the bound pair needs to make on each other is a bubbling
27
27
  `CustomEvent`, dispatched at the resolved target (or document-delegated,
28
- whichever the existing read direction already established) never a
28
+ whichever the existing read direction already established), never a
29
29
  direct property write (`target.prop = …`) or method call
30
30
  (`target.method()`) across the binding.
31
31
  - `chart-legend-ui`, `chart-ui`, and `tooltip-ui[follows=pointer]` are
32
- already verified compliant don't touch them to "align" with a new
32
+ already verified compliant, don't touch them to "align" with a new
33
33
  pattern; the ADR names them explicitly out of scope for its own sweep.
34
34
  - `table-toolbar-ui`→`table-ui`'s inbound command surface is
35
35
  `toolbar-search` / `toolbar-filter-set` / `toolbar-filter-clear` /
36
- `toolbar-columns-set` / `toolbar-paginate` dispatched by the toolbar at
36
+ `toolbar-columns-set` / `toolbar-paginate`, dispatched by the toolbar at
37
37
  its resolved `#target`, applied by `table-ui` via the same public-API call
38
38
  the direct write used to make. This is additive to `table-ui`'s contract:
39
39
  any consumer may dispatch these events, not only `table-toolbar-ui`. The
@@ -43,8 +43,8 @@ dispatches a bubbling event, the target listens on `document` filtered by
43
43
  alternative in the ADR).
44
44
  - A component whose `for=` was CSS-selector-flavored (`context-menu-ui`'s
45
45
  prior shape) renames the selector-flavored attribute to `target-selector`;
46
- `for` survives only as a **deprecated back-compat alias** still resolved
47
- as a selector (never dual-purposed into id-ref sniffing rejected in the
46
+ `for` survives only as a **deprecated back-compat alias**, still resolved
47
+ as a selector (never dual-purposed into id-ref sniffing, rejected in the
48
48
  ADR as ambiguous), honored only when `target-selector` is unset, with a
49
49
  one-shot `console.warn` pointing at the replacement. Document the alias as
50
50
  deprecated in the yaml SoT, the class doc comment, and the examples.
@@ -52,11 +52,11 @@ dispatches a bubbling event, the target listens on `document` filtered by
52
52
  ## What NOT to do
53
53
 
54
54
  - Don't invent a document-level delegated-listener shape for a pair that
55
- already resolves a concrete target reference for its read direction a
55
+ already resolves a concrete target reference for its read direction, a
56
56
  targeted dispatch at the resolved element is simpler and still satisfies
57
57
  "events, not direct writes" (rejected alternative in the ADR, re:
58
58
  `table-toolbar-ui`).
59
59
  - Don't dual-purpose one attribute name for two resolution contracts
60
- (id-ref sometimes, selector sometimes) this is the exact same-name/
60
+ (id-ref sometimes, selector sometimes): this is the exact same-name/
61
61
  different-contract collision ADR-0053's no-shadowing doctrine forbids for
62
62
  global attributes, and the defect this ADR exists to end.