@1agh/maude 0.16.0 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +4 -0
  2. package/cli/cli-wrapper.cjs +0 -0
  3. package/cli/commands/design.mjs +264 -16
  4. package/package.json +12 -18
  5. package/plugins/design/dev-server/annotations-context-toolbar.tsx +8 -8
  6. package/plugins/design/dev-server/annotations-layer.tsx +8 -10
  7. package/plugins/design/dev-server/api.ts +41 -0
  8. package/plugins/design/dev-server/bin/_enumerate-artboards-playwright.mjs +40 -0
  9. package/plugins/design/dev-server/bin/_html-playwright.mjs +129 -0
  10. package/plugins/design/dev-server/bin/_pdf-playwright.mjs +105 -0
  11. package/plugins/design/dev-server/bin/_png-playwright.mjs +143 -0
  12. package/plugins/design/dev-server/bin/_pptx-playwright.mjs +98 -0
  13. package/plugins/design/dev-server/bin/_svg-playwright.mjs +141 -0
  14. package/plugins/design/dev-server/build.ts +118 -6
  15. package/plugins/design/dev-server/canvas-lib.tsx +12 -13
  16. package/plugins/design/dev-server/canvas-pipeline.ts +5 -0
  17. package/plugins/design/dev-server/canvas-shell.tsx +32 -4
  18. package/plugins/design/dev-server/client/app.jsx +18 -1
  19. package/plugins/design/dev-server/context-menu.tsx +36 -9
  20. package/plugins/design/dev-server/dist/client.bundle.js +11 -3
  21. package/plugins/design/dev-server/export-dialog.tsx +401 -0
  22. package/plugins/design/dev-server/exporters/_browser-bundles.ts +89 -0
  23. package/plugins/design/dev-server/exporters/canva-handoff-prompt.ts +74 -0
  24. package/plugins/design/dev-server/exporters/canva.ts +126 -0
  25. package/plugins/design/dev-server/exporters/html.ts +103 -0
  26. package/plugins/design/dev-server/exporters/index.ts +135 -0
  27. package/plugins/design/dev-server/exporters/pdf.ts +109 -0
  28. package/plugins/design/dev-server/exporters/png.ts +136 -0
  29. package/plugins/design/dev-server/exporters/pptx.ts +263 -0
  30. package/plugins/design/dev-server/exporters/scope.ts +196 -0
  31. package/plugins/design/dev-server/exporters/svg.ts +122 -0
  32. package/plugins/design/dev-server/exporters/zip.ts +109 -0
  33. package/plugins/design/dev-server/http.ts +80 -0
  34. package/plugins/design/dev-server/inspect.ts +1 -1
  35. package/plugins/design/dev-server/server.mjs +1 -1
  36. package/plugins/design/dev-server/test/compile-entry.test.ts +134 -0
  37. package/plugins/design/dev-server/test/exporters/canva.test.ts +64 -0
  38. package/plugins/design/dev-server/test/exporters/endpoint.test.ts +121 -0
  39. package/plugins/design/dev-server/test/exporters/history.test.ts +79 -0
  40. package/plugins/design/dev-server/test/exporters/html.test.ts +26 -0
  41. package/plugins/design/dev-server/test/exporters/pdf.test.ts +53 -0
  42. package/plugins/design/dev-server/test/exporters/png.test.ts +32 -0
  43. package/plugins/design/dev-server/test/exporters/pptx.test.ts +31 -0
  44. package/plugins/design/dev-server/test/exporters/scope.test.ts +0 -0
  45. package/plugins/design/dev-server/test/exporters/svg.test.ts +29 -0
  46. package/plugins/design/dev-server/test/exporters/zip.test.ts +105 -0
  47. package/plugins/design/dev-server/tool-palette.tsx +34 -16
  48. package/plugins/design/templates/_shell.html +33 -0
@@ -0,0 +1,29 @@
1
+ // Phase 6.5 T4 — SVG adapter contract tests.
2
+ //
3
+ // Real Playwright walk is integration-shape (lands as scenario). Here we
4
+ // cover empty-input + file-tree-rejection.
5
+
6
+ import { describe, expect, test } from 'bun:test';
7
+
8
+ import { run } from '../../exporters/svg.ts';
9
+
10
+ const CTX = {
11
+ designRoot: '/tmp/.design',
12
+ repoRoot: '/tmp',
13
+ serverOrigin: 'http://localhost:0',
14
+ };
15
+
16
+ describe('svg adapter — contract', () => {
17
+ test('empty targets → zero-byte SVG placeholder', async () => {
18
+ const r = await run([], {}, CTX);
19
+ expect(r.contentType).toBe('image/svg+xml');
20
+ expect(r.body.byteLength).toBe(0);
21
+ expect(r.filename.endsWith('.svg')).toBe(true);
22
+ });
23
+
24
+ test('file-tree targets → throws', async () => {
25
+ await expect(run([{ kind: 'file-tree', paths: ['ui/Home.tsx'] }], {}, CTX)).rejects.toThrow(
26
+ /element targets/i
27
+ );
28
+ });
29
+ });
@@ -0,0 +1,105 @@
1
+ // Phase 6.5 T7 — project-raw ZIP adapter tests.
2
+ //
3
+ // Real walk + bundle against a sandboxed designRoot, then unzip the result
4
+ // and diff against expected contents.
5
+
6
+ import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
7
+ import { tmpdir } from 'node:os';
8
+ import { join } from 'node:path';
9
+
10
+ import { describe, expect, test } from 'bun:test';
11
+ import JSZip from 'jszip';
12
+
13
+ import { resolveScope } from '../../exporters/scope.ts';
14
+ import { run } from '../../exporters/zip.ts';
15
+
16
+ function setupTree(): { root: string; designRoot: string } {
17
+ const root = mkdtempSync(join(tmpdir(), 'zip-adapter-'));
18
+ const designRoot = join(root, '.design');
19
+ mkdirSync(join(designRoot, 'ui'), { recursive: true });
20
+ mkdirSync(join(designRoot, 'system', 'project'), { recursive: true });
21
+ mkdirSync(join(designRoot, '_history', 'old'), { recursive: true });
22
+ writeFileSync(join(designRoot, 'config.json'), '{"name":"x"}');
23
+ writeFileSync(join(designRoot, 'README.md'), '# proj');
24
+ writeFileSync(join(designRoot, 'ui', 'Home.tsx'), 'export default ()=>null');
25
+ writeFileSync(join(designRoot, 'system', 'project', 'README.md'), '# ds');
26
+ writeFileSync(join(designRoot, '_history', 'old', 'snap.tsx'), '// snap');
27
+ writeFileSync(join(designRoot, '.DS_Store'), ' ');
28
+ return { root, designRoot };
29
+ }
30
+
31
+ describe('zip adapter — project-raw', () => {
32
+ test('bundles designRoot and excludes runtime artefacts by default', async () => {
33
+ const { root, designRoot } = setupTree();
34
+ const targets = await resolveScope({
35
+ scope: 'project-raw',
36
+ activeJson: { active: null, selected: null },
37
+ designRoot,
38
+ repoRoot: root,
39
+ });
40
+ const r = await run(targets, {}, { designRoot, repoRoot: root, serverOrigin: '' });
41
+ expect(r.contentType).toBe('application/zip');
42
+ expect(r.body.byteLength).toBeGreaterThan(0);
43
+
44
+ const unzipped = await JSZip.loadAsync(r.body);
45
+ const names = Object.keys(unzipped.files).sort();
46
+ expect(names).toContain('config.json');
47
+ expect(names).toContain('README.md');
48
+ expect(names).toContain('ui/Home.tsx');
49
+ expect(names).toContain('system/project/README.md');
50
+ expect(names).not.toContain('.DS_Store');
51
+ expect(names.every((n) => !n.startsWith('_history/'))).toBe(true);
52
+ });
53
+
54
+ test('options.exclude prunes additional paths', async () => {
55
+ const { root, designRoot } = setupTree();
56
+ const targets = await resolveScope({
57
+ scope: 'project-raw',
58
+ activeJson: { active: null, selected: null },
59
+ designRoot,
60
+ repoRoot: root,
61
+ });
62
+ const r = await run(
63
+ targets,
64
+ { exclude: ['ui/**'] },
65
+ { designRoot, repoRoot: root, serverOrigin: '' }
66
+ );
67
+ const unzipped = await JSZip.loadAsync(r.body);
68
+ const names = Object.keys(unzipped.files);
69
+ expect(names.every((n) => !n.startsWith('ui/'))).toBe(true);
70
+ expect(names).toContain('system/project/README.md');
71
+ });
72
+
73
+ test('options.include narrows to a single subtree', async () => {
74
+ const { root, designRoot } = setupTree();
75
+ const targets = await resolveScope({
76
+ scope: 'project-raw',
77
+ activeJson: { active: null, selected: null },
78
+ designRoot,
79
+ repoRoot: root,
80
+ });
81
+ const r = await run(
82
+ targets,
83
+ { include: ['system'] },
84
+ { designRoot, repoRoot: root, serverOrigin: '' }
85
+ );
86
+ const unzipped = await JSZip.loadAsync(r.body);
87
+ const names = Object.keys(unzipped.files);
88
+ expect(names.every((n) => n.startsWith('system/'))).toBe(true);
89
+ });
90
+
91
+ test('empty targets → zero-byte ZIP placeholder', async () => {
92
+ const r = await run([], {}, { designRoot: '/tmp/.design', repoRoot: '/tmp', serverOrigin: '' });
93
+ expect(r.body.byteLength).toBe(0);
94
+ });
95
+
96
+ test('element targets → throws', async () => {
97
+ await expect(
98
+ run(
99
+ [{ kind: 'element', cssPath: '.x', canvasSlug: 'x', file: 'ui/x.tsx' }],
100
+ {},
101
+ { designRoot: '/tmp/.design', repoRoot: '/tmp', serverOrigin: '' }
102
+ )
103
+ ).rejects.toThrow(/file-tree targets/i);
104
+ });
105
+ });
@@ -32,13 +32,13 @@ const PALETTE_CSS = `
32
32
  transform: translateX(-50%);
33
33
  display: flex;
34
34
  align-items: stretch;
35
- background: var(--bg-1, rgba(255,255,255,0.98));
36
- border: 1px solid var(--u-border-2, rgba(0,0,0,0.08));
37
- border-radius: 8px;
38
- box-shadow: 0 6px 24px rgba(0,0,0,0.08);
39
- font-family: ui-sans-serif, system-ui, sans-serif;
35
+ background: var(--u-bg-2, var(--bg-1, rgba(255,255,255,0.98)));
36
+ border: 1px solid var(--u-fg-0, #1c1917);
37
+ border-radius: 0;
38
+ box-shadow: 4px 4px 0 var(--u-fg-0, #1c1917);
39
+ font-family: var(--u-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
40
40
  font-size: 12px;
41
- color: var(--fg-0, #1a1a1a);
41
+ color: var(--u-fg-0, var(--fg-0, #1a1a1a));
42
42
  z-index: 6;
43
43
  user-select: none;
44
44
  /* Intentionally NO overflow:hidden — the zoom popover (.dc-tp-popover) is
@@ -54,14 +54,14 @@ const PALETTE_CSS = `
54
54
  }
55
55
  .dc-tool-palette .dc-tp-sep {
56
56
  width: 1px;
57
- background: var(--u-border-3, rgba(0,0,0,0.08));
57
+ background: var(--u-border-subtle, rgba(0,0,0,0.08));
58
58
  margin: 6px 0;
59
59
  }
60
60
  .dc-tool-palette button {
61
61
  appearance: none;
62
62
  background: transparent;
63
63
  border: 0;
64
- border-radius: 6px;
64
+ border-radius: 0;
65
65
  padding: 0;
66
66
  width: 32px;
67
67
  height: 32px;
@@ -85,10 +85,10 @@ const PALETTE_CSS = `
85
85
  min-width: 56px;
86
86
  padding: 0 8px;
87
87
  font-variant-numeric: tabular-nums;
88
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
88
+ font-family: var(--u-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
89
89
  font-size: 11px;
90
90
  letter-spacing: 0.04em;
91
- border-radius: 6px;
91
+ border-radius: 0;
92
92
  display: inline-flex;
93
93
  align-items: center;
94
94
  gap: 4px;
@@ -98,21 +98,22 @@ const PALETTE_CSS = `
98
98
  position: absolute;
99
99
  right: 4px;
100
100
  bottom: 44px;
101
- background: var(--bg-1, rgba(255,255,255,0.98));
102
- border: 1px solid var(--u-border-2, rgba(0,0,0,0.08));
103
- border-radius: 8px;
104
- box-shadow: 0 6px 24px rgba(0,0,0,0.08);
101
+ background: var(--u-bg-2, var(--bg-1, rgba(255,255,255,0.98)));
102
+ border: 1px solid var(--u-fg-0, #1c1917);
103
+ border-radius: 0;
104
+ box-shadow: 4px 4px 0 var(--u-fg-0, #1c1917);
105
105
  display: flex;
106
106
  flex-direction: column;
107
107
  padding: 4px;
108
108
  min-width: 160px;
109
109
  z-index: 7;
110
+ font-family: var(--u-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
110
111
  }
111
112
  .dc-tp-popover button {
112
113
  appearance: none;
113
114
  background: transparent;
114
115
  border: 0;
115
- border-radius: 6px;
116
+ border-radius: 0;
116
117
  padding: 6px 10px;
117
118
  text-align: left;
118
119
  font: inherit;
@@ -121,7 +122,6 @@ const PALETTE_CSS = `
121
122
  display: flex;
122
123
  justify-content: space-between;
123
124
  gap: 16px;
124
- font-family: ui-sans-serif, system-ui, sans-serif;
125
125
  width: 100%;
126
126
  }
127
127
  .dc-tp-popover button:hover { background: rgba(0,0,0,0.04); }
@@ -201,6 +201,24 @@ export function ToolPalette() {
201
201
  </div>
202
202
  <div className="dc-tp-sep" />
203
203
  <div className="dc-tp-group">
204
+ <button
205
+ type="button"
206
+ aria-label="Export (⌘E)"
207
+ title="Export (⌘E)"
208
+ onClick={() => {
209
+ // Phase 6.5 T9 — dispatch the same custom event the context-menu
210
+ // uses; the dialog provider opens with the default scope.
211
+ try {
212
+ window.dispatchEvent(new CustomEvent('maude:open-export', { detail: {} }));
213
+ } catch {
214
+ /* ignore — non-window environments */
215
+ }
216
+ }}
217
+ >
218
+ <span aria-hidden="true" style={{ fontSize: 14, lineHeight: 1 }}>
219
+
220
+ </span>
221
+ </button>
204
222
  <button
205
223
  type="button"
206
224
  aria-label={
@@ -43,6 +43,32 @@
43
43
  }
44
44
  #canvas-mount-error:empty { display: none; }
45
45
  </style>
46
+ <!-- Phase 6.5 export — when the shell is loaded with `&hide-chrome=1`
47
+ (the exporters set this), suppress every dev-server overlay so the
48
+ capture only carries the artboard content. The runtime chrome —
49
+ tool palette, mini-world-map, comment pins, draw layer, snap guides,
50
+ selection halos, the artboard label button — is not part of the
51
+ design and would otherwise show up in PNG / PDF / SVG exports. -->
52
+ <style id="canvas-hide-chrome" media="not all">
53
+ .dc-tool-palette,
54
+ .dc-cv-halo,
55
+ .dc-cv-group-bbox,
56
+ .dc-mini-map,
57
+ .dc-mini-world-map,
58
+ .dc-world-map,
59
+ .dc-snap-guide,
60
+ .dc-annot-overlay,
61
+ .dc-annot-svg,
62
+ .dc-annot-chrome,
63
+ .dc-annot-ctx,
64
+ .cm-layer,
65
+ .cm-pin,
66
+ .cm-thread,
67
+ .cm-composer,
68
+ .dc-artboard-label,
69
+ [data-dc-overlay],
70
+ [data-mdcc-annotations] { display: none !important; }
71
+ </style>
46
72
  <!-- Tokens + DS component CSS — loaded by the host app via the canvas's
47
73
  .meta.json (set later via window.parent postMessage, OR by the dev-
48
74
  server when it knows the active DS). For now we load both common
@@ -73,6 +99,13 @@
73
99
  const tokensRel = params.get('tokens') || '';
74
100
  const componentsRel = params.get('components') || '';
75
101
  const layoutRel = params.get('layout') || '';
102
+ // Phase 6.5 — exporters pass ?hide-chrome=1 to flip the export-mode
103
+ // stylesheet from `media="not all"` to `media="all"`, hiding the
104
+ // dev-server overlays during capture. See `<style id="canvas-hide-chrome">`.
105
+ if (params.get('hide-chrome') === '1') {
106
+ const styleEl = document.getElementById('canvas-hide-chrome');
107
+ if (styleEl) styleEl.media = 'all';
108
+ }
76
109
 
77
110
  function showError(msg) {
78
111
  const el = document.getElementById('canvas-mount-error');