@motion-proto/live-tokens 0.97.0 → 0.99.0

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 (191) 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 +339 -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 +144 -19
  10. package/bin/lib/findings.mjs +20 -4
  11. package/bin/rules/a11y.mjs +53 -0
  12. package/dist-plugin/{chunk-A4TBP4MM.js → chunk-SLICH4IH.js} +2 -2
  13. package/dist-plugin/index.cjs +204 -3
  14. package/dist-plugin/index.js +306 -105
  15. package/dist-plugin/setColors/index.js +1 -1
  16. package/package.json +47 -2
  17. package/src/demo/LabeledSelect.svelte +1 -1
  18. package/src/editor/bootstrap.ts +5 -1
  19. package/src/editor/component-editor/BadgeEditor.svelte +13 -0
  20. package/src/editor/component-editor/ButtonEditor.svelte +33 -2
  21. package/src/editor/component-editor/CalloutEditor.svelte +13 -0
  22. package/src/editor/component-editor/CardEditor.svelte +12 -2
  23. package/src/editor/component-editor/CodeSnippetEditor.svelte +11 -1
  24. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +18 -1
  25. package/src/editor/component-editor/CornerBadgeEditor.svelte +13 -0
  26. package/src/editor/component-editor/DialogEditor.svelte +15 -0
  27. package/src/editor/component-editor/IconButtonEditor.svelte +29 -1
  28. package/src/editor/component-editor/ImageEditor.svelte +20 -9
  29. package/src/editor/component-editor/ImageLightboxEditor.svelte +44 -0
  30. package/src/editor/component-editor/InlineEditActionsEditor.svelte +13 -1
  31. package/src/editor/component-editor/InputEditor.svelte +23 -0
  32. package/src/editor/component-editor/MenuSelectEditor.svelte +27 -1
  33. package/src/editor/component-editor/NotificationEditor.svelte +15 -0
  34. package/src/editor/component-editor/PanelEditor.svelte +1 -1
  35. package/src/editor/component-editor/ProgressBarEditor.svelte +13 -0
  36. package/src/editor/component-editor/RadioButtonEditor.svelte +20 -1
  37. package/src/editor/component-editor/SectionDividerEditor.svelte +40 -13
  38. package/src/editor/component-editor/SegmentedControlEditor.svelte +29 -1
  39. package/src/editor/component-editor/SideNavigationEditor.svelte +40 -1
  40. package/src/editor/component-editor/SlidePagerEditor.svelte +20 -0
  41. package/src/editor/component-editor/SliderEditor.svelte +33 -0
  42. package/src/editor/component-editor/TabBarEditor.svelte +23 -2
  43. package/src/editor/component-editor/TableEditor.svelte +14 -0
  44. package/src/editor/component-editor/ToggleEditor.svelte +17 -0
  45. package/src/editor/component-editor/TooltipEditor.svelte +11 -2
  46. package/src/editor/component-editor/VideoFrameEditor.svelte +8 -0
  47. package/src/editor/component-editor/VideoLightboxEditor.svelte +10 -0
  48. package/src/editor/component-editor/registration.ts +2 -1
  49. package/src/editor/component-editor/registry.ts +69 -27
  50. package/src/editor/component-editor/scaffolding/DemoHeader.svelte +2 -0
  51. package/src/editor/component-editor/scaffolding/StateBlock.svelte +19 -8
  52. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +3 -0
  53. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +70 -3
  54. package/src/editor/component-editor/scaffolding/editorContext.ts +7 -0
  55. package/src/editor/core/a11y/a11yReport.ts +164 -0
  56. package/src/editor/core/a11y/a11yReportTypes.ts +77 -0
  57. package/src/editor/core/a11y/contrastFailures.ts +22 -0
  58. package/src/editor/core/a11y/contrastFixes.ts +41 -0
  59. package/src/editor/core/components/contrastPairRegistry.ts +18 -0
  60. package/src/editor/core/palettes/contrast.ts +44 -0
  61. package/src/editor/core/palettes/contrastPairs.ts +82 -0
  62. package/src/editor/core/palettes/resolveColor.ts +77 -0
  63. package/src/editor/core/store/editorStore.ts +4 -0
  64. package/src/editor/core/store/editorViewStore.ts +2 -2
  65. package/src/editor/core/store/unsavedEdits.ts +10 -0
  66. package/src/editor/core/themes/liveStateStream.ts +7 -1
  67. package/src/editor/core/themes/parsers/gradient.ts +5 -2
  68. package/src/editor/docs/chapters.ts +1 -0
  69. package/src/editor/docs/content/accessibility.md +411 -0
  70. package/src/editor/docs/content/editing-tokens.md +32 -1
  71. package/src/editor/docs/content.generated.ts +2 -1
  72. package/src/editor/pages/ComponentEditorPage.svelte +11 -1
  73. package/src/editor/pages/EditorShell.svelte +98 -10
  74. package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
  75. package/src/editor/skill-atlas/trees/check-compliance.ts +3 -3
  76. package/src/editor/skill-atlas/trees/create-component.ts +12 -12
  77. package/src/editor/styles/ui-editor.css +2 -0
  78. package/src/editor/ui/EditorViewSwitcher.svelte +15 -2
  79. package/src/editor/ui/GradientEditor.svelte +9 -1
  80. package/src/editor/ui/ThemePanel.svelte +4 -4
  81. package/src/editor/ui/UIContrastReadout.svelte +186 -0
  82. package/src/editor/ui/UIInfoPopover.svelte +16 -2
  83. package/src/editor/ui/UIPaletteSelector.svelte +9 -0
  84. package/src/editor/ui/UITokenSelector.svelte +26 -2
  85. package/src/editor/ui/a11y/A11yByEye.svelte +264 -0
  86. package/src/editor/ui/a11y/A11yComponentIssues.svelte +121 -0
  87. package/src/editor/ui/a11y/A11yFindingRow.svelte +119 -0
  88. package/src/editor/ui/a11y/A11yIssueRow.svelte +152 -0
  89. package/src/editor/ui/a11y/A11yOverview.svelte +246 -0
  90. package/src/editor/ui/a11y/A11yPanel.svelte +153 -0
  91. package/src/editor/ui/a11y/A11yPropertyMark.svelte +84 -0
  92. package/src/editor/ui/a11y/A11yRailMark.svelte +41 -0
  93. package/src/editor/ui/a11y/AccessibilityTab.svelte +321 -0
  94. package/src/editor/ui/a11y/a11yView.ts +257 -0
  95. package/src/editor/ui/contrastReadout.ts +45 -0
  96. package/src/live-tokens/data/colors-and-type/halloween.json +1 -1
  97. package/src/live-tokens/data/themes/autumn.json +35 -35
  98. package/src/live-tokens/data/themes/halloween.json +36 -36
  99. package/src/live-tokens/data/themes/midnight-study.json +35 -35
  100. package/src/live-tokens/data/themes/ocean.json +35 -35
  101. package/src/live-tokens/data/themes/royal-velvet.json +35 -35
  102. package/src/live-tokens/data/themes/sketchy.json +35 -35
  103. package/src/live-tokens/data/themes/spring-meadow.json +35 -35
  104. package/src/live-tokens/data/themes/sunset.json +35 -35
  105. package/src/system/components/Badge.svelte +3 -3
  106. package/src/system/components/Button.svelte +12 -6
  107. package/src/system/components/CodeSnippet.svelte +8 -1
  108. package/src/system/components/CornerBadge.svelte +3 -3
  109. package/src/system/components/Dialog.svelte +43 -14
  110. package/src/system/components/IconButton.svelte +1 -1
  111. package/src/system/components/Image.svelte +7 -0
  112. package/src/system/components/ImageLightbox.svelte +18 -25
  113. package/src/system/components/InlineEditActions.svelte +1 -1
  114. package/src/system/components/Input.svelte +9 -2
  115. package/src/system/components/MenuSelect.svelte +31 -3
  116. package/src/system/components/Notification.svelte +1 -1
  117. package/src/system/components/ProgressBar.svelte +7 -1
  118. package/src/system/components/RadioButton.svelte +36 -3
  119. package/src/system/components/SegmentedControl.svelte +36 -2
  120. package/src/system/components/SlidePager.svelte +3 -3
  121. package/src/system/components/Slider.svelte +19 -15
  122. package/src/system/components/TabBar.svelte +33 -3
  123. package/src/system/components/Toggle.svelte +3 -3
  124. package/src/system/components/VideoFrame.svelte +1 -1
  125. package/src/system/components/VideoLightbox.svelte +2 -2
  126. package/src/system/internal/focusTrap.ts +35 -0
  127. package/src/testing-js/__aria__/badge.yml +40 -0
  128. package/src/testing-js/__aria__/button.yml +88 -0
  129. package/src/testing-js/__aria__/callout.yml +12 -0
  130. package/src/testing-js/__aria__/card.yml +8 -0
  131. package/src/testing-js/__aria__/codesnippet.yml +21 -0
  132. package/src/testing-js/__aria__/collapsiblesection.yml +23 -0
  133. package/src/testing-js/__aria__/cornerbadge.yml +24 -0
  134. package/src/testing-js/__aria__/dialog.yml +42 -0
  135. package/src/testing-js/__aria__/iconbutton.yml +57 -0
  136. package/src/testing-js/__aria__/image.yml +10 -0
  137. package/src/testing-js/__aria__/imagelightbox.yml +50 -0
  138. package/src/testing-js/__aria__/inlineeditactions.yml +16 -0
  139. package/src/testing-js/__aria__/input.yml +61 -0
  140. package/src/testing-js/__aria__/menuselect.yml +30 -0
  141. package/src/testing-js/__aria__/notification.yml +30 -0
  142. package/src/testing-js/__aria__/panel.yml +2 -0
  143. package/src/testing-js/__aria__/progressbar.yml +2 -0
  144. package/src/testing-js/__aria__/radiobutton.yml +20 -0
  145. package/src/testing-js/__aria__/sectiondivider.yml +9 -0
  146. package/src/testing-js/__aria__/segmentedcontrol.yml +35 -0
  147. package/src/testing-js/__aria__/sidenavigation.yml +304 -0
  148. package/src/testing-js/__aria__/slidepager.yml +72 -0
  149. package/src/testing-js/__aria__/slider.yml +28 -0
  150. package/src/testing-js/__aria__/tabbar.yml +35 -0
  151. package/src/testing-js/__aria__/table.yml +144 -0
  152. package/src/testing-js/__aria__/toggle.yml +12 -0
  153. package/src/testing-js/__aria__/tooltip.yml +2 -0
  154. package/src/testing-js/__aria__/videoframe.yml +6 -0
  155. package/src/testing-js/__aria__/videolightbox.yml +12 -0
  156. package/src/testing-js/{chunk-WIZ6W7UT.js → chunk-3WCLMOM7.js} +2 -2
  157. package/src/testing-js/{chunk-NB3NZRBM.js → chunk-7CM5D7PF.js} +5 -1
  158. package/src/testing-js/chunk-7CM5D7PF.js.map +1 -0
  159. package/src/testing-js/{chunk-X6KHEVIG.js → chunk-7QXUJRSO.js} +97 -29
  160. package/src/testing-js/chunk-7QXUJRSO.js.map +1 -0
  161. package/src/testing-js/chunk-GZKUYLDW.js +193 -0
  162. package/src/testing-js/chunk-GZKUYLDW.js.map +1 -0
  163. package/src/testing-js/chunk-RTZQLW3F.js +649 -0
  164. package/src/testing-js/chunk-RTZQLW3F.js.map +1 -0
  165. package/src/testing-js/{chunk-SHBYZUPD.js → chunk-RWOT2XDB.js} +212 -15
  166. package/src/testing-js/chunk-RWOT2XDB.js.map +1 -0
  167. package/src/testing-js/{chunk-GKO27NGN.js → chunk-THWGMSRH.js} +2 -2
  168. package/src/testing-js/{chunk-3UKGXCDL.js → chunk-YVVR3GJZ.js} +1 -1
  169. package/src/testing-js/chunk-YVVR3GJZ.js.map +1 -0
  170. package/src/testing-js/component-a11y.contract.js +313 -0
  171. package/src/testing-js/component-a11y.contract.js.map +1 -0
  172. package/src/testing-js/component-alias.contract.js +2 -2
  173. package/src/testing-js/component-behavior.contract.js +2 -2
  174. package/src/testing-js/component-editor.contract.js +6 -5
  175. package/src/testing-js/component-editor.contract.js.map +1 -1
  176. package/src/testing-js/component-render.contract.js +10 -6
  177. package/src/testing-js/component-render.contract.js.map +1 -1
  178. package/src/testing-js/index.d.ts +44 -16
  179. package/src/testing-js/index.js +11 -11
  180. package/src/testing-js/index.js.map +1 -1
  181. package/src/testing-js/page-compliance.contract.js +132 -13
  182. package/src/testing-js/page-compliance.contract.js.map +1 -1
  183. package/src/testing-js/vitest.js +3 -3
  184. package/template/_gitignore +3 -0
  185. package/template/package.json +9 -0
  186. package/src/testing-js/chunk-3UKGXCDL.js.map +0 -1
  187. package/src/testing-js/chunk-NB3NZRBM.js.map +0 -1
  188. package/src/testing-js/chunk-SHBYZUPD.js.map +0 -1
  189. package/src/testing-js/chunk-X6KHEVIG.js.map +0 -1
  190. /package/src/testing-js/{chunk-WIZ6W7UT.js.map → chunk-3WCLMOM7.js.map} +0 -0
  191. /package/src/testing-js/{chunk-GKO27NGN.js.map → chunk-THWGMSRH.js.map} +0 -0
@@ -13,7 +13,7 @@ import {
13
13
  parseGradientValue,
14
14
  reconcilePalettesFromCssVars,
15
15
  sanitizeFileName
16
- } from "./chunk-A4TBP4MM.js";
16
+ } from "./chunk-SLICH4IH.js";
17
17
  import {
18
18
  CURRENT_COMPONENT_SCHEMA_VERSION
19
19
  } from "./chunk-QQASYPW7.js";
@@ -36,9 +36,9 @@ import {
36
36
  } from "./chunk-6WGFOJXO.js";
37
37
 
38
38
  // vite-plugin/themeFileApi.ts
39
- import fs2 from "fs";
39
+ import fs3 from "fs";
40
40
  import { createHash as createHash2 } from "crypto";
41
- import path3 from "path";
41
+ import path4 from "path";
42
42
 
43
43
  // src/editor/core/themes/migrations/2026-07-21-palette-oklch-basis.ts
44
44
  var round = (v, dp) => {
@@ -748,18 +748,158 @@ function formatReport({ propertiesBefore, propertiesAfter, bytesSaved }) {
748
748
  return `live-tokens: the production CSS declares ${propertiesAfter.toLocaleString("en-US")} of ${propertiesBefore.toLocaleString("en-US")} custom properties and is ${kB} kB ${bytesSaved >= 0 ? "smaller" : "larger"}.`;
749
749
  }
750
750
 
751
+ // vite-plugin/a11y/a11yRunServer.ts
752
+ import { spawn } from "child_process";
753
+ import fs2 from "fs";
754
+ import path3 from "path";
755
+ var A11Y_RUN_ARGS = ["check-component", "--tests", "--a11y", "--no-fix", "--json", "--open-theme"];
756
+ var A11Y_REPORTS_DIR_NAME = "_a11y-reports";
757
+ var STDERR_TAIL = 2e3;
758
+ var CONTRAST_ROLES = /* @__PURE__ */ new Set(["text", "non-text"]);
759
+ function spawnCli(cliPath) {
760
+ return (args, cwd) => {
761
+ const child = spawn(process.execPath, [cliPath, ...args], { cwd, stdio: ["ignore", "pipe", "pipe"] });
762
+ const done = new Promise((resolve) => {
763
+ let stdout = "";
764
+ let stderr = "";
765
+ child.stdout.on("data", (chunk) => stdout += chunk);
766
+ child.stderr.on("data", (chunk) => stderr += chunk);
767
+ child.on("error", (error) => resolve({ code: null, stdout, stderr: `${stderr}${error.message}` }));
768
+ child.on("close", (code) => resolve({ code, stdout, stderr }));
769
+ });
770
+ return { done, stop: () => child.kill("SIGINT") };
771
+ };
772
+ }
773
+ function isContrastFailure(value) {
774
+ const v = value;
775
+ return !!v && typeof v === "object" && typeof v.component === "string" && typeof v.variable === "string" && v.variable.startsWith("--") && Array.isArray(v.backdrop) && v.backdrop.every((layer) => typeof layer === "string") && typeof v.role === "string" && CONTRAST_ROLES.has(v.role) && typeof v.ratio === "number" && typeof v.floor === "number" && (v.context === void 0 || typeof v.context === "string");
776
+ }
777
+ function readContrast(body) {
778
+ const contrast = body?.contrast;
779
+ return Array.isArray(contrast) && contrast.every(isContrastFailure) ? contrast : null;
780
+ }
781
+ function parseCliReport(stdout) {
782
+ try {
783
+ const parsed = JSON.parse(stdout);
784
+ return parsed && Array.isArray(parsed.findings) ? parsed : null;
785
+ } catch {
786
+ return null;
787
+ }
788
+ }
789
+ function a11yRunServer({ root, reportsDir, openTheme, runCli, broadcast }) {
790
+ let running = null;
791
+ const reportFile = (theme) => path3.join(reportsDir, `${theme}.json`);
792
+ function readReport(theme) {
793
+ const listed = fs2.existsSync(reportsDir) && fs2.readdirSync(reportsDir).includes(`${theme}.json`);
794
+ return listed ? JSON.parse(fs2.readFileSync(reportFile(theme), "utf-8")) : null;
795
+ }
796
+ function writeReport(report) {
797
+ fs2.mkdirSync(reportsDir, { recursive: true });
798
+ fs2.writeFileSync(reportFile(report.theme), `${JSON.stringify(report, null, 2)}
799
+ `);
800
+ }
801
+ function withConfirmed(report, confirmed) {
802
+ const review = new Set(report.findings.filter((f) => f.rule === "a11y-review").map((f) => f.message));
803
+ const kept = [...new Set(confirmed)].filter((message) => review.has(message)).sort();
804
+ const out = { ...report, confirmed: kept };
805
+ if (kept.length === 0) delete out.confirmed;
806
+ return out;
807
+ }
808
+ function reports() {
809
+ if (!fs2.existsSync(reportsDir)) return {};
810
+ return Object.fromEntries(
811
+ fs2.readdirSync(reportsDir).filter((name) => name.endsWith(".json")).map((name) => [name.slice(0, -".json".length), JSON.parse(fs2.readFileSync(path3.join(reportsDir, name), "utf-8"))])
812
+ );
813
+ }
814
+ function settle(started, outcome) {
815
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
816
+ const cli = parseCliReport(outcome.stdout);
817
+ if (!cli) {
818
+ const stderr = outcome.stderr.trim().slice(-STDERR_TAIL);
819
+ return {
820
+ ...started,
821
+ state: "failed",
822
+ finishedAt,
823
+ error: `check-component exited with code ${outcome.code} and printed no report.${stderr ? `
824
+ ${stderr}` : ""}`
825
+ };
826
+ }
827
+ return {
828
+ ...started,
829
+ state: "finished",
830
+ finishedAt,
831
+ findings: cli.findings.filter((f) => f.details?.engineRule !== "color-contrast")
832
+ };
833
+ }
834
+ function start(body) {
835
+ if (running) {
836
+ return { status: 409, body: { error: "An accessibility run is already going.", code: "A11Y_RUN_IN_PROGRESS" } };
837
+ }
838
+ const contrast = readContrast(body);
839
+ if (!contrast) {
840
+ return {
841
+ status: 400,
842
+ body: { error: "Body must be { contrast: [...] } with one entry per failing pair", code: "INVALID_BODY" }
843
+ };
844
+ }
845
+ const report = { state: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString(), theme: openTheme(), contrast };
846
+ const run = runCli(A11Y_RUN_ARGS, root);
847
+ running = { report, stop: run.stop };
848
+ broadcast(report);
849
+ void run.done.then((outcome) => {
850
+ const settled = settle(report, outcome);
851
+ const final = settled.state === "finished" ? withConfirmed(settled, readReport(settled.theme)?.confirmed ?? []) : settled;
852
+ if (final.state === "finished") writeReport(final);
853
+ running = null;
854
+ broadcast(final);
855
+ });
856
+ return { status: 202, body: report };
857
+ }
858
+ function confirm(body) {
859
+ const { theme, messages, confirmed } = body ?? {};
860
+ if (typeof theme !== "string" || typeof confirmed !== "boolean" || !Array.isArray(messages) || !messages.every((m) => typeof m === "string")) {
861
+ return {
862
+ status: 400,
863
+ body: { error: "Body must be { theme, messages: [...], confirmed: true | false }", code: "INVALID_BODY" }
864
+ };
865
+ }
866
+ const report = readReport(theme);
867
+ if (!report) return { status: 404, body: { error: `No report for theme "${theme}"`, code: "NO_REPORT" } };
868
+ const marked = new Set(report.confirmed ?? []);
869
+ for (const message of messages) {
870
+ if (confirmed) marked.add(message);
871
+ else marked.delete(message);
872
+ }
873
+ const updated = withConfirmed(report, marked);
874
+ writeReport(updated);
875
+ broadcast(updated);
876
+ return { status: 200, body: updated };
877
+ }
878
+ return {
879
+ start,
880
+ confirm,
881
+ report: () => ({ status: 200, body: { running: running?.report ?? null, reports: reports() } }),
882
+ /** Removes a deleted theme's report. */
883
+ forget: (theme) => fs2.rmSync(reportFile(theme), { force: true }),
884
+ stop: () => running?.stop()
885
+ };
886
+ }
887
+
888
+ // src/editor/core/a11y/a11yReportTypes.ts
889
+ var A11Y_EVENT = "a11y";
890
+
751
891
  // vite-plugin/themeFileApi.ts
752
892
  import { fileURLToPath as fileURLToPath2 } from "url";
753
893
  var PKG_VERSION = (() => {
754
894
  try {
755
- let dir = path3.dirname(fileURLToPath2(import.meta.url));
895
+ let dir = path4.dirname(fileURLToPath2(import.meta.url));
756
896
  for (let i = 0; i < 4; i++) {
757
- const p = path3.join(dir, "package.json");
758
- if (fs2.existsSync(p)) {
759
- const json = JSON.parse(fs2.readFileSync(p, "utf-8"));
897
+ const p = path4.join(dir, "package.json");
898
+ if (fs3.existsSync(p)) {
899
+ const json = JSON.parse(fs3.readFileSync(p, "utf-8"));
760
900
  if (json?.name === "@motion-proto/live-tokens") return json.version ?? "";
761
901
  }
762
- const up = path3.dirname(dir);
902
+ const up = path4.dirname(dir);
763
903
  if (up === dir) break;
764
904
  dir = up;
765
905
  }
@@ -803,46 +943,48 @@ function themeFileApi(opts) {
803
943
  const COMPONENT_CONFIGS_DIR = dataDirs.componentConfigsDir;
804
944
  const THEMES_DIR = dataDirs.themesDir;
805
945
  const SKETCH_STYLES_DIR = dataDirs.sketchStylesDir;
806
- const CSS_PATH = path3.resolve(opts.tokensCssPath);
946
+ const CSS_PATH = path4.resolve(opts.tokensCssPath);
807
947
  const ISOLATED_DATA_DIR = testDataDir();
808
- const GENERATED_CSS_PATH = ISOLATED_DATA_DIR ? path3.join(ISOLATED_DATA_DIR, "tokens.generated.css") : opts.tokensGeneratedCssPath ? path3.resolve(opts.tokensGeneratedCssPath) : path3.join(dataDirs.dataDir, "tokens.generated.css");
809
- const FONTS_CSS_PATH = ISOLATED_DATA_DIR ? path3.join(ISOLATED_DATA_DIR, "fonts.css") : opts.fontsCssPath ? path3.resolve(opts.fontsCssPath) : path3.join(path3.dirname(CSS_PATH), "fonts.css");
948
+ const GENERATED_CSS_PATH = ISOLATED_DATA_DIR ? path4.join(ISOLATED_DATA_DIR, "tokens.generated.css") : opts.tokensGeneratedCssPath ? path4.resolve(opts.tokensGeneratedCssPath) : path4.join(dataDirs.dataDir, "tokens.generated.css");
949
+ const FONTS_CSS_PATH = ISOLATED_DATA_DIR ? path4.join(ISOLATED_DATA_DIR, "fonts.css") : opts.fontsCssPath ? path4.resolve(opts.fontsCssPath) : path4.join(path4.dirname(CSS_PATH), "fonts.css");
950
+ const A11Y_REPORTS_DIR = path4.join(dataDirs.dataDir, A11Y_REPORTS_DIR_NAME);
810
951
  const API_BASE = opts.apiBase ?? "/api/live-tokens";
811
- const consumerComponentDirs = opts.componentsSrcDir ? [path3.resolve(opts.componentsSrcDir)] : [path3.resolve("src/components"), path3.resolve("src/system/components")];
812
- const packageComponentsDir = path3.resolve(
813
- path3.dirname(fileURLToPath2(import.meta.url)),
952
+ const consumerComponentDirs = opts.componentsSrcDir ? [path4.resolve(opts.componentsSrcDir)] : [path4.resolve("src/components"), path4.resolve("src/system/components")];
953
+ const packageComponentsDir = path4.resolve(
954
+ path4.dirname(fileURLToPath2(import.meta.url)),
814
955
  "..",
815
956
  "src",
816
957
  "system",
817
958
  "components"
818
959
  );
819
- const packageDataDir = testPackageDataDir() ?? path3.resolve(
820
- path3.dirname(fileURLToPath2(import.meta.url)),
960
+ const PACKAGE_CLI_PATH = path4.resolve(path4.dirname(fileURLToPath2(import.meta.url)), "..", "bin", "cli.mjs");
961
+ const packageDataDir = testPackageDataDir() ?? path4.resolve(
962
+ path4.dirname(fileURLToPath2(import.meta.url)),
821
963
  "..",
822
964
  "src",
823
965
  "live-tokens",
824
966
  "data"
825
967
  );
826
- const packageColorsAndTypeDir = path3.join(packageDataDir, "colors-and-type");
827
- const packageThemesDir = path3.join(packageDataDir, "themes");
828
- const packageComponentConfigsDir = path3.join(packageDataDir, "component-configs");
829
- const packageSketchStylesDir = path3.join(packageDataDir, "sketch-styles");
968
+ const packageColorsAndTypeDir = path4.join(packageDataDir, "colors-and-type");
969
+ const packageThemesDir = path4.join(packageDataDir, "themes");
970
+ const packageComponentConfigsDir = path4.join(packageDataDir, "component-configs");
971
+ const packageSketchStylesDir = path4.join(packageDataDir, "sketch-styles");
830
972
  function shippedNames(dataDir, subdir) {
831
973
  try {
832
- const pkgRoot = path3.resolve(dataDir, "..", "..", "..");
833
- const pkg = JSON.parse(fs2.readFileSync(path3.join(pkgRoot, "package.json"), "utf-8"));
974
+ const pkgRoot = path4.resolve(dataDir, "..", "..", "..");
975
+ const pkg = JSON.parse(fs3.readFileSync(path4.join(pkgRoot, "package.json"), "utf-8"));
834
976
  const files = Array.isArray(pkg.files) ? pkg.files : [];
835
977
  const owned = new RegExp(`^src/live-tokens/data/${subdir}/[^/]+\\.json$`);
836
- return files.filter((f) => owned.test(f)).map((f) => path3.basename(f, ".json"));
978
+ return files.filter((f) => owned.test(f)).map((f) => path4.basename(f, ".json"));
837
979
  } catch {
838
980
  return [];
839
981
  }
840
982
  }
841
983
  const COMPONENTS_SCAN_DIRS = [...consumerComponentDirs];
842
- if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && fs2.existsSync(packageComponentsDir)) {
984
+ if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && fs3.existsSync(packageComponentsDir)) {
843
985
  COMPONENTS_SCAN_DIRS.push(packageComponentsDir);
844
986
  }
845
- const isLiveStateFile = (file) => file === path3.join(THEMES_DIR, "_active.json") || path3.basename(file) === "_working.json" && (file.startsWith(COLORS_AND_TYPE_DIR + path3.sep) || file.startsWith(COMPONENT_CONFIGS_DIR + path3.sep));
987
+ const isLiveStateFile = (file) => file === path4.join(THEMES_DIR, "_active.json") || path4.basename(file) === "_working.json" && (file.startsWith(COLORS_AND_TYPE_DIR + path4.sep) || file.startsWith(COMPONENT_CONFIGS_DIR + path4.sep));
846
988
  const liveWatch = dataTreeWatch({
847
989
  dirs: [COLORS_AND_TYPE_DIR, COMPONENT_CONFIGS_DIR, THEMES_DIR],
848
990
  isLive: isLiveStateFile
@@ -858,8 +1000,8 @@ function themeFileApi(opts) {
858
1000
  let r = componentResourceCache.get(comp);
859
1001
  if (!r) {
860
1002
  r = versionedFileResourceServer({
861
- dir: path3.join(COMPONENT_CONFIGS_DIR, comp),
862
- packageDir: path3.join(packageComponentConfigsDir, comp),
1003
+ dir: path4.join(COMPONENT_CONFIGS_DIR, comp),
1004
+ packageDir: path4.join(packageComponentConfigsDir, comp),
863
1005
  onWrite: liveWatch.noteOwnWrite
864
1006
  });
865
1007
  componentResourceCache.set(comp, r);
@@ -947,7 +1089,7 @@ function themeFileApi(opts) {
947
1089
  const productionThemeName = themesResource.getProductionName();
948
1090
  const productionTheme = readTheme(productionThemeName)?.theme ?? null;
949
1091
  if (themesResource.existingPath(productionThemeName) !== null && !productionTheme?.colorsAndType) {
950
- const tail = `${path3.basename(GENERATED_CSS_PATH)} was left as it was.`;
1092
+ const tail = `${path4.basename(GENERATED_CSS_PATH)} was left as it was.`;
951
1093
  throw new Error(
952
1094
  unreadableThemeReason(productionThemeName) === "colors-and-type" ? `[live-tokens] ${notAThemeError(productionThemeName)} ${tail}` : `[live-tokens] Production theme "${productionThemeName}" is unreadable, so ${tail} Repair the theme file or adopt another theme.`
953
1095
  );
@@ -985,7 +1127,7 @@ function themeFileApi(opts) {
985
1127
  }
986
1128
  }
987
1129
  let componentOverrideCount = 0;
988
- if (fs2.existsSync(COMPONENT_CONFIGS_DIR)) {
1130
+ if (fs3.existsSync(COMPONENT_CONFIGS_DIR)) {
989
1131
  const blocks = [];
990
1132
  for (const comp of listComponentNames()) {
991
1133
  const prodCfg = productionTheme?.componentConfigs[comp];
@@ -1030,18 +1172,18 @@ function themeFileApi(opts) {
1030
1172
  `/* Bake fingerprint: ${bakeFingerprint(body)} */`,
1031
1173
  ""
1032
1174
  ];
1033
- if (!fs2.existsSync(path3.dirname(GENERATED_CSS_PATH))) {
1034
- fs2.mkdirSync(path3.dirname(GENERATED_CSS_PATH), { recursive: true });
1175
+ if (!fs3.existsSync(path4.dirname(GENERATED_CSS_PATH))) {
1176
+ fs3.mkdirSync(path4.dirname(GENERATED_CSS_PATH), { recursive: true });
1035
1177
  }
1036
- fs2.writeFileSync(GENERATED_CSS_PATH, [...header, body].join("\n"));
1178
+ fs3.writeFileSync(GENERATED_CSS_PATH, [...header, body].join("\n"));
1037
1179
  console.log(
1038
- `[regenerateTokensCss] Wrote ${path3.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
1180
+ `[regenerateTokensCss] Wrote ${path4.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
1039
1181
  );
1040
1182
  }
1041
1183
  function bakeIsCurrent() {
1042
1184
  let baked;
1043
1185
  try {
1044
- baked = fs2.readFileSync(GENERATED_CSS_PATH, "utf-8");
1186
+ baked = fs3.readFileSync(GENERATED_CSS_PATH, "utf-8");
1045
1187
  } catch {
1046
1188
  return false;
1047
1189
  }
@@ -1077,18 +1219,18 @@ function themeFileApi(opts) {
1077
1219
  lines.push("");
1078
1220
  }
1079
1221
  const content = lines.join("\n");
1080
- if (!fs2.existsSync(path3.dirname(FONTS_CSS_PATH))) {
1081
- fs2.mkdirSync(path3.dirname(FONTS_CSS_PATH), { recursive: true });
1222
+ if (!fs3.existsSync(path4.dirname(FONTS_CSS_PATH))) {
1223
+ fs3.mkdirSync(path4.dirname(FONTS_CSS_PATH), { recursive: true });
1082
1224
  }
1083
- fs2.writeFileSync(FONTS_CSS_PATH, content);
1084
- console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${path3.basename(FONTS_CSS_PATH)}`);
1225
+ fs3.writeFileSync(FONTS_CSS_PATH, content);
1226
+ console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${path4.basename(FONTS_CSS_PATH)}`);
1085
1227
  }
1086
1228
  function componentNameFromFile(filePath) {
1087
- return path3.basename(filePath, ".svelte").toLowerCase();
1229
+ return path4.basename(filePath, ".svelte").toLowerCase();
1088
1230
  }
1089
1231
  function isThemeAwareComponent(filePath) {
1090
1232
  try {
1091
- const source = fs2.readFileSync(filePath, "utf-8");
1233
+ const source = fs3.readFileSync(filePath, "utf-8");
1092
1234
  return /:global\(:root\)\s*\{/.test(source);
1093
1235
  } catch {
1094
1236
  return false;
@@ -1097,10 +1239,10 @@ function themeFileApi(opts) {
1097
1239
  function listComponentSourcePaths() {
1098
1240
  const byName = /* @__PURE__ */ new Map();
1099
1241
  for (const dir of COMPONENTS_SCAN_DIRS) {
1100
- if (!fs2.existsSync(dir)) continue;
1101
- for (const f of fs2.readdirSync(dir)) {
1242
+ if (!fs3.existsSync(dir)) continue;
1243
+ for (const f of fs3.readdirSync(dir)) {
1102
1244
  if (!f.endsWith(".svelte")) continue;
1103
- const full = path3.join(dir, f);
1245
+ const full = path4.join(dir, f);
1104
1246
  if (!isThemeAwareComponent(full)) continue;
1105
1247
  const name = componentNameFromFile(f);
1106
1248
  if (!byName.has(name)) byName.set(name, full);
@@ -1114,18 +1256,18 @@ function themeFileApi(opts) {
1114
1256
  function warnOnTokenDrift(log) {
1115
1257
  let tokensCss = "";
1116
1258
  try {
1117
- tokensCss = fs2.readFileSync(CSS_PATH, "utf-8");
1259
+ tokensCss = fs3.readFileSync(CSS_PATH, "utf-8");
1118
1260
  } catch {
1119
1261
  return;
1120
1262
  }
1121
1263
  let generatedCss = "";
1122
1264
  try {
1123
- generatedCss = fs2.readFileSync(GENERATED_CSS_PATH, "utf-8");
1265
+ generatedCss = fs3.readFileSync(GENERATED_CSS_PATH, "utf-8");
1124
1266
  } catch {
1125
1267
  }
1126
1268
  const componentSources = listComponentSourcePaths().map((p) => ({
1127
1269
  name: componentNameFromFile(p),
1128
- source: fs2.readFileSync(p, "utf-8")
1270
+ source: fs3.readFileSync(p, "utf-8")
1129
1271
  }));
1130
1272
  const missing = validateTokensCss({ tokensCss, generatedCss, componentSources });
1131
1273
  if (missing.length === 0) return;
@@ -1133,7 +1275,7 @@ function themeFileApi(opts) {
1133
1275
  (m) => ` ${m.token} (referenced by ${m.referencedBy.join(", ")})`
1134
1276
  );
1135
1277
  log(
1136
- `[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${path3.relative(process.cwd(), CSS_PATH)}:
1278
+ `[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${path4.relative(process.cwd(), CSS_PATH)}:
1137
1279
  ${lines.join("\n")}
1138
1280
  These render as blank/empty editor slots. Run \`npx live-tokens migrate\` to reconcile.`
1139
1281
  );
@@ -1141,30 +1283,30 @@ ${lines.join("\n")}
1141
1283
  function autoMigrateAdditive(log) {
1142
1284
  let before = "";
1143
1285
  try {
1144
- before = fs2.readFileSync(CSS_PATH, "utf-8");
1286
+ before = fs3.readFileSync(CSS_PATH, "utf-8");
1145
1287
  } catch {
1146
1288
  return;
1147
1289
  }
1148
1290
  const { css, applied, changed } = runAdditiveTokensCssMigrations(before);
1149
1291
  if (!changed) return;
1150
- fs2.writeFileSync(CSS_PATH, css);
1292
+ fs3.writeFileSync(CSS_PATH, css);
1151
1293
  log(
1152
- `[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${path3.relative(process.cwd(), CSS_PATH)}: ${applied.join(", ")}. Review the diff in git.`
1294
+ `[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${path4.relative(process.cwd(), CSS_PATH)}: ${applied.join(", ")}. Review the diff in git.`
1153
1295
  );
1154
1296
  }
1155
1297
  function generateDefaultConfig(comp, sourcePath) {
1156
- if (!fs2.existsSync(sourcePath)) return;
1298
+ if (!fs3.existsSync(sourcePath)) return;
1157
1299
  const r = componentResource(comp);
1158
1300
  r.ensureDir();
1159
1301
  const defaultPath = r.filePath("default");
1160
- const sourceStat = fs2.statSync(sourcePath);
1302
+ const sourceStat = fs3.statSync(sourcePath);
1161
1303
  let createdAt = (/* @__PURE__ */ new Date()).toISOString();
1162
1304
  let existingUpdatedAt;
1163
1305
  let existingAliases;
1164
1306
  let existingSchemaVersion;
1165
- if (fs2.existsSync(defaultPath)) {
1307
+ if (fs3.existsSync(defaultPath)) {
1166
1308
  try {
1167
- const existing = JSON.parse(fs2.readFileSync(defaultPath, "utf-8"));
1309
+ const existing = JSON.parse(fs3.readFileSync(defaultPath, "utf-8"));
1168
1310
  if (existing.createdAt) createdAt = existing.createdAt;
1169
1311
  if (typeof existing.updatedAt === "string") existingUpdatedAt = existing.updatedAt;
1170
1312
  if (existing.aliases && typeof existing.aliases === "object") {
@@ -1173,13 +1315,13 @@ ${lines.join("\n")}
1173
1315
  if (typeof existing.schemaVersion === "number") existingSchemaVersion = existing.schemaVersion;
1174
1316
  } catch {
1175
1317
  }
1176
- const defaultStat = fs2.statSync(defaultPath);
1318
+ const defaultStat = fs3.statSync(defaultPath);
1177
1319
  if (defaultStat.mtimeMs >= sourceStat.mtimeMs && existingSchemaVersion === CURRENT_COMPONENT_SCHEMA_VERSION) {
1178
1320
  return;
1179
1321
  }
1180
1322
  }
1181
1323
  const now = (/* @__PURE__ */ new Date()).toISOString();
1182
- const source = fs2.readFileSync(sourcePath, "utf-8");
1324
+ const source = fs3.readFileSync(sourcePath, "utf-8");
1183
1325
  const body = extractGlobalRootBody(source);
1184
1326
  const aliases = extractAliasDeclarations(body);
1185
1327
  const aliasesUnchanged = existingAliases !== void 0 && JSON.stringify(existingAliases) === JSON.stringify(aliases);
@@ -1194,14 +1336,14 @@ ${lines.join("\n")}
1194
1336
  };
1195
1337
  if (aliasesUnchanged && schemaStampCurrent && existingUpdatedAt) {
1196
1338
  const t = /* @__PURE__ */ new Date();
1197
- fs2.utimesSync(defaultPath, t, t);
1339
+ fs3.utimesSync(defaultPath, t, t);
1198
1340
  return;
1199
1341
  }
1200
- fs2.writeFileSync(defaultPath, JSON.stringify(defaultConfig, null, 2));
1342
+ fs3.writeFileSync(defaultPath, JSON.stringify(defaultConfig, null, 2));
1201
1343
  }
1202
1344
  function ensureComponentConfigsDir() {
1203
- if (!fs2.existsSync(COMPONENT_CONFIGS_DIR)) {
1204
- fs2.mkdirSync(COMPONENT_CONFIGS_DIR, { recursive: true });
1345
+ if (!fs3.existsSync(COMPONENT_CONFIGS_DIR)) {
1346
+ fs3.mkdirSync(COMPONENT_CONFIGS_DIR, { recursive: true });
1205
1347
  }
1206
1348
  for (const sourcePath of listComponentSourcePaths()) {
1207
1349
  const comp = componentNameFromFile(sourcePath);
@@ -1214,14 +1356,14 @@ ${lines.join("\n")}
1214
1356
  const legacy = [];
1215
1357
  const check = (dir) => {
1216
1358
  for (const name of ["_active.json", "_production.json"]) {
1217
- const p = path3.join(dir, name);
1218
- if (fs2.existsSync(p)) legacy.push(path3.relative(process.cwd(), p));
1359
+ const p = path4.join(dir, name);
1360
+ if (fs3.existsSync(p)) legacy.push(path4.relative(process.cwd(), p));
1219
1361
  }
1220
1362
  };
1221
1363
  check(COLORS_AND_TYPE_DIR);
1222
- if (fs2.existsSync(COMPONENT_CONFIGS_DIR)) {
1223
- for (const entry of fs2.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
1224
- if (entry.isDirectory()) check(path3.join(COMPONENT_CONFIGS_DIR, entry.name));
1364
+ if (fs3.existsSync(COMPONENT_CONFIGS_DIR)) {
1365
+ for (const entry of fs3.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
1366
+ if (entry.isDirectory()) check(path4.join(COMPONENT_CONFIGS_DIR, entry.name));
1225
1367
  }
1226
1368
  }
1227
1369
  return legacy;
@@ -1229,19 +1371,19 @@ ${lines.join("\n")}
1229
1371
  function warnOnLegacyPointers(legacy, bakeHeld, warn) {
1230
1372
  if (legacy.length === 0) return;
1231
1373
  warn(
1232
- `[live-tokens] ${legacy.length} legacy pointer file(s) left from the pre-working-set data model, starting with ${legacy[0]}. They are no longer read. Run \`npx live-tokens migrate\` to heal the tree.` + (bakeHeld ? ` Until then this tree has named no production theme, so ${path3.basename(GENERATED_CSS_PATH)} is left exactly as it is rather than rebuilt from the default.` : "")
1374
+ `[live-tokens] ${legacy.length} legacy pointer file(s) left from the pre-working-set data model, starting with ${legacy[0]}. They are no longer read. Run \`npx live-tokens migrate\` to heal the tree.` + (bakeHeld ? ` Until then this tree has named no production theme, so ${path4.basename(GENERATED_CSS_PATH)} is left exactly as it is rather than rebuilt from the default.` : "")
1233
1375
  );
1234
1376
  }
1235
1377
  function warnOnLegacyLayout(evidence, warn) {
1236
1378
  warn(
1237
- `[live-tokens] This project still uses the data layout from 0.47 and earlier: ${evidence}. Since 0.48 the whole-theme theme files live in \`themes/\` and the colors and type live in \`colors-and-type/\`. Nothing in the data directory was written, the editor cannot save, and ${path3.basename(GENERATED_CSS_PATH)} is left exactly as it is. Run \`npx live-tokens migrate\` to move the directories and heal the tree, then restart the dev server.`
1379
+ `[live-tokens] This project still uses the data layout from 0.47 and earlier: ${evidence}. Since 0.48 the whole-theme theme files live in \`themes/\` and the colors and type live in \`colors-and-type/\`. Nothing in the data directory was written, the editor cannot save, and ${path4.basename(GENERATED_CSS_PATH)} is left exactly as it is. Run \`npx live-tokens migrate\` to move the directories and heal the tree, then restart the dev server.`
1238
1380
  );
1239
1381
  }
1240
1382
  function warnOnUnmigratedSketchStyles(warn) {
1241
- const legacyDir = path3.join(dataDirs.dataDir, "sketch-presets");
1242
- if (!fs2.existsSync(legacyDir)) return;
1383
+ const legacyDir = path4.join(dataDirs.dataDir, "sketch-presets");
1384
+ if (!fs3.existsSync(legacyDir)) return;
1243
1385
  warn(
1244
- `[live-tokens] Saved sketchstyles still live under the retired ${path3.basename(legacyDir)}/ directory name. Run \`npx live-tokens migrate\` to rename it to ${path3.basename(SKETCH_STYLES_DIR)}/; until then they will not show up in the Sketchstyle view.`
1386
+ `[live-tokens] Saved sketchstyles still live under the retired ${path4.basename(legacyDir)}/ directory name. Run \`npx live-tokens migrate\` to rename it to ${path4.basename(SKETCH_STYLES_DIR)}/; until then they will not show up in the Sketchstyle view.`
1245
1387
  );
1246
1388
  }
1247
1389
  const diskThemeResolvers = {
@@ -1265,7 +1407,7 @@ ${lines.join("\n")}
1265
1407
  const resolved = themesResource.existingPath(fileName);
1266
1408
  if (resolved === null) return "missing";
1267
1409
  try {
1268
- if (isColorsAndTypeShaped(JSON.parse(fs2.readFileSync(resolved, "utf-8")))) {
1410
+ if (isColorsAndTypeShaped(JSON.parse(fs3.readFileSync(resolved, "utf-8")))) {
1269
1411
  return "colors-and-type";
1270
1412
  }
1271
1413
  } catch {
@@ -1273,7 +1415,7 @@ ${lines.join("\n")}
1273
1415
  return "corrupt";
1274
1416
  }
1275
1417
  function notAThemeError(fileName) {
1276
- return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${path3.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme skill "create-theme".`;
1418
+ return `"${fileName}" is a colors-and-type file, not a theme. Before 0.48 those lived in themes/; move it to ${path4.basename(COLORS_AND_TYPE_DIR)}/ or delete it. To build a theme, use the theme skill "create-theme".`;
1277
1419
  }
1278
1420
  function respondUnreadableTheme(res, fileName, missingError) {
1279
1421
  switch (unreadableThemeReason(fileName)) {
@@ -1292,18 +1434,18 @@ ${lines.join("\n")}
1292
1434
  }
1293
1435
  function writeTheme(fileName, theme) {
1294
1436
  themesResource.ensureDir();
1295
- fs2.writeFileSync(themesResource.filePath(fileName), JSON.stringify(theme, null, 2));
1437
+ fs3.writeFileSync(themesResource.filePath(fileName), JSON.stringify(theme, null, 2));
1296
1438
  }
1297
1439
  function migrateLocalThemes(warn) {
1298
- if (path3.resolve(THEMES_DIR) === path3.resolve(packageThemesDir)) return;
1299
- if (!fs2.existsSync(THEMES_DIR)) return;
1300
- for (const file of fs2.readdirSync(THEMES_DIR)) {
1440
+ if (path4.resolve(THEMES_DIR) === path4.resolve(packageThemesDir)) return;
1441
+ if (!fs3.existsSync(THEMES_DIR)) return;
1442
+ for (const file of fs3.readdirSync(THEMES_DIR)) {
1301
1443
  if (!file.endsWith(".json") || file.startsWith("_")) continue;
1302
1444
  const fileName = file.slice(0, -".json".length);
1303
1445
  if (fileName === "default") continue;
1304
1446
  let raw;
1305
1447
  try {
1306
- raw = JSON.parse(fs2.readFileSync(path3.join(THEMES_DIR, file), "utf-8"));
1448
+ raw = JSON.parse(fs3.readFileSync(path4.join(THEMES_DIR, file), "utf-8"));
1307
1449
  } catch {
1308
1450
  continue;
1309
1451
  }
@@ -1324,7 +1466,7 @@ ${lines.join("\n")}
1324
1466
  function ensureDefaultTheme(warn) {
1325
1467
  let existing = null;
1326
1468
  try {
1327
- existing = JSON.parse(fs2.readFileSync(themesResource.filePath("default"), "utf-8"));
1469
+ existing = JSON.parse(fs3.readFileSync(themesResource.filePath("default"), "utf-8"));
1328
1470
  } catch {
1329
1471
  }
1330
1472
  if (isColorsAndTypeShaped(existing)) {
@@ -1361,9 +1503,9 @@ ${lines.join("\n")}
1361
1503
  themesResource.ensureDir();
1362
1504
  ensureDefaultTheme(warn);
1363
1505
  migrateLocalThemes(warn);
1364
- const productionPointerHeld = !fs2.existsSync(themesResource.productionPath) && listLegacyPointers().length > 0;
1506
+ const productionPointerHeld = !fs3.existsSync(themesResource.productionPath) && listLegacyPointers().length > 0;
1365
1507
  if (productionPointerHeld) {
1366
- if (!fs2.existsSync(themesResource.activePath)) themesResource.setActiveName("default");
1508
+ if (!fs3.existsSync(themesResource.activePath)) themesResource.setActiveName("default");
1367
1509
  } else {
1368
1510
  themesResource.ensureMeta();
1369
1511
  }
@@ -1442,8 +1584,8 @@ ${lines.join("\n")}
1442
1584
  }
1443
1585
  function clearAllWorking() {
1444
1586
  colorsAndTypeResource.clearWorking();
1445
- if (!fs2.existsSync(COMPONENT_CONFIGS_DIR)) return;
1446
- for (const entry of fs2.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
1587
+ if (!fs3.existsSync(COMPONENT_CONFIGS_DIR)) return;
1588
+ for (const entry of fs3.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
1447
1589
  if (entry.isDirectory()) componentResource(entry.name).clearWorking();
1448
1590
  }
1449
1591
  }
@@ -1487,6 +1629,9 @@ ${lines.join("\n")}
1487
1629
  const SKETCH_STYLE_BY_NAME_REGEX = new RegExp(`^${escapedBase}/sketch-styles/([a-z0-9\\-_]+)$`);
1488
1630
  const PRODUCTION_ROUTE = `${API_BASE}/production`;
1489
1631
  const EVENTS_ROUTE = `${API_BASE}/events`;
1632
+ const A11Y_RUN_ROUTE = `${API_BASE}/a11y/run`;
1633
+ const A11Y_REPORT_ROUTE = `${API_BASE}/a11y/report`;
1634
+ const A11Y_CONFIRMED_ROUTE = `${API_BASE}/a11y/confirmed`;
1490
1635
  async function handleListColorsAndType(_ctx) {
1491
1636
  const files = colorsAndTypeResource.listNames().map((fileName) => {
1492
1637
  const data = colorsAndTypeResource.readJson(fileName);
@@ -1571,15 +1716,15 @@ ${lines.join("\n")}
1571
1716
  }
1572
1717
  const body = JSON.parse(await readBody(req));
1573
1718
  body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1574
- if (fs2.existsSync(filePath)) {
1719
+ if (fs3.existsSync(filePath)) {
1575
1720
  try {
1576
- const existing = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
1721
+ const existing = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
1577
1722
  if (existing.createdAt) body.createdAt = existing.createdAt;
1578
1723
  } catch {
1579
1724
  }
1580
1725
  }
1581
1726
  if (!body.createdAt) body.createdAt = body.updatedAt;
1582
- fs2.writeFileSync(filePath, JSON.stringify(body, null, 2));
1727
+ fs3.writeFileSync(filePath, JSON.stringify(body, null, 2));
1583
1728
  jsonResponse(res, 200, { ok: true, fileName });
1584
1729
  return;
1585
1730
  }
@@ -1588,8 +1733,8 @@ ${lines.join("\n")}
1588
1733
  jsonResponse(res, 403, { error: "Cannot delete the default colors and type" });
1589
1734
  return;
1590
1735
  }
1591
- if (fs2.existsSync(filePath)) {
1592
- fs2.unlinkSync(filePath);
1736
+ if (fs3.existsSync(filePath)) {
1737
+ fs3.unlinkSync(filePath);
1593
1738
  } else if (colorsAndTypeResource.existingPath(fileName)) {
1594
1739
  jsonResponse(res, 403, {
1595
1740
  error: "Cannot delete colors and type shipped with the package. Saving it creates a local copy; deleting that copy restores the shipped version.",
@@ -1676,16 +1821,16 @@ ${lines.join("\n")}
1676
1821
  body.component = comp;
1677
1822
  body.name = body.name || name;
1678
1823
  body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1679
- if (fs2.existsSync(configPath)) {
1824
+ if (fs3.existsSync(configPath)) {
1680
1825
  try {
1681
- const existing = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
1826
+ const existing = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1682
1827
  if (existing.createdAt) body.createdAt = existing.createdAt;
1683
1828
  } catch {
1684
1829
  }
1685
1830
  }
1686
1831
  if (!body.createdAt) body.createdAt = body.updatedAt;
1687
1832
  r.ensureDir();
1688
- fs2.writeFileSync(configPath, JSON.stringify(body, null, 2));
1833
+ fs3.writeFileSync(configPath, JSON.stringify(body, null, 2));
1689
1834
  jsonResponse(res, 200, { ok: true, fileName: name });
1690
1835
  return;
1691
1836
  }
@@ -1694,7 +1839,7 @@ ${lines.join("\n")}
1694
1839
  jsonResponse(res, 403, { error: "Cannot delete default config" });
1695
1840
  return;
1696
1841
  }
1697
- if (fs2.existsSync(configPath)) fs2.unlinkSync(configPath);
1842
+ if (fs3.existsSync(configPath)) fs3.unlinkSync(configPath);
1698
1843
  jsonResponse(res, 200, { ok: true });
1699
1844
  return;
1700
1845
  }
@@ -1702,7 +1847,7 @@ ${lines.join("\n")}
1702
1847
  async function handleListComponentConfigs({ params, res }) {
1703
1848
  const [comp] = params;
1704
1849
  const r = componentResource(comp);
1705
- if (!fs2.existsSync(r.dir)) {
1850
+ if (!fs3.existsSync(r.dir)) {
1706
1851
  jsonResponse(res, 404, { error: "Component not found" });
1707
1852
  return;
1708
1853
  }
@@ -1790,9 +1935,9 @@ ${lines.join("\n")}
1790
1935
  const body = JSON.parse(await readBody(req));
1791
1936
  const { theme } = normalizeTheme(body, diskThemeResolvers);
1792
1937
  theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1793
- if (fs2.existsSync(filePath)) {
1938
+ if (fs3.existsSync(filePath)) {
1794
1939
  try {
1795
- const existing = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
1940
+ const existing = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
1796
1941
  if (existing.createdAt) theme.createdAt = existing.createdAt;
1797
1942
  } catch {
1798
1943
  }
@@ -1814,7 +1959,7 @@ ${lines.join("\n")}
1814
1959
  });
1815
1960
  return;
1816
1961
  }
1817
- if (fs2.existsSync(filePath)) {
1962
+ if (fs3.existsSync(filePath)) {
1818
1963
  const deletingActive = themesResource.getActiveName() === fileName;
1819
1964
  const liveColors = deletingActive ? resolveLiveColorsAndType(activeTheme()) : null;
1820
1965
  const liveComponents = {};
@@ -1824,7 +1969,8 @@ ${lines.join("\n")}
1824
1969
  if (resolved) liveComponents[comp] = resolved.data;
1825
1970
  }
1826
1971
  }
1827
- fs2.unlinkSync(filePath);
1972
+ fs3.unlinkSync(filePath);
1973
+ a11yRuns.forget(fileName);
1828
1974
  if (themesResource.existingPath(fileName) === null && deletingActive) {
1829
1975
  themesResource.setActiveName("default");
1830
1976
  }
@@ -1913,7 +2059,49 @@ data: ${JSON.stringify(state)}
1913
2059
 
1914
2060
  `);
1915
2061
  });
1916
- req.on("close", unsubscribe);
2062
+ eventClients.add(res);
2063
+ req.on("close", () => {
2064
+ unsubscribe();
2065
+ eventClients.delete(res);
2066
+ });
2067
+ }
2068
+ const eventClients = /* @__PURE__ */ new Set();
2069
+ const a11yRuns = a11yRunServer({
2070
+ root: process.cwd(),
2071
+ reportsDir: A11Y_REPORTS_DIR,
2072
+ openTheme: () => themesResource.getActiveName(),
2073
+ runCli: spawnCli(PACKAGE_CLI_PATH),
2074
+ broadcast: (report) => {
2075
+ const frame = `event: ${A11Y_EVENT}
2076
+ data: ${JSON.stringify(report)}
2077
+
2078
+ `;
2079
+ for (const client of eventClients) client.write(frame);
2080
+ }
2081
+ });
2082
+ async function handleStartA11yRun({ req, res }) {
2083
+ let body;
2084
+ try {
2085
+ body = JSON.parse(await readBody(req));
2086
+ } catch {
2087
+ body = void 0;
2088
+ }
2089
+ const answer = a11yRuns.start(body);
2090
+ jsonResponse(res, answer.status, answer.body);
2091
+ }
2092
+ async function handleConfirmA11yReview({ req, res }) {
2093
+ let body;
2094
+ try {
2095
+ body = JSON.parse(await readBody(req));
2096
+ } catch {
2097
+ body = void 0;
2098
+ }
2099
+ const answer = a11yRuns.confirm(body);
2100
+ jsonResponse(res, answer.status, answer.body);
2101
+ }
2102
+ async function handleGetA11yReport({ res }) {
2103
+ const answer = a11yRuns.report();
2104
+ jsonResponse(res, answer.status, answer.body);
1917
2105
  }
1918
2106
  async function handleExportTheme({ params, res }) {
1919
2107
  const [fileName] = params;
@@ -2074,22 +2262,22 @@ data: ${JSON.stringify(state)}
2074
2262
  }
2075
2263
  body.name = String(body.name || fileName);
2076
2264
  body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2077
- if (fs2.existsSync(filePath)) {
2265
+ if (fs3.existsSync(filePath)) {
2078
2266
  try {
2079
- const existing = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
2267
+ const existing = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
2080
2268
  if (existing.createdAt) body.createdAt = existing.createdAt;
2081
2269
  } catch {
2082
2270
  }
2083
2271
  }
2084
2272
  if (!body.createdAt) body.createdAt = body.updatedAt;
2085
2273
  sketchStylesResource.ensureDir();
2086
- fs2.writeFileSync(filePath, JSON.stringify(body, null, 2));
2274
+ fs3.writeFileSync(filePath, JSON.stringify(body, null, 2));
2087
2275
  jsonResponse(res, 200, { ok: true, fileName });
2088
2276
  return;
2089
2277
  }
2090
2278
  if (req.method === "DELETE") {
2091
- if (fs2.existsSync(filePath)) {
2092
- fs2.unlinkSync(filePath);
2279
+ if (fs3.existsSync(filePath)) {
2280
+ fs3.unlinkSync(filePath);
2093
2281
  } else if (sketchStylesResource.existingPath(fileName)) {
2094
2282
  jsonResponse(res, 403, {
2095
2283
  error: "Cannot delete a sketchstyle shipped with the package. Saving it creates a local copy; deleting that copy restores the shipped version.",
@@ -2186,12 +2374,24 @@ data: ${JSON.stringify(state)}
2186
2374
  { method: "DELETE", pattern: SKETCH_STYLE_BY_NAME_REGEX, handler: handleSketchStyleByName },
2187
2375
  { method: "POST", pattern: SKETCH_STYLE_BY_NAME_REGEX, handler: methodNotAllowed },
2188
2376
  { method: "GET", pattern: EVENTS_ROUTE, handler: handleEvents },
2377
+ { method: "POST", pattern: A11Y_RUN_ROUTE, handler: handleStartA11yRun },
2378
+ { method: "GET", pattern: A11Y_RUN_ROUTE, handler: methodNotAllowed },
2379
+ { method: "PUT", pattern: A11Y_RUN_ROUTE, handler: methodNotAllowed },
2380
+ { method: "DELETE", pattern: A11Y_RUN_ROUTE, handler: methodNotAllowed },
2381
+ { method: "GET", pattern: A11Y_REPORT_ROUTE, handler: handleGetA11yReport },
2382
+ { method: "PUT", pattern: A11Y_REPORT_ROUTE, handler: methodNotAllowed },
2383
+ { method: "POST", pattern: A11Y_REPORT_ROUTE, handler: methodNotAllowed },
2384
+ { method: "DELETE", pattern: A11Y_REPORT_ROUTE, handler: methodNotAllowed },
2385
+ { method: "PUT", pattern: A11Y_CONFIRMED_ROUTE, handler: handleConfirmA11yReview },
2386
+ { method: "GET", pattern: A11Y_CONFIRMED_ROUTE, handler: methodNotAllowed },
2387
+ { method: "POST", pattern: A11Y_CONFIRMED_ROUTE, handler: methodNotAllowed },
2388
+ { method: "DELETE", pattern: A11Y_CONFIRMED_ROUTE, handler: methodNotAllowed },
2189
2389
  { method: "PUT", pattern: PRODUCTION_ROUTE, handler: handleAdoptTheme },
2190
2390
  { method: "GET", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed },
2191
2391
  { method: "POST", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed },
2192
2392
  { method: "DELETE", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed }
2193
2393
  ];
2194
- const isEditorOwnedJson = (file) => file.endsWith(".json") && path3.resolve(file).startsWith(dataDirs.dataDir + path3.sep);
2394
+ const isEditorOwnedJson = (file) => file.endsWith(".json") && path4.resolve(file).startsWith(dataDirs.dataDir + path4.sep);
2195
2395
  let buildLogger = null;
2196
2396
  let buildChecked = false;
2197
2397
  let productionCssLogger = null;
@@ -2241,7 +2441,7 @@ data: ${JSON.stringify(state)}
2241
2441
  const { productionPointerHeld } = ensureThemesDir((msg) => server.config.logger.warn(msg));
2242
2442
  const openTheme = activeTheme();
2243
2443
  if (openTheme) pruneMatchingWorking(openTheme);
2244
- if (!productionPointerHeld || !fs2.existsSync(GENERATED_CSS_PATH)) regenerateTokensCss();
2444
+ if (!productionPointerHeld || !fs3.existsSync(GENERATED_CSS_PATH)) regenerateTokensCss();
2245
2445
  if (opts.autoMigrate && !ISOLATED_DATA_DIR) {
2246
2446
  autoMigrateAdditive((msg) => server.config.logger.info(msg));
2247
2447
  }
@@ -2264,10 +2464,11 @@ data: ${JSON.stringify(state)}
2264
2464
  const handled = await dispatch(req, res, routes);
2265
2465
  if (!handled) next();
2266
2466
  });
2467
+ server.httpServer?.once("close", () => a11yRuns.stop());
2267
2468
  },
2268
2469
  handleHotUpdate(ctx) {
2269
2470
  if (legacyLayout) return;
2270
- const normalized = path3.resolve(ctx.file);
2471
+ const normalized = path4.resolve(ctx.file);
2271
2472
  if (!COMPONENTS_SCAN_DIRS.some((d) => normalized.startsWith(d))) return;
2272
2473
  if (!normalized.endsWith(".svelte")) return;
2273
2474
  if (!isThemeAwareComponent(normalized)) return;