@1agh/maude 0.27.0 → 0.28.1
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/cli/commands/design-link.test.mjs +46 -0
- package/cli/commands/design.mjs +69 -3
- package/cli/commands/design.test.mjs +97 -1
- package/cli/commands/doctor.mjs +110 -5
- package/cli/commands/doctor.test.mjs +52 -0
- package/cli/lib/cache.mjs +1 -1
- package/cli/lib/config-lint.mjs +1 -1
- package/cli/lib/copy-tree.mjs +1 -1
- package/cli/lib/design-link.mjs +39 -9
- package/cli/lib/flow-design-integration.test.mjs +1 -1
- package/cli/lib/gitignore-block.test.mjs +2 -2
- package/cli/lib/stack-detect.mjs +1 -1
- package/package.json +9 -9
- package/plugins/design/dev-server/activity.ts +256 -0
- package/plugins/design/dev-server/ai-banner.tsx +4 -0
- package/plugins/design/dev-server/annotations-context-toolbar.tsx +1 -1
- package/plugins/design/dev-server/annotations-layer.tsx +3 -3
- package/plugins/design/dev-server/api.ts +1 -1
- package/plugins/design/dev-server/artboard-activity-overlay.tsx +67 -0
- package/plugins/design/dev-server/bin/scenario-report.mjs +1 -1
- package/plugins/design/dev-server/bin/server-up.sh +102 -9
- package/plugins/design/dev-server/bin/visual-sanity.sh +19 -3
- package/plugins/design/dev-server/canvas-comment-mount.tsx +165 -10
- package/plugins/design/dev-server/canvas-cursors.ts +1 -1
- package/plugins/design/dev-server/canvas-lib.tsx +72 -36
- package/plugins/design/dev-server/canvas-shell.tsx +1 -1
- package/plugins/design/dev-server/collab/awareness-bridge.ts +1 -1
- package/plugins/design/dev-server/collab/index.ts +4 -4
- package/plugins/design/dev-server/collab/persistence.ts +1 -1
- package/plugins/design/dev-server/config.schema.json +2 -1
- package/plugins/design/dev-server/context-menu.tsx +1 -1
- package/plugins/design/dev-server/dist/client.bundle.js +19 -19
- package/plugins/design/dev-server/dist/comment-mount.js +84 -6
- package/plugins/design/dev-server/dist/runtime/lib0_decoding.js +1 -620
- package/plugins/design/dev-server/dist/runtime/lib0_encoding.js +1 -604
- package/plugins/design/dev-server/dist/runtime/motion.js +4 -4944
- package/plugins/design/dev-server/dist/runtime/motion_react.js +10 -10054
- package/plugins/design/dev-server/dist/runtime/pixi-js.js +2775 -52482
- package/plugins/design/dev-server/dist/runtime/react-dom.js +1 -231
- package/plugins/design/dev-server/dist/runtime/react-dom_client.js +9 -10357
- package/plugins/design/dev-server/dist/runtime/react.js +1 -535
- package/plugins/design/dev-server/dist/runtime/react_jsx-dev-runtime.js +1 -60
- package/plugins/design/dev-server/dist/runtime/react_jsx-runtime.js +1 -85
- package/plugins/design/dev-server/dist/runtime/y-protocols_awareness.js +1 -376
- package/plugins/design/dev-server/dist/runtime/y-protocols_sync.js +1 -100
- package/plugins/design/dev-server/dist/runtime/yjs.js +5 -6687
- package/plugins/design/dev-server/draw/brush.ts +3 -3
- package/plugins/design/dev-server/draw/index.ts +5 -5
- package/plugins/design/dev-server/draw/layout.ts +1 -1
- package/plugins/design/dev-server/draw/test/geometry.test.ts +1 -1
- package/plugins/design/dev-server/draw/test/layout.test.ts +1 -1
- package/plugins/design/dev-server/draw/test/primitives.test.ts +1 -1
- package/plugins/design/dev-server/draw/test/serialize.test.ts +1 -1
- package/plugins/design/dev-server/equal-spacing-handles.tsx +1 -1
- package/plugins/design/dev-server/examples/perf-100-artboards.tsx +1 -2
- package/plugins/design/dev-server/export-dialog.tsx +1 -1
- package/plugins/design/dev-server/exporters/html.ts +1 -1
- package/plugins/design/dev-server/exporters/index.ts +2 -2
- package/plugins/design/dev-server/exporters/pdf.ts +1 -1
- package/plugins/design/dev-server/exporters/png.ts +1 -1
- package/plugins/design/dev-server/exporters/pptx.ts +1 -1
- package/plugins/design/dev-server/exporters/svg.ts +1 -1
- package/plugins/design/dev-server/http.ts +1 -1
- package/plugins/design/dev-server/inspect.ts +31 -1
- package/plugins/design/dev-server/marquee-overlay.tsx +1 -1
- package/plugins/design/dev-server/participants-chrome.tsx +86 -1
- package/plugins/design/dev-server/server.ts +11 -2
- package/plugins/design/dev-server/sync/agent.ts +1 -1
- package/plugins/design/dev-server/sync/index.ts +29 -24
- package/plugins/design/dev-server/sync/materialize.ts +2 -2
- package/plugins/design/dev-server/sync/migrate-seed.ts +1 -1
- package/plugins/design/dev-server/sync/projection.ts +3 -3
- package/plugins/design/dev-server/test/activity.test.ts +195 -0
- package/plugins/design/dev-server/test/ai-activity.test.ts +1 -1
- package/plugins/design/dev-server/test/annotations-api.test.ts +1 -2
- package/plugins/design/dev-server/test/annotations-draw-modifiers.test.ts +2 -2
- package/plugins/design/dev-server/test/annotations-layer.test.ts +6 -6
- package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +1 -1
- package/plugins/design/dev-server/test/artboard-activity-overlay.test.tsx +56 -0
- package/plugins/design/dev-server/test/boot-self-heal.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-cursors.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-edit.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-header.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-hmr-runtime.test.tsx +114 -0
- package/plugins/design/dev-server/test/canvas-lib-inline.test.ts +1 -1
- package/plugins/design/dev-server/test/canvas-lib-resolver.test.ts +1 -2
- package/plugins/design/dev-server/test/canvas-meta-api.test.ts +1 -2
- package/plugins/design/dev-server/test/canvas-origin-gate.test.ts +1 -2
- package/plugins/design/dev-server/test/collab-annotations-bridge.test.ts +1 -1
- package/plugins/design/dev-server/test/collab-bridge.test.ts +1 -1
- package/plugins/design/dev-server/test/collab-protocol.test.ts +2 -2
- package/plugins/design/dev-server/test/collab-room.test.ts +1 -1
- package/plugins/design/dev-server/test/collab-stress.test.ts +1 -1
- package/plugins/design/dev-server/test/comments-api.test.ts +1 -2
- package/plugins/design/dev-server/test/compile-entry.test.ts +1 -2
- package/plugins/design/dev-server/test/exporters/canva.test.ts +1 -2
- package/plugins/design/dev-server/test/exporters/history.test.ts +1 -2
- package/plugins/design/dev-server/test/exporters/pw-launch.test.ts +1 -1
- package/plugins/design/dev-server/test/exporters/scope.test.ts +1 -2
- package/plugins/design/dev-server/test/exporters/zip.test.ts +1 -2
- package/plugins/design/dev-server/test/git-lifecycle.test.ts +1 -2
- package/plugins/design/dev-server/test/handoff-static-frames.test.ts +1 -1
- package/plugins/design/dev-server/test/handoff.test.ts +1 -1
- package/plugins/design/dev-server/test/hmr-broadcast.test.ts +1 -1
- package/plugins/design/dev-server/test/input-router.test.ts +2 -2
- package/plugins/design/dev-server/test/locator.test.ts +1 -1
- package/plugins/design/dev-server/test/runtime-bundle.test.ts +1 -1
- package/plugins/design/dev-server/test/shared-doc-convergence.test.ts +1 -1
- package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +2 -3
- package/plugins/design/dev-server/test/shared-doc-projection.test.ts +1 -1
- package/plugins/design/dev-server/test/snap-distance-pill.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-agent.test.ts +2 -3
- package/plugins/design/dev-server/test/sync-atomic-write.test.ts +2 -3
- package/plugins/design/dev-server/test/sync-codec.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-connection-state.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-echo-guard.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-fs-mirror.test.ts +1 -2
- package/plugins/design/dev-server/test/sync-hardening.test.ts +4 -5
- package/plugins/design/dev-server/test/sync-hubs-config.test.ts +1 -2
- package/plugins/design/dev-server/test/sync-meta-codec.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-runtime.test.ts +37 -14
- package/plugins/design/dev-server/test/sync-status.test.ts +1 -1
- package/plugins/design/dev-server/test/sync-untrusted.test.ts +1 -2
- package/plugins/design/dev-server/test/undo-stack.test.ts +2 -2
- package/plugins/design/dev-server/test/use-agent-presence.test.tsx +117 -0
- package/plugins/design/dev-server/test/use-artboard-drag.test.ts +3 -3
- package/plugins/design/dev-server/test/use-canvas-activity.test.tsx +206 -0
- package/plugins/design/dev-server/test/use-selection-set.test.tsx +1 -1
- package/plugins/design/dev-server/test/use-snap-guides.test.ts +1 -1
- package/plugins/design/dev-server/test/use-undo-stack.test.tsx +2 -2
- package/plugins/design/dev-server/use-agent-presence.tsx +244 -0
- package/plugins/design/dev-server/use-annotation-resize.tsx +1 -5
- package/plugins/design/dev-server/use-annotation-selection.tsx +2 -6
- package/plugins/design/dev-server/use-annotations-visibility.tsx +1 -1
- package/plugins/design/dev-server/use-artboard-drag.tsx +1 -1
- package/plugins/design/dev-server/use-canvas-activity.tsx +252 -0
- package/plugins/design/dev-server/use-collab.tsx +3 -4
- package/plugins/design/dev-server/use-selection-set.tsx +1 -1
- package/plugins/design/dev-server/use-tool-mode.tsx +1 -1
- package/plugins/design/dev-server/use-undo-stack.tsx +4 -5
- package/plugins/design/dev-server/ws.ts +21 -2
- package/plugins/design/templates/_shell.html +108 -3
- package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +18 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agh/maude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.1",
|
|
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": {
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"prepublishOnly": "bash scripts/check-version-parity.sh && bash plugins/design/dev-server/bin/check-runtime-bundles.sh"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@1agh/maude-darwin-arm64": "0.
|
|
45
|
-
"@1agh/maude-darwin-x64": "0.
|
|
46
|
-
"@1agh/maude-linux-arm64": "0.
|
|
47
|
-
"@1agh/maude-linux-arm64-musl": "0.
|
|
48
|
-
"@1agh/maude-linux-x64": "0.
|
|
49
|
-
"@1agh/maude-linux-x64-musl": "0.
|
|
50
|
-
"@1agh/maude-win32-x64": "0.
|
|
44
|
+
"@1agh/maude-darwin-arm64": "0.28.1",
|
|
45
|
+
"@1agh/maude-darwin-x64": "0.28.1",
|
|
46
|
+
"@1agh/maude-linux-arm64": "0.28.1",
|
|
47
|
+
"@1agh/maude-linux-arm64-musl": "0.28.1",
|
|
48
|
+
"@1agh/maude-linux-x64": "0.28.1",
|
|
49
|
+
"@1agh/maude-linux-x64-musl": "0.28.1",
|
|
50
|
+
"@1agh/maude-win32-x64": "0.28.1"
|
|
51
51
|
},
|
|
52
52
|
"files": [
|
|
53
53
|
"cli",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"scaffold"
|
|
86
86
|
],
|
|
87
87
|
"devDependencies": {
|
|
88
|
-
"@biomejs/biome": "^
|
|
88
|
+
"@biomejs/biome": "^2.4.16",
|
|
89
89
|
"@changesets/cli": "^2.27.10",
|
|
90
90
|
"@playwright/test": "^1.60.0"
|
|
91
91
|
},
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// Canvas activity tracker (Phase 13 / DDR-029).
|
|
2
|
+
//
|
|
3
|
+
// Subscribes to the existing `fs:any` bus event, filters for canvas-shaped
|
|
4
|
+
// files under <designRoot>, and maintains a per-file `active | idle` status with
|
|
5
|
+
// a debounce: rapid saves stay `active`, ACTIVITY_IDLE_MS of fs silence flips a
|
|
6
|
+
// file to `idle`. Every transition emits `activity:change` on the bus, which
|
|
7
|
+
// ws.ts forwards to canvas iframes as `{ type: 'activity', … }`. The injected
|
|
8
|
+
// canvas runtime (use-canvas-activity.tsx) turns that into a live "agent works
|
|
9
|
+
// here" overlay on the affected artboards.
|
|
10
|
+
//
|
|
11
|
+
// Bun-native (DDR-009): setTimeout/clearTimeout + Bun.file for the Task 7 diff;
|
|
12
|
+
// no node:fs reads where Bun.file works. Pure, fs-injected helpers (isCanvasFile
|
|
13
|
+
// / diffArtboardIds) unit-test without touching disk.
|
|
14
|
+
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
|
|
17
|
+
import type { Context } from './context.ts';
|
|
18
|
+
|
|
19
|
+
/** Idle debounce — fs silence longer than this flips a file `active` → `idle`. */
|
|
20
|
+
export const ACTIVITY_IDLE_MS = 3000;
|
|
21
|
+
|
|
22
|
+
/** LRU cap on the per-file text stash used by the Task 7 region diff. */
|
|
23
|
+
const STASH_CAP = 50;
|
|
24
|
+
|
|
25
|
+
// Mirrors api.ts SKIP_DIRS — directories that never hold user-facing canvases.
|
|
26
|
+
const SKIP_DIRS = new Set([
|
|
27
|
+
'node_modules',
|
|
28
|
+
'.git',
|
|
29
|
+
'.next',
|
|
30
|
+
'.turbo',
|
|
31
|
+
'dist',
|
|
32
|
+
'build',
|
|
33
|
+
'.expo',
|
|
34
|
+
'coverage',
|
|
35
|
+
'dev-server',
|
|
36
|
+
'_history',
|
|
37
|
+
]);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* True when a design-root-relative path is a user-facing canvas the overlay
|
|
41
|
+
* should light up. `.tsx` / `.html` only, minus:
|
|
42
|
+
* - runtime artifacts + any `_`-prefixed file or directory (`_history/`,
|
|
43
|
+
* `_draw/`, `_smoke/`, `_locator.json`, …),
|
|
44
|
+
* - SKIP_DIRS segments (node_modules, dist, …),
|
|
45
|
+
* - DS preview specimens under `system/<ds>/preview/**` — a critic editing a
|
|
46
|
+
* preview emits a real `fs:any`, but those aren't canvases the user is
|
|
47
|
+
* iterating on, so an overlay there is a false positive (plan Task 1 gotcha;
|
|
48
|
+
* extended to `.tsx` specimens, not just the `.html` the plan named, because
|
|
49
|
+
* the rationale is identical).
|
|
50
|
+
*
|
|
51
|
+
* Pure — no disk access. Accepts back-slash or forward-slash input.
|
|
52
|
+
*/
|
|
53
|
+
export function isCanvasFile(rel: string): boolean {
|
|
54
|
+
if (typeof rel !== 'string') return false;
|
|
55
|
+
const p = rel.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
56
|
+
if (!p) return false;
|
|
57
|
+
const segs = p.split('/');
|
|
58
|
+
const base = segs[segs.length - 1] ?? '';
|
|
59
|
+
const dot = base.lastIndexOf('.');
|
|
60
|
+
const ext = dot >= 0 ? base.slice(dot).toLowerCase() : '';
|
|
61
|
+
if (ext !== '.tsx' && ext !== '.html') return false;
|
|
62
|
+
for (const s of segs) {
|
|
63
|
+
if (SKIP_DIRS.has(s)) return false;
|
|
64
|
+
if (s.startsWith('_')) return false;
|
|
65
|
+
}
|
|
66
|
+
if (/(^|\/)system\/[^/]+\/preview\//.test(p)) return false;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ActivityEntry {
|
|
71
|
+
status: 'active' | 'idle';
|
|
72
|
+
/** ISO timestamp of the last transition. */
|
|
73
|
+
ts: string;
|
|
74
|
+
/**
|
|
75
|
+
* Artboard ids the change was scoped to (Task 7), or null when the change is
|
|
76
|
+
* file-level (every artboard in the file lights up). Always null on the first
|
|
77
|
+
* sight of a file (no baseline to diff against).
|
|
78
|
+
*/
|
|
79
|
+
artboardIds: string[] | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ActivityChange {
|
|
83
|
+
file: string;
|
|
84
|
+
status: 'active' | 'idle';
|
|
85
|
+
artboard_ids: string[] | null;
|
|
86
|
+
ts: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface Activity {
|
|
90
|
+
/** WS-open snapshot — keyed by design-root-relative canvas path. */
|
|
91
|
+
state: Record<string, ActivityEntry>;
|
|
92
|
+
/**
|
|
93
|
+
* Test seam — synchronously mark a file `active` (file-level) and schedule its
|
|
94
|
+
* idle flip. The live path calls this from the `fs:any` handler, then refines
|
|
95
|
+
* `artboardIds` asynchronously via the Task 7 diff.
|
|
96
|
+
*/
|
|
97
|
+
mark(file: string): void;
|
|
98
|
+
/** Unsubscribe from the bus and clear all pending idle timers. */
|
|
99
|
+
stop(): void;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface ActivityOptions {
|
|
103
|
+
/** Override the idle debounce (tests pass a small value). */
|
|
104
|
+
idleMs?: number;
|
|
105
|
+
/** Disable the Task 7 per-artboard diff (file-level only). Default: enabled. */
|
|
106
|
+
diff?: boolean;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function createActivity(ctx: Context, opts: ActivityOptions = {}): Activity {
|
|
110
|
+
const idleMs = opts.idleMs ?? ACTIVITY_IDLE_MS;
|
|
111
|
+
const diffEnabled = opts.diff !== false;
|
|
112
|
+
|
|
113
|
+
const state: Record<string, ActivityEntry> = {};
|
|
114
|
+
const idleTimers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
115
|
+
// Last-seen file text, for the Task 7 region diff. Insertion-ordered Map = LRU.
|
|
116
|
+
const lastText = new Map<string, string>();
|
|
117
|
+
|
|
118
|
+
function emit(file: string) {
|
|
119
|
+
const e = state[file];
|
|
120
|
+
if (!e) return;
|
|
121
|
+
const change: ActivityChange = {
|
|
122
|
+
file,
|
|
123
|
+
status: e.status,
|
|
124
|
+
artboard_ids: e.artboardIds,
|
|
125
|
+
ts: e.ts,
|
|
126
|
+
};
|
|
127
|
+
ctx.bus.emit('activity:change', change);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function scheduleIdle(file: string) {
|
|
131
|
+
const prev = idleTimers.get(file);
|
|
132
|
+
if (prev) clearTimeout(prev);
|
|
133
|
+
const t = setTimeout(() => {
|
|
134
|
+
idleTimers.delete(file);
|
|
135
|
+
const e = state[file];
|
|
136
|
+
if (!e || e.status === 'idle') return;
|
|
137
|
+
e.status = 'idle';
|
|
138
|
+
e.ts = new Date().toISOString();
|
|
139
|
+
// Idle carries no artboard scope — the whole file cross-fades out.
|
|
140
|
+
e.artboardIds = null;
|
|
141
|
+
emit(file);
|
|
142
|
+
}, idleMs);
|
|
143
|
+
idleTimers.set(file, t);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Public + internal entry. Marks the file `active` file-level (preserving any
|
|
147
|
+
// artboardIds already refined this active window), refreshes the idle timer,
|
|
148
|
+
// and emits. Emits on the idle→active transition AND on every refresh-while-
|
|
149
|
+
// active (plan Task 2) — idempotent for the client, which only fades on idle.
|
|
150
|
+
function mark(file: string) {
|
|
151
|
+
const prev = state[file];
|
|
152
|
+
const carryIds = prev && prev.status === 'active' ? prev.artboardIds : null;
|
|
153
|
+
state[file] = { status: 'active', ts: new Date().toISOString(), artboardIds: carryIds };
|
|
154
|
+
scheduleIdle(file);
|
|
155
|
+
emit(file);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Task 7 — refine the active file's artboardIds via a cheap region diff of the
|
|
159
|
+
// previous vs current file text. Best-effort: any read race / parse ambiguity
|
|
160
|
+
// leaves the file-level highlight in place. Runs at most once per fs:any (which
|
|
161
|
+
// fs-watch already debounces 50 ms), and never blocks the synchronous mark().
|
|
162
|
+
async function refineArtboards(file: string) {
|
|
163
|
+
if (!ctx.paths?.designRoot) return;
|
|
164
|
+
try {
|
|
165
|
+
const abs = path.join(ctx.paths.designRoot, file);
|
|
166
|
+
const next = await Bun.file(abs).text();
|
|
167
|
+
const prev = lastText.get(file);
|
|
168
|
+
stash(file, next);
|
|
169
|
+
if (prev == null) return; // first sight — no baseline, stay file-level
|
|
170
|
+
const ids = diffArtboardIds(prev, next);
|
|
171
|
+
if (!ids || ids.length === 0) return; // ambiguous / outside-region change
|
|
172
|
+
const e = state[file];
|
|
173
|
+
if (e && e.status === 'active') {
|
|
174
|
+
e.artboardIds = ids;
|
|
175
|
+
emit(file);
|
|
176
|
+
}
|
|
177
|
+
} catch {
|
|
178
|
+
/* read race, file gone, or designRoot unset in tests — stay file-level */
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function stash(file: string, text: string) {
|
|
183
|
+
if (lastText.has(file)) lastText.delete(file);
|
|
184
|
+
lastText.set(file, text);
|
|
185
|
+
while (lastText.size > STASH_CAP) {
|
|
186
|
+
const oldest = lastText.keys().next().value;
|
|
187
|
+
if (oldest === undefined) break;
|
|
188
|
+
lastText.delete(oldest);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const off = ctx.bus.on('fs:any', (rel: string) => {
|
|
193
|
+
if (!isCanvasFile(rel)) return;
|
|
194
|
+
mark(rel);
|
|
195
|
+
if (diffEnabled) void refineArtboards(rel);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
function stop() {
|
|
199
|
+
off();
|
|
200
|
+
for (const t of idleTimers.values()) clearTimeout(t);
|
|
201
|
+
idleTimers.clear();
|
|
202
|
+
lastText.clear();
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return { state, mark, stop };
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Task 7 helpers — pure, regex-bounded region diff. Exported for unit tests.
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Map each `<DCArtboard id="X">…</DCArtboard>` to its inner body text. Returns
|
|
213
|
+
* `null` when no artboard markers are found (the file can't be region-scoped, so
|
|
214
|
+
* the caller falls back to a file-level highlight). DCArtboards aren't nested in
|
|
215
|
+
* practice, so a non-greedy body match is sufficient — deliberately not AST-
|
|
216
|
+
* based (a TSX parser would mean a new dep, against DDR-009's zero-dep spirit).
|
|
217
|
+
*/
|
|
218
|
+
function extractArtboardRegions(src: string): Map<string, string> | null {
|
|
219
|
+
const re = /<DCArtboard\b[^>]*\bid=["']([^"']+)["'][^>]*>([\s\S]*?)<\/DCArtboard>/g;
|
|
220
|
+
const map = new Map<string, string>();
|
|
221
|
+
let m: RegExpExecArray | null;
|
|
222
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: canonical regex-exec loop.
|
|
223
|
+
while ((m = re.exec(src)) !== null) {
|
|
224
|
+
map.set(m[1] as string, m[2] as string);
|
|
225
|
+
}
|
|
226
|
+
return map.size === 0 ? null : map;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** The file with every artboard BODY stripped — what's left is the "shell". */
|
|
230
|
+
function artboardSkeleton(src: string): string {
|
|
231
|
+
return src.replace(
|
|
232
|
+
/(<DCArtboard\b[^>]*\bid=["'][^"']+["'][^>]*>)[\s\S]*?(<\/DCArtboard>)/g,
|
|
233
|
+
'$1$2'
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Ids of the `<DCArtboard>` blocks whose body changed between two file
|
|
239
|
+
* revisions, or `null` to signal "fall back to file-level" when:
|
|
240
|
+
* - either revision has no parseable artboard markers, OR
|
|
241
|
+
* - anything outside the artboard bodies changed (imports, opening-tag attrs,
|
|
242
|
+
* artboard added/removed → the shell differs), OR
|
|
243
|
+
* - nothing inside any artboard body changed.
|
|
244
|
+
*/
|
|
245
|
+
export function diffArtboardIds(prev: string, next: string): string[] | null {
|
|
246
|
+
const prevRegions = extractArtboardRegions(prev);
|
|
247
|
+
const nextRegions = extractArtboardRegions(next);
|
|
248
|
+
if (!prevRegions || !nextRegions) return null;
|
|
249
|
+
if (artboardSkeleton(prev) !== artboardSkeleton(next)) return null;
|
|
250
|
+
const ids = new Set([...prevRegions.keys(), ...nextRegions.keys()]);
|
|
251
|
+
const changed: string[] = [];
|
|
252
|
+
for (const id of ids) {
|
|
253
|
+
if (prevRegions.get(id) !== nextRegions.get(id)) changed.push(id);
|
|
254
|
+
}
|
|
255
|
+
return changed.length ? changed : null;
|
|
256
|
+
}
|
|
@@ -125,6 +125,10 @@ export function AiBanner(): JSX.Element | null {
|
|
|
125
125
|
|
|
126
126
|
// postMessage relay path — parent sends `ai-activity` events.
|
|
127
127
|
const onMessage = (e: MessageEvent) => {
|
|
128
|
+
// DDR-078 security follow-up: only the trusted embedding parent relays
|
|
129
|
+
// ai-activity. Reject canvas self-posts faking a "Claude is editing"
|
|
130
|
+
// banner. Standalone (no parent) uses the own-WS path below instead.
|
|
131
|
+
if (e.source !== window.parent || window.parent === window) return;
|
|
128
132
|
const m = e.data as { dgn?: string; file?: string; entry?: AiEntry | null } | null;
|
|
129
133
|
if (!m || typeof m !== 'object') return;
|
|
130
134
|
if (m.dgn !== 'ai-activity') return;
|
|
@@ -36,12 +36,12 @@ import {
|
|
|
36
36
|
IconAlignCenter,
|
|
37
37
|
IconAlignLeft,
|
|
38
38
|
IconAlignRight,
|
|
39
|
-
IconArrowNone,
|
|
40
39
|
IconArrowheadCircle,
|
|
41
40
|
IconArrowheadDiamond,
|
|
42
41
|
IconArrowheadLine,
|
|
43
42
|
IconArrowheadTriangle,
|
|
44
43
|
IconArrowheadTriangleOutline,
|
|
44
|
+
IconArrowNone,
|
|
45
45
|
IconBold,
|
|
46
46
|
IconChevronDown,
|
|
47
47
|
IconCornerPill,
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
|
|
24
24
|
import {
|
|
25
25
|
type CSSProperties,
|
|
26
|
-
type PointerEvent as ReactPointerEvent,
|
|
27
26
|
createContext,
|
|
27
|
+
type PointerEvent as ReactPointerEvent,
|
|
28
28
|
useCallback,
|
|
29
29
|
useContext,
|
|
30
30
|
useEffect,
|
|
@@ -39,13 +39,13 @@ import {
|
|
|
39
39
|
ARROW_HEADS,
|
|
40
40
|
type ArrowHead,
|
|
41
41
|
type ArrowLineType,
|
|
42
|
-
type SvgPrimitive,
|
|
43
42
|
arrowPrimitives,
|
|
43
|
+
type SvgPrimitive,
|
|
44
44
|
} from './canvas-arrowheads.ts';
|
|
45
45
|
import { IconLineThick, IconLineThin } from './canvas-icons.tsx';
|
|
46
46
|
import { useViewportControllerContext, useWorldRefContext } from './canvas-lib.tsx';
|
|
47
47
|
import { buildAnnotationStrokesRecord } from './commands/annotation-strokes-command.ts';
|
|
48
|
-
import { type Tool
|
|
48
|
+
import { crossedDragThreshold, type Tool } from './input-router.tsx';
|
|
49
49
|
import { AnnotationResizeOverlay } from './use-annotation-resize.tsx';
|
|
50
50
|
import { useAnnotationSelectionOptional } from './use-annotation-selection.tsx';
|
|
51
51
|
import { useAnnotationsVisibility } from './use-annotations-visibility.tsx';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
import crypto from 'node:crypto';
|
|
5
5
|
import type { Dirent } from 'node:fs';
|
|
6
|
-
import {
|
|
6
|
+
import { readdir, readFile, stat as statp } from 'node:fs/promises';
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
|
|
9
9
|
import type { Context } from './context.ts';
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file artboard-activity-overlay.tsx — Phase 13 / DDR-029 overlay.
|
|
3
|
+
* @scope plugins/design/dev-server/artboard-activity-overlay.tsx
|
|
4
|
+
* @purpose The "agent works here" chrome for a single artboard: an animated
|
|
5
|
+
* rim + a top-right `editing — <label>` badge. Rendered by
|
|
6
|
+
* `DCArtboard` as a world-coord sibling of the `<article>`, so it
|
|
7
|
+
* pans/zooms with the artboard for free.
|
|
8
|
+
*
|
|
9
|
+
* Decorative — `aria-hidden` + `pointer-events:none`. All visual styling
|
|
10
|
+
* (border, pulse keyframes, reduced-motion fallback) lives in the inspector
|
|
11
|
+
* style block injected by `inspect.ts` (single injection point — DDR-029), so
|
|
12
|
+
* this component only places the box and toggles `data-fading` for the 200 ms
|
|
13
|
+
* cross-fade out when the file goes idle.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { CSSProperties } from 'react';
|
|
17
|
+
|
|
18
|
+
export interface OverlayRect {
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
w: number;
|
|
22
|
+
h: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function ArtboardActivityOverlay({
|
|
26
|
+
rect,
|
|
27
|
+
label,
|
|
28
|
+
active,
|
|
29
|
+
color,
|
|
30
|
+
}: {
|
|
31
|
+
rect: OverlayRect;
|
|
32
|
+
label: string;
|
|
33
|
+
/** true = editing now (pulse, opacity 1); false = idle, cross-fading out. */
|
|
34
|
+
active: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Per-agent presence color (Phase 13.2 / DDR-078). Overrides the default
|
|
37
|
+
* `--mdcc-activity` hue so each agent's rim + badge carry its own color.
|
|
38
|
+
* Omitted for manual / non-agent edits (default inspector blue).
|
|
39
|
+
*/
|
|
40
|
+
color?: string;
|
|
41
|
+
}) {
|
|
42
|
+
const style: CSSProperties = {
|
|
43
|
+
position: 'absolute',
|
|
44
|
+
left: rect.x,
|
|
45
|
+
top: rect.y,
|
|
46
|
+
width: rect.w,
|
|
47
|
+
height: rect.h,
|
|
48
|
+
pointerEvents: 'none',
|
|
49
|
+
// The injected CSS reads `var(--mdcc-activity)`; setting it here recolors the
|
|
50
|
+
// border, the ::after glow, and the badge background in one shot.
|
|
51
|
+
...(color ? ({ '--mdcc-activity': color } as CSSProperties) : {}),
|
|
52
|
+
};
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className="dc-activity-rim"
|
|
56
|
+
data-fading={active ? undefined : 'true'}
|
|
57
|
+
aria-hidden
|
|
58
|
+
style={style}
|
|
59
|
+
>
|
|
60
|
+
{/* Phase 13.3 — full-artboard scan beam (top→bottom sweep). Styled +
|
|
61
|
+
animated by the injected inspector CSS; this just mounts the cover. */}
|
|
62
|
+
<div className="dc-activity-scan" />
|
|
63
|
+
<span className="dc-activity-badge">editing — {label}</span>
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
ArtboardActivityOverlay.displayName = 'ArtboardActivityOverlay';
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
//
|
|
22
22
|
// Output: <run-dir>/report.md (or --out), zero external dependencies (node:fs only).
|
|
23
23
|
|
|
24
|
-
import { existsSync,
|
|
24
|
+
import { existsSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
|
|
25
25
|
import { basename, join } from 'node:path';
|
|
26
26
|
|
|
27
27
|
const PLATFORM_ORDER = ['web-desktop', 'web-mobile', 'ios-phone', 'ios-tablet', 'android-phone'];
|
|
@@ -9,13 +9,17 @@
|
|
|
9
9
|
# $DESIGN_ROOT/_server.json (PID + port the running server wrote)
|
|
10
10
|
# $DESIGN_ROOT/_server.log (stdout/stderr of the spawned server)
|
|
11
11
|
#
|
|
12
|
-
# Runtime selection (DDR-020):
|
|
13
|
-
# -
|
|
14
|
-
# -
|
|
15
|
-
#
|
|
12
|
+
# Runtime selection (DDR-020 / DDR-009 / DDR-084), in priority order:
|
|
13
|
+
# - --allow-legacy: opt-in node + server.mjs (debug only; no TSX pipeline, no HMR).
|
|
14
|
+
# - compiled platform binary: the PRODUCTION runtime — embeds yjs + every dep, so
|
|
15
|
+
# it boots on a global @1agh/maude install where `bun server.ts` from source
|
|
16
|
+
# cannot. Path comes from MAUDE_DEV_SERVER_BIN (set by `maude design <verb>`) or
|
|
17
|
+
# the postinstall side-channel <pkgRoot>/cli/.platform-binary-path.
|
|
18
|
+
# - bun + server.ts: dev tree / fallback. MAUDE_FORCE_SOURCE=1 forces this.
|
|
16
19
|
#
|
|
17
20
|
# Output: prints the port on stdout. Diagnostic lines go to stderr.
|
|
18
|
-
# Exit: 0 = server ready / 1 = start timeout or missing runtime / 2 = bad args
|
|
21
|
+
# Exit: 0 = server ready / 1 = start timeout or missing runtime / 2 = bad args /
|
|
22
|
+
# 3 = dev-server runtime deps missing (yjs/y-protocols/lib0 — see preflight).
|
|
19
23
|
|
|
20
24
|
REPO=""
|
|
21
25
|
TIMEOUT=10
|
|
@@ -41,9 +45,15 @@ if [ -z "$REPO" ]; then
|
|
|
41
45
|
REPO="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"
|
|
42
46
|
fi
|
|
43
47
|
|
|
44
|
-
# Resolve plugin root + pick a server runtime.
|
|
45
|
-
#
|
|
46
|
-
#
|
|
48
|
+
# Resolve plugin root + pick a server runtime.
|
|
49
|
+
# --allow-legacy → node + server.mjs (debug-only — no TSX pipeline, no HMR).
|
|
50
|
+
# compiled platform binary → the PRODUCTION runtime (DDR-009/DDR-084). It embeds
|
|
51
|
+
# yjs + every dev-server dep, so it boots on a global `@1agh/maude` install
|
|
52
|
+
# where `bun server.ts` from source CANNOT (the deps live in a nested
|
|
53
|
+
# package.json the npm tarball excludes — the historic yjs-at-boot crash).
|
|
54
|
+
# `maude design <verb>` resolves it and passes MAUDE_DEV_SERVER_BIN; a direct
|
|
55
|
+
# `bash server-up.sh` falls back to the postinstall side-channel.
|
|
56
|
+
# bun + server.ts → dev tree / fallback (DDR-020 made bun authoritative for source).
|
|
47
57
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
48
58
|
PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
49
59
|
SERVER_TS="$PLUGIN_ROOT/dev-server/server.ts"
|
|
@@ -51,6 +61,37 @@ SERVER_MJS="$PLUGIN_ROOT/dev-server/server.mjs"
|
|
|
51
61
|
if [ ! -f "$SERVER_TS" ]; then SERVER_TS="$SCRIPT_DIR/../server.ts"; fi
|
|
52
62
|
if [ ! -f "$SERVER_MJS" ]; then SERVER_MJS="$SCRIPT_DIR/../server.mjs"; fi
|
|
53
63
|
|
|
64
|
+
# Resolve the compiled binary: env (set by `maude design <verb>`) wins; else the
|
|
65
|
+
# postinstall side-channel at <pkgRoot>/cli/.platform-binary-path (covers a direct
|
|
66
|
+
# `bash server-up.sh` on a production install). MAUDE_FORCE_SOURCE=1 forces source.
|
|
67
|
+
SERVER_BIN="${MAUDE_DEV_SERVER_BIN:-}"
|
|
68
|
+
if [ -z "$SERVER_BIN" ] && [ "${MAUDE_FORCE_SOURCE:-0}" != "1" ]; then
|
|
69
|
+
# PLUGIN_ROOT=<pkgRoot>/plugins/design, so the side-channel is ../../cli.
|
|
70
|
+
# `head -n1` (parity with the Node readers' `.trim()`) tolerates a trailing newline.
|
|
71
|
+
SC="$PLUGIN_ROOT/../../cli/.platform-binary-path"
|
|
72
|
+
if [ -f "$SC" ]; then
|
|
73
|
+
CAND="$(head -n1 "$SC" 2>/dev/null)"
|
|
74
|
+
[ -n "$CAND" ] && [ -x "$CAND" ] && SERVER_BIN="$CAND"
|
|
75
|
+
fi
|
|
76
|
+
fi
|
|
77
|
+
# Structural allowlist (DDR-084 hardening): only spawn a path that LOOKS like the
|
|
78
|
+
# compiled platform binary — basename `maude`/`maude.exe` inside a `maude-<slug>/`
|
|
79
|
+
# dir. A poisoned side-channel file or an injected MAUDE_DEV_SERVER_BIN pointing
|
|
80
|
+
# elsewhere is ignored (fall back to source), so the env/file can only DENY the
|
|
81
|
+
# binary, never redirect the spawn to an arbitrary executable. Mirrors
|
|
82
|
+
# isPlausiblePlatformBinary() in cli/commands/design.mjs.
|
|
83
|
+
if [ -n "$SERVER_BIN" ]; then
|
|
84
|
+
_sbase="$(basename "$SERVER_BIN")"
|
|
85
|
+
_sparent="$(basename "$(dirname "$SERVER_BIN")")"
|
|
86
|
+
case "$_sbase:$_sparent" in
|
|
87
|
+
maude:maude-* | maude.exe:maude-*) : ;;
|
|
88
|
+
*)
|
|
89
|
+
echo "server-up.sh: ignoring dev-server binary outside the expected maude-<slug>/ layout ($SERVER_BIN) — falling back to source" >&2
|
|
90
|
+
SERVER_BIN=""
|
|
91
|
+
;;
|
|
92
|
+
esac
|
|
93
|
+
fi
|
|
94
|
+
|
|
54
95
|
RUNTIME=""
|
|
55
96
|
RUNTIME_CMD=""
|
|
56
97
|
if [ $ALLOW_LEGACY -eq 1 ]; then
|
|
@@ -61,6 +102,10 @@ if [ $ALLOW_LEGACY -eq 1 ]; then
|
|
|
61
102
|
RUNTIME="node"
|
|
62
103
|
RUNTIME_CMD="node $SERVER_MJS"
|
|
63
104
|
echo "server-up.sh: WARNING — running legacy server.mjs (no TSX canvas pipeline, no HMR). DDR-020 sunsets this in Phase B." >&2
|
|
105
|
+
elif [ -n "$SERVER_BIN" ] && [ -x "$SERVER_BIN" ] && [ "${MAUDE_FORCE_SOURCE:-0}" != "1" ]; then
|
|
106
|
+
RUNTIME="binary"
|
|
107
|
+
RUNTIME_CMD="$SERVER_BIN"
|
|
108
|
+
echo "server-up.sh: using compiled dev-server binary ($SERVER_BIN)" >&2
|
|
64
109
|
elif command -v bun >/dev/null 2>&1 && [ -f "$SERVER_TS" ]; then
|
|
65
110
|
RUNTIME="bun"
|
|
66
111
|
RUNTIME_CMD="bun $SERVER_TS"
|
|
@@ -106,10 +151,58 @@ if [ -f "$STATE" ]; then
|
|
|
106
151
|
fi
|
|
107
152
|
fi
|
|
108
153
|
|
|
154
|
+
# Step 1.5 — dependency preflight (source `bun server.ts` path ONLY; runs on cold
|
|
155
|
+
# start). The compiled binary embeds its deps, so this is skipped when RUNTIME=binary
|
|
156
|
+
# — it only guards the source fallback. The dev-server's runtime deps live in a
|
|
157
|
+
# NESTED package.json (plugins/design/dev-server/) that a global `@1agh/maude` npm
|
|
158
|
+
# install or a fresh `git worktree` does NOT populate; a missing `yjs` (imported at
|
|
159
|
+
# boot by sync/index.ts) crashes `bun server.ts` AFTER spawn → without this guard we
|
|
160
|
+
# poll the full ${TIMEOUT}s and report a generic "start timeout", burying the cause
|
|
161
|
+
# in _server.log and silently degrading the mandatory visual-sanity gate. Fail loud
|
|
162
|
+
# NOW with a context-correct hint. No auto-install: boot-self-heal.ts (DDR-044)
|
|
163
|
+
# dropped that, and the durable production fix is booting the compiled binary
|
|
164
|
+
# (DDR-009/DDR-084), not a boot-time install. setup-ds Round-2 / DDR-083 + DDR-084.
|
|
165
|
+
if [ "$RUNTIME" = "bun" ]; then
|
|
166
|
+
DEV_SERVER_DIR="$(cd "$(dirname "$SERVER_TS")" && pwd)"
|
|
167
|
+
_resolve_dep() {
|
|
168
|
+
# Mirror node/bun bare-specifier resolution: walk node_modules up to /.
|
|
169
|
+
# Handles both the dev-server-local install and a workspace-hoisted one.
|
|
170
|
+
local dep="$1" d="$DEV_SERVER_DIR"
|
|
171
|
+
while [ -n "$d" ] && [ "$d" != "/" ]; do
|
|
172
|
+
[ -e "$d/node_modules/$dep/package.json" ] && return 0
|
|
173
|
+
d="$(dirname "$d")"
|
|
174
|
+
done
|
|
175
|
+
return 1
|
|
176
|
+
}
|
|
177
|
+
MISSING_DEPS=""
|
|
178
|
+
for dep in yjs y-protocols lib0; do
|
|
179
|
+
_resolve_dep "$dep" || MISSING_DEPS="$MISSING_DEPS $dep"
|
|
180
|
+
done
|
|
181
|
+
if [ -n "$MISSING_DEPS" ]; then
|
|
182
|
+
{
|
|
183
|
+
echo "✗ dev-server runtime deps not installed:$MISSING_DEPS"
|
|
184
|
+
if [ -f "$DEV_SERVER_DIR/package.json" ]; then
|
|
185
|
+
# A manifest is present (local dev tree / checkout) — install resolves it.
|
|
186
|
+
echo " → run: (cd \"$DEV_SERVER_DIR\" && bun install) # or: pnpm install at the repo root"
|
|
187
|
+
else
|
|
188
|
+
# No manifest beside server.ts AND no compiled binary resolved → the
|
|
189
|
+
# '@1agh/maude' install is incomplete. The platform binary (which embeds
|
|
190
|
+
# these deps) is the production runtime — reinstall to get it.
|
|
191
|
+
echo " (no dev-server package.json here and no compiled binary resolved — the"
|
|
192
|
+
echo " '@1agh/maude' install is incomplete; the platform binary embeds these deps)"
|
|
193
|
+
echo " → reinstall: npm i -g @1agh/maude (or: npm rebuild -g @1agh/maude)"
|
|
194
|
+
fi
|
|
195
|
+
} >&2
|
|
196
|
+
exit 3
|
|
197
|
+
fi
|
|
198
|
+
fi
|
|
199
|
+
|
|
109
200
|
# Step 2 — spawn.
|
|
110
201
|
mkdir -p "$DESIGN_ROOT"
|
|
111
202
|
echo "→ starting dev server: $RUNTIME_CMD --root $REPO" >&2
|
|
112
|
-
if [ "$RUNTIME" = "
|
|
203
|
+
if [ "$RUNTIME" = "binary" ]; then
|
|
204
|
+
nohup "$SERVER_BIN" --root "$REPO" > "$DESIGN_ROOT/_server.log" 2>&1 &
|
|
205
|
+
elif [ "$RUNTIME" = "bun" ]; then
|
|
113
206
|
nohup bun "$SERVER_TS" --root "$REPO" > "$DESIGN_ROOT/_server.log" 2>&1 &
|
|
114
207
|
elif [ "$RUNTIME" = "node" ]; then
|
|
115
208
|
nohup node "$SERVER_MJS" --root "$REPO" > "$DESIGN_ROOT/_server.log" 2>&1 &
|
|
@@ -41,6 +41,9 @@
|
|
|
41
41
|
# 2 bad args
|
|
42
42
|
# 3 one or more specimen screenshots failed
|
|
43
43
|
# 4 no specimens existed on disk (DS not scaffolded? typo?)
|
|
44
|
+
# 5 dev-server runtime deps missing (yjs/y-protocols/lib0) — recovery is
|
|
45
|
+
# `bun install`, NOT skip-or-retry; distinct from a generic boot failure
|
|
46
|
+
# so the caller routes to the right hint (server-up.sh exit 3). DDR-083.
|
|
44
47
|
|
|
45
48
|
DS=""
|
|
46
49
|
SPECIMENS=""
|
|
@@ -142,18 +145,31 @@ echo "→ booting dev-server (timeout ${BOOT_TIMEOUT}s)" >&2
|
|
|
142
145
|
PORT=$(bash "$SCRIPT_DIR/server-up.sh" --root "$ROOT_ABS" --timeout "$BOOT_TIMEOUT")
|
|
143
146
|
SERVER_RC=$?
|
|
144
147
|
if [ $SERVER_RC -ne 0 ] || [ -z "$PORT" ]; then
|
|
145
|
-
|
|
148
|
+
# server-up.sh exit 3 = the dev-server's runtime deps aren't installed. That's
|
|
149
|
+
# a distinct failure from a generic boot crash/timeout: the fix is `bun install`,
|
|
150
|
+
# not "skip or retry". Surface a dedicated status + exit code (5) so the caller
|
|
151
|
+
# routes to the right remediation instead of the boot-failed AskUserQuestion.
|
|
152
|
+
# The actionable hint already went to stderr from server-up.sh. DDR-083.
|
|
153
|
+
if [ $SERVER_RC -eq 3 ]; then
|
|
154
|
+
echo "✗ dev-server runtime deps missing — run \`bun install\` in the dev-server dir (see hint above)" >&2
|
|
155
|
+
VS_STATUS="server-deps-missing"
|
|
156
|
+
VS_EXIT=5
|
|
157
|
+
else
|
|
158
|
+
echo "✗ dev-server boot failed (server-up.sh exit $SERVER_RC); see $DESIGN_ROOT/_server.log" >&2
|
|
159
|
+
VS_STATUS="server-boot-failed"
|
|
160
|
+
VS_EXIT=1
|
|
161
|
+
fi
|
|
146
162
|
cat > "$OUT_DIR/_manifest.json" <<EOF
|
|
147
163
|
{
|
|
148
164
|
"ds": "$DS",
|
|
149
165
|
"ts": "$TS",
|
|
150
|
-
"status": "
|
|
166
|
+
"status": "$VS_STATUS",
|
|
151
167
|
"server_up_rc": $SERVER_RC,
|
|
152
168
|
"specimens_requested": $(printf '%s\n' "${SPEC_ARR[@]}" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | sed '/^$/d' | sed 's/.*/"&"/' | paste -sd, -),
|
|
153
169
|
"specimens_captured": []
|
|
154
170
|
}
|
|
155
171
|
EOF
|
|
156
|
-
exit
|
|
172
|
+
exit $VS_EXIT
|
|
157
173
|
fi
|
|
158
174
|
echo "✓ dev-server on port $PORT" >&2
|
|
159
175
|
|