@motion-proto/live-tokens 0.96.0 → 0.98.2

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 (206) hide show
  1. package/.claude/skills/live-tokens-check-compliance/SKILL.md +5 -0
  2. package/.claude/skills/live-tokens-create-component/SKILL.md +4 -3
  3. package/.claude/skills/live-tokens-create-component/references/contract-tests.md +71 -11
  4. package/CHANGELOG.md +309 -0
  5. package/README.md +2 -2
  6. package/bin/check-component.mjs +8 -0
  7. package/bin/check-page.mjs +6 -0
  8. package/bin/cli.mjs +36 -6
  9. package/bin/contractRunner.mjs +142 -17
  10. package/bin/lib/findings.mjs +20 -4
  11. package/bin/rules/a11y.mjs +53 -0
  12. package/bin/set-colors.mjs +1 -0
  13. package/bin/setup-claude.mjs +2 -2
  14. package/dist-plugin/{chunk-A4TBP4MM.js → chunk-SLICH4IH.js} +2 -2
  15. package/dist-plugin/index.cjs +148 -3
  16. package/dist-plugin/index.js +250 -105
  17. package/dist-plugin/setColors/index.cjs +7 -1
  18. package/dist-plugin/setColors/index.d.cts +9 -4
  19. package/dist-plugin/setColors/index.d.ts +9 -4
  20. package/dist-plugin/setColors/index.js +8 -2
  21. package/dist-plugin/setGeometry/index.d.cts +1 -1
  22. package/dist-plugin/setGeometry/index.d.ts +1 -1
  23. package/dist-plugin/setType/index.d.cts +1 -1
  24. package/dist-plugin/setType/index.d.ts +1 -1
  25. package/dist-plugin/{themeTypes-B8_Idrp4.d.cts → themeTypes-BaTQUyac.d.cts} +10 -1
  26. package/dist-plugin/{themeTypes-B8_Idrp4.d.ts → themeTypes-BaTQUyac.d.ts} +10 -1
  27. package/package.json +49 -2
  28. package/src/demo/LabeledSelect.svelte +1 -1
  29. package/src/editor/bootstrap.ts +3 -1
  30. package/src/editor/component-editor/BadgeEditor.svelte +13 -0
  31. package/src/editor/component-editor/ButtonEditor.svelte +33 -2
  32. package/src/editor/component-editor/CalloutEditor.svelte +13 -0
  33. package/src/editor/component-editor/CardEditor.svelte +12 -2
  34. package/src/editor/component-editor/CodeSnippetEditor.svelte +11 -1
  35. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +18 -1
  36. package/src/editor/component-editor/CornerBadgeEditor.svelte +13 -0
  37. package/src/editor/component-editor/DialogEditor.svelte +15 -0
  38. package/src/editor/component-editor/IconButtonEditor.svelte +29 -1
  39. package/src/editor/component-editor/ImageEditor.svelte +20 -9
  40. package/src/editor/component-editor/ImageLightboxEditor.svelte +44 -0
  41. package/src/editor/component-editor/InlineEditActionsEditor.svelte +13 -1
  42. package/src/editor/component-editor/InputEditor.svelte +23 -0
  43. package/src/editor/component-editor/MenuSelectEditor.svelte +27 -1
  44. package/src/editor/component-editor/NotificationEditor.svelte +15 -0
  45. package/src/editor/component-editor/PanelEditor.svelte +1 -1
  46. package/src/editor/component-editor/ProgressBarEditor.svelte +13 -0
  47. package/src/editor/component-editor/RadioButtonEditor.svelte +20 -1
  48. package/src/editor/component-editor/SectionDividerEditor.svelte +41 -13
  49. package/src/editor/component-editor/SegmentedControlEditor.svelte +29 -1
  50. package/src/editor/component-editor/SideNavigationEditor.svelte +40 -1
  51. package/src/editor/component-editor/SlidePagerEditor.svelte +20 -0
  52. package/src/editor/component-editor/SliderEditor.svelte +33 -0
  53. package/src/editor/component-editor/TabBarEditor.svelte +23 -2
  54. package/src/editor/component-editor/TableEditor.svelte +14 -0
  55. package/src/editor/component-editor/ToggleEditor.svelte +17 -0
  56. package/src/editor/component-editor/TooltipEditor.svelte +11 -2
  57. package/src/editor/component-editor/VideoFrameEditor.svelte +8 -0
  58. package/src/editor/component-editor/VideoLightboxEditor.svelte +10 -0
  59. package/src/editor/component-editor/registration.ts +2 -1
  60. package/src/editor/component-editor/registry.ts +69 -27
  61. package/src/editor/component-editor/scaffolding/DemoHeader.svelte +2 -0
  62. package/src/editor/component-editor/scaffolding/StateBlock.svelte +19 -8
  63. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +3 -0
  64. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +70 -10
  65. package/src/editor/component-editor/scaffolding/editorContext.ts +7 -0
  66. package/src/editor/core/a11y/a11yReport.ts +101 -0
  67. package/src/editor/core/a11y/a11yReportTypes.ts +69 -0
  68. package/src/editor/core/a11y/contrastFailures.ts +22 -0
  69. package/src/editor/core/components/contrastPairRegistry.ts +18 -0
  70. package/src/editor/core/palettes/contrast.ts +44 -0
  71. package/src/editor/core/palettes/contrastPairs.ts +79 -0
  72. package/src/editor/core/palettes/resolveColor.ts +77 -0
  73. package/src/editor/core/store/editorPersistence.ts +9 -1
  74. package/src/editor/core/store/editorRenderer.ts +2 -0
  75. package/src/editor/core/store/editorStore.ts +29 -0
  76. package/src/editor/core/store/editorTypes.ts +9 -0
  77. package/src/editor/core/store/editorViewStore.ts +2 -2
  78. package/src/editor/core/store/unsavedEdits.ts +10 -0
  79. package/src/editor/core/themes/buildColors.ts +16 -5
  80. package/src/editor/core/themes/liveStateStream.ts +7 -1
  81. package/src/editor/core/themes/parsers/gradient.ts +5 -2
  82. package/src/editor/core/themes/slices/customColors.ts +226 -0
  83. package/src/editor/core/themes/themeTypes.ts +10 -0
  84. package/src/editor/docs/chapters.ts +1 -0
  85. package/src/editor/docs/content/accessibility.md +362 -0
  86. package/src/editor/docs/content/editing-tokens.md +56 -1
  87. package/src/editor/docs/content.generated.ts +2 -1
  88. package/src/editor/pages/ComponentEditorPage.svelte +10 -1
  89. package/src/editor/pages/EditorShell.svelte +97 -10
  90. package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
  91. package/src/editor/skill-atlas/trees/check-compliance.ts +3 -3
  92. package/src/editor/skill-atlas/trees/create-component.ts +12 -12
  93. package/src/editor/ui/CustomColorReplaceStep.svelte +118 -0
  94. package/src/editor/ui/EditorViewSwitcher.svelte +15 -2
  95. package/src/editor/ui/GradientEditor.svelte +9 -1
  96. package/src/editor/ui/ThemePanel.svelte +4 -4
  97. package/src/editor/ui/UIContrastReadout.svelte +186 -0
  98. package/src/editor/ui/UIInfoPopover.svelte +16 -2
  99. package/src/editor/ui/UIPaletteSelector.svelte +347 -15
  100. package/src/editor/ui/UITokenSelector.svelte +33 -14
  101. package/src/editor/ui/VariablesTab.svelte +3 -0
  102. package/src/editor/ui/a11y/A11yComponentIssues.svelte +103 -0
  103. package/src/editor/ui/a11y/A11yFindingRow.svelte +57 -0
  104. package/src/editor/ui/a11y/A11yIssueRow.svelte +121 -0
  105. package/src/editor/ui/a11y/A11yOverview.svelte +242 -0
  106. package/src/editor/ui/a11y/A11yPanel.svelte +128 -0
  107. package/src/editor/ui/a11y/A11yPropertyMark.svelte +84 -0
  108. package/src/editor/ui/a11y/A11yRailMark.svelte +34 -0
  109. package/src/editor/ui/a11y/AccessibilityTab.svelte +334 -0
  110. package/src/editor/ui/a11y/a11yView.ts +218 -0
  111. package/src/editor/ui/anchorToTrigger.ts +63 -0
  112. package/src/editor/ui/contrastReadout.ts +45 -0
  113. package/src/editor/ui/sections/CustomColorsSection.svelte +254 -0
  114. package/src/live-tokens/data/themes/autumn.json +20 -20
  115. package/src/live-tokens/data/themes/halloween.json +20 -20
  116. package/src/live-tokens/data/themes/midnight-study.json +20 -20
  117. package/src/live-tokens/data/themes/ocean.json +20 -20
  118. package/src/live-tokens/data/themes/royal-velvet.json +20 -20
  119. package/src/live-tokens/data/themes/sketchy.json +20 -20
  120. package/src/live-tokens/data/themes/spring-meadow.json +20 -20
  121. package/src/live-tokens/data/themes/sunset.json +20 -20
  122. package/src/system/components/Badge.svelte +3 -3
  123. package/src/system/components/Button.svelte +9 -3
  124. package/src/system/components/CodeSnippet.svelte +8 -1
  125. package/src/system/components/CornerBadge.svelte +3 -3
  126. package/src/system/components/Dialog.svelte +43 -14
  127. package/src/system/components/IconButton.svelte +1 -1
  128. package/src/system/components/Image.svelte +7 -0
  129. package/src/system/components/ImageLightbox.svelte +26 -26
  130. package/src/system/components/Input.svelte +8 -1
  131. package/src/system/components/MenuSelect.svelte +30 -2
  132. package/src/system/components/Notification.svelte +1 -1
  133. package/src/system/components/ProgressBar.svelte +6 -0
  134. package/src/system/components/RadioButton.svelte +36 -3
  135. package/src/system/components/SegmentedControl.svelte +36 -2
  136. package/src/system/components/SlidePager.svelte +2 -2
  137. package/src/system/components/Slider.svelte +13 -9
  138. package/src/system/components/TabBar.svelte +33 -3
  139. package/src/system/components/Toggle.svelte +2 -2
  140. package/src/system/components/VideoFrame.svelte +1 -1
  141. package/src/system/components/VideoLightbox.svelte +2 -2
  142. package/src/system/internal/focusTrap.ts +35 -0
  143. package/src/testing-js/__aria__/badge.yml +40 -0
  144. package/src/testing-js/__aria__/button.yml +88 -0
  145. package/src/testing-js/__aria__/callout.yml +12 -0
  146. package/src/testing-js/__aria__/card.yml +8 -0
  147. package/src/testing-js/__aria__/codesnippet.yml +21 -0
  148. package/src/testing-js/__aria__/collapsiblesection.yml +23 -0
  149. package/src/testing-js/__aria__/cornerbadge.yml +24 -0
  150. package/src/testing-js/__aria__/dialog.yml +42 -0
  151. package/src/testing-js/__aria__/iconbutton.yml +57 -0
  152. package/src/testing-js/__aria__/image.yml +10 -0
  153. package/src/testing-js/__aria__/imagelightbox.yml +50 -0
  154. package/src/testing-js/__aria__/inlineeditactions.yml +16 -0
  155. package/src/testing-js/__aria__/input.yml +61 -0
  156. package/src/testing-js/__aria__/menuselect.yml +30 -0
  157. package/src/testing-js/__aria__/notification.yml +30 -0
  158. package/src/testing-js/__aria__/panel.yml +2 -0
  159. package/src/testing-js/__aria__/progressbar.yml +2 -0
  160. package/src/testing-js/__aria__/radiobutton.yml +20 -0
  161. package/src/testing-js/__aria__/sectiondivider.yml +9 -0
  162. package/src/testing-js/__aria__/segmentedcontrol.yml +35 -0
  163. package/src/testing-js/__aria__/sidenavigation.yml +304 -0
  164. package/src/testing-js/__aria__/slidepager.yml +72 -0
  165. package/src/testing-js/__aria__/slider.yml +28 -0
  166. package/src/testing-js/__aria__/tabbar.yml +35 -0
  167. package/src/testing-js/__aria__/table.yml +144 -0
  168. package/src/testing-js/__aria__/toggle.yml +12 -0
  169. package/src/testing-js/__aria__/tooltip.yml +2 -0
  170. package/src/testing-js/__aria__/videoframe.yml +6 -0
  171. package/src/testing-js/__aria__/videolightbox.yml +12 -0
  172. package/src/testing-js/{chunk-WIZ6W7UT.js → chunk-3WCLMOM7.js} +2 -2
  173. package/src/testing-js/{chunk-SHBYZUPD.js → chunk-5HNZHIHG.js} +211 -14
  174. package/src/testing-js/chunk-5HNZHIHG.js.map +1 -0
  175. package/src/testing-js/{chunk-NB3NZRBM.js → chunk-7CM5D7PF.js} +5 -1
  176. package/src/testing-js/chunk-7CM5D7PF.js.map +1 -0
  177. package/src/testing-js/chunk-EWSGE27V.js +168 -0
  178. package/src/testing-js/chunk-EWSGE27V.js.map +1 -0
  179. package/src/testing-js/{chunk-GKO27NGN.js → chunk-R54I7YL6.js} +2 -2
  180. package/src/testing-js/{chunk-X6KHEVIG.js → chunk-RWH46IU5.js} +97 -29
  181. package/src/testing-js/chunk-RWH46IU5.js.map +1 -0
  182. package/src/testing-js/chunk-SHLPDNAQ.js +658 -0
  183. package/src/testing-js/chunk-SHLPDNAQ.js.map +1 -0
  184. package/src/testing-js/{chunk-3UKGXCDL.js → chunk-YVVR3GJZ.js} +1 -1
  185. package/src/testing-js/chunk-YVVR3GJZ.js.map +1 -0
  186. package/src/testing-js/component-a11y.contract.js +283 -0
  187. package/src/testing-js/component-a11y.contract.js.map +1 -0
  188. package/src/testing-js/component-alias.contract.js +2 -2
  189. package/src/testing-js/component-behavior.contract.js +2 -2
  190. package/src/testing-js/component-editor.contract.js +6 -5
  191. package/src/testing-js/component-editor.contract.js.map +1 -1
  192. package/src/testing-js/component-render.contract.js +10 -6
  193. package/src/testing-js/component-render.contract.js.map +1 -1
  194. package/src/testing-js/index.d.ts +44 -16
  195. package/src/testing-js/index.js +9 -9
  196. package/src/testing-js/page-compliance.contract.js +163 -18
  197. package/src/testing-js/page-compliance.contract.js.map +1 -1
  198. package/src/testing-js/vitest.js +3 -3
  199. package/template/package.json +9 -0
  200. package/src/editor/ui/keepInViewport.ts +0 -84
  201. package/src/testing-js/chunk-3UKGXCDL.js.map +0 -1
  202. package/src/testing-js/chunk-NB3NZRBM.js.map +0 -1
  203. package/src/testing-js/chunk-SHBYZUPD.js.map +0 -1
  204. package/src/testing-js/chunk-X6KHEVIG.js.map +0 -1
  205. /package/src/testing-js/{chunk-WIZ6W7UT.js.map → chunk-3WCLMOM7.js.map} +0 -0
  206. /package/src/testing-js/{chunk-GKO27NGN.js.map → chunk-R54I7YL6.js.map} +0 -0
@@ -26,6 +26,7 @@ Both checkers are static. They read the project's source, never open a browser,
26
26
  - the fixes the checkers applied, each with its count and any visible shift
27
27
  - the remaining changes by rule, each with its count and any visible shift
28
28
  - the findings left, each with its reason and any config entry the user chose
29
+ - the `a11y-*` alerts, each with its element and guidance, for the user to review
29
30
  - both checker commands with their exit codes
30
31
 
31
32
  `check-page <path>` scopes a run to one page. `check-component <id>` scopes a run to one component: its runtime, its editor, and its registration. The checkers read tokens.css from its default location.
@@ -50,6 +51,10 @@ Every finding's `repair` says what moving it costs.
50
51
  - **`choice`.** A role or an ambiguous value determines the fix, such as a color literal and the role it plays, or a spacing literal tied between two steps. `details` lists the candidates, and `guidance` says how to pick one.
51
52
  - **`authored`.** New code is the fix: a runtime that has to start behaving, an editor schema that has to name a token, a route that has to move. `guidance` names what to write. No candidate list applies.
52
53
 
54
+ ## Accessibility alerts
55
+
56
+ A `--tests` run adds the `a11y-*` rules, which test the rendered page or component against WCAG 2 A and AA with axe, Siteimprove Alfa, the contract's keyboard cases, and the suite's own focus indicator, control boundary, text spacing, and reduced motion checks. Each reports at `warn` and blocks only under `--strict`. Every `a11y-*` finding is an alert the user reviews: list it in the reply, and repair it from its guidance when the repair scope includes warnings. An `a11y-review` finding marks a check axe could not decide, such as text over a gradient, and the user judges it by eye. Inside the live-tokens repository, an `a11y-component` finding with engine rule `aria-snapshot` marks an accessibility tree that changed since it was recorded; after a deliberate markup change, `check-component <id> --tests --update-aria` records it anew.
57
+
53
58
  ## Scope
54
59
 
55
60
  - Add no token to `tokens.css`. Map a literal with no matching token to the nearest existing token by role. When no token fits, leave the finding and say so.
@@ -161,11 +161,12 @@ The excerpt shows the chain for part of the property map. Every editable value r
161
161
 
162
162
  ## Component editor
163
163
 
164
- Create `src/system/components/StatCardEditor.svelte` beside the runtime file. The editor has three parts.
164
+ Create `src/system/components/StatCardEditor.svelte` beside the runtime file. The editor has four parts.
165
165
 
166
166
  1. A `<script module>` block exports `component`, the id, and `allTokens`, one row per property in the map. A row is `{ label, variable, element? }`; `element` groups rows in the panel by part, and `label` names the property in the row.
167
167
  2. The instance script imports the runtime component and the editor primitives from the package's public paths, and maps the state being edited to preview props.
168
168
  3. The markup mounts `ComponentEditorBase` with one `VariantGroup` per variant, each rendering a preview.
169
+ 4. The same `<script module>` block exports `contrastPairs`, one pair for each foreground color and each backdrop it sits on, and the registration passes it as `contrastPairs`. Every `allTokens` color with a text or icon suffix (`-text`, `-label`, `-icon`, `-title`, `-body`, `-value`, and the other text roles in `references/token-naming.md`) is the foreground of at least one pair. A pair is `{ foreground, backdrop, role, fontSize?, fontWeight?, inactive?, context? }`. `backdrop` lists the properties the runtime paints behind the foreground, top layer first, and leaves out `--page-bg`, which the editor puts under the last layer. `role` is `'text'`, or `'non-text'` for an icon or another mark. A text pair names its `-font-size` property as `fontSize`, the smaller one when the text renders at two sizes, and its `-font-weight` property as `fontWeight`. A disabled state's pair sets `inactive: true`, because WCAG exempts disabled controls. A foreground over several backdrops takes one pair per backdrop, each with its own `context`. A pair names only properties in `allTokens` and intrinsic variables, and no two pairs share a foreground, a backdrop, and a context.
169
170
 
170
171
  ```svelte
171
172
  <script module lang="ts">
@@ -224,7 +225,7 @@ registerComponent({
224
225
  });
225
226
  ```
226
227
 
227
- Import the module from `src/main.ts`, before `bootLiveTokens` or `mount`, and name it as `registrySetup` in `live-tokens.testing.ts`. One module then serves the running app and `check-component --tests`, which imports it to see the registration without mounting the app. The component's contract, one `ComponentContract` in the module `contractsModule` names, is what the eight component suites run; without it `--tests` reports `contract-missing`. Read `references/contract-tests.md` for both. A component that declares intrinsics adds `intrinsics` to the entry. `check-component` finds the registration by the id literal inside the call.
228
+ Import the module from `src/main.ts`, before `bootLiveTokens` or `mount`, and name it as `registrySetup` in `live-tokens.testing.ts`. One module then serves the running app and `check-component --tests`, which imports it to see the registration without mounting the app. The component's contract, one `ComponentContract` in the module `contractsModule` names, is what the eight component suites run; without it `--tests` reports `contract-missing`. Read `references/contract-tests.md` for both. An interactive component's contract declares a keyboard case for each key its WAI-ARIA pattern handles: Enter or Space activates, an arrow key moves where the pattern moves, and Escape closes an overlay. A component that declares intrinsics adds `intrinsics` to the entry. `check-component` finds the registration by the id literal inside the call.
228
229
 
229
230
  At boot the plugin reads the `:global(:root)` block and writes `component-configs/<id>/default.json`, one token per property. An edit in the editor writes `_working.json`; Save As writes a named config. The assignments stay token references through that flow.
230
231
 
@@ -239,7 +240,7 @@ A fixed overlay portals to `<body>`: read `references/fixed-overlays.md`. A cont
239
240
  ## Verification
240
241
 
241
242
  1. Run `npx live-tokens check-component <id> --tests --strict --json`. Inside the live-tokens repository, run `node bin/cli.mjs check-component <id> --tests --strict --json`. It applies every `auto` repair, runs the registry contract and, for a component with a contract in the module `contractsModule` names, the component contract suites, and returns the fixes it applied, the findings that remain, and coverage by rule. `--off=<rule>` silences a rule for one run, and `--no-fix` reports without editing.
242
- 2. Each remaining finding carries a rule id, a line, and its `guidance`. Make each repair from its guidance, and run the command again until it exits 0 with complete applicable coverage and no disabled checks.
243
+ 2. Each remaining finding carries a rule id, a line, and its `guidance`. Make each repair from its guidance, and run the command again until it exits 0 with complete applicable coverage and no disabled checks. The `a11y-*` findings report accessibility at `warn`, and `--strict` makes them block with the rest. An `a11y-review` finding marks a check axe could not decide, such as text over a gradient: judge it by eye against its help page, and repair it when it fails. Name the ones that pass in the reply, and add `--off=a11y-review` to the final run, the one disabled check this step allows. Inside the live-tokens repository, an `aria-snapshot` finding marks an accessibility tree that changed since it was recorded; after a deliberate markup change, rerun with `--update-aria`.
243
244
  3. Run the project's Svelte check and its build.
244
245
  4. Reply with the files, the component id, the props, and the result of each check, naming any check the environment prevented.
245
246
 
@@ -119,10 +119,10 @@ export const contracts: ComponentContract[] = [
119
119
  },
120
120
  theme: {
121
121
  theme: 'brand',
122
- changed: ['--statcard-radius'],
123
- unchanged: ['--statcard-padding'],
124
- aliasedTo: { '--statcard-radius': '--radius-none' },
125
- observe: { part: 'root', css: 'borderRadius', variable: '--statcard-radius' },
122
+ changed: ['--statcard-surface'],
123
+ unchanged: ['--statcard-radius', '--statcard-padding'],
124
+ aliasedTo: { '--statcard-surface': '--surface-neutral' },
125
+ observe: { part: 'root', css: 'backgroundColor', variable: '--statcard-surface' },
126
126
  },
127
127
  interaction: { applicable: false, reason: 'a stat card carries no interactive role' },
128
128
  sketch: {
@@ -145,18 +145,78 @@ Each obligation and what it needs from the project:
145
145
  forces. `applicable: false` when the editor renders no state strip.
146
146
  - `persistence`: an edit the run makes through the editor, saves, reloads, and
147
147
  reads back. `resetVariable` is a property one of the cases moves.
148
- - `theme`: a theme in the project's data tree whose block for the component
149
- reassigns a property. Save one with the editor before the first run.
150
- `changed` names the properties the theme moves, `unchanged` the ones it
151
- leaves, and `aliasedTo` the design token each reassigned property resolves
152
- to under it.
153
- - `interaction`: the interactive part's role and its cases. `applicable:
154
- false` for a component with no interactive role.
148
+ - `theme`: a saved theme in the project's data tree, previewed as it stands.
149
+ The run checks that the component follows the theme's design tokens.
150
+ `changed` names the properties the theme moves through its design tokens,
151
+ such as colors, shadows, and fonts, `unchanged` the ones it leaves, and `aliasedTo` the design token each
152
+ changed property resolves to under it. A theme assigns geometry steps only
153
+ to the components it holds a block for, so a new component's radius,
154
+ padding, and border width belong in `unchanged`.
155
+ - `interaction`: the interactive part's role and its cases, keyboard cases
156
+ among them. See Keyboard cases below. `applicable: false` for a component
157
+ with no interactive role.
158
+ - `boundaries`: optional. The parts that draw a control's visible edge,
159
+ such as a text field or a switch track, each with a label:
160
+ `[{ part: 'field', label: 'Field' }]`. The accessibility suite measures
161
+ each part's border and surface against the backdrop behind it at 3:1, per
162
+ WCAG 2.2 SC 1.4.11. A component with no control edge leaves it out.
155
163
  - `behavior`: what a callback prop does. See Behavior below.
156
164
  - `sketch`: a sketch style id and the fill and stroke each reserved-class part
157
165
  resolves to under it. `applicable: false` only for a component
158
166
  `references/sketch-mode.md` exempts.
159
167
 
168
+ ## Keyboard cases
169
+
170
+ An interactive component declares one `press` case for each key its WAI-ARIA
171
+ pattern handles: Enter or Space activates, an arrow key moves focus or value
172
+ where the pattern moves it, and Escape closes an overlay. Each sets
173
+ `rule: 'a11y-keyboard'`. A failed keyboard case reports `a11y-keyboard` at
174
+ `warn` and lets the test pass; a case without `rule` fails as
175
+ `contract-interaction`, an error.
176
+
177
+ ```ts
178
+ interaction: {
179
+ part: 'secondTab',
180
+ role: 'button',
181
+ cases: [
182
+ {
183
+ name: 'Enter activates a tab',
184
+ rule: 'a11y-keyboard',
185
+ action: { kind: 'press', part: 'secondTab', key: 'Enter' },
186
+ expect: { kind: 'activated', part: 'secondTab' },
187
+ },
188
+ {
189
+ name: 'arrow right moves focus to the next tab',
190
+ rule: 'a11y-keyboard',
191
+ action: { kind: 'press', part: 'firstTab', key: 'ArrowRight' },
192
+ expect: { kind: 'focused', part: 'secondTab', value: true },
193
+ },
194
+ ],
195
+ },
196
+ ```
197
+
198
+ - `press` focuses `part` and sends `key`. Without `part`, the key goes to the
199
+ element that holds focus, such as an overlay that focused itself as it
200
+ opened.
201
+ - `activated` passes when the key fired a click on the part, as a native
202
+ button does for Enter and Space. The preview never feeds a controlled
203
+ component's callback back into its props, so a click is the activation it
204
+ shows.
205
+ - `focused` with `value: true` names the part an arrow key moves focus to.
206
+ Arrow cases need parts that keep their position as the selection moves,
207
+ such as `firstTab` and `secondTab`.
208
+ - `present` with `value: false` passes when the part has left the document,
209
+ as a dialog's root does when Escape closes it:
210
+ `{ kind: 'present', part: 'root', value: false }`.
211
+
212
+ The accessibility suite reads the same contract. It scans every view that
213
+ `properties` and `states` name with axe and Siteimprove Alfa's rule SIA-R65,
214
+ measures focus indicators and the `boundaries` parts at 3:1, checks text
215
+ spacing and reduced motion, and reports each failure at `warn` under an
216
+ `a11y-*` rule. Mark placeholder
217
+ content the editor adds to its preview, such as a demo label, with
218
+ `data-preview-demo`, and the scans skip it.
219
+
160
220
  ## Behavior
161
221
 
162
222
  `behavior` runs under Vitest with `mount` from Svelte and happy-dom, not in
package/CHANGELOG.md CHANGED
@@ -1,5 +1,314 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.98.2 — Accessibility testing
4
+
5
+ The v0.98.0 and v0.98.1 tags never reached npm. Release checks stopped both:
6
+ a test time limit and a rule count the accessibility suite outgrew.
7
+
8
+ ### Added
9
+
10
+ - **`check-component --tests` scans components for accessibility.** A new
11
+ suite opens every variant and state a component's contract names and runs
12
+ axe-core against WCAG 2 A and AA and Siteimprove Alfa's focus-visibility
13
+ rule SIA-R65 over the preview. Findings report as `a11y-component`,
14
+ `a11y-focus-visible`, and `a11y-review`, the last for checks axe could not
15
+ decide, such as text over a gradient. Editor demo content marked
16
+ `data-preview-demo` stays out of the scan. The library's own test run
17
+ repeats axe's contrast rule under the eight preset themes.
18
+ - **`check-page --tests` scans pages for accessibility.** Each page and
19
+ viewport gains three tests. `page-a11y` runs axe outside the shipped
20
+ components, with contrast left to `page-contrast`. `page-text-spacing`
21
+ reports overflow the WCAG 2.2 SC 1.4.12 text spacing override adds.
22
+ `page-reduced-motion` reports animations still running 200 ms after load
23
+ under `prefers-reduced-motion: reduce`. They report as `a11y-page`,
24
+ `a11y-review`, `a11y-text-spacing`, and `a11y-reduced-motion`.
25
+ - **Accessibility findings alert by default.** All seven `a11y-*` rules
26
+ report at `warn`, and `--strict` makes them errors, as it does every other
27
+ warning. `--off` and `live-tokens.config.json` silence or raise each one.
28
+ A scan that crashes reports `tests-setup`.
29
+ - **Each shipped component's accessibility tree is recorded.** The trees
30
+ live in `src/testing/contracts/__aria__/<id>.yml`, one block per view. A
31
+ run that reads a different tree reports `a11y-component` with engine rule
32
+ `aria-snapshot`, and `check-component --tests --update-aria` records the
33
+ trees anew after a deliberate markup change. A project's own components
34
+ get no recorded tree yet.
35
+ - **Component contracts take keyboard cases.** An `InteractionCase` with
36
+ `rule: 'a11y-keyboard'` reports its failure as an `a11y-keyboard` warning
37
+ and lets the test pass. A `press` action may omit `part`
38
+ to send the key to whatever holds focus. `InteractionOutcome` gains
39
+ `activated`, for a key that fired a click on the part, and `present`, for
40
+ whether the part is in the document. The fifteen interactive shipped
41
+ components carry 27 keyboard cases for activation, arrow movement, and
42
+ Escape.
43
+ - **The accessibility engines ship as optional peers.** `axe-core` 4.11.1 or
44
+ later, `@axe-core/playwright`, and seven `@siteimprove/alfa-*` packages at
45
+ 0.119 join `peerDependencies` as optional, and the `create` template
46
+ installs them. Without one, the tests it drives report as inapplicable and
47
+ the run carries on.
48
+ - **The editor gains an Accessibility view.** Run test checks every
49
+ component in the open theme as saved, in about three and a half minutes,
50
+ and lists each component's findings by repair. An overview opens the
51
+ report with the total, the count in each repair group, and a table of every
52
+ component with a finding, whose names scroll to their sections. Each
53
+ section's **Open component** opens the component's page at its
54
+ accessibility panel. "Fix with a property" holds
55
+ the colour pairs the editor's contrast checker finds below their floor and
56
+ the focus rings and control boundaries below 3:1. "Fix in the component's
57
+ code" holds every other finding: axe failures, unchanged focus styles,
58
+ failed keyboard cases, clipped text, and motion that ignores reduced
59
+ motion. Results to check by eye count in the totals and collect in one
60
+ closed list. Findings that share an engine rule, a help text, and their
61
+ traced properties merge into one row that names its element count and
62
+ views, and each traced property gets a "→". A row's right-hand link opens
63
+ the rule, success criterion, technique, or pattern the finding cites and
64
+ names its kind. A property's label leads with
65
+ the words from its variable name that the editor's label leaves out, such
66
+ as "Focused border color". With edits pending, Run test asks for a save and
67
+ starts nothing. A missing test tool shows its install command. The dev
68
+ server runs one check at a time and keeps the last report in
69
+ `test-results/live-tokens/a11y-report.json`.
70
+ - **The editor marks the components the last run named.** Every finding
71
+ marks its component, results to check by eye included. The component list
72
+ leads each one's name with a warning triangle, in the sidebar, the narrow
73
+ menu, and the standalone component page. A condensed list shows the
74
+ triangle on the component's icon. Each one's page opens with a panel under its
75
+ title that lists its findings in three groups: "Fix with a property", "Fix
76
+ in the component's code", and "Check by eye". Each "→" in the panel opens
77
+ the tab holding the property, an intrinsic's hover tab included, and
78
+ highlights its row. The marks follow the last finished run; the contrast
79
+ readout on each colour row stays live.
80
+ - **Property rows carry the run's findings.** A row whose property a finding
81
+ traces to shows a warning triangle beside its label, or a grey "?" when
82
+ every finding on it is a result to check by eye. The mark opens a popover that lists the
83
+ findings with their elements, views, help pages, and traced properties.
84
+ Token selectors, gradient editors, and toggle rows carry it. The editor's own
85
+ contrast failures add no row mark, because the live readout already shows
86
+ them.
87
+ - **Accessibility findings name the properties that fix them.** The
88
+ component suite reads the CSS rules that match each failing element
89
+ through the Chrome DevTools protocol and follows each `var()` to the
90
+ component's semantic properties and intrinsics. `check-component --tests
91
+ --json` lists them, sorted, in each finding's `details.variables`, and
92
+ leaves the field out when the trace finds nothing. The field is additive.
93
+ - **The component suite runs four more checks.** Focus indicator contrast
94
+ and control boundary contrast measure a focus ring and a control's edge at
95
+ 3:1 against the backdrop, under WCAG 2.2 SC 1.4.11. Text spacing applies
96
+ the WCAG 2.2 SC 1.4.12 override inside each component and reports text
97
+ that clips under it. Reduced motion reports motion longer than 200 ms that
98
+ still runs under `prefers-reduced-motion: reduce`, after WCAG technique
99
+ C39 for SC 2.3.3. The results are warnings under the existing rules:
100
+ `a11y-component` (engine rules `focus-indicator-contrast` and
101
+ `control-boundary-contrast`), `a11y-review` for a ring or edge over a
102
+ gradient or image, `a11y-text-spacing`, and `a11y-reduced-motion`, which
103
+ `check-component` now reports too. `--strict` raises them with the rest.
104
+ - **Component contracts take `boundaries`.** The optional field,
105
+ `{ part: string; label: string }[]`, lists the parts that draw a control's
106
+ visible edge, for the control boundary check. Input, RadioButton, Slider,
107
+ Toggle, and SegmentedControl declare it.
108
+ - **Selected states join the control boundary check.** TabBar's selected
109
+ tab, SegmentedControl's selected option, and SideNavigation's selected
110
+ title, section, item, and footer show their state by surface and edge
111
+ alone, so their contracts list them in `boundaries`. Each measures against
112
+ the bar or panel behind it. A `boundaries` entry takes `when`, a selector
113
+ the part must also match, and the check reads an outline and every border
114
+ side and keeps the best. The trace follows only the winning declaration of
115
+ a private property, so a selected tab's finding names the selected tab's
116
+ properties alone.
117
+ - **Image Lightbox's editor sets the tile fit.** The Tile tab gains a "tile
118
+ fit" row, Contain or Cover, which writes `--imagelightbox-tile-object-fit`
119
+ and carries the accessibility mark. ImageLightbox's `fit` prop no longer
120
+ defaults to `contain` on every tile, so a tile without it follows the
121
+ theme's value.
122
+ - **`check-component --tests` takes `--a11y` and `--open-theme`.** `--a11y`
123
+ runs the accessibility suite and the keyboard cases alone, with no static
124
+ checks or fixes, and reports only `a11y-*` and `tests-*` findings.
125
+ `--open-theme` tests the theme the editor has open, as saved, and still
126
+ leaves every unsaved buffer out of the run.
127
+ - **`--json` output names the tested theme and each finding's component.**
128
+ `check-component --tests --json` adds a top-level `theme`, and each
129
+ Playwright finding that names a component carries it in a `component`
130
+ field. Both fields are additive.
131
+ - **The guide gains an Accessibility chapter.** It covers the suites, the
132
+ rules, the focus indicator, boundary, text spacing, and motion checks,
133
+ property tracing, review findings, the recorded trees, keyboard cases,
134
+ `boundaries`, the Accessibility view, the marks in the editor, the
135
+ engines, and the limits.
136
+ - **The skills cover accessibility.** `live-tokens-check-compliance` lists
137
+ the `a11y-*` findings as alerts for the user to review.
138
+ `live-tokens-create-component` declares keyboard cases for a new component
139
+ and judges `a11y-review` findings by eye.
140
+ - **Component editors show the contrast ratio of every foreground color.** A
141
+ color row whose variable paints text, an icon or another foreground mark
142
+ shows the WCAG ratio between that color and the layers behind it, down to the
143
+ page background. A pair below its floor turns amber and gains an exclamation
144
+ mark. Click the ratio to open the rule it tracks, each pair's measured ratio
145
+ and layers, and a link to the success criterion. A disabled state shows its
146
+ ratio in a dimmer gray, because WCAG exempts disabled controls. The readout
147
+ recomputes after every edit and theme load, and the rows keep their places.
148
+ - **Editor modules and `registerComponent()` take `contrastPairs`.** Each pair
149
+ names a foreground variable, the variables that paint behind it top first, its
150
+ role (`text` or `non-text`), and for text its font size and weight. A pair
151
+ for a disabled state sets `inactive`. A foreground over several backdrops
152
+ takes one pair per backdrop, each with a `context`, and the readout shows the
153
+ pair closest to failing.
154
+ - **`UITokenSelector` takes a `metaAfter` snippet.** It renders after the value
155
+ name in the same column, and the value name truncates first.
156
+
157
+ ### Changed
158
+
159
+ - **Button's catalogue entry covers navigation.** A Button is a labelled
160
+ trigger that runs an action or opens another page, such as View details.
161
+ An anchor stays the choice for a link inside running text.
162
+
163
+ ### Fixed
164
+
165
+ - **Check reports reach a pipe whole.** On macOS, `check-component` and
166
+ `check-page` cut piped output at 64 KiB, because the process exited before
167
+ the pipe drained. Both checkers now write their report synchronously, so
168
+ `--json` output read through a pipe parses.
169
+ - **`page-overflow` passes a component inside a scrolling ancestor.** The
170
+ check reported a component as clipped by the nearest ancestor with any
171
+ overflow other than `visible`, so a Badge at the end of a Table row on a
172
+ phone failed against the Table's own `overflow-x: auto` wrapper. An
173
+ ancestor that scrolls sideways now keeps the component in reach, and only
174
+ one that hides overflow without scrolling clips it.
175
+ - **The theme contract example tests adherence to a saved theme.** The
176
+ example in `live-tokens-create-component`'s `references/contract-tests.md`
177
+ expected a theme to move a new component's radius, which only a theme block
178
+ for that component can do, and told the agent to save one first. The
179
+ example now expects the theme to move a color property through its design
180
+ tokens and lists the component's radius and padding as unchanged.
181
+ - **TabBar follows the WAI-ARIA tabs pattern.** The bar is a `tablist` of
182
+ `tab` elements that carry `aria-selected`. Tab reaches the selected tab
183
+ alone, the arrow keys move focus between enabled tabs and wrap, Home and
184
+ End jump to the first and last, and Enter or Space selects.
185
+ - **TabBar's selected indicator meets 3:1.** `--tabbar-selected-border` maps
186
+ to `--border-brand-medium` in place of `--color-brand-500`, which lifts the
187
+ underline from 2.96:1 to 3.47:1 against the default theme's page.
188
+ - **SegmentedControl follows the WAI-ARIA radio group pattern.** Tab reaches
189
+ the checked option alone, and the arrow keys move focus and selection to
190
+ the next or previous enabled option and wrap. A focused option shows a ring
191
+ outside its box in `--border-neutral-strong`, so focus on the selected
192
+ option reads apart from its brand outline.
193
+ - **SegmentedControl's selected outline meets 3:1.**
194
+ `--segmentedcontrol-selected-border` maps to `--border-brand-medium` in
195
+ place of `--border-brand`, which lifts the outline from 2.67:1 to 4.31:1
196
+ against the default theme's bar.
197
+ - **Control edges meet 3:1.** Input's field, RadioButton's circle in every
198
+ state, SegmentedControl's bar, Slider's track at both variants and Toggle's
199
+ track now draw their edge in `--border-neutral-medium`, in place of
200
+ `--border-neutral` (`--border-neutral-subtle` for Slider). Toggle's on
201
+ track and Slider's thumbs draw `--border-brand-medium` in place of
202
+ `--border-brand`. Against the default theme's page the neutral edges rise
203
+ from 2.42:1 to 3.97:1, Slider's track from 1.45:1, and Toggle's on track
204
+ from 2.14:1 to 3.47:1. Slider's focus ring, drawn in the thumb's border
205
+ colour, rises from 2.14:1 to 3.47:1.
206
+ - **The preset themes carry the 3:1 control edges.** Each preset holds its
207
+ own copy of the component mappings, so the eight presets now carry the new
208
+ edges above and TabBar's and SegmentedControl's selected borders. In
209
+ autumn, ocean, royal-velvet, sketchy and spring-meadow every control edge
210
+ and selected indicator now measures 3:1 or more, where autumn's Slider
211
+ track measured 1.4:1 and its selected tab 2.47:1. Halloween,
212
+ midnight-study and sunset paint a gradient page, so their edges stay a
213
+ check by eye.
214
+ - **VideoFrame, VideoLightbox and SlidePager draw a focus ring at 3:1.** The
215
+ trigger, the close button, the stage and the pager controls drew
216
+ `--ring-focus-md`, a brand colour at 10% opacity that measured 1.06:1 to
217
+ 1.09:1. Each now draws the neutral ring SegmentedControl uses, a
218
+ `--border-width-2` box shadow in `--border-neutral-strong`.
219
+ - **Badge, CornerBadge and Notification text meets 4.5:1.** The brand and
220
+ danger badges, and Notification's danger variant, sit on the next lower
221
+ surface step, `--surface-brand-low` and `--surface-danger-low`, which lifts
222
+ their text from 3.87:1 to 5.38:1 and from 3.30:1 to 4.56:1 on the default
223
+ theme. The canvas badges keep `--surface-canvas` and take
224
+ `--text-secondary`, since no canvas text step reaches 4.5:1 on a canvas
225
+ surface; they rise from 1.55:1 to 7.15:1. The eight presets carry the same
226
+ mappings, and under each of them axe reports no `color-contrast` failure
227
+ on the three components.
228
+ - **Input shows focus in its error state.** The red error outline replaced
229
+ the focus outline, so focusing an invalid field changed nothing visible.
230
+ Focus now adds a `--border-neutral-strong` ring outside the red outline.
231
+ - **Slider's thumbs take the pointer over 24px.** Each native thumb and its
232
+ input now measure `max(thumb size, --space-24)`, the WCAG 2.2 SC 2.5.8
233
+ minimum, while the visible cap keeps its own size. axe reported both range
234
+ thumbs under `target-size`, because their inputs share one box.
235
+ - **RadioButton follows the WAI-ARIA radio group pattern.** Each option is a
236
+ `radio` with `aria-checked`. Tab reaches the checked option alone, and the
237
+ arrow keys move focus and selection to the next or previous option and
238
+ wrap. A group is the nearest `role="radiogroup"` ancestor, else the parent
239
+ the options share; wrap a set in a `radiogroup` with a name.
240
+ - **TabBar's icon-only tabs carry a name.** With `iconOnly` set, each tab
241
+ takes its `label` as `aria-label`, and the icon is hidden from assistive
242
+ technology.
243
+ - **CodeSnippet's code line takes keyboard focus.** A long snippet scrolls
244
+ sideways, so the code element now sits in the tab order and shows the
245
+ neutral focus ring, and the arrow keys scroll it.
246
+ - **MenuSelect follows the WAI-ARIA listbox pattern.** The list items carry
247
+ `role="none"`, so each option sits directly in the listbox or menu. Tab
248
+ reaches the selected option alone, Up and Down move focus between enabled
249
+ options and wrap, and Home and End jump to the ends. A new optional
250
+ `ariaLabel` prop names the list; pass the text of the Button that opens
251
+ it.
252
+ - **Dialog follows the WAI-ARIA dialog pattern.** The panel is a `dialog`
253
+ named by its title, and `aria-modal` outside the inline preview. Escape
254
+ takes the cancel path: it calls `cancel.onClick` when a cancel button is
255
+ set, else `onclose`, as the close icon does.
256
+ - **Button's shimmer stops under reduced motion.** With
257
+ `prefers-reduced-motion: reduce`, the 500 ms sweep across a hovered
258
+ Button no longer draws. The `--button-shimmer` switch is unchanged.
259
+ - **Image's hover zoom jumps under reduced motion.** With
260
+ `prefers-reduced-motion: reduce`, the zoom and grow transforms apply at
261
+ once in place of the 300 ms transition.
262
+ - **Progress Bar's fill moves at once under reduced motion.** With
263
+ `prefers-reduced-motion: reduce`, the fill takes its new width without the
264
+ 400 ms transition.
265
+ - **Image Lightbox's tile holds still under reduced motion.** With
266
+ `prefers-reduced-motion: reduce`, the hovered tile scales without its
267
+ 250 ms transition, as the lightbox's own open and close animations already
268
+ did.
269
+ - **Dialog holds focus while it is open.** Outside the inline preview,
270
+ opening the dialog moves focus to its confirm, cancel or close button,
271
+ else its first focusable element, else the panel. Tab and Shift+Tab cycle
272
+ inside the panel, and closing returns focus to the element that held it
273
+ before. Image Lightbox's Tab handling now shares the same helper.
274
+
275
+ ## 0.97.0 — Custom colors
276
+
277
+ ### Added
278
+
279
+ - **Every color picker offers custom colors.** A **Custom** row after Danger
280
+ lists the open theme's own colors, `--color-custom-1`, `--color-custom-2`
281
+ and so on, each holding one OKLCH value for light and dark. The row's
282
+ plus swatch starts a new color from the slot's current color and opens
283
+ the OKLCH editor; confirm assigns it to the slot in one undo step, and
284
+ cancel removes it. A slot picks an existing custom color as it picks a
285
+ palette step, opacity included. The pencil on a custom swatch edits it in
286
+ place, and the edit moves every slot that names it. The Tokens view gains a
287
+ **Custom Colours** section that lists, edits and removes them. Removing a
288
+ color that a component property, gradient stop or wash names first asks
289
+ for a replacement, preselects the nearest token, and rewrites each
290
+ reference with its opacity intact, all in one undo step.
291
+ - **Themes carry an optional `customColors` field.**
292
+ `ColorsAndType.customColors` sits beside `gradients` and holds
293
+ `{ variable, color }` entries. The field is additive: a theme without it
294
+ loads with no custom colors, `THEME_SCHEMA_VERSION` stays 5, and no
295
+ migration runs. Save, export and import keep the list, and Adopt writes
296
+ each custom color into `tokens.generated.css` with the theme's other
297
+ variables. `set-colors` and `save-theme` carry the field through
298
+ unchanged. `tokens.css` and the shipped themes declare no custom color, so
299
+ `check-page` reports `unknown-token` for page CSS that names
300
+ `var(--color-custom-1)`; reach a custom color through a component property.
301
+
302
+ ## 0.96.1 — Vitest 5 peer range
303
+
304
+ ### Fixed
305
+
306
+ - **Installs alongside Vitest 5.** The optional `vitest` peer range now reads
307
+ `^4.0 || ^5.0`. With `^4.0` alone, npm stopped any install that re-resolved
308
+ the tree with `ERESOLVE` once Vitest 5 sat anywhere in it, including as an
309
+ uninstalled optional peer of Vite's devtools. The `testing` contract helpers
310
+ pass the full suite on Vitest 5.
311
+
3
312
  ## 0.96.0 — Lightbox zoom readout in natural pixels
4
313
 
5
314
  ### Changed
package/README.md CHANGED
@@ -326,8 +326,8 @@ npx @motion-proto/live-tokens <command>
326
326
  | `components [id] [--json]` | List every component the project has, shipped and its own, with the props each takes; with an id, its props, variants, tokens, and defaults. |
327
327
  | `tokens [--scale <name>] [--json]` | List every design token the project's `tokens.css` declares, by scale, with its value. |
328
328
  | `report [--json]` | The project as facts: pending migrations, tokens each component reads, which page renders which component, and both checkers' findings by rule. Always exits 0. |
329
- | `check-component [id]` | Validate a component's runtime, editor, and registration against the authoring contract; with no id, every component authored under `src/system/components`. |
330
- | `check-page [paths...]` | Validate pages against the create-page contract: catalogue components and their props, theme tokens over literals, route wiring. |
329
+ | `check-component [id] [--tests] [--update-aria] [--a11y] [--open-theme]` | Validate a component's runtime, editor, and registration against the authoring contract; with no id, every component authored under `src/system/components`. `--tests` also runs the contract suites, the accessibility scans among them; `--update-aria` records each scanned component's accessibility tree anew after a deliberate markup change; `--a11y` runs the accessibility scans and keyboard cases alone; `--open-theme` tests the theme the editor has open, as saved. |
330
+ | `check-page [paths...] [--tests]` | Validate pages against the create-page contract: catalogue components and their props, theme tokens over literals, route wiring. `--tests` also opens each page in the app and checks its rendered paint, contrast, layout, and accessibility. |
331
331
  | `set-colors <base-colors.json> [--dry-run]` | Build the theme's whole color identity from 10 OKLCH base colors, enforce AA contrast on the derived text tokens, and write the result to the unsaved colors-and-type buffer. |
332
332
  | `set-type <pairing.json> [--dry-run] [--no-verify]` | Bind Google Fonts families to the theme's font stacks, verified against the API. |
333
333
  | `set-geometry <ops.json> [--dry-run]` | Move radius, padding, gap, and border-width aliases along their token scales. |
@@ -33,6 +33,7 @@ import {
33
33
  extractGlobalRootBlocks,
34
34
  loadVocabulary,
35
35
  } from './lib/tokenVocabulary.mjs';
36
+ import * as a11y from './rules/a11y.mjs';
36
37
  import * as componentStructure from './rules/componentStructure.mjs';
37
38
  import * as importsAndRoutes from './rules/importsAndRoutes.mjs';
38
39
  import * as testRuns from './rules/testRuns.mjs';
@@ -83,6 +84,12 @@ export const COMPONENT_RULES = assembleRules(
83
84
  'contract-listed',
84
85
  'contract-sketch',
85
86
  'contract-missing',
87
+ 'a11y-component',
88
+ 'a11y-review',
89
+ 'a11y-focus-visible',
90
+ 'a11y-keyboard',
91
+ 'a11y-text-spacing',
92
+ 'a11y-reduced-motion',
86
93
  'tests-not-installed',
87
94
  'tests-setup',
88
95
  'tests-incomplete',
@@ -91,6 +98,7 @@ export const COMPONENT_RULES = assembleRules(
91
98
  tokenRules.componentRules,
92
99
  importsAndRoutes.componentRules,
93
100
  testRuns.componentRules,
101
+ a11y.componentRules,
94
102
  );
95
103
 
96
104
  function capitalize(id) {
@@ -16,6 +16,7 @@ import { assembleRules, isExcluded, lineOf } from './lib/findings.mjs';
16
16
  import { codeRegion, inlineStyleRegions, styleRegions } from './lib/pageSource.mjs';
17
17
  import { loadVocabulary, walk } from './lib/tokenVocabulary.mjs';
18
18
  import { resolveTokensCssPath } from './migrate.mjs';
19
+ import * as a11y from './rules/a11y.mjs';
19
20
  import * as componentUse from './rules/componentUse.mjs';
20
21
  import * as importsAndRoutes from './rules/importsAndRoutes.mjs';
21
22
  import * as testRuns from './rules/testRuns.mjs';
@@ -49,6 +50,10 @@ export const PAGE_RULES = assembleRules(
49
50
  'page-contrast',
50
51
  'page-grid',
51
52
  'page-overflow',
53
+ 'a11y-page',
54
+ 'a11y-review',
55
+ 'a11y-text-spacing',
56
+ 'a11y-reduced-motion',
52
57
  'tests-not-installed',
53
58
  'tests-setup',
54
59
  'tests-incomplete',
@@ -57,6 +62,7 @@ export const PAGE_RULES = assembleRules(
57
62
  importsAndRoutes.pageRules,
58
63
  tokenRules.pageRules,
59
64
  testRuns.pageRules,
65
+ a11y.pageRules,
60
66
  );
61
67
 
62
68
  // Directories that hold the system, not pages built on it.
package/bin/cli.mjs CHANGED
@@ -80,7 +80,7 @@ Commands:
80
80
  renders which component, and both checkers'
81
81
  findings by rule under the project's severities
82
82
  and under --strict. Always exits 0
83
- check-component [id] [--tests]
83
+ check-component [id] [--tests] [--update-aria] [--a11y] [--open-theme]
84
84
  Validate <id>'s runtime, editor, and registration
85
85
  against the live-tokens-create-component contract.
86
86
  --tests also runs the registry contract under
@@ -88,7 +88,16 @@ Commands:
88
88
  Playwright, reporting coverage by rule. Needs
89
89
  @playwright/test, vitest, and happy-dom; a
90
90
  missing one is a tests-not-installed finding
91
- naming the install command
91
+ naming the install command. --update-aria
92
+ records each component's accessibility tree
93
+ anew, after a deliberate markup change.
94
+ --a11y runs the accessibility checks alone: the
95
+ accessibility suite and the keyboard cases,
96
+ with no static checks or fixes, reporting only
97
+ a11y-* and tests-* findings. --open-theme tests
98
+ the theme the editor has loaded, as saved; the
99
+ buffer stays out of the run. Both need --tests.
100
+ --json names the tested theme under "theme"
92
101
  check-page [paths...] [--tests]
93
102
  Validate pages against the live-tokens-create-page
94
103
  contract: catalogue components only, and every CSS
@@ -222,13 +231,13 @@ if (command === 'create') {
222
231
  }
223
232
  }
224
233
 
225
- function reportChecks(label, findings, checked, rules, opts, { coverage, hardFailure, exclude, fix } = {}) {
234
+ function reportChecks(label, findings, checked, rules, opts, { coverage, hardFailure, exclude, fix, theme } = {}) {
226
235
  const checksConfig = readChecksConfig(process.cwd());
227
236
  const resolved = applySeverity(findings, rules, opts, checksConfig, { exclude });
228
237
  const resolvedCoverage = coverage ? applyCoverageSeverity(coverage, rules, opts, checksConfig) : coverage;
229
- console.log(
238
+ writeOut(
230
239
  opts.json
231
- ? toJson(resolved, { label, checked, coverage: resolvedCoverage, fix })
240
+ ? toJson(resolved, { label, checked, coverage: resolvedCoverage, fix, theme })
232
241
  : [fix ? formatFixes(fix) : '', formatFindings(resolved, { label, checked })].filter(Boolean).join('\n'),
233
242
  );
234
243
  // Decision 2: a missing tool or a setup failure under --tests is an error
@@ -319,7 +328,23 @@ if (command === 'report') {
319
328
 
320
329
  if (command === 'check-component') {
321
330
  const opts = parseCheckFlags(rest);
331
+ if ((opts.a11y || opts.openTheme) && !opts.tests) fail('--a11y and --open-theme run with --tests.');
322
332
  const ids = opts.rest.length > 0 ? [opts.rest[0]] : discoverComponents();
333
+ if (opts.a11y) {
334
+ const { hasHardFailure, runContractTests } = await import('./contractRunner.mjs');
335
+ const outcome = await runContractTests(opts.rest[0], {
336
+ root: process.cwd(),
337
+ updateAria: opts.updateAria,
338
+ a11y: true,
339
+ openTheme: opts.openTheme,
340
+ });
341
+ const label = `check-component${ids.length === 1 ? ` ${ids[0]}` : ''} --tests --a11y`;
342
+ reportChecks(label, outcome.findings, Math.max(ids.length, 1), COMPONENT_RULES, opts, {
343
+ coverage: outcome.coverage,
344
+ hardFailure: hasHardFailure(outcome.findings),
345
+ theme: outcome.theme,
346
+ });
347
+ }
323
348
  if (ids.length === 0 && !opts.tests) {
324
349
  console.log('✓ check-component: no component authored under src/system/components yet.');
325
350
  process.exit(0);
@@ -342,12 +367,17 @@ if (command === 'check-component') {
342
367
  reportChecks(label, [...migration.findings, ...results.flatMap(([, r]) => r.findings)], ids.length, COMPONENT_RULES, opts, { fix });
343
368
  }
344
369
  const { hasHardFailure, runContractTests } = await import('./contractRunner.mjs');
345
- const testOutcome = await runContractTests(opts.rest[0], { root: process.cwd() });
370
+ const testOutcome = await runContractTests(opts.rest[0], {
371
+ root: process.cwd(),
372
+ updateAria: opts.updateAria,
373
+ openTheme: opts.openTheme,
374
+ });
346
375
  const findings = dedupeAliasFindings([...migration.findings, ...results.flatMap(([, r]) => r.findings), ...testOutcome.findings]);
347
376
  reportChecks(label, findings, Math.max(ids.length, 1), COMPONENT_RULES, opts, {
348
377
  fix,
349
378
  coverage: testOutcome.coverage,
350
379
  hardFailure: hasHardFailure(testOutcome.findings),
380
+ theme: testOutcome.theme,
351
381
  });
352
382
  }
353
383