@1agh/maude 0.24.0 → 0.26.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 (84) hide show
  1. package/cli/commands/design.mjs +5 -0
  2. package/cli/lib/design-link.mjs +13 -6
  3. package/cli/lib/gitignore-block.mjs +1 -0
  4. package/cli/lib/gitignore-block.test.mjs +1 -0
  5. package/package.json +8 -8
  6. package/plugins/design/dependencies.json +30 -2
  7. package/plugins/design/dev-server/annotations-context-toolbar.tsx +481 -78
  8. package/plugins/design/dev-server/annotations-layer.tsx +817 -170
  9. package/plugins/design/dev-server/api.ts +15 -1
  10. package/plugins/design/dev-server/bin/_enumerate-artboards-playwright.mjs +2 -2
  11. package/plugins/design/dev-server/bin/_html-playwright.mjs +2 -2
  12. package/plugins/design/dev-server/bin/_pdf-playwright.mjs +25 -8
  13. package/plugins/design/dev-server/bin/_png-playwright.mjs +20 -20
  14. package/plugins/design/dev-server/bin/_pptx-playwright.mjs +2 -2
  15. package/plugins/design/dev-server/bin/_pw-launch.mjs +61 -0
  16. package/plugins/design/dev-server/bin/_screenshot-playwright.mjs +2 -2
  17. package/plugins/design/dev-server/bin/_svg-optimize.mjs +35 -0
  18. package/plugins/design/dev-server/bin/_svg-playwright.mjs +125 -19
  19. package/plugins/design/dev-server/bin/draw-build.sh +48 -0
  20. package/plugins/design/dev-server/bin/draw-proof.sh +129 -0
  21. package/plugins/design/dev-server/bin/smoke.sh +114 -12
  22. package/plugins/design/dev-server/bin/svg-optimize.sh +24 -0
  23. package/plugins/design/dev-server/canvas-arrowheads.ts +220 -0
  24. package/plugins/design/dev-server/canvas-comment-mount.tsx +8 -24
  25. package/plugins/design/dev-server/canvas-cursors.ts +130 -82
  26. package/plugins/design/dev-server/canvas-icons.tsx +169 -0
  27. package/plugins/design/dev-server/canvas-lib.tsx +110 -0
  28. package/plugins/design/dev-server/canvas-shell.tsx +113 -89
  29. package/plugins/design/dev-server/client/app.jsx +1084 -417
  30. package/plugins/design/dev-server/config.schema.json +10 -0
  31. package/plugins/design/dev-server/context.ts +9 -0
  32. package/plugins/design/dev-server/dist/client.bundle.js +242 -20
  33. package/plugins/design/dev-server/dist/comment-mount.js +40 -62
  34. package/plugins/design/dev-server/draw/brush.ts +639 -0
  35. package/plugins/design/dev-server/draw/composition.ts +229 -0
  36. package/plugins/design/dev-server/draw/geometry.ts +578 -0
  37. package/plugins/design/dev-server/draw/index.ts +28 -0
  38. package/plugins/design/dev-server/draw/layout.ts +260 -0
  39. package/plugins/design/dev-server/draw/optimize.ts +65 -0
  40. package/plugins/design/dev-server/draw/palette.ts +417 -0
  41. package/plugins/design/dev-server/draw/primitives.ts +643 -0
  42. package/plugins/design/dev-server/draw/serialize.ts +458 -0
  43. package/plugins/design/dev-server/draw/test/brush.test.ts +213 -0
  44. package/plugins/design/dev-server/draw/test/composition.test.ts +141 -0
  45. package/plugins/design/dev-server/draw/test/geometry.test.ts +199 -0
  46. package/plugins/design/dev-server/draw/test/gradient.test.ts +167 -0
  47. package/plugins/design/dev-server/draw/test/layout.test.ts +120 -0
  48. package/plugins/design/dev-server/draw/test/optimize.test.ts +40 -0
  49. package/plugins/design/dev-server/draw/test/palette.test.ts +123 -0
  50. package/plugins/design/dev-server/draw/test/primitives.test.ts +129 -0
  51. package/plugins/design/dev-server/draw/test/serialize.test.ts +105 -0
  52. package/plugins/design/dev-server/export-dialog.tsx +189 -1
  53. package/plugins/design/dev-server/exporters/html.ts +4 -1
  54. package/plugins/design/dev-server/exporters/index.ts +4 -1
  55. package/plugins/design/dev-server/exporters/pdf.ts +7 -1
  56. package/plugins/design/dev-server/exporters/png.ts +21 -2
  57. package/plugins/design/dev-server/exporters/pptx.ts +330 -201
  58. package/plugins/design/dev-server/exporters/scope.ts +107 -11
  59. package/plugins/design/dev-server/exporters/svg.ts +4 -1
  60. package/plugins/design/dev-server/http.ts +40 -9
  61. package/plugins/design/dev-server/input-router.tsx +9 -2
  62. package/plugins/design/dev-server/sync/index.ts +73 -17
  63. package/plugins/design/dev-server/test/annotations-draw-modifiers.test.ts +206 -0
  64. package/plugins/design/dev-server/test/annotations-layer.test.ts +83 -3
  65. package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +243 -0
  66. package/plugins/design/dev-server/test/canvas-cursors.test.ts +95 -6
  67. package/plugins/design/dev-server/test/canvas-route.test.ts +4 -1
  68. package/plugins/design/dev-server/test/exporters/png.test.ts +24 -1
  69. package/plugins/design/dev-server/test/exporters/pptx-deck.test.ts +112 -0
  70. package/plugins/design/dev-server/test/exporters/pw-launch.test.ts +49 -0
  71. package/plugins/design/dev-server/test/exporters/scope.test.ts +0 -0
  72. package/plugins/design/dev-server/test/fixtures/phase-21-annotations.svg +1 -0
  73. package/plugins/design/dev-server/test/input-router.test.ts +11 -4
  74. package/plugins/design/dev-server/test/sanitize-annotation-svg.test.ts +32 -0
  75. package/plugins/design/dev-server/test/sync-runtime.test.ts +52 -0
  76. package/plugins/design/dev-server/test/use-annotation-resize.test.ts +200 -0
  77. package/plugins/design/dev-server/test/use-tool-mode.test.tsx +9 -11
  78. package/plugins/design/dev-server/tool-palette.tsx +140 -11
  79. package/plugins/design/dev-server/tsconfig.json +8 -1
  80. package/plugins/design/dev-server/use-annotation-resize.tsx +208 -61
  81. package/plugins/design/dev-server/use-tool-mode.tsx +55 -9
  82. package/plugins/design/templates/_shell.html +36 -9
  83. package/plugins/design/templates/design-system-inspiration/_MAPPING.md +15 -0
  84. package/plugins/design/templates/design-system-inspiration/core/config.json.tpl +1 -0
@@ -253,53 +253,19 @@ const HALO_CSS = `
253
253
  transition: none !important;
254
254
  }
255
255
  }
256
- /*
257
- * Force tool cursor across the canvas tree in comment / hand modes. Without
258
- * !important on every descendant, buttons and links with their own cursor
259
- * declaration would flip the cursor away from the tool affordance the moment
260
- * the user hovers an interactive element — wrong signal when native
261
- * interactions are suppressed by the router anyway.
262
- */
263
- /* T22 — per-tool SVG cursors. Each cursor is a 16 × 16 SVG data-URI with a
264
- declared hotspot. The crosshair / cell / grab fallbacks remain in the
265
- chain so older browsers still get a recognisable affordance. SVGs are
266
- utf-8 encoded inline (Chromium / Safari 16+ / Firefox 117+ all accept). */
267
- .dc-canvas {
268
- --cursor-pen: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path d='M14 1.5 L9 6.5 L2.5 13.5 L1.5 14.5 L2 13 L8.5 6 L13.5 1.5 Z' fill='%23111' stroke='white' stroke-width='0.7' stroke-linejoin='round'/></svg>") 2 14;
269
- --cursor-rect: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><line x1='0.5' y1='8' x2='15.5' y2='8' stroke='%23111' stroke-width='1'/><line x1='8' y1='0.5' x2='8' y2='15.5' stroke='%23111' stroke-width='1'/><rect x='10.5' y='10.5' width='4' height='3' fill='white' stroke='%23111' stroke-width='1'/></svg>") 8 8;
270
- --cursor-ellipse: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><line x1='0.5' y1='8' x2='15.5' y2='8' stroke='%23111' stroke-width='1'/><line x1='8' y1='0.5' x2='8' y2='15.5' stroke='%23111' stroke-width='1'/><ellipse cx='12.5' cy='12' rx='2.5' ry='1.5' fill='white' stroke='%23111' stroke-width='1'/></svg>") 8 8;
271
- --cursor-arrow: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><line x1='0.5' y1='8' x2='15.5' y2='8' stroke='%23111' stroke-width='1'/><line x1='8' y1='0.5' x2='8' y2='15.5' stroke='%23111' stroke-width='1'/><path d='M10 13 L15 13 M12.5 11 L15 13 L12.5 15' fill='none' stroke='%23111' stroke-width='1' stroke-linecap='round' stroke-linejoin='round'/></svg>") 8 8;
272
- --cursor-eraser: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><rect x='3' y='9' width='8' height='5' rx='1' fill='%23ffb3da' stroke='%23111' stroke-width='1' transform='rotate(-20 7 11.5)'/><rect x='3' y='9' width='8' height='2' fill='%23d63b6e' transform='rotate(-20 7 10)' opacity='0.6'/></svg>") 8 14;
273
- --cursor-comment: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'><path d='M3 3 L14 3 L14 11 L8 11 L4 14 L4 11 L3 11 Z' fill='white' stroke='%23111' stroke-width='1' stroke-linejoin='round'/></svg>") 4 4;
274
- }
275
- .dc-canvas[data-active-tool="comment"],
276
- .dc-canvas[data-active-tool="comment"] * {
277
- cursor: var(--cursor-comment), crosshair !important;
278
- }
279
- .dc-canvas[data-active-tool="pen"],
280
- .dc-canvas[data-active-tool="pen"] * {
281
- cursor: var(--cursor-pen), crosshair !important;
282
- }
283
- .dc-canvas[data-active-tool="rect"],
284
- .dc-canvas[data-active-tool="rect"] * {
285
- cursor: var(--cursor-rect), crosshair !important;
286
- }
287
- .dc-canvas[data-active-tool="ellipse"],
288
- .dc-canvas[data-active-tool="ellipse"] * {
289
- cursor: var(--cursor-ellipse), crosshair !important;
290
- }
291
- .dc-canvas[data-active-tool="arrow"],
292
- .dc-canvas[data-active-tool="arrow"] * {
293
- cursor: var(--cursor-arrow), crosshair !important;
294
- }
295
- .dc-canvas[data-active-tool="hand"],
296
- .dc-canvas[data-active-tool="hand"] * {
297
- cursor: grab !important;
298
- }
299
- .dc-canvas[data-active-tool="eraser"],
300
- .dc-canvas[data-active-tool="eraser"] * {
301
- cursor: var(--cursor-eraser), cell !important;
302
- }
256
+ /* Phase 24 — per-tool cursors are owned SOLELY by use-tool-mode.tsx, which
257
+ injects ONE unified Kenney-glyph rule ('* { cursor: <tool> !important }')
258
+ into the same document. The old T22 per-tool '.dc-canvas[data-active-tool=…]'
259
+ rules used to live here, but their higher specificity ('.class[attr] *'
260
+ beats '*') silently SHADOWED the new cursors for comment/pen/arrow/hand/
261
+ eraser they kept showing the stale 16px glyphs (or their native fallback)
262
+ while move/shape/sticky/text (which had no shadowing rule) correctly showed
263
+ the Kenney set. Removing them lets the single source of truth win for every
264
+ tool. 'data-active-tool' is still set on '.dc-canvas' (see CanvasCore) and
265
+ keyed by canvas-lib's move-mode label cursor + use-cursor-modifiers those
266
+ stay. See DDR-067 / Phase 24. NOTE: keep this comment backtick-free — it
267
+ lives inside the HALO_CSS template literal and a stray backtick closes it
268
+ (bun parse fail, DDR-067 §6). */
303
269
 
304
270
  /* T31 — Level of detail. Below 0.35 zoom we hide pre-attentive chrome that
305
271
  becomes visual noise (corner ticks, distance pills, active-artboard ring,
@@ -352,12 +318,12 @@ export function CanvasShell({
352
318
  }) {
353
319
  ensureHaloStyles();
354
320
 
355
- // D9"Follow chrome" artboards. When the chrome theme flips (the theme
356
- // postMessage handler in CanvasRouter updates `data-maude-theme` on <html>),
357
- // re-point every artboard the user marked as a follower at the new theme.
358
- // A single observer per canvas (cleaned up on unmount) — no per-artboard
359
- // listeners to leak. The override itself is a CSS rule keyed by data-dc-screen
360
- // (see setArtboardTheme), so it survives React re-renders without flicker.
321
+ // theme-default-followevery un-pinned artboard tracks the chrome theme.
322
+ // When it flips (the theme postMessage handler in CanvasRouter updates
323
+ // `data-maude-theme` on <html>), re-theme them. A single observer per canvas
324
+ // (cleaned up on unmount) — no per-artboard listeners to leak. The rule is a
325
+ // CSS block keyed by data-dc-screen (see rebuildArtboardThemeStyle), so it
326
+ // survives React re-renders without flicker.
361
327
  useEffect(() => {
362
328
  if (typeof MutationObserver === 'undefined' || typeof document === 'undefined') return;
363
329
  const obs = new MutationObserver(() => applyArtboardFollowers());
@@ -365,7 +331,23 @@ export function CanvasShell({
365
331
  attributes: true,
366
332
  attributeFilter: ['data-maude-theme'],
367
333
  });
368
- return () => obs.disconnect();
334
+ // Initial follow — theme every un-pinned artboard once the DS stylesheet has
335
+ // parsed. The observer only fires on a `data-maude-theme` CHANGE, which may
336
+ // not happen on first paint (the chrome theme can already equal the canvas
337
+ // default), so first paint needs its own trigger. `window 'load'` guarantees
338
+ // every <link>/<style> sheet is parsed → collectThemeDeclarations sees the
339
+ // DS tokens. Runs immediately if the document is already complete.
340
+ let onLoad: (() => void) | null = null;
341
+ if (document.readyState === 'complete') {
342
+ applyArtboardFollowers();
343
+ } else {
344
+ onLoad = () => applyArtboardFollowers();
345
+ window.addEventListener('load', onLoad, { once: true });
346
+ }
347
+ return () => {
348
+ obs.disconnect();
349
+ if (onLoad) window.removeEventListener('load', onLoad);
350
+ };
369
351
  }, []);
370
352
 
371
353
  // ToolProvider is mounted by DesignCanvas one level up (so the viewport
@@ -715,11 +697,14 @@ function CanvasCore({
715
697
  }
716
698
 
717
699
  // ─────────────────────────────────────────────────────────────────────────────
718
- // Per-artboard theme override (D9). The canvas-shell CHROME follows the Maude
719
- // theme via `data-maude-theme`; ARTBOARDS keep their DS theme by default. This
720
- // block backs the right-click `Theme DS default / Light / Dark / Follow
721
- // chrome` submenu it re-themes a single artboard by stamping its
722
- // `.dc-artboard` <article> with the DS's theme-wrapper convention.
700
+ // Artboard theme (D9 + theme-default-follow, 2026-05-31). The status-bar theme
701
+ // toggle (vpravo dole) is the SINGLE SOURCE OF TRUTH: the canvas-shell chrome
702
+ // AND every artboard follow it by default via `data-maude-theme`. The right-
703
+ // click `Theme DS default / Light / Dark / Follow chrome` submenu PINS one
704
+ // artboard to override that default — re-themed by stamping its content wrapper
705
+ // with the DS's theme-wrapper convention. (Before this date the default was the
706
+ // inverse — artboards kept their own DS theme and following was opt-in — which
707
+ // produced the recurring "canvas dark / chrome light" mismatch.)
723
708
  //
724
709
  // DS theme-wrapper conventions vary (`.mdcc[data-theme]`, `.app[data-theme]`,
725
710
  // bare `[data-theme]`, …) and there's no reliable config flag, so we DETECT it
@@ -768,7 +753,12 @@ function detectDsThemeSupport(): DsThemeSupport {
768
753
  }
769
754
  }
770
755
  document.body.removeChild(host);
771
- _dsThemeSupport = found;
756
+ // Cache only a POSITIVE detection. With follow-chrome now the artboard
757
+ // default, this can run on first paint — before the DS stylesheet has
758
+ // parsed — where the probe reads empty tokens and wrongly reports
759
+ // unsupported. Caching that would permanently disable theming; instead
760
+ // re-probe until support is confirmed (or the DS genuinely has one theme).
761
+ if (found.supported) _dsThemeSupport = found;
772
762
  return found;
773
763
  } catch {
774
764
  return fallback;
@@ -830,12 +820,20 @@ function collectThemeDeclarations(theme: 'light' | 'dark'): string {
830
820
  }
831
821
  }
832
822
  }
833
- _themeDecls[theme] = decls;
823
+ // Cache only a NON-EMPTY result — same first-paint race as detectDsThemeSupport:
824
+ // a scan that runs before the DS stylesheet parses finds nothing, and caching
825
+ // '' would freeze the artboard un-themed forever. Leave it uncached so the
826
+ // post-load rebuild (window 'load' / the theme MutationObserver) repopulates it.
827
+ if (decls) _themeDecls[theme] = decls;
834
828
  return decls;
835
829
  }
836
830
 
837
- const _artboardThemes = new Map<string, 'light' | 'dark'>();
838
- const _artboardFollowers = new Set<string>();
831
+ // Every artboard FOLLOWS the chrome theme by default (the status-bar toggle is
832
+ // the source of truth). A per-artboard pin OVERRIDES that: 'ds' = the design
833
+ // system's native theme (opt out of following entirely), 'light' / 'dark' = a
834
+ // fixed theme. Absence from this map = follow chrome.
835
+ type ArtboardPin = 'ds' | 'light' | 'dark';
836
+ const _artboardPins = new Map<string, ArtboardPin>();
839
837
 
840
838
  function cssEsc(v: string): string {
841
839
  return typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(v) : v;
@@ -854,6 +852,21 @@ function artboardScopeSelector(screenId: string): string {
854
852
  return `${base} [data-theme],${base}[data-theme]`;
855
853
  }
856
854
 
855
+ // Like artboardScopeSelector but matches EVERY artboard except the pinned ones —
856
+ // backs the default-follow rule, which themes all un-pinned artboards in one
857
+ // declaration. Pinned ids are :not()-excluded so the per-screen pin rule never
858
+ // has to out-specify this global one (they target disjoint elements).
859
+ function globalArtboardScopeSelector(excludeIds: string[]): string {
860
+ const { wrapperClass } = detectDsThemeSupport();
861
+ const not = excludeIds.map((id) => `:not([data-dc-screen="${cssEsc(id)}"])`).join('');
862
+ const base = `[data-dc-screen]${not}`;
863
+ if (wrapperClass) {
864
+ const c = cssEsc(wrapperClass);
865
+ return `${base} .${c},${base}.${c}`;
866
+ }
867
+ return `${base} [data-theme],${base}[data-theme]`;
868
+ }
869
+
857
870
  function rebuildArtboardThemeStyle(): void {
858
871
  if (typeof document === 'undefined') return;
859
872
  let el = document.getElementById('dc-artboard-theme-css') as HTMLStyleElement | null;
@@ -863,8 +876,20 @@ function rebuildArtboardThemeStyle(): void {
863
876
  document.head.appendChild(el);
864
877
  }
865
878
  let css = '';
866
- for (const [screenId, theme] of _artboardThemes) {
867
- const decls = collectThemeDeclarations(theme);
879
+ // (1) Default every un-pinned artboard follows the chrome theme. Pinned
880
+ // artboards are :not()-excluded so this rule never competes with their
881
+ // per-screen rule below. When the DS doesn't define the chrome theme's tokens
882
+ // (collectThemeDeclarations → ''), nothing is emitted and the artboard keeps
883
+ // its native DS theme — there's no alternate palette to follow to.
884
+ const pinnedIds = [..._artboardPins.keys()];
885
+ const followDecls = collectThemeDeclarations(currentChromeTheme());
886
+ if (followDecls) css += `${globalArtboardScopeSelector(pinnedIds)}{${followDecls}}\n`;
887
+ // (2) Explicit per-artboard pins. 'ds' emits nothing (the :not() above already
888
+ // released that artboard back to its native DS theme); 'light' / 'dark'
889
+ // re-declare the chosen theme's tokens scoped to that one artboard.
890
+ for (const [screenId, pin] of _artboardPins) {
891
+ if (pin === 'ds') continue;
892
+ const decls = collectThemeDeclarations(pin);
868
893
  if (decls) css += `${artboardScopeSelector(screenId)}{${decls}}\n`;
869
894
  }
870
895
  el.textContent = css;
@@ -877,28 +902,26 @@ function currentChromeTheme(): 'light' | 'dark' {
877
902
  }
878
903
 
879
904
  /**
880
- * Set (or clear) a single artboard's theme override, keyed by its stable
881
- * `data-dc-screen` id. `theme === null` remove the override (DS default).
882
- * `follow === true` mirror the live chrome theme and keep tracking toggles.
905
+ * Pin a single artboard's theme (keyed by its stable `data-dc-screen` id), or
906
+ * clear the pin so it returns to the default following the chrome theme.
907
+ * 'follow'remove the pin (default: track the chrome theme toggle)
908
+ * 'ds' → pin to the design system's native theme (stop following)
909
+ * 'light' / 'dark' → pin to a fixed theme
883
910
  */
884
- function setArtboardTheme(screenId: string, theme: 'light' | 'dark' | null, follow = false): void {
885
- _artboardFollowers.delete(screenId);
886
- if (follow) {
887
- _artboardFollowers.add(screenId);
888
- _artboardThemes.set(screenId, currentChromeTheme());
889
- } else if (theme === null) {
890
- _artboardThemes.delete(screenId);
891
- } else {
892
- _artboardThemes.set(screenId, theme);
893
- }
911
+ function setArtboardTheme(screenId: string, mode: 'follow' | ArtboardPin): void {
912
+ if (mode === 'follow') _artboardPins.delete(screenId);
913
+ else _artboardPins.set(screenId, mode);
894
914
  rebuildArtboardThemeStyle();
895
915
  }
896
916
 
897
- /** Re-point every "Follow chrome" artboard at the current chrome theme. */
917
+ /**
918
+ * Re-theme every following artboard when the chrome theme flips. The
919
+ * default-follow rule in rebuildArtboardThemeStyle reads currentChromeTheme()
920
+ * live, so a plain rebuild re-points every un-pinned artboard at the new theme
921
+ * in one shot. Kept under the original name so the MutationObserver call site
922
+ * and the initial 'load' trigger stay untouched.
923
+ */
898
924
  function applyArtboardFollowers(): void {
899
- if (_artboardFollowers.size === 0) return;
900
- const t = currentChromeTheme();
901
- for (const id of _artboardFollowers) _artboardThemes.set(id, t);
902
925
  rebuildArtboardThemeStyle();
903
926
  }
904
927
 
@@ -986,10 +1009,11 @@ function buildRegistry(deps: {
986
1009
  },
987
1010
  });
988
1011
 
989
- // D9 — per-artboard theme override, keyed by the stable `data-dc-screen` id
990
- // (target.artboardId) via an injected stylesheet (see setArtboardTheme). The
991
- // DS-supports-both probe gates the explicit Light/Dark entries; "DS default"
992
- // + "Follow chrome" are always available.
1012
+ // Per-artboard theme PIN, keyed by the stable `data-dc-screen` id
1013
+ // (target.artboardId) via an injected stylesheet (see setArtboardTheme).
1014
+ // Following the chrome theme is the default; this submenu overrides it for one
1015
+ // artboard. The DS-supports-both probe gates Light / Dark / "Follow chrome"
1016
+ // (all no-ops on a single-theme DS); only "DS default" is always meaningful.
993
1017
  const themeSupport = detectDsThemeSupport();
994
1018
  const themeHint = 'This design system defines only one theme';
995
1019
  const themeItem: MenuItem = {
@@ -1003,7 +1027,7 @@ function buildRegistry(deps: {
1003
1027
  id: 'theme-ds-default',
1004
1028
  label: 'DS default',
1005
1029
  onSelect: (target) => {
1006
- if (target.artboardId) setArtboardTheme(target.artboardId, null);
1030
+ if (target.artboardId) setArtboardTheme(target.artboardId, 'ds');
1007
1031
  },
1008
1032
  },
1009
1033
  {
@@ -1026,11 +1050,11 @@ function buildRegistry(deps: {
1026
1050
  },
1027
1051
  {
1028
1052
  id: 'theme-follow',
1029
- label: 'Follow chrome',
1053
+ label: 'Follow chrome (default)',
1030
1054
  disabled: !themeSupport.supported,
1031
1055
  disabledHint: themeHint,
1032
1056
  onSelect: (target) => {
1033
- if (target.artboardId) setArtboardTheme(target.artboardId, null, true);
1057
+ if (target.artboardId) setArtboardTheme(target.artboardId, 'follow');
1034
1058
  },
1035
1059
  },
1036
1060
  ],