@motion-proto/live-tokens 0.97.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 (184) 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 +272 -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/dist-plugin/{chunk-A4TBP4MM.js → chunk-SLICH4IH.js} +2 -2
  13. package/dist-plugin/index.cjs +148 -3
  14. package/dist-plugin/index.js +250 -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 +3 -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 +41 -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 +101 -0
  56. package/src/editor/core/a11y/a11yReportTypes.ts +69 -0
  57. package/src/editor/core/a11y/contrastFailures.ts +22 -0
  58. package/src/editor/core/components/contrastPairRegistry.ts +18 -0
  59. package/src/editor/core/palettes/contrast.ts +44 -0
  60. package/src/editor/core/palettes/contrastPairs.ts +79 -0
  61. package/src/editor/core/palettes/resolveColor.ts +77 -0
  62. package/src/editor/core/store/editorStore.ts +4 -0
  63. package/src/editor/core/store/editorViewStore.ts +2 -2
  64. package/src/editor/core/store/unsavedEdits.ts +10 -0
  65. package/src/editor/core/themes/liveStateStream.ts +7 -1
  66. package/src/editor/core/themes/parsers/gradient.ts +5 -2
  67. package/src/editor/docs/chapters.ts +1 -0
  68. package/src/editor/docs/content/accessibility.md +362 -0
  69. package/src/editor/docs/content/editing-tokens.md +28 -1
  70. package/src/editor/docs/content.generated.ts +2 -1
  71. package/src/editor/pages/ComponentEditorPage.svelte +10 -1
  72. package/src/editor/pages/EditorShell.svelte +96 -10
  73. package/src/editor/skill-atlas/skillSources.generated.ts +3 -3
  74. package/src/editor/skill-atlas/trees/check-compliance.ts +3 -3
  75. package/src/editor/skill-atlas/trees/create-component.ts +12 -12
  76. package/src/editor/ui/EditorViewSwitcher.svelte +15 -2
  77. package/src/editor/ui/GradientEditor.svelte +9 -1
  78. package/src/editor/ui/ThemePanel.svelte +4 -4
  79. package/src/editor/ui/UIContrastReadout.svelte +186 -0
  80. package/src/editor/ui/UIInfoPopover.svelte +16 -2
  81. package/src/editor/ui/UIPaletteSelector.svelte +9 -0
  82. package/src/editor/ui/UITokenSelector.svelte +26 -2
  83. package/src/editor/ui/a11y/A11yComponentIssues.svelte +103 -0
  84. package/src/editor/ui/a11y/A11yFindingRow.svelte +57 -0
  85. package/src/editor/ui/a11y/A11yIssueRow.svelte +121 -0
  86. package/src/editor/ui/a11y/A11yOverview.svelte +242 -0
  87. package/src/editor/ui/a11y/A11yPanel.svelte +128 -0
  88. package/src/editor/ui/a11y/A11yPropertyMark.svelte +84 -0
  89. package/src/editor/ui/a11y/A11yRailMark.svelte +34 -0
  90. package/src/editor/ui/a11y/AccessibilityTab.svelte +334 -0
  91. package/src/editor/ui/a11y/a11yView.ts +218 -0
  92. package/src/editor/ui/contrastReadout.ts +45 -0
  93. package/src/live-tokens/data/themes/autumn.json +20 -20
  94. package/src/live-tokens/data/themes/halloween.json +20 -20
  95. package/src/live-tokens/data/themes/midnight-study.json +20 -20
  96. package/src/live-tokens/data/themes/ocean.json +20 -20
  97. package/src/live-tokens/data/themes/royal-velvet.json +20 -20
  98. package/src/live-tokens/data/themes/sketchy.json +20 -20
  99. package/src/live-tokens/data/themes/spring-meadow.json +20 -20
  100. package/src/live-tokens/data/themes/sunset.json +20 -20
  101. package/src/system/components/Badge.svelte +3 -3
  102. package/src/system/components/Button.svelte +9 -3
  103. package/src/system/components/CodeSnippet.svelte +8 -1
  104. package/src/system/components/CornerBadge.svelte +3 -3
  105. package/src/system/components/Dialog.svelte +43 -14
  106. package/src/system/components/IconButton.svelte +1 -1
  107. package/src/system/components/Image.svelte +7 -0
  108. package/src/system/components/ImageLightbox.svelte +18 -25
  109. package/src/system/components/Input.svelte +8 -1
  110. package/src/system/components/MenuSelect.svelte +30 -2
  111. package/src/system/components/Notification.svelte +1 -1
  112. package/src/system/components/ProgressBar.svelte +6 -0
  113. package/src/system/components/RadioButton.svelte +36 -3
  114. package/src/system/components/SegmentedControl.svelte +36 -2
  115. package/src/system/components/SlidePager.svelte +2 -2
  116. package/src/system/components/Slider.svelte +13 -9
  117. package/src/system/components/TabBar.svelte +33 -3
  118. package/src/system/components/Toggle.svelte +2 -2
  119. package/src/system/components/VideoFrame.svelte +1 -1
  120. package/src/system/components/VideoLightbox.svelte +2 -2
  121. package/src/system/internal/focusTrap.ts +35 -0
  122. package/src/testing-js/__aria__/badge.yml +40 -0
  123. package/src/testing-js/__aria__/button.yml +88 -0
  124. package/src/testing-js/__aria__/callout.yml +12 -0
  125. package/src/testing-js/__aria__/card.yml +8 -0
  126. package/src/testing-js/__aria__/codesnippet.yml +21 -0
  127. package/src/testing-js/__aria__/collapsiblesection.yml +23 -0
  128. package/src/testing-js/__aria__/cornerbadge.yml +24 -0
  129. package/src/testing-js/__aria__/dialog.yml +42 -0
  130. package/src/testing-js/__aria__/iconbutton.yml +57 -0
  131. package/src/testing-js/__aria__/image.yml +10 -0
  132. package/src/testing-js/__aria__/imagelightbox.yml +50 -0
  133. package/src/testing-js/__aria__/inlineeditactions.yml +16 -0
  134. package/src/testing-js/__aria__/input.yml +61 -0
  135. package/src/testing-js/__aria__/menuselect.yml +30 -0
  136. package/src/testing-js/__aria__/notification.yml +30 -0
  137. package/src/testing-js/__aria__/panel.yml +2 -0
  138. package/src/testing-js/__aria__/progressbar.yml +2 -0
  139. package/src/testing-js/__aria__/radiobutton.yml +20 -0
  140. package/src/testing-js/__aria__/sectiondivider.yml +9 -0
  141. package/src/testing-js/__aria__/segmentedcontrol.yml +35 -0
  142. package/src/testing-js/__aria__/sidenavigation.yml +304 -0
  143. package/src/testing-js/__aria__/slidepager.yml +72 -0
  144. package/src/testing-js/__aria__/slider.yml +28 -0
  145. package/src/testing-js/__aria__/tabbar.yml +35 -0
  146. package/src/testing-js/__aria__/table.yml +144 -0
  147. package/src/testing-js/__aria__/toggle.yml +12 -0
  148. package/src/testing-js/__aria__/tooltip.yml +2 -0
  149. package/src/testing-js/__aria__/videoframe.yml +6 -0
  150. package/src/testing-js/__aria__/videolightbox.yml +12 -0
  151. package/src/testing-js/{chunk-WIZ6W7UT.js → chunk-3WCLMOM7.js} +2 -2
  152. package/src/testing-js/{chunk-SHBYZUPD.js → chunk-5HNZHIHG.js} +211 -14
  153. package/src/testing-js/chunk-5HNZHIHG.js.map +1 -0
  154. package/src/testing-js/{chunk-NB3NZRBM.js → chunk-7CM5D7PF.js} +5 -1
  155. package/src/testing-js/chunk-7CM5D7PF.js.map +1 -0
  156. package/src/testing-js/chunk-EWSGE27V.js +168 -0
  157. package/src/testing-js/chunk-EWSGE27V.js.map +1 -0
  158. package/src/testing-js/{chunk-GKO27NGN.js → chunk-R54I7YL6.js} +2 -2
  159. package/src/testing-js/{chunk-X6KHEVIG.js → chunk-RWH46IU5.js} +97 -29
  160. package/src/testing-js/chunk-RWH46IU5.js.map +1 -0
  161. package/src/testing-js/chunk-SHLPDNAQ.js +658 -0
  162. package/src/testing-js/chunk-SHLPDNAQ.js.map +1 -0
  163. package/src/testing-js/{chunk-3UKGXCDL.js → chunk-YVVR3GJZ.js} +1 -1
  164. package/src/testing-js/chunk-YVVR3GJZ.js.map +1 -0
  165. package/src/testing-js/component-a11y.contract.js +283 -0
  166. package/src/testing-js/component-a11y.contract.js.map +1 -0
  167. package/src/testing-js/component-alias.contract.js +2 -2
  168. package/src/testing-js/component-behavior.contract.js +2 -2
  169. package/src/testing-js/component-editor.contract.js +6 -5
  170. package/src/testing-js/component-editor.contract.js.map +1 -1
  171. package/src/testing-js/component-render.contract.js +10 -6
  172. package/src/testing-js/component-render.contract.js.map +1 -1
  173. package/src/testing-js/index.d.ts +44 -16
  174. package/src/testing-js/index.js +9 -9
  175. package/src/testing-js/page-compliance.contract.js +132 -13
  176. package/src/testing-js/page-compliance.contract.js.map +1 -1
  177. package/src/testing-js/vitest.js +3 -3
  178. package/template/package.json +9 -0
  179. package/src/testing-js/chunk-3UKGXCDL.js.map +0 -1
  180. package/src/testing-js/chunk-NB3NZRBM.js.map +0 -1
  181. package/src/testing-js/chunk-SHBYZUPD.js.map +0 -1
  182. package/src/testing-js/chunk-X6KHEVIG.js.map +0 -1
  183. /package/src/testing-js/{chunk-WIZ6W7UT.js.map → chunk-3WCLMOM7.js.map} +0 -0
  184. /package/src/testing-js/{chunk-GKO27NGN.js.map → chunk-R54I7YL6.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,119 @@ 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_REPORT_PATH = path3.join("test-results", "live-tokens", "a11y-report.json");
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, reportFile, runCli, broadcast }) {
790
+ let running = null;
791
+ function current() {
792
+ if (running) return running.report;
793
+ if (!fs2.existsSync(reportFile)) return null;
794
+ return JSON.parse(fs2.readFileSync(reportFile, "utf-8"));
795
+ }
796
+ function settle(started, outcome) {
797
+ const finishedAt = (/* @__PURE__ */ new Date()).toISOString();
798
+ const cli = parseCliReport(outcome.stdout);
799
+ if (!cli) {
800
+ const stderr = outcome.stderr.trim().slice(-STDERR_TAIL);
801
+ return {
802
+ ...started,
803
+ state: "failed",
804
+ finishedAt,
805
+ error: `check-component exited with code ${outcome.code} and printed no report.${stderr ? `
806
+ ${stderr}` : ""}`
807
+ };
808
+ }
809
+ return {
810
+ ...started,
811
+ state: "finished",
812
+ finishedAt,
813
+ ...cli.theme ? { theme: cli.theme } : {},
814
+ findings: cli.findings.filter((f) => f.details?.engineRule !== "color-contrast")
815
+ };
816
+ }
817
+ function start(body) {
818
+ if (running) {
819
+ return { status: 409, body: { error: "An accessibility run is already going.", code: "A11Y_RUN_IN_PROGRESS" } };
820
+ }
821
+ const contrast = readContrast(body);
822
+ if (!contrast) {
823
+ return {
824
+ status: 400,
825
+ body: { error: "Body must be { contrast: [...] } with one entry per failing pair", code: "INVALID_BODY" }
826
+ };
827
+ }
828
+ const report = { state: "running", startedAt: (/* @__PURE__ */ new Date()).toISOString(), contrast };
829
+ const run = runCli(A11Y_RUN_ARGS, root);
830
+ running = { report, stop: run.stop };
831
+ broadcast(report);
832
+ void run.done.then((outcome) => {
833
+ const settled = settle(report, outcome);
834
+ fs2.mkdirSync(path3.dirname(reportFile), { recursive: true });
835
+ fs2.writeFileSync(reportFile, `${JSON.stringify(settled, null, 2)}
836
+ `);
837
+ running = null;
838
+ broadcast(settled);
839
+ });
840
+ return { status: 202, body: report };
841
+ }
842
+ return {
843
+ start,
844
+ report: () => ({ status: 200, body: { report: current() } }),
845
+ stop: () => running?.stop()
846
+ };
847
+ }
848
+
849
+ // src/editor/core/a11y/a11yReportTypes.ts
850
+ var A11Y_EVENT = "a11y";
851
+
751
852
  // vite-plugin/themeFileApi.ts
752
853
  import { fileURLToPath as fileURLToPath2 } from "url";
753
854
  var PKG_VERSION = (() => {
754
855
  try {
755
- let dir = path3.dirname(fileURLToPath2(import.meta.url));
856
+ let dir = path4.dirname(fileURLToPath2(import.meta.url));
756
857
  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"));
858
+ const p = path4.join(dir, "package.json");
859
+ if (fs3.existsSync(p)) {
860
+ const json = JSON.parse(fs3.readFileSync(p, "utf-8"));
760
861
  if (json?.name === "@motion-proto/live-tokens") return json.version ?? "";
761
862
  }
762
- const up = path3.dirname(dir);
863
+ const up = path4.dirname(dir);
763
864
  if (up === dir) break;
764
865
  dir = up;
765
866
  }
@@ -803,46 +904,48 @@ function themeFileApi(opts) {
803
904
  const COMPONENT_CONFIGS_DIR = dataDirs.componentConfigsDir;
804
905
  const THEMES_DIR = dataDirs.themesDir;
805
906
  const SKETCH_STYLES_DIR = dataDirs.sketchStylesDir;
806
- const CSS_PATH = path3.resolve(opts.tokensCssPath);
907
+ const CSS_PATH = path4.resolve(opts.tokensCssPath);
807
908
  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");
909
+ 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");
910
+ 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");
911
+ const A11Y_REPORT_FILE = ISOLATED_DATA_DIR ? path4.join(ISOLATED_DATA_DIR, path4.basename(A11Y_REPORT_PATH)) : path4.join(process.cwd(), A11Y_REPORT_PATH);
810
912
  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)),
913
+ const consumerComponentDirs = opts.componentsSrcDir ? [path4.resolve(opts.componentsSrcDir)] : [path4.resolve("src/components"), path4.resolve("src/system/components")];
914
+ const packageComponentsDir = path4.resolve(
915
+ path4.dirname(fileURLToPath2(import.meta.url)),
814
916
  "..",
815
917
  "src",
816
918
  "system",
817
919
  "components"
818
920
  );
819
- const packageDataDir = testPackageDataDir() ?? path3.resolve(
820
- path3.dirname(fileURLToPath2(import.meta.url)),
921
+ const PACKAGE_CLI_PATH = path4.resolve(path4.dirname(fileURLToPath2(import.meta.url)), "..", "bin", "cli.mjs");
922
+ const packageDataDir = testPackageDataDir() ?? path4.resolve(
923
+ path4.dirname(fileURLToPath2(import.meta.url)),
821
924
  "..",
822
925
  "src",
823
926
  "live-tokens",
824
927
  "data"
825
928
  );
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");
929
+ const packageColorsAndTypeDir = path4.join(packageDataDir, "colors-and-type");
930
+ const packageThemesDir = path4.join(packageDataDir, "themes");
931
+ const packageComponentConfigsDir = path4.join(packageDataDir, "component-configs");
932
+ const packageSketchStylesDir = path4.join(packageDataDir, "sketch-styles");
830
933
  function shippedNames(dataDir, subdir) {
831
934
  try {
832
- const pkgRoot = path3.resolve(dataDir, "..", "..", "..");
833
- const pkg = JSON.parse(fs2.readFileSync(path3.join(pkgRoot, "package.json"), "utf-8"));
935
+ const pkgRoot = path4.resolve(dataDir, "..", "..", "..");
936
+ const pkg = JSON.parse(fs3.readFileSync(path4.join(pkgRoot, "package.json"), "utf-8"));
834
937
  const files = Array.isArray(pkg.files) ? pkg.files : [];
835
938
  const owned = new RegExp(`^src/live-tokens/data/${subdir}/[^/]+\\.json$`);
836
- return files.filter((f) => owned.test(f)).map((f) => path3.basename(f, ".json"));
939
+ return files.filter((f) => owned.test(f)).map((f) => path4.basename(f, ".json"));
837
940
  } catch {
838
941
  return [];
839
942
  }
840
943
  }
841
944
  const COMPONENTS_SCAN_DIRS = [...consumerComponentDirs];
842
- if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && fs2.existsSync(packageComponentsDir)) {
945
+ if (!COMPONENTS_SCAN_DIRS.includes(packageComponentsDir) && fs3.existsSync(packageComponentsDir)) {
843
946
  COMPONENTS_SCAN_DIRS.push(packageComponentsDir);
844
947
  }
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));
948
+ 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
949
  const liveWatch = dataTreeWatch({
847
950
  dirs: [COLORS_AND_TYPE_DIR, COMPONENT_CONFIGS_DIR, THEMES_DIR],
848
951
  isLive: isLiveStateFile
@@ -858,8 +961,8 @@ function themeFileApi(opts) {
858
961
  let r = componentResourceCache.get(comp);
859
962
  if (!r) {
860
963
  r = versionedFileResourceServer({
861
- dir: path3.join(COMPONENT_CONFIGS_DIR, comp),
862
- packageDir: path3.join(packageComponentConfigsDir, comp),
964
+ dir: path4.join(COMPONENT_CONFIGS_DIR, comp),
965
+ packageDir: path4.join(packageComponentConfigsDir, comp),
863
966
  onWrite: liveWatch.noteOwnWrite
864
967
  });
865
968
  componentResourceCache.set(comp, r);
@@ -947,7 +1050,7 @@ function themeFileApi(opts) {
947
1050
  const productionThemeName = themesResource.getProductionName();
948
1051
  const productionTheme = readTheme(productionThemeName)?.theme ?? null;
949
1052
  if (themesResource.existingPath(productionThemeName) !== null && !productionTheme?.colorsAndType) {
950
- const tail = `${path3.basename(GENERATED_CSS_PATH)} was left as it was.`;
1053
+ const tail = `${path4.basename(GENERATED_CSS_PATH)} was left as it was.`;
951
1054
  throw new Error(
952
1055
  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
1056
  );
@@ -985,7 +1088,7 @@ function themeFileApi(opts) {
985
1088
  }
986
1089
  }
987
1090
  let componentOverrideCount = 0;
988
- if (fs2.existsSync(COMPONENT_CONFIGS_DIR)) {
1091
+ if (fs3.existsSync(COMPONENT_CONFIGS_DIR)) {
989
1092
  const blocks = [];
990
1093
  for (const comp of listComponentNames()) {
991
1094
  const prodCfg = productionTheme?.componentConfigs[comp];
@@ -1030,18 +1133,18 @@ function themeFileApi(opts) {
1030
1133
  `/* Bake fingerprint: ${bakeFingerprint(body)} */`,
1031
1134
  ""
1032
1135
  ];
1033
- if (!fs2.existsSync(path3.dirname(GENERATED_CSS_PATH))) {
1034
- fs2.mkdirSync(path3.dirname(GENERATED_CSS_PATH), { recursive: true });
1136
+ if (!fs3.existsSync(path4.dirname(GENERATED_CSS_PATH))) {
1137
+ fs3.mkdirSync(path4.dirname(GENERATED_CSS_PATH), { recursive: true });
1035
1138
  }
1036
- fs2.writeFileSync(GENERATED_CSS_PATH, [...header, body].join("\n"));
1139
+ fs3.writeFileSync(GENERATED_CSS_PATH, [...header, body].join("\n"));
1037
1140
  console.log(
1038
- `[regenerateTokensCss] Wrote ${path3.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
1141
+ `[regenerateTokensCss] Wrote ${path4.basename(GENERATED_CSS_PATH)} (${colorsAndTypeVarCount} theme vars, ${componentOverrideCount} component overrides)`
1039
1142
  );
1040
1143
  }
1041
1144
  function bakeIsCurrent() {
1042
1145
  let baked;
1043
1146
  try {
1044
- baked = fs2.readFileSync(GENERATED_CSS_PATH, "utf-8");
1147
+ baked = fs3.readFileSync(GENERATED_CSS_PATH, "utf-8");
1045
1148
  } catch {
1046
1149
  return false;
1047
1150
  }
@@ -1077,18 +1180,18 @@ function themeFileApi(opts) {
1077
1180
  lines.push("");
1078
1181
  }
1079
1182
  const content = lines.join("\n");
1080
- if (!fs2.existsSync(path3.dirname(FONTS_CSS_PATH))) {
1081
- fs2.mkdirSync(path3.dirname(FONTS_CSS_PATH), { recursive: true });
1183
+ if (!fs3.existsSync(path4.dirname(FONTS_CSS_PATH))) {
1184
+ fs3.mkdirSync(path4.dirname(FONTS_CSS_PATH), { recursive: true });
1082
1185
  }
1083
- fs2.writeFileSync(FONTS_CSS_PATH, content);
1084
- console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${path3.basename(FONTS_CSS_PATH)}`);
1186
+ fs3.writeFileSync(FONTS_CSS_PATH, content);
1187
+ console.log(`[syncFontsToCss] Wrote ${sources.length} source(s) into ${path4.basename(FONTS_CSS_PATH)}`);
1085
1188
  }
1086
1189
  function componentNameFromFile(filePath) {
1087
- return path3.basename(filePath, ".svelte").toLowerCase();
1190
+ return path4.basename(filePath, ".svelte").toLowerCase();
1088
1191
  }
1089
1192
  function isThemeAwareComponent(filePath) {
1090
1193
  try {
1091
- const source = fs2.readFileSync(filePath, "utf-8");
1194
+ const source = fs3.readFileSync(filePath, "utf-8");
1092
1195
  return /:global\(:root\)\s*\{/.test(source);
1093
1196
  } catch {
1094
1197
  return false;
@@ -1097,10 +1200,10 @@ function themeFileApi(opts) {
1097
1200
  function listComponentSourcePaths() {
1098
1201
  const byName = /* @__PURE__ */ new Map();
1099
1202
  for (const dir of COMPONENTS_SCAN_DIRS) {
1100
- if (!fs2.existsSync(dir)) continue;
1101
- for (const f of fs2.readdirSync(dir)) {
1203
+ if (!fs3.existsSync(dir)) continue;
1204
+ for (const f of fs3.readdirSync(dir)) {
1102
1205
  if (!f.endsWith(".svelte")) continue;
1103
- const full = path3.join(dir, f);
1206
+ const full = path4.join(dir, f);
1104
1207
  if (!isThemeAwareComponent(full)) continue;
1105
1208
  const name = componentNameFromFile(f);
1106
1209
  if (!byName.has(name)) byName.set(name, full);
@@ -1114,18 +1217,18 @@ function themeFileApi(opts) {
1114
1217
  function warnOnTokenDrift(log) {
1115
1218
  let tokensCss = "";
1116
1219
  try {
1117
- tokensCss = fs2.readFileSync(CSS_PATH, "utf-8");
1220
+ tokensCss = fs3.readFileSync(CSS_PATH, "utf-8");
1118
1221
  } catch {
1119
1222
  return;
1120
1223
  }
1121
1224
  let generatedCss = "";
1122
1225
  try {
1123
- generatedCss = fs2.readFileSync(GENERATED_CSS_PATH, "utf-8");
1226
+ generatedCss = fs3.readFileSync(GENERATED_CSS_PATH, "utf-8");
1124
1227
  } catch {
1125
1228
  }
1126
1229
  const componentSources = listComponentSourcePaths().map((p) => ({
1127
1230
  name: componentNameFromFile(p),
1128
- source: fs2.readFileSync(p, "utf-8")
1231
+ source: fs3.readFileSync(p, "utf-8")
1129
1232
  }));
1130
1233
  const missing = validateTokensCss({ tokensCss, generatedCss, componentSources });
1131
1234
  if (missing.length === 0) return;
@@ -1133,7 +1236,7 @@ function themeFileApi(opts) {
1133
1236
  (m) => ` ${m.token} (referenced by ${m.referencedBy.join(", ")})`
1134
1237
  );
1135
1238
  log(
1136
- `[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${path3.relative(process.cwd(), CSS_PATH)}:
1239
+ `[live-tokens] ${missing.length} token(s) referenced by components are not defined in ${path4.relative(process.cwd(), CSS_PATH)}:
1137
1240
  ${lines.join("\n")}
1138
1241
  These render as blank/empty editor slots. Run \`npx live-tokens migrate\` to reconcile.`
1139
1242
  );
@@ -1141,30 +1244,30 @@ ${lines.join("\n")}
1141
1244
  function autoMigrateAdditive(log) {
1142
1245
  let before = "";
1143
1246
  try {
1144
- before = fs2.readFileSync(CSS_PATH, "utf-8");
1247
+ before = fs3.readFileSync(CSS_PATH, "utf-8");
1145
1248
  } catch {
1146
1249
  return;
1147
1250
  }
1148
1251
  const { css, applied, changed } = runAdditiveTokensCssMigrations(before);
1149
1252
  if (!changed) return;
1150
- fs2.writeFileSync(CSS_PATH, css);
1253
+ fs3.writeFileSync(CSS_PATH, css);
1151
1254
  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.`
1255
+ `[live-tokens] autoMigrate applied ${applied.length} additive migration(s) to ${path4.relative(process.cwd(), CSS_PATH)}: ${applied.join(", ")}. Review the diff in git.`
1153
1256
  );
1154
1257
  }
1155
1258
  function generateDefaultConfig(comp, sourcePath) {
1156
- if (!fs2.existsSync(sourcePath)) return;
1259
+ if (!fs3.existsSync(sourcePath)) return;
1157
1260
  const r = componentResource(comp);
1158
1261
  r.ensureDir();
1159
1262
  const defaultPath = r.filePath("default");
1160
- const sourceStat = fs2.statSync(sourcePath);
1263
+ const sourceStat = fs3.statSync(sourcePath);
1161
1264
  let createdAt = (/* @__PURE__ */ new Date()).toISOString();
1162
1265
  let existingUpdatedAt;
1163
1266
  let existingAliases;
1164
1267
  let existingSchemaVersion;
1165
- if (fs2.existsSync(defaultPath)) {
1268
+ if (fs3.existsSync(defaultPath)) {
1166
1269
  try {
1167
- const existing = JSON.parse(fs2.readFileSync(defaultPath, "utf-8"));
1270
+ const existing = JSON.parse(fs3.readFileSync(defaultPath, "utf-8"));
1168
1271
  if (existing.createdAt) createdAt = existing.createdAt;
1169
1272
  if (typeof existing.updatedAt === "string") existingUpdatedAt = existing.updatedAt;
1170
1273
  if (existing.aliases && typeof existing.aliases === "object") {
@@ -1173,13 +1276,13 @@ ${lines.join("\n")}
1173
1276
  if (typeof existing.schemaVersion === "number") existingSchemaVersion = existing.schemaVersion;
1174
1277
  } catch {
1175
1278
  }
1176
- const defaultStat = fs2.statSync(defaultPath);
1279
+ const defaultStat = fs3.statSync(defaultPath);
1177
1280
  if (defaultStat.mtimeMs >= sourceStat.mtimeMs && existingSchemaVersion === CURRENT_COMPONENT_SCHEMA_VERSION) {
1178
1281
  return;
1179
1282
  }
1180
1283
  }
1181
1284
  const now = (/* @__PURE__ */ new Date()).toISOString();
1182
- const source = fs2.readFileSync(sourcePath, "utf-8");
1285
+ const source = fs3.readFileSync(sourcePath, "utf-8");
1183
1286
  const body = extractGlobalRootBody(source);
1184
1287
  const aliases = extractAliasDeclarations(body);
1185
1288
  const aliasesUnchanged = existingAliases !== void 0 && JSON.stringify(existingAliases) === JSON.stringify(aliases);
@@ -1194,14 +1297,14 @@ ${lines.join("\n")}
1194
1297
  };
1195
1298
  if (aliasesUnchanged && schemaStampCurrent && existingUpdatedAt) {
1196
1299
  const t = /* @__PURE__ */ new Date();
1197
- fs2.utimesSync(defaultPath, t, t);
1300
+ fs3.utimesSync(defaultPath, t, t);
1198
1301
  return;
1199
1302
  }
1200
- fs2.writeFileSync(defaultPath, JSON.stringify(defaultConfig, null, 2));
1303
+ fs3.writeFileSync(defaultPath, JSON.stringify(defaultConfig, null, 2));
1201
1304
  }
1202
1305
  function ensureComponentConfigsDir() {
1203
- if (!fs2.existsSync(COMPONENT_CONFIGS_DIR)) {
1204
- fs2.mkdirSync(COMPONENT_CONFIGS_DIR, { recursive: true });
1306
+ if (!fs3.existsSync(COMPONENT_CONFIGS_DIR)) {
1307
+ fs3.mkdirSync(COMPONENT_CONFIGS_DIR, { recursive: true });
1205
1308
  }
1206
1309
  for (const sourcePath of listComponentSourcePaths()) {
1207
1310
  const comp = componentNameFromFile(sourcePath);
@@ -1214,14 +1317,14 @@ ${lines.join("\n")}
1214
1317
  const legacy = [];
1215
1318
  const check = (dir) => {
1216
1319
  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));
1320
+ const p = path4.join(dir, name);
1321
+ if (fs3.existsSync(p)) legacy.push(path4.relative(process.cwd(), p));
1219
1322
  }
1220
1323
  };
1221
1324
  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));
1325
+ if (fs3.existsSync(COMPONENT_CONFIGS_DIR)) {
1326
+ for (const entry of fs3.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
1327
+ if (entry.isDirectory()) check(path4.join(COMPONENT_CONFIGS_DIR, entry.name));
1225
1328
  }
1226
1329
  }
1227
1330
  return legacy;
@@ -1229,19 +1332,19 @@ ${lines.join("\n")}
1229
1332
  function warnOnLegacyPointers(legacy, bakeHeld, warn) {
1230
1333
  if (legacy.length === 0) return;
1231
1334
  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.` : "")
1335
+ `[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
1336
  );
1234
1337
  }
1235
1338
  function warnOnLegacyLayout(evidence, warn) {
1236
1339
  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.`
1340
+ `[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
1341
  );
1239
1342
  }
1240
1343
  function warnOnUnmigratedSketchStyles(warn) {
1241
- const legacyDir = path3.join(dataDirs.dataDir, "sketch-presets");
1242
- if (!fs2.existsSync(legacyDir)) return;
1344
+ const legacyDir = path4.join(dataDirs.dataDir, "sketch-presets");
1345
+ if (!fs3.existsSync(legacyDir)) return;
1243
1346
  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.`
1347
+ `[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
1348
  );
1246
1349
  }
1247
1350
  const diskThemeResolvers = {
@@ -1265,7 +1368,7 @@ ${lines.join("\n")}
1265
1368
  const resolved = themesResource.existingPath(fileName);
1266
1369
  if (resolved === null) return "missing";
1267
1370
  try {
1268
- if (isColorsAndTypeShaped(JSON.parse(fs2.readFileSync(resolved, "utf-8")))) {
1371
+ if (isColorsAndTypeShaped(JSON.parse(fs3.readFileSync(resolved, "utf-8")))) {
1269
1372
  return "colors-and-type";
1270
1373
  }
1271
1374
  } catch {
@@ -1273,7 +1376,7 @@ ${lines.join("\n")}
1273
1376
  return "corrupt";
1274
1377
  }
1275
1378
  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".`;
1379
+ 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
1380
  }
1278
1381
  function respondUnreadableTheme(res, fileName, missingError) {
1279
1382
  switch (unreadableThemeReason(fileName)) {
@@ -1292,18 +1395,18 @@ ${lines.join("\n")}
1292
1395
  }
1293
1396
  function writeTheme(fileName, theme) {
1294
1397
  themesResource.ensureDir();
1295
- fs2.writeFileSync(themesResource.filePath(fileName), JSON.stringify(theme, null, 2));
1398
+ fs3.writeFileSync(themesResource.filePath(fileName), JSON.stringify(theme, null, 2));
1296
1399
  }
1297
1400
  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)) {
1401
+ if (path4.resolve(THEMES_DIR) === path4.resolve(packageThemesDir)) return;
1402
+ if (!fs3.existsSync(THEMES_DIR)) return;
1403
+ for (const file of fs3.readdirSync(THEMES_DIR)) {
1301
1404
  if (!file.endsWith(".json") || file.startsWith("_")) continue;
1302
1405
  const fileName = file.slice(0, -".json".length);
1303
1406
  if (fileName === "default") continue;
1304
1407
  let raw;
1305
1408
  try {
1306
- raw = JSON.parse(fs2.readFileSync(path3.join(THEMES_DIR, file), "utf-8"));
1409
+ raw = JSON.parse(fs3.readFileSync(path4.join(THEMES_DIR, file), "utf-8"));
1307
1410
  } catch {
1308
1411
  continue;
1309
1412
  }
@@ -1324,7 +1427,7 @@ ${lines.join("\n")}
1324
1427
  function ensureDefaultTheme(warn) {
1325
1428
  let existing = null;
1326
1429
  try {
1327
- existing = JSON.parse(fs2.readFileSync(themesResource.filePath("default"), "utf-8"));
1430
+ existing = JSON.parse(fs3.readFileSync(themesResource.filePath("default"), "utf-8"));
1328
1431
  } catch {
1329
1432
  }
1330
1433
  if (isColorsAndTypeShaped(existing)) {
@@ -1361,9 +1464,9 @@ ${lines.join("\n")}
1361
1464
  themesResource.ensureDir();
1362
1465
  ensureDefaultTheme(warn);
1363
1466
  migrateLocalThemes(warn);
1364
- const productionPointerHeld = !fs2.existsSync(themesResource.productionPath) && listLegacyPointers().length > 0;
1467
+ const productionPointerHeld = !fs3.existsSync(themesResource.productionPath) && listLegacyPointers().length > 0;
1365
1468
  if (productionPointerHeld) {
1366
- if (!fs2.existsSync(themesResource.activePath)) themesResource.setActiveName("default");
1469
+ if (!fs3.existsSync(themesResource.activePath)) themesResource.setActiveName("default");
1367
1470
  } else {
1368
1471
  themesResource.ensureMeta();
1369
1472
  }
@@ -1442,8 +1545,8 @@ ${lines.join("\n")}
1442
1545
  }
1443
1546
  function clearAllWorking() {
1444
1547
  colorsAndTypeResource.clearWorking();
1445
- if (!fs2.existsSync(COMPONENT_CONFIGS_DIR)) return;
1446
- for (const entry of fs2.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
1548
+ if (!fs3.existsSync(COMPONENT_CONFIGS_DIR)) return;
1549
+ for (const entry of fs3.readdirSync(COMPONENT_CONFIGS_DIR, { withFileTypes: true })) {
1447
1550
  if (entry.isDirectory()) componentResource(entry.name).clearWorking();
1448
1551
  }
1449
1552
  }
@@ -1487,6 +1590,8 @@ ${lines.join("\n")}
1487
1590
  const SKETCH_STYLE_BY_NAME_REGEX = new RegExp(`^${escapedBase}/sketch-styles/([a-z0-9\\-_]+)$`);
1488
1591
  const PRODUCTION_ROUTE = `${API_BASE}/production`;
1489
1592
  const EVENTS_ROUTE = `${API_BASE}/events`;
1593
+ const A11Y_RUN_ROUTE = `${API_BASE}/a11y/run`;
1594
+ const A11Y_REPORT_ROUTE = `${API_BASE}/a11y/report`;
1490
1595
  async function handleListColorsAndType(_ctx) {
1491
1596
  const files = colorsAndTypeResource.listNames().map((fileName) => {
1492
1597
  const data = colorsAndTypeResource.readJson(fileName);
@@ -1571,15 +1676,15 @@ ${lines.join("\n")}
1571
1676
  }
1572
1677
  const body = JSON.parse(await readBody(req));
1573
1678
  body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1574
- if (fs2.existsSync(filePath)) {
1679
+ if (fs3.existsSync(filePath)) {
1575
1680
  try {
1576
- const existing = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
1681
+ const existing = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
1577
1682
  if (existing.createdAt) body.createdAt = existing.createdAt;
1578
1683
  } catch {
1579
1684
  }
1580
1685
  }
1581
1686
  if (!body.createdAt) body.createdAt = body.updatedAt;
1582
- fs2.writeFileSync(filePath, JSON.stringify(body, null, 2));
1687
+ fs3.writeFileSync(filePath, JSON.stringify(body, null, 2));
1583
1688
  jsonResponse(res, 200, { ok: true, fileName });
1584
1689
  return;
1585
1690
  }
@@ -1588,8 +1693,8 @@ ${lines.join("\n")}
1588
1693
  jsonResponse(res, 403, { error: "Cannot delete the default colors and type" });
1589
1694
  return;
1590
1695
  }
1591
- if (fs2.existsSync(filePath)) {
1592
- fs2.unlinkSync(filePath);
1696
+ if (fs3.existsSync(filePath)) {
1697
+ fs3.unlinkSync(filePath);
1593
1698
  } else if (colorsAndTypeResource.existingPath(fileName)) {
1594
1699
  jsonResponse(res, 403, {
1595
1700
  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 +1781,16 @@ ${lines.join("\n")}
1676
1781
  body.component = comp;
1677
1782
  body.name = body.name || name;
1678
1783
  body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1679
- if (fs2.existsSync(configPath)) {
1784
+ if (fs3.existsSync(configPath)) {
1680
1785
  try {
1681
- const existing = JSON.parse(fs2.readFileSync(configPath, "utf-8"));
1786
+ const existing = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
1682
1787
  if (existing.createdAt) body.createdAt = existing.createdAt;
1683
1788
  } catch {
1684
1789
  }
1685
1790
  }
1686
1791
  if (!body.createdAt) body.createdAt = body.updatedAt;
1687
1792
  r.ensureDir();
1688
- fs2.writeFileSync(configPath, JSON.stringify(body, null, 2));
1793
+ fs3.writeFileSync(configPath, JSON.stringify(body, null, 2));
1689
1794
  jsonResponse(res, 200, { ok: true, fileName: name });
1690
1795
  return;
1691
1796
  }
@@ -1694,7 +1799,7 @@ ${lines.join("\n")}
1694
1799
  jsonResponse(res, 403, { error: "Cannot delete default config" });
1695
1800
  return;
1696
1801
  }
1697
- if (fs2.existsSync(configPath)) fs2.unlinkSync(configPath);
1802
+ if (fs3.existsSync(configPath)) fs3.unlinkSync(configPath);
1698
1803
  jsonResponse(res, 200, { ok: true });
1699
1804
  return;
1700
1805
  }
@@ -1702,7 +1807,7 @@ ${lines.join("\n")}
1702
1807
  async function handleListComponentConfigs({ params, res }) {
1703
1808
  const [comp] = params;
1704
1809
  const r = componentResource(comp);
1705
- if (!fs2.existsSync(r.dir)) {
1810
+ if (!fs3.existsSync(r.dir)) {
1706
1811
  jsonResponse(res, 404, { error: "Component not found" });
1707
1812
  return;
1708
1813
  }
@@ -1790,9 +1895,9 @@ ${lines.join("\n")}
1790
1895
  const body = JSON.parse(await readBody(req));
1791
1896
  const { theme } = normalizeTheme(body, diskThemeResolvers);
1792
1897
  theme.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1793
- if (fs2.existsSync(filePath)) {
1898
+ if (fs3.existsSync(filePath)) {
1794
1899
  try {
1795
- const existing = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
1900
+ const existing = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
1796
1901
  if (existing.createdAt) theme.createdAt = existing.createdAt;
1797
1902
  } catch {
1798
1903
  }
@@ -1814,7 +1919,7 @@ ${lines.join("\n")}
1814
1919
  });
1815
1920
  return;
1816
1921
  }
1817
- if (fs2.existsSync(filePath)) {
1922
+ if (fs3.existsSync(filePath)) {
1818
1923
  const deletingActive = themesResource.getActiveName() === fileName;
1819
1924
  const liveColors = deletingActive ? resolveLiveColorsAndType(activeTheme()) : null;
1820
1925
  const liveComponents = {};
@@ -1824,7 +1929,7 @@ ${lines.join("\n")}
1824
1929
  if (resolved) liveComponents[comp] = resolved.data;
1825
1930
  }
1826
1931
  }
1827
- fs2.unlinkSync(filePath);
1932
+ fs3.unlinkSync(filePath);
1828
1933
  if (themesResource.existingPath(fileName) === null && deletingActive) {
1829
1934
  themesResource.setActiveName("default");
1830
1935
  }
@@ -1913,7 +2018,38 @@ data: ${JSON.stringify(state)}
1913
2018
 
1914
2019
  `);
1915
2020
  });
1916
- req.on("close", unsubscribe);
2021
+ eventClients.add(res);
2022
+ req.on("close", () => {
2023
+ unsubscribe();
2024
+ eventClients.delete(res);
2025
+ });
2026
+ }
2027
+ const eventClients = /* @__PURE__ */ new Set();
2028
+ const a11yRuns = a11yRunServer({
2029
+ root: process.cwd(),
2030
+ reportFile: A11Y_REPORT_FILE,
2031
+ runCli: spawnCli(PACKAGE_CLI_PATH),
2032
+ broadcast: (report) => {
2033
+ const frame = `event: ${A11Y_EVENT}
2034
+ data: ${JSON.stringify(report)}
2035
+
2036
+ `;
2037
+ for (const client of eventClients) client.write(frame);
2038
+ }
2039
+ });
2040
+ async function handleStartA11yRun({ req, res }) {
2041
+ let body;
2042
+ try {
2043
+ body = JSON.parse(await readBody(req));
2044
+ } catch {
2045
+ body = void 0;
2046
+ }
2047
+ const answer = a11yRuns.start(body);
2048
+ jsonResponse(res, answer.status, answer.body);
2049
+ }
2050
+ async function handleGetA11yReport({ res }) {
2051
+ const answer = a11yRuns.report();
2052
+ jsonResponse(res, answer.status, answer.body);
1917
2053
  }
1918
2054
  async function handleExportTheme({ params, res }) {
1919
2055
  const [fileName] = params;
@@ -2074,22 +2210,22 @@ data: ${JSON.stringify(state)}
2074
2210
  }
2075
2211
  body.name = String(body.name || fileName);
2076
2212
  body.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2077
- if (fs2.existsSync(filePath)) {
2213
+ if (fs3.existsSync(filePath)) {
2078
2214
  try {
2079
- const existing = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
2215
+ const existing = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
2080
2216
  if (existing.createdAt) body.createdAt = existing.createdAt;
2081
2217
  } catch {
2082
2218
  }
2083
2219
  }
2084
2220
  if (!body.createdAt) body.createdAt = body.updatedAt;
2085
2221
  sketchStylesResource.ensureDir();
2086
- fs2.writeFileSync(filePath, JSON.stringify(body, null, 2));
2222
+ fs3.writeFileSync(filePath, JSON.stringify(body, null, 2));
2087
2223
  jsonResponse(res, 200, { ok: true, fileName });
2088
2224
  return;
2089
2225
  }
2090
2226
  if (req.method === "DELETE") {
2091
- if (fs2.existsSync(filePath)) {
2092
- fs2.unlinkSync(filePath);
2227
+ if (fs3.existsSync(filePath)) {
2228
+ fs3.unlinkSync(filePath);
2093
2229
  } else if (sketchStylesResource.existingPath(fileName)) {
2094
2230
  jsonResponse(res, 403, {
2095
2231
  error: "Cannot delete a sketchstyle shipped with the package. Saving it creates a local copy; deleting that copy restores the shipped version.",
@@ -2186,12 +2322,20 @@ data: ${JSON.stringify(state)}
2186
2322
  { method: "DELETE", pattern: SKETCH_STYLE_BY_NAME_REGEX, handler: handleSketchStyleByName },
2187
2323
  { method: "POST", pattern: SKETCH_STYLE_BY_NAME_REGEX, handler: methodNotAllowed },
2188
2324
  { method: "GET", pattern: EVENTS_ROUTE, handler: handleEvents },
2325
+ { method: "POST", pattern: A11Y_RUN_ROUTE, handler: handleStartA11yRun },
2326
+ { method: "GET", pattern: A11Y_RUN_ROUTE, handler: methodNotAllowed },
2327
+ { method: "PUT", pattern: A11Y_RUN_ROUTE, handler: methodNotAllowed },
2328
+ { method: "DELETE", pattern: A11Y_RUN_ROUTE, handler: methodNotAllowed },
2329
+ { method: "GET", pattern: A11Y_REPORT_ROUTE, handler: handleGetA11yReport },
2330
+ { method: "PUT", pattern: A11Y_REPORT_ROUTE, handler: methodNotAllowed },
2331
+ { method: "POST", pattern: A11Y_REPORT_ROUTE, handler: methodNotAllowed },
2332
+ { method: "DELETE", pattern: A11Y_REPORT_ROUTE, handler: methodNotAllowed },
2189
2333
  { method: "PUT", pattern: PRODUCTION_ROUTE, handler: handleAdoptTheme },
2190
2334
  { method: "GET", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed },
2191
2335
  { method: "POST", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed },
2192
2336
  { method: "DELETE", pattern: PRODUCTION_ROUTE, handler: methodNotAllowed }
2193
2337
  ];
2194
- const isEditorOwnedJson = (file) => file.endsWith(".json") && path3.resolve(file).startsWith(dataDirs.dataDir + path3.sep);
2338
+ const isEditorOwnedJson = (file) => file.endsWith(".json") && path4.resolve(file).startsWith(dataDirs.dataDir + path4.sep);
2195
2339
  let buildLogger = null;
2196
2340
  let buildChecked = false;
2197
2341
  let productionCssLogger = null;
@@ -2241,7 +2385,7 @@ data: ${JSON.stringify(state)}
2241
2385
  const { productionPointerHeld } = ensureThemesDir((msg) => server.config.logger.warn(msg));
2242
2386
  const openTheme = activeTheme();
2243
2387
  if (openTheme) pruneMatchingWorking(openTheme);
2244
- if (!productionPointerHeld || !fs2.existsSync(GENERATED_CSS_PATH)) regenerateTokensCss();
2388
+ if (!productionPointerHeld || !fs3.existsSync(GENERATED_CSS_PATH)) regenerateTokensCss();
2245
2389
  if (opts.autoMigrate && !ISOLATED_DATA_DIR) {
2246
2390
  autoMigrateAdditive((msg) => server.config.logger.info(msg));
2247
2391
  }
@@ -2264,10 +2408,11 @@ data: ${JSON.stringify(state)}
2264
2408
  const handled = await dispatch(req, res, routes);
2265
2409
  if (!handled) next();
2266
2410
  });
2411
+ server.httpServer?.once("close", () => a11yRuns.stop());
2267
2412
  },
2268
2413
  handleHotUpdate(ctx) {
2269
2414
  if (legacyLayout) return;
2270
- const normalized = path3.resolve(ctx.file);
2415
+ const normalized = path4.resolve(ctx.file);
2271
2416
  if (!COMPONENTS_SCAN_DIRS.some((d) => normalized.startsWith(d))) return;
2272
2417
  if (!normalized.endsWith(".svelte")) return;
2273
2418
  if (!isThemeAwareComponent(normalized)) return;