@1agh/maude 0.23.0 → 0.25.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 (127) hide show
  1. package/README.md +34 -1
  2. package/cli/bin/maude.mjs +3 -0
  3. package/cli/commands/cache.mjs +181 -0
  4. package/cli/commands/cache.test.mjs +166 -0
  5. package/cli/commands/design-link.test.mjs +32 -2
  6. package/cli/commands/design.mjs +95 -4
  7. package/cli/commands/design.test.mjs +56 -0
  8. package/cli/commands/help.mjs +34 -0
  9. package/cli/commands/hub.mjs +255 -30
  10. package/cli/commands/hub.test.mjs +126 -2
  11. package/cli/commands/init.mjs +3 -0
  12. package/cli/commands/preflight.mjs +11 -0
  13. package/cli/commands/preflight.test.mjs +46 -0
  14. package/cli/commands/scenario-report.mjs +45 -0
  15. package/cli/lib/cache.mjs +407 -0
  16. package/cli/lib/cache.test.mjs +303 -0
  17. package/cli/lib/design-link.mjs +74 -10
  18. package/cli/lib/flow-design-integration.test.mjs +165 -0
  19. package/cli/lib/gitignore-block.mjs +90 -0
  20. package/cli/lib/gitignore-block.test.mjs +123 -0
  21. package/cli/lib/plugin-cli-reachability.test.mjs +56 -0
  22. package/cli/lib/preflight.mjs +41 -10
  23. package/package.json +8 -8
  24. package/plugins/design/dependencies.json +30 -2
  25. package/plugins/design/dev-server/ai-banner.tsx +2 -2
  26. package/plugins/design/dev-server/annotations-context-toolbar.tsx +763 -123
  27. package/plugins/design/dev-server/annotations-layer.tsx +1624 -208
  28. package/plugins/design/dev-server/api.ts +123 -4
  29. package/plugins/design/dev-server/bin/_enumerate-artboards-playwright.mjs +2 -2
  30. package/plugins/design/dev-server/bin/_html-playwright.mjs +2 -2
  31. package/plugins/design/dev-server/bin/_pdf-playwright.mjs +25 -8
  32. package/plugins/design/dev-server/bin/_png-playwright.mjs +20 -20
  33. package/plugins/design/dev-server/bin/_pptx-playwright.mjs +2 -2
  34. package/plugins/design/dev-server/bin/_pw-launch.mjs +61 -0
  35. package/plugins/design/dev-server/bin/_screenshot-playwright.mjs +2 -2
  36. package/plugins/design/dev-server/bin/_svg-playwright.mjs +125 -19
  37. package/plugins/design/dev-server/bin/preflight.sh +21 -10
  38. package/plugins/design/dev-server/bin/prep.sh +211 -0
  39. package/plugins/design/dev-server/bin/scenario-report.mjs +209 -0
  40. package/plugins/design/dev-server/bin/screenshot.sh +18 -1
  41. package/plugins/design/dev-server/bin/smoke.sh +208 -23
  42. package/plugins/design/dev-server/canvas-arrowheads.ts +220 -0
  43. package/plugins/design/dev-server/canvas-comment-mount.tsx +8 -24
  44. package/plugins/design/dev-server/canvas-cursors.ts +173 -0
  45. package/plugins/design/dev-server/canvas-icons.tsx +299 -0
  46. package/plugins/design/dev-server/canvas-lib.tsx +25 -21
  47. package/plugins/design/dev-server/canvas-meta.schema.json +20 -0
  48. package/plugins/design/dev-server/canvas-shell.tsx +404 -66
  49. package/plugins/design/dev-server/client/app.jsx +1231 -416
  50. package/plugins/design/dev-server/client/styles/3-shell.css +10 -0
  51. package/plugins/design/dev-server/collab/index.ts +87 -9
  52. package/plugins/design/dev-server/collab/persistence.ts +34 -3
  53. package/plugins/design/dev-server/collab/registry.ts +80 -2
  54. package/plugins/design/dev-server/collab/room.ts +21 -8
  55. package/plugins/design/dev-server/context-menu.tsx +167 -23
  56. package/plugins/design/dev-server/context.ts +24 -0
  57. package/plugins/design/dev-server/contextual-toolbar.tsx +7 -7
  58. package/plugins/design/dev-server/dist/client.bundle.js +364 -29
  59. package/plugins/design/dev-server/dist/comment-mount.js +78 -33
  60. package/plugins/design/dev-server/dist/styles.css +16 -0
  61. package/plugins/design/dev-server/equal-spacing-handles.tsx +1 -1
  62. package/plugins/design/dev-server/export-dialog.tsx +208 -18
  63. package/plugins/design/dev-server/exporters/html.ts +4 -1
  64. package/plugins/design/dev-server/exporters/index.ts +4 -1
  65. package/plugins/design/dev-server/exporters/pdf.ts +7 -1
  66. package/plugins/design/dev-server/exporters/png.ts +21 -2
  67. package/plugins/design/dev-server/exporters/pptx.ts +330 -201
  68. package/plugins/design/dev-server/exporters/scope.ts +107 -11
  69. package/plugins/design/dev-server/exporters/svg.ts +4 -1
  70. package/plugins/design/dev-server/fs-watch.ts +1 -0
  71. package/plugins/design/dev-server/http.ts +299 -28
  72. package/plugins/design/dev-server/input-router.tsx +35 -5
  73. package/plugins/design/dev-server/participants-chrome.tsx +10 -10
  74. package/plugins/design/dev-server/server.ts +123 -1
  75. package/plugins/design/dev-server/sync/agent.ts +95 -0
  76. package/plugins/design/dev-server/sync/codec.ts +155 -0
  77. package/plugins/design/dev-server/sync/connection-state.ts +203 -0
  78. package/plugins/design/dev-server/sync/index.ts +479 -35
  79. package/plugins/design/dev-server/sync/materialize.ts +62 -0
  80. package/plugins/design/dev-server/sync/migrate-seed.ts +163 -0
  81. package/plugins/design/dev-server/sync/origins.ts +57 -0
  82. package/plugins/design/dev-server/sync/projection.ts +368 -0
  83. package/plugins/design/dev-server/sync/status.ts +115 -0
  84. package/plugins/design/dev-server/sync/untrusted.ts +153 -0
  85. package/plugins/design/dev-server/test/_helpers.ts +6 -2
  86. package/plugins/design/dev-server/test/annotations-draw-modifiers.test.ts +206 -0
  87. package/plugins/design/dev-server/test/annotations-layer.test.ts +311 -0
  88. package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +519 -0
  89. package/plugins/design/dev-server/test/canvas-cursors.test.ts +162 -0
  90. package/plugins/design/dev-server/test/canvas-origin-gate.test.ts +76 -0
  91. package/plugins/design/dev-server/test/canvas-route.test.ts +4 -1
  92. package/plugins/design/dev-server/test/collab-reseed-guard.test.ts +78 -0
  93. package/plugins/design/dev-server/test/collab-room.test.ts +21 -10
  94. package/plugins/design/dev-server/test/csp-canvas-shell.test.ts +46 -0
  95. package/plugins/design/dev-server/test/exporters/png.test.ts +24 -1
  96. package/plugins/design/dev-server/test/exporters/pptx-deck.test.ts +112 -0
  97. package/plugins/design/dev-server/test/exporters/pw-launch.test.ts +49 -0
  98. package/plugins/design/dev-server/test/exporters/scope.test.ts +0 -0
  99. package/plugins/design/dev-server/test/fixtures/phase-20-annotations.svg +1 -0
  100. package/plugins/design/dev-server/test/fixtures/phase-21-annotations.svg +1 -0
  101. package/plugins/design/dev-server/test/input-router.test.ts +32 -4
  102. package/plugins/design/dev-server/test/sanitize-annotation-svg.test.ts +132 -0
  103. package/plugins/design/dev-server/test/shared-doc-convergence.test.ts +265 -0
  104. package/plugins/design/dev-server/test/shared-doc-foundation.test.ts +63 -0
  105. package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +160 -0
  106. package/plugins/design/dev-server/test/shared-doc-projection.test.ts +238 -0
  107. package/plugins/design/dev-server/test/sync-connection-state.test.ts +146 -0
  108. package/plugins/design/dev-server/test/sync-meta-codec.test.ts +123 -0
  109. package/plugins/design/dev-server/test/sync-runtime.test.ts +531 -4
  110. package/plugins/design/dev-server/test/sync-status.test.ts +80 -0
  111. package/plugins/design/dev-server/test/sync-untrusted.test.ts +104 -0
  112. package/plugins/design/dev-server/test/use-annotation-resize.test.ts +200 -0
  113. package/plugins/design/dev-server/test/use-tool-mode.test.tsx +39 -13
  114. package/plugins/design/dev-server/tool-palette.tsx +155 -24
  115. package/plugins/design/dev-server/undo-hud.tsx +4 -4
  116. package/plugins/design/dev-server/undo-stack.ts +20 -4
  117. package/plugins/design/dev-server/use-annotation-resize.tsx +219 -61
  118. package/plugins/design/dev-server/use-tool-mode.tsx +68 -19
  119. package/plugins/design/dev-server/ws.ts +40 -1
  120. package/plugins/design/templates/_shell.html +85 -10
  121. package/plugins/design/templates/canvas.tsx.template +13 -0
  122. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +14 -7
  123. package/plugins/flow/.claude-plugin/config.schema.json +5 -0
  124. package/plugins/flow/templates/ai-skeleton/README.md +1 -1
  125. package/plugins/flow/templates/ai-skeleton/gitignore +10 -0
  126. package/plugins/flow/templates/ai-skeleton/scenarios/README.md +3 -1
  127. package/plugins/flow/templates/ai-skeleton/workflows.config.json +2 -1
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @file canvas-cursors.ts — Phase 24 custom tool cursors (Kenney CC0)
3
+ * @scope plugins/design/dev-server/canvas-cursors.ts
4
+ * @purpose FigJam/Figma-style custom cursors for EVERY canvas tool. The
5
+ * native crosshair / text / arrow cursors are tiny + thin and
6
+ * vanish on busy canvases; these are 32×32 SVG cursors that read on
7
+ * any background.
8
+ *
9
+ * Phase 24 — ALL tools draw from ONE library, the **Kenney Cursor
10
+ * Pack (1.1)** "Outline" set, which is **CC0 / public domain**
11
+ * (https://kenney.nl/assets/cursor-pack — no attribution required,
12
+ * redistributable inside this MIT package + marketplace clone). See
13
+ * DDR-067 for the licence gate (Bibata GPL-3.0 was rejected as
14
+ * copyleft). One library = one visual identity across move / hand /
15
+ * comment / pen / shape / sticky / text / eraser. The glyph paths
16
+ * are Kenney's; we RECOLOUR them (see `kenney()`) to a dark-glyph +
17
+ * white-halo treatment so they read crisply on light AND dark
18
+ * canvases. Per-tool glyph map:
19
+ * move→pointer_a · hand→hand_open · comment→message_dots_square ·
20
+ * pen→drawing_pencil · shape→line_cross · eraser→drawing_eraser.
21
+ * text (I-beam) + sticky (folded-corner note) have no clean Kenney
22
+ * equivalent, so they are AUTHORED in the identical dark-glyph +
23
+ * white-halo treatment to keep one visual identity.
24
+ *
25
+ * Delivered as CSS `cursor: url("data:image/svg+xml,…") hx hy, fb`
26
+ * — no runtime dependency, no asset files. 32×32 is the
27
+ * cross-browser-safe ceiling; (hx, hy) is the click hotspot.
28
+ */
29
+
30
+ import type { Tool } from './input-router.tsx';
31
+
32
+ /** Build a CSS cursor value from an inline SVG + hotspot + native fallback. */
33
+ function svgCursor(svg: string, hx: number, hy: number, fallback: string): string {
34
+ // encodeURIComponent is the safest escaping for a data: URI inside url("…").
35
+ return `url("data:image/svg+xml,${encodeURIComponent(svg.trim())}") ${hx} ${hy}, ${fallback}`;
36
+ }
37
+
38
+ // Phase 24 — 24px rendered box (down from 32) so cursors aren't oversized; the
39
+ // glyph coords stay in the 32-unit viewBox and scale into 24px. Hotspots are
40
+ // scaled to match (see the generator).
41
+ const W = `width='24' height='24' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'`;
42
+ const INK = '#1f1f1f';
43
+ const HALO = "stroke='#ffffff' stroke-linejoin='round' stroke-linecap='round'";
44
+
45
+ // Kenney CC0 glyph, recoloured to a FigJam-style **dark glyph + white halo**:
46
+ // Kenney's outline band (`b`) is painted WHITE (the halo) and its inner fill
47
+ // (`w`) is painted dark INK. Reads crisply on a LIGHT canvas (solid dark glyph)
48
+ // AND a dark one (white halo edge).
49
+ function kenney(b: string, w: string): string {
50
+ return `<svg ${W}><path fill='#ffffff' d='${b}'/><path fill='${INK}' d='${w}'/></svg>`;
51
+ }
52
+
53
+ const MOVE = svgCursor(
54
+ kenney(
55
+ 'M16.75 23.9 L20 22.55 20.5 22.15 20.35 21.55 18.4 17.75 21.25 17.25 21.75 17 22 16.45 Q22 16.1 21.6 15.9 L11.45 8.2 11.4 8.2 10.85 8 10.2 8.25 10 8.8 10 20.75 Q10 21.55 10.85 21.55 L11.4 21.35 13.6 19.8 15.6 23.5 16.05 23.95 16.75 23.9 M8 8.8 Q7.95 8.05 8.55 7.15 L8.95 6.75 Q9.8 6 10.85 6 11.55 6 12.2 6.35 L12.65 6.6 22.65 14.15 Q24.15 15 24 16.45 24.05 17.55 23.2 18.45 L23.05 18.55 Q22.1 19.3 21.45 19.3 L22.1 20.55 Q22.5 21.2 22.5 22.15 L22.05 23.4 20.9 24.35 20.8 24.4 17.55 25.75 15.25 25.8 Q14.15 25.4 13.75 24.25 L12.95 22.75 12.65 22.9 12.55 23 Q11.75 23.55 10.85 23.55 7.95 23.55 8 20.75 L8 8.8',
56
+ 'M16.75 23.9 L16.05 23.95 15.6 23.5 13.6 19.8 11.4 21.35 10.85 21.55 Q10 21.55 10 20.75 L10 8.8 10.2 8.25 10.85 8 11.4 8.2 11.45 8.2 21.6 15.9 Q22 16.1 22 16.45 L21.75 17 21.25 17.25 18.4 17.75 20.35 21.55 20.5 22.15 20 22.55 16.75 23.9'
57
+ ),
58
+ 6,
59
+ 5,
60
+ 'default'
61
+ );
62
+ const HAND = svgCursor(
63
+ kenney(
64
+ 'M28.55 17.8 Q29.4 20.3 28.75 22.8 L28.7 23 28.15 24.55 28.05 24.75 Q26.4 28.2 22.75 29.4 L22.45 29.5 18 29.9 17.75 29.85 Q15.8 29.25 13.8 28.1 L13.45 27.95 Q12 27.05 10.35 26.8 L10.25 26.75 7.9 26.7 7.8 26.7 Q6.1 26.9 4.8 25.8 L4.75 25.8 4.65 25.7 4.6 25.65 Q3.25 24.5 3.05 22.85 L3 22.75 Q2.8 20.95 4 19.5 5.05 18.15 6.75 17.95 L8.4 17.9 7.5 15.55 7.3 15.2 7.2 15 5 10.95 Q3.9 9.15 4.3 7.75 L4.4 7.5 Q4.75 6.15 6.45 5.2 9 3.65 11 5 11.05 4.1 11.5 3.45 12.15 2.1 14.2 1.6 L14.35 1.6 Q18.05 0.6 19.6 4.4 L19.65 4.55 Q20.75 3.65 22.7 3.75 L22.75 3.75 Q26.75 4 27 8.15 L27 8.25 27 8.35 27 8.45 Q26.9 10.75 27.1 12.6 27.15 14.65 28.2 16.75 L28.55 17.8 M26.65 18.4 L26.1 16.8 26.1 16.85 Q24.8 13.7 25 8.35 L25 8.25 Q24.85 5.9 22.6 5.75 20.7 5.65 20.4 7.35 L20.1 7.95 19.5 8.2 18.85 8.05 18.45 7.5 17.75 5.15 Q16.85 2.95 14.7 3.55 L14.65 3.55 Q12.55 4.05 13.15 6.15 L13.85 8.75 13.75 9.45 13.25 9.95 12.55 9.95 12 9.55 10.9 7.7 Q9.5 5.7 7.45 6.9 6.5 7.45 6.25 8.15 6.1 8.9 6.7 9.85 9.2 13.9 10.8 18.65 L10.8 19.3 10.4 19.8 9.75 19.95 Q8.35 19.8 6.95 19.95 6.1 20.05 5.55 20.75 4.9 21.55 5 22.55 5.15 23.55 6 24.2 L6.05 24.25 Q6.75 24.85 7.65 24.7 L7.7 24.7 10.65 24.8 Q12.7 25.15 14.45 26.2 L14.5 26.2 Q16.4 27.35 18.3 27.9 L22.05 27.5 22.1 27.5 Q24.95 26.55 26.25 23.85 L26.8 22.3 Q27.3 20.35 26.65 18.4',
65
+ 'M26.65 18.4 Q27.3 20.35 26.8 22.3 L26.25 23.85 Q24.95 26.55 22.1 27.5 L22.05 27.5 18.3 27.9 Q16.4 27.35 14.5 26.2 L14.45 26.2 Q12.7 25.15 10.65 24.8 L7.7 24.7 7.65 24.7 Q6.75 24.85 6.05 24.25 L6 24.2 Q5.15 23.55 5 22.55 4.9 21.55 5.55 20.75 6.1 20.05 6.95 19.95 8.35 19.8 9.75 19.95 L10.4 19.8 10.8 19.3 10.8 18.65 Q9.2 13.9 6.7 9.85 6.1 8.9 6.25 8.15 6.5 7.45 7.45 6.9 9.5 5.7 10.9 7.7 L12 9.55 12.55 9.95 13.25 9.95 13.75 9.45 13.85 8.75 13.15 6.15 Q12.55 4.05 14.65 3.55 L14.7 3.55 Q16.85 2.95 17.75 5.15 L18.45 7.5 18.85 8.05 19.5 8.2 20.1 7.95 20.4 7.35 Q20.7 5.65 22.6 5.75 24.85 5.9 25 8.25 L25 8.35 Q24.8 13.7 26.1 16.85 L26.1 16.8 26.65 18.4'
66
+ ),
67
+ 12,
68
+ 12,
69
+ 'grab'
70
+ );
71
+ const COMMENT = svgCursor(
72
+ kenney(
73
+ 'M8 4 Q4 4 4 8 L4 20 Q4 24 8 24 L12 24 15.3 27.3 16 27.6 16.7 27.3 20 24 24 24 Q28 24 28 20 L28 8 Q28 4 24 4 L8 4 M24 2 Q30 2 30 8 L30 20 Q30 26 24 26 L20.85 26 18.15 28.75 Q17.2 29.6 16 29.6 14.8 29.6 13.9 28.75 L11.15 26 8 26 Q2 26 2 20 L2 8 Q2 2 8 2 L24 2 M24 14 Q24 14.85 23.4 15.4 22.85 16 22 16 21.15 16 20.55 15.4 20 14.85 20 14 20 13.15 20.55 12.55 21.15 12 22 12 22.85 12 23.4 12.55 24 13.15 24 14 M12 14 Q12 14.85 11.4 15.4 10.85 16 10 16 9.15 16 8.55 15.4 8 14.85 8 14 8 13.15 8.55 12.55 9.15 12 10 12 10.85 12 11.4 12.55 12 13.15 12 14 M18 14 Q18 14.85 17.4 15.4 16.85 16 16 16 15.15 16 14.55 15.4 14 14.85 14 14 14 13.15 14.55 12.55 15.15 12 16 12 16.85 12 17.4 12.55 18 13.15 18 14',
74
+ 'M18 14 Q18 13.15 17.4 12.55 16.85 12 16 12 15.15 12 14.55 12.55 14 13.15 14 14 14 14.85 14.55 15.4 15.15 16 16 16 16.85 16 17.4 15.4 18 14.85 18 14 M8 4 L24 4 Q28 4 28 8 L28 20 Q28 24 24 24 L20 24 16.7 27.3 16 27.6 15.3 27.3 12 24 8 24 Q4 24 4 20 L4 8 Q4 4 8 4 M12 14 Q12 13.15 11.4 12.55 10.85 12 10 12 9.15 12 8.55 12.55 8 13.15 8 14 8 14.85 8.55 15.4 9.15 16 10 16 10.85 16 11.4 15.4 12 14.85 12 14 M24 14 Q24 13.15 23.4 12.55 22.85 12 22 12 21.15 12 20.55 12.55 20 13.15 20 14 20 14.85 20.55 15.4 21.15 16 22 16 22.85 16 23.4 15.4 24 14.85 24 14'
75
+ ),
76
+ 12,
77
+ 22,
78
+ 'crosshair'
79
+ );
80
+ const PEN = svgCursor(
81
+ kenney(
82
+ 'M5.15 10.85 Q4 9.65 4 8.1 L4 7.95 4 7.85 Q3.95 6.2 5.1 5.15 6.25 4 7.85 4 L7.95 4 8.1 4 Q9.65 4 10.85 5.15 L11 5.3 14.3 5.25 Q15.15 5.25 15.75 5.85 L29.9 20 Q31 21.15 31 22.75 L31 22.85 31 23 Q31.05 24.5 29.9 25.7 L29.55 25.95 25.65 29.9 25.65 29.95 Q24.45 31.1 22.75 31.1 21.2 31.15 20 29.95 L5.85 15.8 Q5.25 15.2 5.25 14.35 L5.25 10.95 5.15 10.85 M19.95 24.2 L18.55 22.8 22.8 18.55 24.2 19.95 19.95 24.2 M28.45 21.4 L14.3 7.25 10.15 7.3 9.4 6.55 Q8.8 5.95 7.95 6 7.1 5.95 6.5 6.55 5.95 7.1 6 7.95 5.95 8.8 6.55 9.4 L7.25 10.1 7.25 14.35 21.4 28.5 Q22 29.1 22.75 29.1 23.6 29.1 24.2 28.5 L28.45 24.25 Q29.05 23.65 29 22.85 29.05 22 28.45 21.4 M9.25 13.5 L9.25 9.3 13.5 9.25 14.3 10.05 10.05 14.3 9.25 13.5 M17.15 21.4 L11.5 15.75 15.75 11.5 21.4 17.15 17.15 21.4',
83
+ 'M17.15 21.4 L21.4 17.15 15.75 11.5 11.5 15.75 17.15 21.4 M28.45 21.4 Q29.05 22 29 22.85 29.05 23.65 28.45 24.25 L24.2 28.5 Q23.6 29.1 22.75 29.1 22 29.1 21.4 28.5 L7.25 14.35 7.25 10.1 6.55 9.4 Q5.95 8.8 6 7.95 5.95 7.1 6.5 6.55 7.1 5.95 7.95 6 8.8 5.95 9.4 6.55 L10.15 7.3 14.3 7.25 28.45 21.4 M19.95 24.2 L24.2 19.95 22.8 18.55 18.55 22.8 19.95 24.2 M9.25 13.5 L10.05 14.3 14.3 10.05 13.5 9.25 9.25 9.3 9.25 13.5'
84
+ ),
85
+ 20,
86
+ 20,
87
+ 'crosshair'
88
+ );
89
+ const CROSSHAIR = svgCursor(
90
+ kenney(
91
+ 'M17 2 Q18.2 2 19.15 2.9 20 3.8 20 5 L20 12 27 12 Q28.2 12 29.15 12.9 30 13.8 30 15 L30 17 Q30 18.2 29.15 19.15 28.2 20 27 20 L20 20 20 27 Q20 28.2 19.15 29.15 18.2 30 17 30 L15 30 Q13.8 30 12.9 29.15 12 28.2 12 27 L12 20 5 20 Q3.8 20 2.9 19.15 2 18.2 2 17 L2 15 Q2 13.8 2.9 12.9 3.8 12 5 12 L12 12 12 5 Q12 3.8 12.9 2.9 13.8 2 15 2 L17 2 M14 27 L14.3 27.7 Q14.6 28 15 28 L17 28 17.7 27.7 18 27 18 18.05 18.05 18 27 18 Q27.4 18 27.7 17.7 L28 17 28 15 27.7 14.3 Q27.4 14 27 14 L18 14 18 5 17.7 4.3 17 4 15 4 Q14.6 4 14.3 4.3 14 4.6 14 5 L14 14 5 14 Q4.6 14 4.3 14.3 4 14.6 4 15 L4 17 Q4 17.4 4.3 17.7 4.6 18 5 18 L14 18 14 27',
92
+ 'M14 27 L14 18 5 18 Q4.6 18 4.3 17.7 4 17.4 4 17 L4 15 Q4 14.6 4.3 14.3 4.6 14 5 14 L14 14 14 5 Q14 4.6 14.3 4.3 14.6 4 15 4 L17 4 17.7 4.3 18 5 18 14 27 14 Q27.4 14 27.7 14.3 L28 15 28 17 27.7 17.7 Q27.4 18 27 18 L18.05 18 18 18.05 18 27 17.7 27.7 17 28 15 28 Q14.6 28 14.3 27.7 L14 27'
93
+ ),
94
+ 12,
95
+ 12,
96
+ 'crosshair'
97
+ );
98
+ const ERASER = svgCursor(
99
+ kenney(
100
+ 'M3.9 11.35 Q2.3 12.5 3.7 13.9 L16.4 26.65 Q17.85 28.05 19.45 26.9 L27.65 21.15 Q29.3 20 27.85 18.6 L15.15 5.85 Q13.75 4.45 12.1 5.6 L3.9 11.35 M1.05 12.4 Q1 10.95 2.75 9.75 L10.95 4 10.95 3.95 Q14 1.85 16.6 4.45 L29.3 17.2 Q30.8 18.65 30.55 20.1 30.55 21.55 28.8 22.8 L20.6 28.55 20.65 28.55 Q17.7 30.65 15 28.1 L15 28.05 2.3 15.3 2.3 15.35 Q0.8 13.85 1.05 12.4 M13.5 7 L19.85 13.35 11.65 19.15 5.3 12.75 13.5 7',
101
+ 'M13.5 7 L5.3 12.75 11.65 19.15 19.85 13.35 13.5 7 M3.9 11.35 L12.1 5.6 Q13.75 4.45 15.15 5.85 L27.85 18.6 Q29.3 20 27.65 21.15 L19.45 26.9 Q17.85 28.05 16.4 26.65 L3.7 13.9 Q2.3 12.5 3.9 11.35'
102
+ ),
103
+ 6,
104
+ 14,
105
+ 'cell'
106
+ );
107
+
108
+ // Text — a classic I-beam (vertical stem + top/bottom serifs). Authored in the
109
+ // same dark-INK glyph + white-HALO treatment as the Kenney glyphs (Kenney's
110
+ // pointer_i reads as a small bracket, not an I-beam). Hotspot dead-centre.
111
+ const TEXT = svgCursor(
112
+ `<svg ${W}><path d='M16 4V28M11 4H21M11 28H21' fill='none' ${HALO} stroke-width='5'/><path d='M16 4V28M11 4H21M11 28H21' fill='none' stroke='${INK}' stroke-width='2.25' stroke-linecap='round'/></svg>`,
113
+ 12,
114
+ 12,
115
+ 'text'
116
+ );
117
+
118
+ // Sticky — a note with a peeled/folded top-right corner (the universal sticky
119
+ // glyph). Authored in the same treatment (Kenney has no sticky-note). Hotspot
120
+ // at the note's top-left, where the note is dropped.
121
+ const STICKY = svgCursor(
122
+ `<svg ${W}><path d='M6 5H21L26 10V27H6Z' fill='${INK}' ${HALO} stroke-width='2.5'/><path d='M21 5V11H26' fill='none' ${HALO} stroke-width='2.25'/></svg>`,
123
+ 5,
124
+ 5,
125
+ 'crosshair'
126
+ );
127
+
128
+ /**
129
+ * Tool → CSS cursor value. Phase 24 — every tool ships a Kenney glyph (no
130
+ * system fallback for `move` anymore; the brief is ONE unified library). The
131
+ * single Shape tool uses the crosshair; `rect`/`ellipse` stay keyed (still in
132
+ * the Tool union) but are no longer directly selectable.
133
+ */
134
+ // Frozen so the docstring's "frozen map" guarantee is real: `resolveToolCursor`
135
+ // is a pure lookup into this object, and freezing forecloses any same-realm
136
+ // shell-side code ever mutating an entry out from under the lookup (the canvas
137
+ // runs cross-origin and can't reach it regardless — this is belt-and-braces).
138
+ export const TOOL_CURSORS: Record<Tool, string> = Object.freeze({
139
+ move: MOVE,
140
+ hand: HAND,
141
+ comment: COMMENT,
142
+ pen: PEN,
143
+ shape: CROSSHAIR,
144
+ rect: CROSSHAIR,
145
+ ellipse: CROSSHAIR,
146
+ sticky: STICKY,
147
+ arrow: CROSSHAIR,
148
+ text: TEXT,
149
+ eraser: ERASER,
150
+ });
151
+
152
+ /**
153
+ * Resolve a tool token — received over the **untrusted** canvas→shell
154
+ * `tool-cursor` postMessage bridge — to a TRUSTED cursor string from
155
+ * {@link TOOL_CURSORS}, or `null` when the token is not a known tool.
156
+ *
157
+ * The app shell applies the value this RETURNS, never the raw message. That is
158
+ * the security boundary: a malicious *synced* canvas (DDR-054 — canvas content
159
+ * is untrusted and may be hub-pushed) can call
160
+ * `window.parent.postMessage({ dgn: 'tool-cursor', tool: … }, '*')` directly,
161
+ * bypassing the honest `ToolProvider` sender. By echoing only a token and
162
+ * looking the cursor up here, the worst that canvas can do is pick *which*
163
+ * known, always-visible glyph the shell shows — it can no longer smuggle an
164
+ * invisible / displaced / zero-content SVG cursor that would act as a
165
+ * clickjacking aid over the un-CSP'd shell (phase-24 ethical-hacker Finding 2;
166
+ * see DDR-067). `hasOwnProperty` (not `in`) keeps `constructor`/`__proto__`
167
+ * off the allowlist, and the `[a-z-]` shape gate bars anything but a tool id.
168
+ */
169
+ export function resolveToolCursor(token: unknown): string | null {
170
+ if (typeof token !== 'string' || !/^[a-z-]+$/.test(token)) return null;
171
+ if (!Object.prototype.hasOwnProperty.call(TOOL_CURSORS, token)) return null;
172
+ return TOOL_CURSORS[token as Tool];
173
+ }
@@ -102,6 +102,26 @@ export function IconEraser(props: IconProps) {
102
102
  );
103
103
  }
104
104
 
105
+ // Phase 21 — sticky note: folded-corner square (dog-ear at the top-right).
106
+ export function IconSticky(props: IconProps) {
107
+ return (
108
+ <Svg {...props}>
109
+ <path d="M4 5h12l4 4v10H4z" />
110
+ <path d="M16 5v4h4" />
111
+ </Svg>
112
+ );
113
+ }
114
+
115
+ // Phase 21 — standalone text: capital-T glyph (cap bar + stem).
116
+ export function IconText(props: IconProps) {
117
+ return (
118
+ <Svg {...props}>
119
+ <path d="M4 6h16" />
120
+ <path d="M12 6v12" />
121
+ </Svg>
122
+ );
123
+ }
124
+
105
125
  export function IconPresentation(props: IconProps) {
106
126
  return (
107
127
  <Svg {...props}>
@@ -119,13 +139,292 @@ export function IconChevronDown(props: IconProps) {
119
139
  );
120
140
  }
121
141
 
142
+ // ── Phase 21 context-toolbar icons ──────────────────────────────────────────
143
+ // Corner radius: a square whose corners show the chosen rounding.
144
+ export function IconCornerSquare(props: IconProps) {
145
+ return (
146
+ <Svg {...props}>
147
+ <rect x="5" y="5" width="14" height="14" rx="0.5" />
148
+ </Svg>
149
+ );
150
+ }
151
+ export function IconCornerSoft(props: IconProps) {
152
+ return (
153
+ <Svg {...props}>
154
+ <rect x="5" y="5" width="14" height="14" rx="4" />
155
+ </Svg>
156
+ );
157
+ }
158
+ export function IconCornerPill(props: IconProps) {
159
+ return (
160
+ <Svg {...props}>
161
+ <rect x="5" y="5" width="14" height="14" rx="7" />
162
+ </Svg>
163
+ );
164
+ }
165
+
166
+ // Arrow direction: a shaft with chevron head(s) on the chosen end(s).
167
+ export function IconArrowNone(props: IconProps) {
168
+ return (
169
+ <Svg {...props}>
170
+ <path d="M4 12h16" />
171
+ </Svg>
172
+ );
173
+ }
174
+ export function IconArrowStartHead(props: IconProps) {
175
+ return (
176
+ <Svg {...props}>
177
+ <path d="M4 12h16" />
178
+ <path d="M9 7l-5 5 5 5" />
179
+ </Svg>
180
+ );
181
+ }
182
+ export function IconArrowEndHead(props: IconProps) {
183
+ return (
184
+ <Svg {...props}>
185
+ <path d="M4 12h16" />
186
+ <path d="M15 7l5 5-5 5" />
187
+ </Svg>
188
+ );
189
+ }
190
+ export function IconArrowBothHeads(props: IconProps) {
191
+ return (
192
+ <Svg {...props}>
193
+ <path d="M5 12h14" />
194
+ <path d="M9 7l-5 5 5 5" />
195
+ <path d="M15 7l5 5-5 5" />
196
+ </Svg>
197
+ );
198
+ }
199
+
200
+ // Dashed line toggle.
201
+ export function IconDash(props: IconProps) {
202
+ return (
203
+ <Svg {...props}>
204
+ <path d="M3 12h4" />
205
+ <path d="M10 12h4" />
206
+ <path d="M17 12h4" />
207
+ </Svg>
208
+ );
209
+ }
210
+
211
+ // Stroke weight: thin vs thick rule.
212
+ export function IconLineThin(props: IconProps) {
213
+ return (
214
+ <Svg {...props} strokeWidth={1.25}>
215
+ <path d="M4 12h16" />
216
+ </Svg>
217
+ );
218
+ }
219
+ export function IconLineThick(props: IconProps) {
220
+ return (
221
+ <Svg {...props} strokeWidth={3.75}>
222
+ <path d="M4 12h16" />
223
+ </Svg>
224
+ );
225
+ }
226
+
227
+ // Letter "A" — font-size chips render it at three sizes (S / M / L).
228
+ export function IconLetterA(props: IconProps) {
229
+ return (
230
+ <Svg {...props}>
231
+ <path d="M12 5L6 19" />
232
+ <path d="M12 5l6 14" />
233
+ <path d="M8.5 14h7" />
234
+ </Svg>
235
+ );
236
+ }
237
+
238
+ // Trash — delete selected annotations.
239
+ export function IconTrash(props: IconProps) {
240
+ return (
241
+ <Svg {...props}>
242
+ <path d="M4 7h16" />
243
+ <path d="M9 7V5a1 1 0 011-1h4a1 1 0 011 1v2" />
244
+ <path d="M6 7l1 12a1 1 0 001 1h8a1 1 0 001-1l1-12" />
245
+ <path d="M10 11v6M14 11v6" />
246
+ </Svg>
247
+ );
248
+ }
249
+
250
+ // ── Phase 24 shape-tool icons ────────────────────────────────────────────────
251
+ // IconShape — the Shape tool button: a square + circle composite (FigJam-style).
252
+ export function IconShape(props: IconProps) {
253
+ return (
254
+ <Svg {...props}>
255
+ <rect x="4" y="4" width="11" height="11" rx="1.5" />
256
+ <circle cx="15" cy="15" r="5" />
257
+ </Svg>
258
+ );
259
+ }
260
+ export function IconSquare(props: IconProps) {
261
+ return (
262
+ <Svg {...props}>
263
+ <rect x="4" y="4" width="16" height="16" rx="0.5" />
264
+ </Svg>
265
+ );
266
+ }
267
+ export function IconRoundedSquare(props: IconProps) {
268
+ return (
269
+ <Svg {...props}>
270
+ <rect x="4" y="4" width="16" height="16" rx="4" />
271
+ </Svg>
272
+ );
273
+ }
274
+ export function IconCircle(props: IconProps) {
275
+ return (
276
+ <Svg {...props}>
277
+ <circle cx="12" cy="12" r="8" />
278
+ </Svg>
279
+ );
280
+ }
281
+ export function IconDiamond(props: IconProps) {
282
+ return (
283
+ <Svg {...props}>
284
+ <path d="M12 3l9 9-9 9-9-9z" />
285
+ </Svg>
286
+ );
287
+ }
288
+ export function IconTriangle(props: IconProps) {
289
+ return (
290
+ <Svg {...props}>
291
+ <path d="M12 4l8 16H4z" />
292
+ </Svg>
293
+ );
294
+ }
295
+ export function IconTriangleDown(props: IconProps) {
296
+ return (
297
+ <Svg {...props}>
298
+ <path d="M4 4h16l-8 16z" />
299
+ </Svg>
300
+ );
301
+ }
302
+
303
+ // ── Phase 24 arrowhead-picker icons ──────────────────────────────────────────
304
+ // Each shows a short shaft ending in the head style on the right.
305
+ export function IconArrowheadLine(props: IconProps) {
306
+ return (
307
+ <Svg {...props}>
308
+ <path d="M3 12h13" />
309
+ <path d="M12 8l5 4-5 4" />
310
+ </Svg>
311
+ );
312
+ }
313
+ export function IconArrowheadTriangle(props: IconProps) {
314
+ return (
315
+ <Svg {...props}>
316
+ <path d="M3 12h11" />
317
+ <path d="M13 8l6 4-6 4z" fill="currentColor" />
318
+ </Svg>
319
+ );
320
+ }
321
+ export function IconArrowheadTriangleOutline(props: IconProps) {
322
+ return (
323
+ <Svg {...props}>
324
+ <path d="M3 12h11" />
325
+ <path d="M13 8l6 4-6 4z" />
326
+ </Svg>
327
+ );
328
+ }
329
+ export function IconArrowheadCircle(props: IconProps) {
330
+ return (
331
+ <Svg {...props}>
332
+ <path d="M3 12h11" />
333
+ <circle cx="17" cy="12" r="3" fill="currentColor" />
334
+ </Svg>
335
+ );
336
+ }
337
+ export function IconArrowheadDiamond(props: IconProps) {
338
+ return (
339
+ <Svg {...props}>
340
+ <path d="M3 12h10" />
341
+ <path d="M17 8l4 4-4 4-4-4z" fill="currentColor" />
342
+ </Svg>
343
+ );
344
+ }
345
+
346
+ // ── Phase 24 line-type icons ─────────────────────────────────────────────────
347
+ export function IconLineStraight(props: IconProps) {
348
+ return (
349
+ <Svg {...props}>
350
+ <path d="M4 12h16" />
351
+ </Svg>
352
+ );
353
+ }
354
+ export function IconLineCurved(props: IconProps) {
355
+ return (
356
+ <Svg {...props}>
357
+ <path d="M4 18C4 9 20 15 20 6" />
358
+ </Svg>
359
+ );
360
+ }
361
+ export function IconLineElbow(props: IconProps) {
362
+ return (
363
+ <Svg {...props}>
364
+ <path d="M4 6v6a2 2 0 002 2h14" />
365
+ </Svg>
366
+ );
367
+ }
368
+
369
+ // ── Phase 24 text-style icons ────────────────────────────────────────────────
370
+ export function IconBold(props: IconProps) {
371
+ return (
372
+ <Svg {...props}>
373
+ <path d="M7 5h6a3.5 3.5 0 010 7H7zM7 12h7a3.5 3.5 0 010 7H7z" />
374
+ </Svg>
375
+ );
376
+ }
377
+ export function IconStrike(props: IconProps) {
378
+ return (
379
+ <Svg {...props}>
380
+ <path d="M5 12h14" />
381
+ <path d="M8 7a4 3 0 016-1M16 16a4 3 0 01-7 1" />
382
+ </Svg>
383
+ );
384
+ }
385
+ export function IconAlignLeft(props: IconProps) {
386
+ return (
387
+ <Svg {...props}>
388
+ <path d="M4 6h16M4 12h10M4 18h13" />
389
+ </Svg>
390
+ );
391
+ }
392
+ export function IconAlignCenter(props: IconProps) {
393
+ return (
394
+ <Svg {...props}>
395
+ <path d="M4 6h16M7 12h10M5 18h14" />
396
+ </Svg>
397
+ );
398
+ }
399
+ export function IconAlignRight(props: IconProps) {
400
+ return (
401
+ <Svg {...props}>
402
+ <path d="M4 6h16M10 12h10M7 18h13" />
403
+ </Svg>
404
+ );
405
+ }
406
+
122
407
  export const TOOL_ICONS: Record<string, (p: IconProps) => JSX.Element> = {
123
408
  move: IconMove,
124
409
  hand: IconHand,
125
410
  comment: IconComment,
126
411
  pen: IconPen,
412
+ // Phase 24 — single Shape tool. rect/ellipse kept for any legacy lookups.
413
+ shape: IconShape,
127
414
  rect: IconRect,
128
415
  ellipse: IconEllipse,
416
+ sticky: IconSticky,
129
417
  arrow: IconArrow,
418
+ text: IconText,
130
419
  eraser: IconEraser,
131
420
  };
421
+
422
+ // Phase 24 — shape-kind → icon, for the palette popover + context toolbar.
423
+ export const SHAPE_KIND_ICONS: Record<string, (p: IconProps) => JSX.Element> = {
424
+ square: IconSquare,
425
+ rounded: IconRoundedSquare,
426
+ circle: IconCircle,
427
+ diamond: IconDiamond,
428
+ triangle: IconTriangle,
429
+ 'triangle-down': IconTriangleDown,
430
+ };
@@ -125,10 +125,13 @@ const ENGINE_CSS = `
125
125
  never visually melts into the selection halo during a drag-snap gesture.
126
126
  OKLCH default approximates FigJam magenta in the project's color space. */
127
127
  --guide-magenta: oklch(62% 0.28 350);
128
- background-color: var(--bg-1, #f4f1ea);
128
+ /* Canvas-shell chrome — the workspace plane + dotted grid follow the Maude
129
+ chrome theme (--maude-chrome-*), NOT the DS palette. See canvas-shell.tsx
130
+ HUD_TOKENS_CSS. Artboards (.dc-artboard) keep the DS theme. */
131
+ background-color: var(--maude-chrome-bg-1, #f4f1ea);
129
132
  background-image:
130
- linear-gradient(var(--border-subtle, rgba(0,0,0,0.08)) 1px, transparent 1px),
131
- linear-gradient(90deg, var(--border-subtle, rgba(0,0,0,0.08)) 1px, transparent 1px);
133
+ linear-gradient(var(--maude-chrome-border, rgba(0,0,0,0.08)) 1px, transparent 1px),
134
+ linear-gradient(90deg, var(--maude-chrome-border, rgba(0,0,0,0.08)) 1px, transparent 1px);
132
135
  background-size: 24px 24px;
133
136
  }
134
137
  /* DDR-046 — Snap guides. Sibling kind = confident magenta + glow + distance
@@ -146,13 +149,13 @@ const ENGINE_CSS = `
146
149
  box-shadow: 0 0 4px color-mix(in oklab, var(--guide-magenta, oklch(62% 0.28 350)) 35%, transparent);
147
150
  }
148
151
  .dc-snap-guide--grid {
149
- background: color-mix(in oklab, var(--fg-3, var(--fg-1, #4a3f30)) 40%, transparent);
152
+ background: color-mix(in oklab, var(--maude-chrome-fg-1, #4a3f30) 40%, transparent);
150
153
  }
151
154
  .dc-snap-pill {
152
155
  position: fixed;
153
156
  pointer-events: none;
154
157
  z-index: 7;
155
- font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
158
+ font-family: var(--maude-chrome-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
156
159
  font-size: 11px;
157
160
  font-weight: 600;
158
161
  line-height: 1;
@@ -1718,7 +1721,8 @@ export function DCPostIt({ children }: { children: ReactNode }) {
1718
1721
  // favor of a soft ambient. The hard offset stays on app-shell chrome only
1719
1722
  // (menubar, header, tab strip) — that's the project's intentional brutalist
1720
1723
  // identity. Floating layer = soft. App frame = hard.
1721
- const FLOATING_SHADOW = '0 6px 24px color-mix(in oklab, var(--fg-0, #1c1917) 10%, transparent)';
1724
+ const FLOATING_SHADOW =
1725
+ '0 6px 24px var(--maude-chrome-shadow, color-mix(in oklab, #1c1917 10%, transparent))';
1722
1726
  const FLOATING_RADIUS = '8px';
1723
1727
 
1724
1728
  const OVERLAY_CSS = `
@@ -1728,12 +1732,12 @@ const OVERLAY_CSS = `
1728
1732
  bottom: 16px;
1729
1733
  width: 196px;
1730
1734
  height: 132px;
1731
- background: var(--bg-0, #ffffff);
1732
- border: 1px solid var(--fg-0, #1c1917);
1735
+ background: var(--maude-chrome-bg-0, #ffffff);
1736
+ border: 1px solid var(--maude-chrome-fg-0, #1c1917);
1733
1737
  border-radius: ${FLOATING_RADIUS};
1734
- font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1738
+ font-family: var(--maude-chrome-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1735
1739
  font-size: 10px;
1736
- color: var(--fg-1, rgba(40,30,20,0.7));
1740
+ color: var(--maude-chrome-fg-1, rgba(40,30,20,0.7));
1737
1741
  z-index: 6;
1738
1742
  user-select: none;
1739
1743
  box-shadow: ${FLOATING_SHADOW};
@@ -1741,11 +1745,11 @@ const OVERLAY_CSS = `
1741
1745
  }
1742
1746
  .dc-mm-hd {
1743
1747
  padding: 5px 8px 4px;
1744
- border-bottom: 1px solid rgba(0,0,0,0.08);
1748
+ border-bottom: 1px solid var(--maude-chrome-border, rgba(0,0,0,0.08));
1745
1749
  letter-spacing: 0.05em;
1746
1750
  text-transform: uppercase;
1747
1751
  font-size: 9px;
1748
- background: var(--bg-1, #f4f1ea);
1752
+ background: var(--maude-chrome-bg-1, #f4f1ea);
1749
1753
  }
1750
1754
  .dc-mm-body {
1751
1755
  position: relative;
@@ -1753,12 +1757,12 @@ const OVERLAY_CSS = `
1753
1757
  height: calc(100% - 22px);
1754
1758
  overflow: hidden;
1755
1759
  cursor: pointer;
1756
- background: var(--bg-1, #f4f1ea);
1760
+ background: var(--maude-chrome-bg-1, #f4f1ea);
1757
1761
  }
1758
1762
  .dc-mm-rect {
1759
1763
  position: absolute;
1760
- background: color-mix(in oklab, var(--fg-0, #1c1917) 14%, transparent);
1761
- border: 1px solid color-mix(in oklab, var(--fg-0, #1c1917) 28%, transparent);
1764
+ background: color-mix(in oklab, var(--maude-chrome-fg-0, #1c1917) 14%, transparent);
1765
+ border: 1px solid color-mix(in oklab, var(--maude-chrome-fg-0, #1c1917) 28%, transparent);
1762
1766
  border-radius: 1px;
1763
1767
  }
1764
1768
  /* Filled viewport indicator — FigJam / Figma both ship a tinted fill, not
@@ -1777,13 +1781,13 @@ const OVERLAY_CSS = `
1777
1781
  transform: translateX(-50%);
1778
1782
  display: flex;
1779
1783
  align-items: stretch;
1780
- background: var(--bg-0, #ffffff);
1781
- border: 1px solid var(--fg-0, #1c1917);
1784
+ background: var(--maude-chrome-bg-0, #ffffff);
1785
+ border: 1px solid var(--maude-chrome-fg-0, #1c1917);
1782
1786
  border-radius: ${FLOATING_RADIUS};
1783
1787
  overflow: hidden;
1784
- font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1788
+ font-family: var(--maude-chrome-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
1785
1789
  font-size: 11px;
1786
- color: var(--fg-1, rgba(40,30,20,0.85));
1790
+ color: var(--maude-chrome-fg-1, rgba(40,30,20,0.85));
1787
1791
  z-index: 6;
1788
1792
  box-shadow: ${FLOATING_SHADOW};
1789
1793
  }
@@ -1791,7 +1795,7 @@ const OVERLAY_CSS = `
1791
1795
  appearance: none;
1792
1796
  background: transparent;
1793
1797
  border: 0;
1794
- border-right: 1px solid rgba(0,0,0,0.08);
1798
+ border-right: 1px solid var(--maude-chrome-border, rgba(0,0,0,0.08));
1795
1799
  padding: 7px 12px;
1796
1800
  font: inherit;
1797
1801
  color: inherit;
@@ -1801,7 +1805,7 @@ const OVERLAY_CSS = `
1801
1805
  transition: background 80ms linear;
1802
1806
  }
1803
1807
  .dc-zoom-tb button:last-child { border-right: 0; }
1804
- .dc-zoom-tb button:hover { background: color-mix(in oklab, var(--fg-0, #1c1917) 5%, transparent); }
1808
+ .dc-zoom-tb button:hover { background: color-mix(in oklab, var(--maude-chrome-fg-0, #1c1917) 5%, transparent); }
1805
1809
  .dc-zoom-tb button:focus-visible { outline: 2px solid var(--maude-hud-accent, #d63b1f); outline-offset: -2px; }
1806
1810
  .dc-zoom-tb-pct { font-variant-numeric: tabular-nums; min-width: 52px; }
1807
1811
  `.trim();
@@ -18,6 +18,26 @@
18
18
  "type": "string",
19
19
  "description": "The full brief that produced this canvas (from /design:new). Preserved for handoff."
20
20
  },
21
+ "brief_sha": {
22
+ "type": "string",
23
+ "description": "Hash of the brief that produced this canvas (stamped at /design:new step 11). Used by /design:new to short-circuit regeneration when a byte-identical brief in the same DS already produced a canvas."
24
+ },
25
+ "tags": {
26
+ "type": "array",
27
+ "uniqueItems": true,
28
+ "items": { "type": "string" },
29
+ "description": "Free-form labels for the canvas — feature slugs ('dark-mode'), surfaces ('settings'), or themes. Read by flow's /flow:plan design-canvas detection (Phase 11) to match a feature name against canvases via exact tag match (the canvas slug substring match is the other half of the heuristic). Optional."
30
+ },
31
+ "status": {
32
+ "type": "string",
33
+ "enum": ["draft", "in-review", "ready-for-handoff", "handed-off"],
34
+ "default": "draft",
35
+ "description": "Lifecycle state of the canvas. 'draft' = work in progress; 'in-review' = awaiting feedback; 'ready-for-handoff' = approved, eligible for /design:handoff; 'handed-off' = shipped to production code (see handoffCommit). Hand-set by the user, or flipped to 'handed-off' by flow's /flow:done handoff sweep (Phase 11) after a successful /design:handoff. Optional — absent means 'draft'."
36
+ },
37
+ "handoffCommit": {
38
+ "type": "string",
39
+ "description": "Git commit SHA (full or short) of the commit that handed this canvas off to production code. Written by /flow:done's handoff sweep when it flips status to 'handed-off'. Optional."
40
+ },
21
41
  "platform": {
22
42
  "type": "string",
23
43
  "enum": ["desktop", "mobile", "tablet", "responsive", "tv", "watch", "other"],