@1agh/maude 0.22.2 → 0.24.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.
- package/README.md +34 -1
- package/cli/bin/maude.mjs +3 -0
- package/cli/commands/cache.mjs +181 -0
- package/cli/commands/cache.test.mjs +166 -0
- package/cli/commands/design-link.test.mjs +84 -2
- package/cli/commands/design.mjs +95 -4
- package/cli/commands/design.test.mjs +56 -0
- package/cli/commands/help.mjs +34 -0
- package/cli/commands/hub.mjs +255 -30
- package/cli/commands/hub.test.mjs +135 -10
- package/cli/commands/init.mjs +3 -0
- package/cli/commands/preflight.mjs +11 -0
- package/cli/commands/preflight.test.mjs +46 -0
- package/cli/commands/scenario-report.mjs +45 -0
- package/cli/lib/cache.mjs +407 -0
- package/cli/lib/cache.test.mjs +303 -0
- package/cli/lib/design-link.mjs +222 -11
- package/cli/lib/flow-design-integration.test.mjs +165 -0
- package/cli/lib/gitignore-block.mjs +90 -0
- package/cli/lib/gitignore-block.test.mjs +123 -0
- package/cli/lib/hubs-config.mjs +42 -4
- package/cli/lib/plugin-cli-reachability.test.mjs +56 -0
- package/cli/lib/preflight.mjs +41 -10
- package/package.json +8 -8
- package/plugins/design/dev-server/ai-banner.tsx +2 -2
- package/plugins/design/dev-server/annotations-context-toolbar.tsx +349 -112
- package/plugins/design/dev-server/annotations-layer.tsx +906 -137
- package/plugins/design/dev-server/api.ts +109 -4
- package/plugins/design/dev-server/bin/preflight.sh +21 -10
- package/plugins/design/dev-server/bin/prep.sh +211 -0
- package/plugins/design/dev-server/bin/scenario-report.mjs +209 -0
- package/plugins/design/dev-server/bin/screenshot.sh +18 -1
- package/plugins/design/dev-server/bin/smoke.sh +94 -11
- package/plugins/design/dev-server/build.ts +69 -3
- package/plugins/design/dev-server/canvas-comment-mount.tsx +384 -0
- package/plugins/design/dev-server/canvas-cursors.ts +125 -0
- package/plugins/design/dev-server/canvas-icons.tsx +130 -0
- package/plugins/design/dev-server/canvas-lib.tsx +47 -27
- package/plugins/design/dev-server/canvas-meta.schema.json +20 -0
- package/plugins/design/dev-server/canvas-shell.tsx +358 -245
- package/plugins/design/dev-server/client/app.jsx +191 -21
- package/plugins/design/dev-server/client/styles/3-shell.css +10 -0
- package/plugins/design/dev-server/collab/awareness-bridge.ts +77 -0
- package/plugins/design/dev-server/collab/index.ts +87 -9
- package/plugins/design/dev-server/collab/persistence.ts +34 -3
- package/plugins/design/dev-server/collab/registry.ts +131 -2
- package/plugins/design/dev-server/collab/room.ts +21 -8
- package/plugins/design/dev-server/config.schema.json +20 -0
- package/plugins/design/dev-server/context-menu.tsx +167 -23
- package/plugins/design/dev-server/context.ts +31 -0
- package/plugins/design/dev-server/contextual-toolbar.tsx +7 -7
- package/plugins/design/dev-server/dist/client.bundle.js +144 -22
- package/plugins/design/dev-server/dist/comment-mount.js +1868 -0
- package/plugins/design/dev-server/dist/styles.css +16 -0
- package/plugins/design/dev-server/dom-selection.ts +156 -0
- package/plugins/design/dev-server/equal-spacing-handles.tsx +1 -1
- package/plugins/design/dev-server/export-dialog.tsx +19 -17
- package/plugins/design/dev-server/fs-watch.ts +1 -0
- package/plugins/design/dev-server/hmr-broadcast.ts +51 -20
- package/plugins/design/dev-server/http.ts +260 -20
- package/plugins/design/dev-server/input-router.tsx +125 -64
- package/plugins/design/dev-server/participants-chrome.tsx +10 -10
- package/plugins/design/dev-server/server.ts +141 -1
- package/plugins/design/dev-server/sync/agent.ts +418 -0
- package/plugins/design/dev-server/sync/atomic-write.ts +103 -0
- package/plugins/design/dev-server/sync/codec.ts +324 -0
- package/plugins/design/dev-server/sync/connection-state.ts +203 -0
- package/plugins/design/dev-server/sync/echo-guard.ts +108 -0
- package/plugins/design/dev-server/sync/fs-mirror.ts +160 -0
- package/plugins/design/dev-server/sync/hubs-config.ts +87 -0
- package/plugins/design/dev-server/sync/index.ts +918 -0
- package/plugins/design/dev-server/sync/materialize.ts +62 -0
- package/plugins/design/dev-server/sync/migrate-seed.ts +163 -0
- package/plugins/design/dev-server/sync/origins.ts +57 -0
- package/plugins/design/dev-server/sync/projection.ts +368 -0
- package/plugins/design/dev-server/sync/status.ts +115 -0
- package/plugins/design/dev-server/sync/untrusted.ts +153 -0
- package/plugins/design/dev-server/test/_helpers.ts +6 -2
- package/plugins/design/dev-server/test/annotations-layer.test.ts +231 -0
- package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +276 -0
- package/plugins/design/dev-server/test/canvas-cursors.test.ts +73 -0
- package/plugins/design/dev-server/test/canvas-origin-gate.test.ts +76 -0
- package/plugins/design/dev-server/test/collab-awareness-bridge.test.ts +223 -0
- package/plugins/design/dev-server/test/collab-reseed-guard.test.ts +78 -0
- package/plugins/design/dev-server/test/collab-room.test.ts +21 -10
- package/plugins/design/dev-server/test/comment-mount.test.ts +87 -0
- package/plugins/design/dev-server/test/csp-canvas-shell.test.ts +46 -0
- package/plugins/design/dev-server/test/fixtures/phase-20-annotations.svg +1 -0
- package/plugins/design/dev-server/test/hmr-classify.test.ts +70 -0
- package/plugins/design/dev-server/test/input-router.test.ts +21 -0
- package/plugins/design/dev-server/test/sanitize-annotation-svg.test.ts +100 -0
- package/plugins/design/dev-server/test/shared-doc-convergence.test.ts +265 -0
- package/plugins/design/dev-server/test/shared-doc-foundation.test.ts +63 -0
- package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +160 -0
- package/plugins/design/dev-server/test/shared-doc-projection.test.ts +238 -0
- package/plugins/design/dev-server/test/sync-agent.test.ts +278 -0
- package/plugins/design/dev-server/test/sync-atomic-write.test.ts +63 -0
- package/plugins/design/dev-server/test/sync-codec.test.ts +165 -0
- package/plugins/design/dev-server/test/sync-connection-state.test.ts +146 -0
- package/plugins/design/dev-server/test/sync-echo-guard.test.ts +96 -0
- package/plugins/design/dev-server/test/sync-fs-mirror.test.ts +182 -0
- package/plugins/design/dev-server/test/sync-hardening.test.ts +520 -0
- package/plugins/design/dev-server/test/sync-hubs-config.test.ts +130 -0
- package/plugins/design/dev-server/test/sync-meta-codec.test.ts +123 -0
- package/plugins/design/dev-server/test/sync-runtime.test.ts +812 -0
- package/plugins/design/dev-server/test/sync-status.test.ts +80 -0
- package/plugins/design/dev-server/test/sync-untrusted.test.ts +104 -0
- package/plugins/design/dev-server/test/use-collab.test.ts +0 -0
- package/plugins/design/dev-server/test/use-tool-mode.test.tsx +38 -10
- package/plugins/design/dev-server/tool-palette.tsx +18 -16
- package/plugins/design/dev-server/undo-hud.tsx +4 -4
- package/plugins/design/dev-server/undo-stack.ts +20 -4
- package/plugins/design/dev-server/use-annotation-resize.tsx +16 -5
- package/plugins/design/dev-server/use-collab.tsx +157 -13
- package/plugins/design/dev-server/use-selection-set.tsx +12 -0
- package/plugins/design/dev-server/use-tool-mode.tsx +27 -12
- package/plugins/design/dev-server/ws.ts +40 -1
- package/plugins/design/templates/_shell.html +63 -5
- package/plugins/design/templates/canvas.tsx.template +13 -0
- package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +15 -7
- package/plugins/flow/.claude-plugin/config.schema.json +5 -0
- package/plugins/flow/templates/ai-skeleton/README.md +1 -1
- package/plugins/flow/templates/ai-skeleton/gitignore +10 -0
- package/plugins/flow/templates/ai-skeleton/scenarios/README.md +3 -1
- package/plugins/flow/templates/ai-skeleton/workflows.config.json +2 -1
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file canvas-cursors.ts — Phase 21 custom tool cursors
|
|
3
|
+
* @scope plugins/design/dev-server/canvas-cursors.ts
|
|
4
|
+
* @purpose FigJam/Figma-style custom cursors for the annotation + nav tools.
|
|
5
|
+
* The native `crosshair` / `text` / `cell` cursors are tiny, thin,
|
|
6
|
+
* and vanish on busy canvases (the "pen is almost invisible"
|
|
7
|
+
* complaint). These are 32×32 SVG cursors with a WHITE outline halo
|
|
8
|
+
* so the glyph reads on any background, light or dark, and each one
|
|
9
|
+
* mirrors its tool-palette icon so the cursor and the tool button
|
|
10
|
+
* share one visual identity.
|
|
11
|
+
*
|
|
12
|
+
* Delivered as CSS `cursor: url("data:image/svg+xml,…") hx hy, fb`
|
|
13
|
+
* — no runtime dependency, no asset files (Figma ships cursors the
|
|
14
|
+
* same way). 32×32 is the cross-browser-safe ceiling; the hotspot
|
|
15
|
+
* (hx, hy) is the pixel the click actually registers at.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { Tool } from './input-router.tsx';
|
|
19
|
+
|
|
20
|
+
/** Build a CSS cursor value from an inline SVG + hotspot + native fallback. */
|
|
21
|
+
function svgCursor(svg: string, hx: number, hy: number, fallback: string): string {
|
|
22
|
+
// encodeURIComponent is the safest escaping for a data: URI inside url("…").
|
|
23
|
+
return `url("data:image/svg+xml,${encodeURIComponent(svg.trim())}") ${hx} ${hy}, ${fallback}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const W = `width='32' height='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'`;
|
|
27
|
+
const HALO = "stroke='#ffffff' stroke-linejoin='round' stroke-linecap='round'";
|
|
28
|
+
const INK = '#1f1f1f';
|
|
29
|
+
|
|
30
|
+
// Precise crosshair with a centre gap — rect / ellipse / arrow. Hotspot 16,16.
|
|
31
|
+
const CROSSHAIR = svgCursor(
|
|
32
|
+
`<svg ${W}>
|
|
33
|
+
<path d='M16 4V13M16 19V28M4 16H13M19 16H28' fill='none' ${HALO} stroke-width='4'/>
|
|
34
|
+
<path d='M16 4V13M16 19V28M4 16H13M19 16H28' fill='none' stroke='${INK}' stroke-width='1.75' stroke-linecap='round'/>
|
|
35
|
+
<circle cx='16' cy='16' r='1.3' fill='${INK}'/>
|
|
36
|
+
</svg>`,
|
|
37
|
+
16,
|
|
38
|
+
16,
|
|
39
|
+
'crosshair'
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Marker/pen — nib points at the draw point (bottom-left). Hotspot 6,26.
|
|
43
|
+
const PEN = svgCursor(
|
|
44
|
+
`<svg ${W}>
|
|
45
|
+
<path d='M22 4l6 6-15 15-7 1 1-7z' fill='${INK}' ${HALO} stroke-width='2.25'/>
|
|
46
|
+
<path d='M18.5 7.5l6 6' ${HALO} stroke-width='2'/>
|
|
47
|
+
<path d='M7 25l-1.5 1.5' ${HALO} stroke-width='2.5'/>
|
|
48
|
+
</svg>`,
|
|
49
|
+
6,
|
|
50
|
+
26,
|
|
51
|
+
'crosshair'
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Bigger I-beam for text. Hotspot 16,16.
|
|
55
|
+
const TEXT = svgCursor(
|
|
56
|
+
`<svg ${W}>
|
|
57
|
+
<path d='M16 5V27M11 5H21M11 27H21' fill='none' ${HALO} stroke-width='4.5'/>
|
|
58
|
+
<path d='M16 5V27M11 5H21M11 27H21' fill='none' stroke='${INK}' stroke-width='2' stroke-linecap='round'/>
|
|
59
|
+
</svg>`,
|
|
60
|
+
16,
|
|
61
|
+
16,
|
|
62
|
+
'text'
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Speech bubble — drops a comment at the tail tip (bottom-left). Hotspot 8,27.
|
|
66
|
+
const COMMENT = svgCursor(
|
|
67
|
+
`<svg ${W}>
|
|
68
|
+
<path d='M8 5H26a4 4 0 0 1 4 4V18a4 4 0 0 1-4 4H14l-6 6v-6H8a4 4 0 0 1-4-4V9a4 4 0 0 1 4-4Z' fill='${INK}' ${HALO} stroke-width='2.25'/>
|
|
69
|
+
<circle cx='12' cy='13.5' r='1.4' fill='#fff'/>
|
|
70
|
+
<circle cx='17' cy='13.5' r='1.4' fill='#fff'/>
|
|
71
|
+
<circle cx='22' cy='13.5' r='1.4' fill='#fff'/>
|
|
72
|
+
</svg>`,
|
|
73
|
+
8,
|
|
74
|
+
27,
|
|
75
|
+
'crosshair'
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// Open hand for pan/grab. Hotspot 16,17.
|
|
79
|
+
const HAND = svgCursor(
|
|
80
|
+
`<svg ${W}>
|
|
81
|
+
<path d='M10 19v-8a1.8 1.8 0 0 1 3.6 0v-1.2a1.8 1.8 0 0 1 3.6 0v.6a1.8 1.8 0 0 1 3.6 0V12a1.8 1.8 0 0 1 3.6 0v6.5a7.5 7.5 0 0 1-7.5 7.5h-1a7.5 7.5 0 0 1-6.4-3.6l-2.4-3.7a1.9 1.9 0 0 1 3-2.3z' fill='${INK}' ${HALO} stroke-width='2'/>
|
|
82
|
+
</svg>`,
|
|
83
|
+
16,
|
|
84
|
+
17,
|
|
85
|
+
'grab'
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
// Eraser — working edge at bottom-left. Hotspot 8,24.
|
|
89
|
+
const ERASER = svgCursor(
|
|
90
|
+
`<svg ${W}>
|
|
91
|
+
<path d='M19 5l8 8-10 10H9l-4-4z' fill='${INK}' ${HALO} stroke-width='2.25'/>
|
|
92
|
+
<path d='M13 11l8 8' ${HALO} stroke-width='2'/>
|
|
93
|
+
</svg>`,
|
|
94
|
+
8,
|
|
95
|
+
24,
|
|
96
|
+
'cell'
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Sticky — a mini yellow note (matches the card). Drag starts at its top-left.
|
|
100
|
+
const STICKY = svgCursor(
|
|
101
|
+
`<svg ${W}>
|
|
102
|
+
<path d='M7 6h11l8 8v12H7z' fill='#ffe27a' stroke='${INK}' stroke-width='2' stroke-linejoin='round'/>
|
|
103
|
+
<path d='M18 6v8h8' fill='none' stroke='${INK}' stroke-width='2' stroke-linejoin='round'/>
|
|
104
|
+
</svg>`,
|
|
105
|
+
8,
|
|
106
|
+
7,
|
|
107
|
+
'crosshair'
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Tool → CSS cursor value. `move` keeps the system arrow (the universal
|
|
112
|
+
* select/move affordance); everything else gets a crafted cursor.
|
|
113
|
+
*/
|
|
114
|
+
export const TOOL_CURSORS: Record<Tool, string> = {
|
|
115
|
+
move: 'default',
|
|
116
|
+
hand: HAND,
|
|
117
|
+
comment: COMMENT,
|
|
118
|
+
pen: PEN,
|
|
119
|
+
rect: CROSSHAIR,
|
|
120
|
+
ellipse: CROSSHAIR,
|
|
121
|
+
sticky: STICKY,
|
|
122
|
+
arrow: CROSSHAIR,
|
|
123
|
+
text: TEXT,
|
|
124
|
+
eraser: ERASER,
|
|
125
|
+
};
|
|
@@ -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,6 +139,114 @@ 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
|
+
|
|
122
250
|
export const TOOL_ICONS: Record<string, (p: IconProps) => JSX.Element> = {
|
|
123
251
|
move: IconMove,
|
|
124
252
|
hand: IconHand,
|
|
@@ -126,6 +254,8 @@ export const TOOL_ICONS: Record<string, (p: IconProps) => JSX.Element> = {
|
|
|
126
254
|
pen: IconPen,
|
|
127
255
|
rect: IconRect,
|
|
128
256
|
ellipse: IconEllipse,
|
|
257
|
+
sticky: IconSticky,
|
|
129
258
|
arrow: IconArrow,
|
|
259
|
+
text: IconText,
|
|
130
260
|
eraser: IconEraser,
|
|
131
261
|
};
|
|
@@ -95,7 +95,7 @@ import {
|
|
|
95
95
|
import { type DragState, useArtboardDrag } from './use-artboard-drag.tsx';
|
|
96
96
|
import { CollabProvider, canvasSlugFromPath } from './use-collab.tsx';
|
|
97
97
|
import { useSelectionSetOptional } from './use-selection-set.tsx';
|
|
98
|
-
import {
|
|
98
|
+
import { MaybeToolProvider, useToolModeOptional } from './use-tool-mode.tsx';
|
|
99
99
|
import { UndoStackProvider, useUndoSinks, useUndoStackOptional } from './use-undo-stack.tsx';
|
|
100
100
|
|
|
101
101
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
@@ -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
|
-
|
|
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
|
|
131
|
-
linear-gradient(90deg, var(--border
|
|
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(--
|
|
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;
|
|
@@ -1161,7 +1164,10 @@ interface DesignCanvasProps {
|
|
|
1161
1164
|
*
|
|
1162
1165
|
* `ToolProvider` lives above `DesignCanvasInner` so the viewport
|
|
1163
1166
|
* controller's `isPanDragActive` predicate can read the live tool state
|
|
1164
|
-
* via `useToolModeOptional` (hand-mode bare-drag pan).
|
|
1167
|
+
* via `useToolModeOptional` (hand-mode bare-drag pan). It's wrapped in
|
|
1168
|
+
* `MaybeToolProvider` so when the shell-owned comment mount layer
|
|
1169
|
+
* (canvas-comment-mount.tsx) already provides a ToolProvider, DesignCanvas
|
|
1170
|
+
* consumes that single instance instead of double-mounting.
|
|
1165
1171
|
*/
|
|
1166
1172
|
export function DesignCanvas(props: DesignCanvasProps) {
|
|
1167
1173
|
// Phase 20 — per-canvas undo/redo stack (DDR-050 rev 2). The provider
|
|
@@ -1178,9 +1184,9 @@ export function DesignCanvas(props: DesignCanvasProps) {
|
|
|
1178
1184
|
// CollabProvider is omitted; useCollab() falls back gracefully to null.
|
|
1179
1185
|
const collabSlug = canvasSlugFromPath(canvasFile);
|
|
1180
1186
|
const inner = (
|
|
1181
|
-
<
|
|
1187
|
+
<MaybeToolProvider>
|
|
1182
1188
|
<DesignCanvasInner {...props} />
|
|
1183
|
-
</
|
|
1189
|
+
</MaybeToolProvider>
|
|
1184
1190
|
);
|
|
1185
1191
|
return (
|
|
1186
1192
|
<UndoStackProvider canvasFile={canvasFile}>
|
|
@@ -1715,7 +1721,8 @@ export function DCPostIt({ children }: { children: ReactNode }) {
|
|
|
1715
1721
|
// favor of a soft ambient. The hard offset stays on app-shell chrome only
|
|
1716
1722
|
// (menubar, header, tab strip) — that's the project's intentional brutalist
|
|
1717
1723
|
// identity. Floating layer = soft. App frame = hard.
|
|
1718
|
-
const FLOATING_SHADOW =
|
|
1724
|
+
const FLOATING_SHADOW =
|
|
1725
|
+
'0 6px 24px var(--maude-chrome-shadow, color-mix(in oklab, #1c1917 10%, transparent))';
|
|
1719
1726
|
const FLOATING_RADIUS = '8px';
|
|
1720
1727
|
|
|
1721
1728
|
const OVERLAY_CSS = `
|
|
@@ -1725,12 +1732,12 @@ const OVERLAY_CSS = `
|
|
|
1725
1732
|
bottom: 16px;
|
|
1726
1733
|
width: 196px;
|
|
1727
1734
|
height: 132px;
|
|
1728
|
-
background: var(--bg-0, #ffffff);
|
|
1729
|
-
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);
|
|
1730
1737
|
border-radius: ${FLOATING_RADIUS};
|
|
1731
|
-
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);
|
|
1732
1739
|
font-size: 10px;
|
|
1733
|
-
color: var(--fg-1, rgba(40,30,20,0.7));
|
|
1740
|
+
color: var(--maude-chrome-fg-1, rgba(40,30,20,0.7));
|
|
1734
1741
|
z-index: 6;
|
|
1735
1742
|
user-select: none;
|
|
1736
1743
|
box-shadow: ${FLOATING_SHADOW};
|
|
@@ -1738,11 +1745,11 @@ const OVERLAY_CSS = `
|
|
|
1738
1745
|
}
|
|
1739
1746
|
.dc-mm-hd {
|
|
1740
1747
|
padding: 5px 8px 4px;
|
|
1741
|
-
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));
|
|
1742
1749
|
letter-spacing: 0.05em;
|
|
1743
1750
|
text-transform: uppercase;
|
|
1744
1751
|
font-size: 9px;
|
|
1745
|
-
background: var(--bg-1, #f4f1ea);
|
|
1752
|
+
background: var(--maude-chrome-bg-1, #f4f1ea);
|
|
1746
1753
|
}
|
|
1747
1754
|
.dc-mm-body {
|
|
1748
1755
|
position: relative;
|
|
@@ -1750,12 +1757,12 @@ const OVERLAY_CSS = `
|
|
|
1750
1757
|
height: calc(100% - 22px);
|
|
1751
1758
|
overflow: hidden;
|
|
1752
1759
|
cursor: pointer;
|
|
1753
|
-
background: var(--bg-1, #f4f1ea);
|
|
1760
|
+
background: var(--maude-chrome-bg-1, #f4f1ea);
|
|
1754
1761
|
}
|
|
1755
1762
|
.dc-mm-rect {
|
|
1756
1763
|
position: absolute;
|
|
1757
|
-
background: color-mix(in oklab, var(--fg-0, #1c1917) 14%, transparent);
|
|
1758
|
-
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);
|
|
1759
1766
|
border-radius: 1px;
|
|
1760
1767
|
}
|
|
1761
1768
|
/* Filled viewport indicator — FigJam / Figma both ship a tinted fill, not
|
|
@@ -1774,13 +1781,13 @@ const OVERLAY_CSS = `
|
|
|
1774
1781
|
transform: translateX(-50%);
|
|
1775
1782
|
display: flex;
|
|
1776
1783
|
align-items: stretch;
|
|
1777
|
-
background: var(--bg-0, #ffffff);
|
|
1778
|
-
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);
|
|
1779
1786
|
border-radius: ${FLOATING_RADIUS};
|
|
1780
1787
|
overflow: hidden;
|
|
1781
|
-
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);
|
|
1782
1789
|
font-size: 11px;
|
|
1783
|
-
color: var(--fg-1, rgba(40,30,20,0.85));
|
|
1790
|
+
color: var(--maude-chrome-fg-1, rgba(40,30,20,0.85));
|
|
1784
1791
|
z-index: 6;
|
|
1785
1792
|
box-shadow: ${FLOATING_SHADOW};
|
|
1786
1793
|
}
|
|
@@ -1788,7 +1795,7 @@ const OVERLAY_CSS = `
|
|
|
1788
1795
|
appearance: none;
|
|
1789
1796
|
background: transparent;
|
|
1790
1797
|
border: 0;
|
|
1791
|
-
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));
|
|
1792
1799
|
padding: 7px 12px;
|
|
1793
1800
|
font: inherit;
|
|
1794
1801
|
color: inherit;
|
|
@@ -1798,7 +1805,7 @@ const OVERLAY_CSS = `
|
|
|
1798
1805
|
transition: background 80ms linear;
|
|
1799
1806
|
}
|
|
1800
1807
|
.dc-zoom-tb button:last-child { border-right: 0; }
|
|
1801
|
-
.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); }
|
|
1802
1809
|
.dc-zoom-tb button:focus-visible { outline: 2px solid var(--maude-hud-accent, #d63b1f); outline-offset: -2px; }
|
|
1803
1810
|
.dc-zoom-tb-pct { font-variant-numeric: tabular-nums; min-width: 52px; }
|
|
1804
1811
|
`.trim();
|
|
@@ -2300,11 +2307,18 @@ export const MOTION_ROLE_DEFAULTS: Record<MotionRole, RoleConfig> = {
|
|
|
2300
2307
|
spring: {
|
|
2301
2308
|
durationToken: '--dur-panel',
|
|
2302
2309
|
easingToken: 'spring',
|
|
2303
|
-
|
|
2310
|
+
// Spring physics animate toward a single target; a 3-point array
|
|
2311
|
+
// (`[0,-16,0]`) makes motion/react's spring no-op (no movement). Use a
|
|
2312
|
+
// 2-point target and let `repeatType: 'reverse'` carry the return leg.
|
|
2313
|
+
keyframes: { y: [0, -16] },
|
|
2304
2314
|
fallbackMs: 320,
|
|
2305
2315
|
},
|
|
2306
2316
|
scroll: {
|
|
2307
|
-
|
|
2317
|
+
// `--dur-route` is intentionally ~instant (route changes are snap, often
|
|
2318
|
+
// 1ms), which makes a scroll-linked drift imperceptible. Bind to the
|
|
2319
|
+
// longer `--dur-soft` so the demo (and any time-driven scroll fallback)
|
|
2320
|
+
// is actually visible.
|
|
2321
|
+
durationToken: '--dur-soft',
|
|
2308
2322
|
easingToken: '--ease-in-out',
|
|
2309
2323
|
keyframes: { x: [0, 24, 0] },
|
|
2310
2324
|
fallbackMs: 480,
|
|
@@ -2425,6 +2439,11 @@ export function MotionDemo({
|
|
|
2425
2439
|
const repeat = reduced || loop === 'once' ? 0 : Number.POSITIVE_INFINITY;
|
|
2426
2440
|
const repeatType: 'reverse' | 'loop' = loop === 'always' ? 'reverse' : 'loop';
|
|
2427
2441
|
const animate = reduced ? undefined : cfg.keyframes;
|
|
2442
|
+
// DS durations are micro-interaction speeds (often <200ms). Looping them with
|
|
2443
|
+
// no gap strobes ~10×/s. Insert a rest between cycles so each loop replays the
|
|
2444
|
+
// REAL token speed, then pauses — readable cadence, not a flicker. Spring's
|
|
2445
|
+
// own settle is the pause, so it skips the extra delay.
|
|
2446
|
+
const repeatDelay = loop === 'always' && !isSpring ? 0.9 : 0;
|
|
2428
2447
|
|
|
2429
2448
|
return (
|
|
2430
2449
|
<div
|
|
@@ -2441,6 +2460,7 @@ export function MotionDemo({
|
|
|
2441
2460
|
type: isSpring ? 'spring' : 'tween',
|
|
2442
2461
|
repeat,
|
|
2443
2462
|
repeatType,
|
|
2463
|
+
repeatDelay,
|
|
2444
2464
|
}}
|
|
2445
2465
|
className="motion-demo__target"
|
|
2446
2466
|
aria-label={label}
|
|
@@ -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"],
|