@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
|
@@ -1210,6 +1210,22 @@
|
|
|
1210
1210
|
color: var(--u-fg-1);
|
|
1211
1211
|
}
|
|
1212
1212
|
|
|
1213
|
+
.sb-sync {
|
|
1214
|
+
cursor: help;
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
.sb-sync-dot {
|
|
1218
|
+
background: var(--u-status-warn);
|
|
1219
|
+
border-radius: 50%;
|
|
1220
|
+
flex: none;
|
|
1221
|
+
width: 7px;
|
|
1222
|
+
height: 7px;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
.sb-sync .sb-key {
|
|
1226
|
+
color: var(--u-fg-2);
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1213
1229
|
.sb-spacer {
|
|
1214
1230
|
border-right: 1px solid var(--u-border-subtle);
|
|
1215
1231
|
flex: 1 0 auto;
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file dom-selection.ts — selection-from-DOM helpers (leaf module)
|
|
3
|
+
* @scope plugins/design/dev-server/dom-selection.ts
|
|
4
|
+
* @purpose Pure DOM → Selection builders shared by the canvas chrome
|
|
5
|
+
* (canvas-shell.tsx) and the shell-owned comment mount layer
|
|
6
|
+
* (canvas-comment-mount.tsx). Lives in its own leaf module — no
|
|
7
|
+
* React, no canvas-lib import — so both consumers can lift the
|
|
8
|
+
* same `hoverTargetToSelection` / `deriveFile` logic without a
|
|
9
|
+
* cycle and without bundling the heavy DesignCanvas tree into the
|
|
10
|
+
* lite comment mount.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { HoverTarget } from './input-router.tsx';
|
|
14
|
+
import type { Selection } from './use-selection-set.tsx';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Canvas file path for the current page. Under the mount harness the page is
|
|
18
|
+
* `/_canvas-shell.html?canvas=<rel>&designRel=<root>`; for legacy `.html`
|
|
19
|
+
* mocks it's the served file path itself.
|
|
20
|
+
*/
|
|
21
|
+
export function deriveFile(): string | undefined {
|
|
22
|
+
if (typeof window === 'undefined') return undefined;
|
|
23
|
+
try {
|
|
24
|
+
const p = window.location.pathname;
|
|
25
|
+
if (p === '/_canvas-shell.html' || p === '/_canvas-shell') {
|
|
26
|
+
const qs = new URLSearchParams(window.location.search);
|
|
27
|
+
const canvas = qs.get('canvas') ?? '';
|
|
28
|
+
const designRel = (qs.get('designRel') ?? '.design').replace(/^\/+|\/+$/g, '');
|
|
29
|
+
return canvas ? `${designRel}/${canvas}` : undefined;
|
|
30
|
+
}
|
|
31
|
+
return decodeURIComponent(p).replace(/^\//, '');
|
|
32
|
+
} catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function realClasses(el: Element | null): string {
|
|
38
|
+
if (!el) return '';
|
|
39
|
+
return (el.getAttribute('class') ?? '')
|
|
40
|
+
.trim()
|
|
41
|
+
.split(/\s+/)
|
|
42
|
+
.filter((c) => c && !c.startsWith('dgn-') && !c.startsWith('dc-cv-'))
|
|
43
|
+
.join(' ');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function shortText(el: Element | null, max: number): string {
|
|
47
|
+
if (!el) return '';
|
|
48
|
+
const t = ((el as HTMLElement).innerText || el.textContent || '').replace(/\s+/g, ' ').trim();
|
|
49
|
+
return t.length > max ? `${t.slice(0, max - 1)}…` : t;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function cssPath(el: Element | null): string {
|
|
53
|
+
if (!el) return '';
|
|
54
|
+
const path: string[] = [];
|
|
55
|
+
let cur: Element | null = el;
|
|
56
|
+
while (cur && cur.nodeType === 1 && path.length < 8) {
|
|
57
|
+
const dscEl = cur.getAttribute?.('data-dc-element');
|
|
58
|
+
if (dscEl) {
|
|
59
|
+
path.unshift(`[data-dc-element="${dscEl}"]`);
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
const dscSc = cur.getAttribute?.('data-dc-screen');
|
|
63
|
+
if (dscSc) {
|
|
64
|
+
path.unshift(`[data-dc-screen="${dscSc}"]`);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
let sel = cur.nodeName.toLowerCase();
|
|
68
|
+
if (cur.id) {
|
|
69
|
+
sel = `#${cur.id}`;
|
|
70
|
+
path.unshift(sel);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
const cls = realClasses(cur).split(/\s+/).filter(Boolean).slice(0, 2);
|
|
74
|
+
if (cls.length) sel += `.${cls.join('.')}`;
|
|
75
|
+
let sib = 1;
|
|
76
|
+
let n: Element | null = cur.previousElementSibling;
|
|
77
|
+
while (n) {
|
|
78
|
+
sib++;
|
|
79
|
+
n = n.previousElementSibling;
|
|
80
|
+
}
|
|
81
|
+
sel += `:nth-child(${sib})`;
|
|
82
|
+
path.unshift(sel);
|
|
83
|
+
cur = cur.parentElement;
|
|
84
|
+
}
|
|
85
|
+
return path.join(' > ');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function domPath(el: Element | null): string[] {
|
|
89
|
+
const hops: string[] = [];
|
|
90
|
+
let cur = el;
|
|
91
|
+
while (cur && cur.nodeType === 1 && hops.length < 8) {
|
|
92
|
+
let label = cur.nodeName.toLowerCase();
|
|
93
|
+
const dEl = cur.getAttribute?.('data-dc-element');
|
|
94
|
+
const dSc = cur.getAttribute?.('data-dc-screen');
|
|
95
|
+
if (dEl) label += `[data-dc-element="${dEl}"]`;
|
|
96
|
+
else if (dSc) label += `[data-dc-screen="${dSc}"]`;
|
|
97
|
+
else if (cur.id) label += `#${cur.id}`;
|
|
98
|
+
const cls = realClasses(cur).split(/\s+/).filter(Boolean).slice(0, 2);
|
|
99
|
+
if (cls.length && !dEl && !dSc) label += `.${cls.join('.')}`;
|
|
100
|
+
hops.unshift(label);
|
|
101
|
+
cur = cur.parentElement;
|
|
102
|
+
}
|
|
103
|
+
return hops;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function cssEscape(s: string): string {
|
|
107
|
+
// Minimal CSS.escape polyfill — only handles chars actually present in
|
|
108
|
+
// pipeline-stamped IDs (alphanumerics + `-` + `_`).
|
|
109
|
+
return s.replace(/[^a-zA-Z0-9_-]/g, (c) => `\\${c}`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Build the wire-shape `Selection` for a resolved hover target. `file`
|
|
114
|
+
* defaults to `deriveFile()`; the comment mount layer passes it explicitly
|
|
115
|
+
* so all three consumers (router, overlay, mount) agree on the same key.
|
|
116
|
+
*/
|
|
117
|
+
export function hoverTargetToSelection(target: HoverTarget, file?: string): Selection {
|
|
118
|
+
const el = target.el;
|
|
119
|
+
const rect =
|
|
120
|
+
el && (el as HTMLElement).getBoundingClientRect
|
|
121
|
+
? (el as HTMLElement).getBoundingClientRect()
|
|
122
|
+
: null;
|
|
123
|
+
// `cdId` is the hit element's OWN data-cd-id (deep mode); resolver never
|
|
124
|
+
// climbs to an ancestor. Falls back to cssPath of the hit when no stable
|
|
125
|
+
// anchor exists.
|
|
126
|
+
const cdId = target.cdId;
|
|
127
|
+
// Selector resolution order:
|
|
128
|
+
// 1. data-cd-id anchor — stable pipeline-stamped id (preferred).
|
|
129
|
+
// 2. data-dc-screen — chrome click promoted to whole-artboard select
|
|
130
|
+
// (T24.5 G8 multi-artboard gesture).
|
|
131
|
+
// 3. cssPath of the hit — last-resort path string.
|
|
132
|
+
const selector = cdId
|
|
133
|
+
? `[data-cd-id="${cdId}"]`
|
|
134
|
+
: !cdId && target.artboardId
|
|
135
|
+
? `[data-dc-screen="${target.artboardId}"]`
|
|
136
|
+
: cssPath(el);
|
|
137
|
+
return {
|
|
138
|
+
file: file ?? deriveFile(),
|
|
139
|
+
id: cdId ?? undefined,
|
|
140
|
+
selector,
|
|
141
|
+
artboardId: target.artboardId,
|
|
142
|
+
tag: el?.tagName.toLowerCase() ?? '',
|
|
143
|
+
classes: realClasses(el),
|
|
144
|
+
text: shortText(el, 240),
|
|
145
|
+
dom_path: domPath(el),
|
|
146
|
+
bounds: rect
|
|
147
|
+
? {
|
|
148
|
+
x: Math.round(rect.left),
|
|
149
|
+
y: Math.round(rect.top),
|
|
150
|
+
w: Math.round(rect.width),
|
|
151
|
+
h: Math.round(rect.height),
|
|
152
|
+
}
|
|
153
|
+
: null,
|
|
154
|
+
html: el ? (el.outerHTML ?? '').slice(0, 4000) : '',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
@@ -48,7 +48,7 @@ const STYLES = `
|
|
|
48
48
|
.dc-cv-eq-pill {
|
|
49
49
|
position: absolute;
|
|
50
50
|
transform: translate(-50%, calc(-100% - 8px));
|
|
51
|
-
font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
|
|
51
|
+
font-family: var(--maude-chrome-font-mono, ui-monospace, SFMono-Regular, monospace);
|
|
52
52
|
font-size: 10px;
|
|
53
53
|
padding: 2px 5px;
|
|
54
54
|
background: #FF24BD;
|
|
@@ -113,35 +113,35 @@ const ExportDialogContext = createContext<ExportDialogValue | null>(null);
|
|
|
113
113
|
|
|
114
114
|
const DIALOG_CSS = `
|
|
115
115
|
.dc-export-dialog {
|
|
116
|
-
border: 1px solid var(--
|
|
116
|
+
border: 1px solid var(--maude-chrome-fg-0, #1c1917);
|
|
117
117
|
padding: 0;
|
|
118
118
|
border-radius: 8px;
|
|
119
|
-
background: var(--
|
|
120
|
-
box-shadow: 0 6px 24px color-mix(in oklab,
|
|
121
|
-
font-family: var(--
|
|
122
|
-
color: var(--
|
|
119
|
+
background: var(--maude-chrome-bg-0, #fff);
|
|
120
|
+
box-shadow: 0 6px 24px var(--maude-chrome-shadow, color-mix(in oklab, #1c1917 10%, transparent));
|
|
121
|
+
font-family: var(--maude-chrome-font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
|
|
122
|
+
color: var(--maude-chrome-fg-0, #1a1a1a);
|
|
123
123
|
width: min(640px, 100vw - 48px);
|
|
124
124
|
max-height: min(560px, 100vh - 48px);
|
|
125
125
|
overflow: hidden;
|
|
126
126
|
}
|
|
127
127
|
.dc-export-dialog::backdrop { background: rgba(20, 20, 30, 0.32); }
|
|
128
|
-
.dc-export-dialog header { padding: 16px 20px; border-bottom: 1px solid var(--
|
|
128
|
+
.dc-export-dialog header { padding: 16px 20px; border-bottom: 1px solid var(--maude-chrome-border, rgba(0,0,0,0.08)); display: flex; justify-content: space-between; align-items: center; }
|
|
129
129
|
.dc-export-dialog header h2 { margin: 0; font-size: 16px; font-weight: 600; letter-spacing: -0.005em; }
|
|
130
|
-
.dc-export-dialog header .dc-ed-close { background: transparent; border: 0; cursor: pointer; padding: 4px 8px; color: var(--fg-1, rgba(40,30,20,0.6)); font: inherit; font-size: 12px; }
|
|
130
|
+
.dc-export-dialog header .dc-ed-close { background: transparent; border: 0; cursor: pointer; padding: 4px 8px; color: var(--maude-chrome-fg-1, rgba(40,30,20,0.6)); font: inherit; font-size: 12px; }
|
|
131
131
|
.dc-export-dialog .dc-ed-body { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; padding: 16px 20px; }
|
|
132
|
-
.dc-export-dialog label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--fg-
|
|
133
|
-
.dc-export-dialog select { width: 100%; padding: 8px 10px; border-radius: 0; border: 1px solid var(--
|
|
134
|
-
.dc-export-dialog .dc-ed-desc { font-size: 12px; color: var(--fg-1, rgba(40,30,20,0.65)); margin-top: 6px; line-height: 1.4; }
|
|
135
|
-
.dc-export-dialog .dc-ed-recent { padding: 12px 20px; border-top: 1px solid var(--
|
|
136
|
-
.dc-export-dialog .dc-ed-recent h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--fg-
|
|
132
|
+
.dc-export-dialog label { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--maude-chrome-fg-1, rgba(40,30,20,0.5)); margin-bottom: 6px; }
|
|
133
|
+
.dc-export-dialog select { width: 100%; padding: 8px 10px; border-radius: 0; border: 1px solid var(--maude-chrome-fg-0, rgba(0,0,0,0.12)); background: var(--maude-chrome-bg-1, #fafafa); font: inherit; font-size: 13px; color: inherit; }
|
|
134
|
+
.dc-export-dialog .dc-ed-desc { font-size: 12px; color: var(--maude-chrome-fg-1, rgba(40,30,20,0.65)); margin-top: 6px; line-height: 1.4; }
|
|
135
|
+
.dc-export-dialog .dc-ed-recent { padding: 12px 20px; border-top: 1px solid var(--maude-chrome-border, rgba(0,0,0,0.08)); background: var(--maude-chrome-bg-2, rgba(0,0,0,0.02)); }
|
|
136
|
+
.dc-export-dialog .dc-ed-recent h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--maude-chrome-fg-1, rgba(40,30,20,0.5)); margin: 0 0 8px; }
|
|
137
137
|
.dc-export-dialog .dc-ed-recent ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 4px; max-height: 120px; overflow-y: auto; }
|
|
138
138
|
.dc-export-dialog .dc-ed-recent button { display: flex; justify-content: space-between; gap: 12px; padding: 5px 12px; background: transparent; border: 1px solid transparent; border-radius: 0; cursor: pointer; font: inherit; font-size: 12px; color: inherit; width: 100%; text-align: left; }
|
|
139
|
-
.dc-export-dialog .dc-ed-recent button:hover { background:
|
|
140
|
-
.dc-export-dialog footer { padding: 12px 20px; border-top: 1px solid var(--
|
|
141
|
-
.dc-export-dialog footer button { padding: 8px 14px; border-radius: 0; border: 1px solid var(--
|
|
139
|
+
.dc-export-dialog .dc-ed-recent button:hover { background: color-mix(in oklab, var(--maude-chrome-fg-0, #1c1917) 5%, transparent); border-color: color-mix(in oklab, var(--maude-chrome-fg-0, #1c1917) 8%, transparent); }
|
|
140
|
+
.dc-export-dialog footer { padding: 12px 20px; border-top: 1px solid var(--maude-chrome-border, rgba(0,0,0,0.08)); display: flex; justify-content: flex-end; gap: 8px; }
|
|
141
|
+
.dc-export-dialog footer button { padding: 8px 14px; border-radius: 0; border: 1px solid var(--maude-chrome-fg-0, rgba(0,0,0,0.12)); background: var(--maude-chrome-bg-1, #fafafa); font: inherit; font-size: 12px; cursor: pointer; color: inherit; }
|
|
142
142
|
.dc-export-dialog footer button.dc-ed-primary { background: var(--maude-hud-accent, #1a1a1a); color: var(--maude-hud-accent-fg, #fff); border-color: transparent; }
|
|
143
143
|
.dc-export-dialog footer button:disabled { opacity: 0.4; cursor: not-allowed; }
|
|
144
|
-
.dc-export-dialog .dc-ed-status { padding: 8px 20px; font-size: 12px; color: var(--fg-1, rgba(40,30,20,0.65)); border-top: 1px solid var(--
|
|
144
|
+
.dc-export-dialog .dc-ed-status { padding: 8px 20px; font-size: 12px; color: var(--maude-chrome-fg-1, rgba(40,30,20,0.65)); border-top: 1px solid var(--maude-chrome-border, rgba(0,0,0,0.08)); }
|
|
145
145
|
.dc-export-dialog .dc-ed-status.is-error { color: var(--status-error, #c0392b); }
|
|
146
146
|
`;
|
|
147
147
|
|
|
@@ -371,7 +371,9 @@ const DialogShell = (() => {
|
|
|
371
371
|
<span>
|
|
372
372
|
{FORMAT_META[h.format].label} · {SCOPE_META[h.scope].label}
|
|
373
373
|
</span>
|
|
374
|
-
<span style={{ color: 'var(--fg-
|
|
374
|
+
<span style={{ color: 'var(--maude-chrome-fg-1, rgba(40,30,20,0.5))' }}>
|
|
375
|
+
{h.filename}
|
|
376
|
+
</span>
|
|
375
377
|
</button>
|
|
376
378
|
</li>
|
|
377
379
|
))}
|
|
@@ -40,6 +40,7 @@ export function createFsWatch(ctx: Context): FsWatch {
|
|
|
40
40
|
// Skip our own runtime artifacts.
|
|
41
41
|
if (filename.startsWith('_server.json')) return;
|
|
42
42
|
if (filename.startsWith('_active.json')) return;
|
|
43
|
+
if (filename.startsWith('_sync.json')) return;
|
|
43
44
|
if (filename.startsWith(`_history${path.sep}`) || filename.startsWith('_history/')) return;
|
|
44
45
|
emit(filename);
|
|
45
46
|
});
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
// loaded via the importmap + Bun.build-produced ESM — there's no React Fast
|
|
18
18
|
// Refresh runtime to register with. Full-reload is the reliable path.
|
|
19
19
|
|
|
20
|
+
import { existsSync } from 'node:fs';
|
|
20
21
|
import path from 'node:path';
|
|
21
22
|
|
|
22
23
|
import type { Context } from './context.ts';
|
|
@@ -64,26 +65,11 @@ export function createHmrBroadcaster(
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
function classify(filename: string): HmrMessage | null {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
73
|
-
if (ext === '.css') {
|
|
74
|
-
return { type: 'canvas-hmr', mode: 'css', file: rel, version, scope: 'canvas' };
|
|
75
|
-
}
|
|
76
|
-
// Phase 8 — canvas-meta sidecar (`<base>.meta.json`) carries the
|
|
77
|
-
// artboard layout / viewport. Emit a `meta` mode so foreign tabs can
|
|
78
|
-
// re-fetch + re-apply the layout WITHOUT a full reload (which would
|
|
79
|
-
// lose React state like tool mode, undo stack, scroll position).
|
|
80
|
-
if (rel.endsWith('.meta.json')) {
|
|
81
|
-
return { type: 'canvas-hmr', mode: 'meta', file: rel, version, scope: 'canvas' };
|
|
82
|
-
}
|
|
83
|
-
if (ext === '.tsx' || ext === '.jsx' || ext === '.ts' || ext === '.js') {
|
|
84
|
-
return { type: 'canvas-hmr', mode: 'module', file: rel, version, scope: 'canvas' };
|
|
85
|
-
}
|
|
86
|
-
return null;
|
|
68
|
+
return classifyChange(filename, (cssRel) => {
|
|
69
|
+
const root = ctx.paths?.designRoot;
|
|
70
|
+
if (!root) return false; // no root resolved → can't probe; keep css swap
|
|
71
|
+
return existsSync(path.join(root, cssRel.replace(/\.css$/i, '.tsx')));
|
|
72
|
+
});
|
|
87
73
|
}
|
|
88
74
|
|
|
89
75
|
function enqueue(msg: HmrMessage) {
|
|
@@ -125,3 +111,48 @@ export function createHmrBroadcaster(
|
|
|
125
111
|
// Helpers — exported for tests.
|
|
126
112
|
|
|
127
113
|
export const HMR_DEBOUNCE_MS = DEBOUNCE_MS;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Classify a changed design-root-relative path into an HMR message. Pure +
|
|
117
|
+
* fs-injected (`hasSiblingTsx`) so it unit-tests without touching disk.
|
|
118
|
+
*
|
|
119
|
+
* CSS routing is the load-bearing part. A canvas/specimen sibling stylesheet
|
|
120
|
+
* (e.g. `system/x/preview/motion.css` next to `motion.tsx`, pulled in via
|
|
121
|
+
* `import './motion.css'`) is INLINED into the built module as a `<style>` tag
|
|
122
|
+
* by canvas-build.ts — there is NO `<link>` for it. The iframe's `mode:'css'`
|
|
123
|
+
* handler swaps `<link href>` only, so a link swap for inlined CSS is a silent
|
|
124
|
+
* no-op and the edit never reaches the browser (the bug this fixes). For those
|
|
125
|
+
* we emit `mode:'module'` keyed on the sibling `.tsx`, so the open canvas
|
|
126
|
+
* reloads and re-inlines the fresh CSS via the existing module-reload path.
|
|
127
|
+
* Link-mounted CSS (DS tokens, `_components.css`, `_layout.css` — no sibling
|
|
128
|
+
* `.tsx`) keeps the fast, state-preserving css swap.
|
|
129
|
+
*/
|
|
130
|
+
export function classifyChange(
|
|
131
|
+
filename: string,
|
|
132
|
+
hasSiblingTsx: (cssRel: string) => boolean
|
|
133
|
+
): HmrMessage | null {
|
|
134
|
+
const rel = filename.replace(/\\/g, '/');
|
|
135
|
+
const ext = path.extname(rel).toLowerCase();
|
|
136
|
+
const version = Date.now();
|
|
137
|
+
if (rel.startsWith('_lib/')) {
|
|
138
|
+
return { type: 'canvas-hmr', mode: 'hard', scope: 'lib', version };
|
|
139
|
+
}
|
|
140
|
+
if (ext === '.css') {
|
|
141
|
+
if (hasSiblingTsx(rel)) {
|
|
142
|
+
const siblingTsx = rel.replace(/\.css$/i, '.tsx');
|
|
143
|
+
return { type: 'canvas-hmr', mode: 'module', file: siblingTsx, version, scope: 'canvas' };
|
|
144
|
+
}
|
|
145
|
+
return { type: 'canvas-hmr', mode: 'css', file: rel, version, scope: 'canvas' };
|
|
146
|
+
}
|
|
147
|
+
// Phase 8 — canvas-meta sidecar (`<base>.meta.json`) carries the
|
|
148
|
+
// artboard layout / viewport. Emit a `meta` mode so foreign tabs can
|
|
149
|
+
// re-fetch + re-apply the layout WITHOUT a full reload (which would
|
|
150
|
+
// lose React state like tool mode, undo stack, scroll position).
|
|
151
|
+
if (rel.endsWith('.meta.json')) {
|
|
152
|
+
return { type: 'canvas-hmr', mode: 'meta', file: rel, version, scope: 'canvas' };
|
|
153
|
+
}
|
|
154
|
+
if (ext === '.tsx' || ext === '.jsx' || ext === '.ts' || ext === '.js') {
|
|
155
|
+
return { type: 'canvas-hmr', mode: 'module', file: rel, version, scope: 'canvas' };
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|