@1agh/maude 0.19.0 → 0.20.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 (66) hide show
  1. package/package.json +9 -9
  2. package/plugins/design/dev-server/annotations-context-toolbar.tsx +126 -44
  3. package/plugins/design/dev-server/annotations-layer.tsx +343 -112
  4. package/plugins/design/dev-server/api.ts +70 -17
  5. package/plugins/design/dev-server/artboard-marquee.tsx +174 -0
  6. package/plugins/design/dev-server/bin/asset-sweep.sh +180 -0
  7. package/plugins/design/dev-server/bin/visual-sanity.sh +221 -0
  8. package/plugins/design/dev-server/canvas-lib.tsx +691 -119
  9. package/plugins/design/dev-server/canvas-shell.tsx +818 -42
  10. package/plugins/design/dev-server/client/app.jsx +173 -52
  11. package/plugins/design/dev-server/client/hmr.mjs +28 -0
  12. package/plugins/design/dev-server/client/styles/1-tokens.css +15 -8
  13. package/plugins/design/dev-server/client/styles/4-components.css +32 -2
  14. package/plugins/design/dev-server/commands/annotation-strokes-command.ts +127 -0
  15. package/plugins/design/dev-server/commands/equal-spacing-command.ts +28 -0
  16. package/plugins/design/dev-server/commands/move-artboards-command.ts +158 -0
  17. package/plugins/design/dev-server/comments-overlay.tsx +12 -4
  18. package/plugins/design/dev-server/config.schema.json +31 -5
  19. package/plugins/design/dev-server/context-menu.tsx +3 -3
  20. package/plugins/design/dev-server/context.ts +37 -3
  21. package/plugins/design/dev-server/contextual-toolbar.tsx +241 -0
  22. package/plugins/design/dev-server/dist/client.bundle.js +212 -103
  23. package/plugins/design/dev-server/dist/runtime/motion.js +165 -0
  24. package/plugins/design/dev-server/dist/runtime/motion_react.js +409 -0
  25. package/plugins/design/dev-server/dist/styles.css +38 -2
  26. package/plugins/design/dev-server/equal-spacing-detector.ts +98 -0
  27. package/plugins/design/dev-server/equal-spacing-handles.tsx +289 -0
  28. package/plugins/design/dev-server/export-dialog.tsx +3 -3
  29. package/plugins/design/dev-server/handoff.ts +24 -0
  30. package/plugins/design/dev-server/http.ts +41 -2
  31. package/plugins/design/dev-server/input-router.tsx +74 -10
  32. package/plugins/design/dev-server/marquee-overlay.tsx +282 -0
  33. package/plugins/design/dev-server/runtime-bundle.ts +7 -0
  34. package/plugins/design/dev-server/server.mjs +84 -18
  35. package/plugins/design/dev-server/test/annotation-strokes-command.test.ts +149 -0
  36. package/plugins/design/dev-server/test/annotations-layer.test.ts +44 -0
  37. package/plugins/design/dev-server/test/canvas-lib-motion.test.ts +131 -0
  38. package/plugins/design/dev-server/test/context-resolve-tokens.test.ts +97 -0
  39. package/plugins/design/dev-server/test/equal-spacing-detector.test.ts +93 -0
  40. package/plugins/design/dev-server/test/input-router.test.ts +87 -1
  41. package/plugins/design/dev-server/test/marquee-overlay.test.ts +94 -0
  42. package/plugins/design/dev-server/test/move-artboards-command.test.ts +108 -0
  43. package/plugins/design/dev-server/test/snap-distance-pill.test.ts +68 -0
  44. package/plugins/design/dev-server/test/system-endpoint.test.ts +144 -0
  45. package/plugins/design/dev-server/test/undo-stack.test.ts +211 -0
  46. package/plugins/design/dev-server/test/use-cursor-modifiers.test.ts +59 -0
  47. package/plugins/design/dev-server/test/use-keyboard-discipline.test.ts +27 -0
  48. package/plugins/design/dev-server/test/use-undo-stack.test.tsx +193 -0
  49. package/plugins/design/dev-server/tool-palette.tsx +71 -15
  50. package/plugins/design/dev-server/undo-hud.tsx +95 -0
  51. package/plugins/design/dev-server/undo-stack.ts +240 -0
  52. package/plugins/design/dev-server/use-annotation-resize.tsx +295 -0
  53. package/plugins/design/dev-server/use-artboard-drag.tsx +6 -3
  54. package/plugins/design/dev-server/use-cursor-modifiers.tsx +122 -0
  55. package/plugins/design/dev-server/use-keyboard-discipline.tsx +125 -0
  56. package/plugins/design/dev-server/use-snap-guides.tsx +25 -1
  57. package/plugins/design/dev-server/use-tool-mode.tsx +31 -2
  58. package/plugins/design/dev-server/use-undo-stack.tsx +355 -0
  59. package/plugins/design/templates/_shell.html +17 -6
  60. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +245 -0
  61. package/plugins/design/templates/design-system-inspiration/_MAPPING.md +2 -2
  62. package/plugins/design/templates/design-system-inspiration/core/preview/_components.css.tpl +129 -0
  63. package/plugins/design/templates/design-system-inspiration/core/preview/_motion-readme.md.tpl +63 -0
  64. package/plugins/design/templates/design-system-inspiration/core/preview/motion.css.tpl +106 -0
  65. package/plugins/design/templates/design-system-inspiration/core/preview/motion.tsx.tpl +208 -0
  66. /package/plugins/design/templates/design-system-inspiration/core/preview/{motion.html → .archive/motion.html} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1agh/maude",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Marketplace of Claude Code plugins by Michal Dovrtěl: `design` (canvas-first design iteration) + `flow` (generic agentic workflow loop with .ai second brain). Ships the `maude` CLI (with `mdcc` legacy alias) to scaffold workspace, run the design dev server, and manage configs.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -18,7 +18,7 @@
18
18
  },
19
19
  "scripts": {
20
20
  "start": "bun run plugins/design/dev-server/server.ts",
21
- "dev": "bun run plugins/design/dev-server/server.ts --port 4399",
21
+ "dev": "bun run plugins/design/dev-server/server.ts --port 4555",
22
22
  "dev:site": "pnpm --filter @maude/site dev --port 4398",
23
23
  "build": "pnpm -r --if-present run build",
24
24
  "build:binary": "bun run plugins/design/dev-server/build.ts --release",
@@ -41,13 +41,13 @@
41
41
  "prepublishOnly": "bash scripts/check-version-parity.sh"
42
42
  },
43
43
  "optionalDependencies": {
44
- "@1agh/maude-darwin-arm64": "0.19.0",
45
- "@1agh/maude-darwin-x64": "0.19.0",
46
- "@1agh/maude-linux-arm64": "0.19.0",
47
- "@1agh/maude-linux-arm64-musl": "0.19.0",
48
- "@1agh/maude-linux-x64": "0.19.0",
49
- "@1agh/maude-linux-x64-musl": "0.19.0",
50
- "@1agh/maude-win32-x64": "0.19.0"
44
+ "@1agh/maude-darwin-arm64": "0.20.0",
45
+ "@1agh/maude-darwin-x64": "0.20.0",
46
+ "@1agh/maude-linux-arm64": "0.20.0",
47
+ "@1agh/maude-linux-arm64-musl": "0.20.0",
48
+ "@1agh/maude-linux-x64": "0.20.0",
49
+ "@1agh/maude-linux-x64-musl": "0.20.0",
50
+ "@1agh/maude-win32-x64": "0.20.0"
51
51
  },
52
52
  "files": [
53
53
  "cli",
@@ -25,9 +25,25 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
25
25
  import { type Stroke, useStrokesStore } from './annotations-layer.tsx';
26
26
  import { useAnnotationSelectionOptional } from './use-annotation-selection.tsx';
27
27
 
28
- const PALETTE = ['#d63b1f', '#f5a623', '#1a8f3e', '#1d6cf0', '#7a4ad3', '#1a1a1a'] as const;
28
+ // G6 FigJam-style 11-color palette. Same colors for both stroke and fill
29
+ // so the swatch row identity stays consistent when the user flips the
30
+ // Stroke|Fill mode toggle (only the target attribute changes, not the
31
+ // visual vocabulary).
32
+ const PALETTE = [
33
+ '#1E1E1E', // black
34
+ '#757575', // gray
35
+ '#E03E3E', // red
36
+ '#F26B3F', // orange
37
+ '#F2C744', // yellow
38
+ '#4CAF50', // green
39
+ '#14B8A6', // teal
40
+ '#3B82F6', // blue
41
+ '#8B5CF6', // purple
42
+ '#EC4899', // pink
43
+ '#FFFFFF', // white
44
+ ] as const;
29
45
 
30
- const FILL_PALETTE = ['#fff4d6', '#e6f4ea', '#e3edff', '#f0e8fb', '#ffe5e0', '#f4f1ee'] as const;
46
+ const FILL_PALETTE = PALETTE;
31
47
 
32
48
  const TOOLBAR_CSS = `
33
49
  .dc-annot-ctx {
@@ -36,11 +52,11 @@ const TOOLBAR_CSS = `
36
52
  display: flex;
37
53
  align-items: center;
38
54
  gap: 6px;
39
- background: var(--u-bg-2, var(--bg-1, rgba(255,255,255,0.98)));
55
+ background: var(--u-bg-0, var(--bg-0, rgba(255,255,255,0.98)));
40
56
  border: 1px solid var(--u-fg-0, #1c1917);
41
- border-radius: 0;
57
+ border-radius: 8px;
42
58
  padding: 6px 8px;
43
- box-shadow: 4px 4px 0 var(--u-fg-0, #1c1917);
59
+ box-shadow: 0 6px 24px color-mix(in oklab, var(--u-fg-0, #1c1917) 10%, transparent);
44
60
  font-family: var(--u-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
45
61
  font-size: 12px;
46
62
  color: var(--u-fg-0, var(--fg-0, #1a1a1a));
@@ -94,6 +110,42 @@ const TOOLBAR_CSS = `
94
110
  .dc-annot-ctx-btn:hover { background: rgba(0,0,0,0.04); }
95
111
  .dc-annot-ctx-btn--danger { color: #b3271a; }
96
112
  .dc-annot-ctx-btn--danger:hover { background: rgba(214,59,31,0.08); }
113
+
114
+ /* T30 / G_S1 — collapsed Stroke|Fill mode toggle. Shown only when the
115
+ selection supports both stroke + fill (rect / ellipse). When the toggle is
116
+ present the swatch row beneath shows ONE palette at a time. */
117
+ .dc-annot-ctx-mode {
118
+ display: inline-flex;
119
+ border-radius: 6px;
120
+ overflow: hidden;
121
+ border: 1px solid rgba(0,0,0,0.18);
122
+ }
123
+ .dc-annot-ctx-mode-btn {
124
+ appearance: none;
125
+ background: transparent;
126
+ border: 0;
127
+ padding: 3px 8px;
128
+ font: inherit;
129
+ color: inherit;
130
+ cursor: pointer;
131
+ font-size: 11px;
132
+ letter-spacing: 0.02em;
133
+ text-transform: uppercase;
134
+ line-height: 1;
135
+ }
136
+ .dc-annot-ctx-mode-btn[aria-pressed="true"] {
137
+ background: color-mix(in oklab, var(--accent, #d63b1f) 14%, transparent);
138
+ color: var(--accent, #d63b1f);
139
+ font-weight: 600;
140
+ }
141
+ .dc-annot-ctx-mode-btn:not([aria-pressed="true"]):hover {
142
+ background: rgba(0,0,0,0.04);
143
+ }
144
+ /* Trailing overflow Delete — quiet by default, expressive on hover.
145
+ Keyboard Backspace remains the primary delete affordance. */
146
+ .dc-annot-ctx-overflow {
147
+ margin-left: auto;
148
+ }
97
149
  `.trim();
98
150
 
99
151
  function ensureToolbarStyles(): void {
@@ -122,6 +174,8 @@ function unionRect(rects: DOMRect[]): { x: number; y: number; w: number; h: numb
122
174
  return { x: xMin, y: yMin, w: xMax - xMin, h: yMax - yMin };
123
175
  }
124
176
 
177
+ type SwatchMode = 'stroke' | 'fill';
178
+
125
179
  export function AnnotationContextToolbar() {
126
180
  ensureToolbarStyles();
127
181
  const annotSel = useAnnotationSelectionOptional();
@@ -129,6 +183,10 @@ export function AnnotationContextToolbar() {
129
183
  const ref = useRef<HTMLDivElement | null>(null);
130
184
  const rafRef = useRef<number | null>(null);
131
185
  const [, force] = useState({});
186
+ // T30 / G_S1 — collapsed Stroke|Fill toggle state. Defaults to 'stroke';
187
+ // auto-reverts to 'stroke' whenever caps.fill is false so the toggle
188
+ // can't get stranded on a hidden mode after the selection changes type.
189
+ const [swatchMode, setSwatchMode] = useState<SwatchMode>('stroke');
132
190
 
133
191
  const selectedStrokes = useMemo<Stroke[]>(() => {
134
192
  if (!annotSel || !store) return [];
@@ -147,7 +205,10 @@ export function AnnotationContextToolbar() {
147
205
  return { color: false, fill: false, thickness: false, fontSize: false };
148
206
  }
149
207
  const allFillable = selectedStrokes.every((s) => s.tool === 'rect' || s.tool === 'ellipse');
150
- const allThickness = selectedStrokes.every((s) => s.tool === 'pen' || s.tool === 'arrow');
208
+ // T20 rect + ellipse now carry stroke weight too.
209
+ const allThickness = selectedStrokes.every(
210
+ (s) => s.tool === 'pen' || s.tool === 'arrow' || s.tool === 'rect' || s.tool === 'ellipse'
211
+ );
151
212
  const anyText = selectedStrokes.some((s) => s.tool === 'text');
152
213
  return {
153
214
  color: true,
@@ -226,7 +287,8 @@ export function AnnotationContextToolbar() {
226
287
  (w: number) => {
227
288
  if (!store) return;
228
289
  for (const s of selectedStrokes) {
229
- if (s.tool === 'pen' || s.tool === 'arrow') {
290
+ // T20 rect + ellipse now carry stroke weight.
291
+ if (s.tool === 'pen' || s.tool === 'arrow' || s.tool === 'rect' || s.tool === 'ellipse') {
230
292
  store.updateStroke(s.id, { width: w } as Partial<Stroke>);
231
293
  }
232
294
  }
@@ -263,13 +325,26 @@ export function AnnotationContextToolbar() {
263
325
  : undefined;
264
326
  const uniqThickness = caps.thickness
265
327
  ? uniformValue(
266
- selectedStrokes.map((s) => (s.tool === 'pen' || s.tool === 'arrow' ? s.width : undefined))
328
+ selectedStrokes.map((s) =>
329
+ s.tool === 'pen' || s.tool === 'arrow' || s.tool === 'rect' || s.tool === 'ellipse'
330
+ ? s.width
331
+ : undefined
332
+ )
267
333
  )
268
334
  : undefined;
269
335
  const uniqFontSize = caps.fontSize
270
336
  ? uniformValue(selectedStrokes.map((s) => (s.tool === 'text' ? s.fontSize : undefined)))
271
337
  : undefined;
272
338
 
339
+ // T30 / G_S1 — when caps.fill is false we never enter fill mode. The
340
+ // useEffect below could call setSwatchMode('stroke') but reading the
341
+ // effective mode inline avoids an extra render cycle.
342
+ const effectiveMode: SwatchMode = caps.fill ? swatchMode : 'stroke';
343
+ const showPalette = effectiveMode === 'stroke' ? PALETTE : FILL_PALETTE;
344
+ const onSwatchClick =
345
+ effectiveMode === 'stroke' ? (c: string) => setColor(c) : (c: string) => setFill(c);
346
+ const activeValue = effectiveMode === 'stroke' ? uniqColor : (uniqFill ?? null);
347
+
273
348
  return (
274
349
  <div
275
350
  ref={ref}
@@ -278,52 +353,60 @@ export function AnnotationContextToolbar() {
278
353
  aria-label="Annotation properties"
279
354
  style={{ display: 'flex', top: -9999, left: -9999 }}
280
355
  >
281
- {PALETTE.map((c) => (
356
+ {caps.fill ? (
357
+ <>
358
+ <div className="dc-annot-ctx-mode" role="radiogroup" aria-label="Swatch target">
359
+ <button
360
+ type="button"
361
+ className="dc-annot-ctx-mode-btn"
362
+ aria-pressed={effectiveMode === 'stroke'}
363
+ onClick={() => setSwatchMode('stroke')}
364
+ >
365
+ Stroke
366
+ </button>
367
+ <button
368
+ type="button"
369
+ className="dc-annot-ctx-mode-btn"
370
+ aria-pressed={effectiveMode === 'fill'}
371
+ onClick={() => setSwatchMode('fill')}
372
+ >
373
+ Fill
374
+ </button>
375
+ </div>
376
+ <div className="dc-annot-ctx-sep" />
377
+ </>
378
+ ) : null}
379
+ {effectiveMode === 'fill' ? (
380
+ <button
381
+ type="button"
382
+ className="dc-annot-ctx-sw dc-annot-ctx-fill--none"
383
+ aria-label="No fill"
384
+ aria-pressed={uniqFill == null}
385
+ title="No fill"
386
+ onClick={() => setFill(null)}
387
+ />
388
+ ) : null}
389
+ {showPalette.map((c) => (
282
390
  <button
283
391
  key={c}
284
392
  type="button"
285
393
  className="dc-annot-ctx-sw"
286
- aria-label={`Color ${c}`}
287
- aria-pressed={uniqColor === c}
288
- title={`Color ${c}`}
394
+ aria-label={`${effectiveMode === 'stroke' ? 'Color' : 'Fill'} ${c}`}
395
+ aria-pressed={activeValue === c}
396
+ title={`${effectiveMode === 'stroke' ? 'Color' : 'Fill'} ${c}`}
289
397
  style={{ background: c }}
290
- onClick={() => setColor(c)}
398
+ onClick={() => onSwatchClick(c)}
291
399
  />
292
400
  ))}
293
- {caps.fill ? (
294
- <>
295
- <div className="dc-annot-ctx-sep" />
296
- <button
297
- type="button"
298
- className="dc-annot-ctx-sw dc-annot-ctx-fill--none"
299
- aria-label="No fill"
300
- aria-pressed={uniqFill == null}
301
- title="No fill"
302
- onClick={() => setFill(null)}
303
- />
304
- {FILL_PALETTE.map((c) => (
305
- <button
306
- key={c}
307
- type="button"
308
- className="dc-annot-ctx-sw"
309
- aria-label={`Fill ${c}`}
310
- aria-pressed={uniqFill === c}
311
- title={`Fill ${c}`}
312
- style={{ background: c }}
313
- onClick={() => setFill(c)}
314
- />
315
- ))}
316
- </>
317
- ) : null}
318
- {caps.thickness ? (
401
+ {caps.thickness && effectiveMode === 'stroke' ? (
319
402
  <>
320
403
  <div className="dc-annot-ctx-sep" />
321
404
  <button
322
405
  type="button"
323
406
  className="dc-annot-ctx-btn"
324
- aria-pressed={uniqThickness === 2}
325
- title="Thin (2px)"
326
- onClick={() => setThickness(2)}
407
+ aria-pressed={uniqThickness === 3}
408
+ title="Thin (3px)"
409
+ onClick={() => setThickness(3)}
327
410
  >
328
411
  Thin
329
412
  </button>
@@ -370,10 +453,9 @@ export function AnnotationContextToolbar() {
370
453
  </button>
371
454
  </>
372
455
  ) : null}
373
- <div className="dc-annot-ctx-sep" />
374
456
  <button
375
457
  type="button"
376
- className="dc-annot-ctx-btn dc-annot-ctx-btn--danger"
458
+ className="dc-annot-ctx-btn dc-annot-ctx-btn--danger dc-annot-ctx-overflow"
377
459
  title="Delete (Backspace)"
378
460
  aria-label="Delete selected annotations"
379
461
  onClick={remove}