@1agh/maude 0.22.0 → 0.23.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 (60) hide show
  1. package/cli/commands/design-link.test.mjs +53 -1
  2. package/cli/commands/hub.test.mjs +10 -9
  3. package/cli/lib/design-link.mjs +154 -7
  4. package/cli/lib/hubs-config.mjs +42 -4
  5. package/package.json +9 -9
  6. package/plugins/design/dev-server/bin/check-runtime-bundles.sh +125 -0
  7. package/plugins/design/dev-server/bin/runtime-health.sh +47 -6
  8. package/plugins/design/dev-server/build.ts +87 -7
  9. package/plugins/design/dev-server/canvas-comment-mount.tsx +384 -0
  10. package/plugins/design/dev-server/canvas-lib.tsx +22 -6
  11. package/plugins/design/dev-server/canvas-shell.tsx +25 -226
  12. package/plugins/design/dev-server/client/app.jsx +37 -15
  13. package/plugins/design/dev-server/collab/awareness-bridge.ts +77 -0
  14. package/plugins/design/dev-server/collab/registry.ts +51 -0
  15. package/plugins/design/dev-server/config.schema.json +20 -0
  16. package/plugins/design/dev-server/context.ts +7 -0
  17. package/plugins/design/dev-server/dist/client.bundle.js +21 -12
  18. package/plugins/design/dev-server/dist/comment-mount.js +1801 -0
  19. package/plugins/design/dev-server/dist/runtime/.min-sizes.json +16 -0
  20. package/plugins/design/dev-server/dist/runtime/lib0_decoding.js +2 -6
  21. package/plugins/design/dev-server/dist/runtime/lib0_encoding.js +2 -6
  22. package/plugins/design/dev-server/dist/runtime/motion.js +4787 -8
  23. package/plugins/design/dev-server/dist/runtime/motion_react.js +9654 -7
  24. package/plugins/design/dev-server/dist/runtime/pixi-js.js +5865 -5469
  25. package/plugins/design/dev-server/dist/runtime/react-dom.js +4 -22
  26. package/plugins/design/dev-server/dist/runtime/react-dom_client.js +5 -23
  27. package/plugins/design/dev-server/dist/runtime/react.js +4 -22
  28. package/plugins/design/dev-server/dist/runtime/react_jsx-dev-runtime.js +4 -22
  29. package/plugins/design/dev-server/dist/runtime/react_jsx-runtime.js +4 -22
  30. package/plugins/design/dev-server/dist/runtime/y-protocols_awareness.js +2 -6
  31. package/plugins/design/dev-server/dist/runtime/y-protocols_sync.js +2 -6
  32. package/plugins/design/dev-server/dist/runtime/yjs.js +2 -6
  33. package/plugins/design/dev-server/dom-selection.ts +156 -0
  34. package/plugins/design/dev-server/hmr-broadcast.ts +51 -20
  35. package/plugins/design/dev-server/input-router.tsx +99 -61
  36. package/plugins/design/dev-server/server.ts +18 -0
  37. package/plugins/design/dev-server/sync/agent.ts +323 -0
  38. package/plugins/design/dev-server/sync/atomic-write.ts +103 -0
  39. package/plugins/design/dev-server/sync/codec.ts +169 -0
  40. package/plugins/design/dev-server/sync/echo-guard.ts +108 -0
  41. package/plugins/design/dev-server/sync/fs-mirror.ts +160 -0
  42. package/plugins/design/dev-server/sync/hubs-config.ts +87 -0
  43. package/plugins/design/dev-server/sync/index.ts +474 -0
  44. package/plugins/design/dev-server/test/collab-awareness-bridge.test.ts +223 -0
  45. package/plugins/design/dev-server/test/comment-mount.test.ts +87 -0
  46. package/plugins/design/dev-server/test/hmr-classify.test.ts +70 -0
  47. package/plugins/design/dev-server/test/sync-agent.test.ts +278 -0
  48. package/plugins/design/dev-server/test/sync-atomic-write.test.ts +63 -0
  49. package/plugins/design/dev-server/test/sync-codec.test.ts +165 -0
  50. package/plugins/design/dev-server/test/sync-echo-guard.test.ts +96 -0
  51. package/plugins/design/dev-server/test/sync-fs-mirror.test.ts +182 -0
  52. package/plugins/design/dev-server/test/sync-hardening.test.ts +520 -0
  53. package/plugins/design/dev-server/test/sync-hubs-config.test.ts +130 -0
  54. package/plugins/design/dev-server/test/sync-runtime.test.ts +285 -0
  55. package/plugins/design/dev-server/test/use-collab.test.ts +0 -0
  56. package/plugins/design/dev-server/use-collab.tsx +157 -13
  57. package/plugins/design/dev-server/use-selection-set.tsx +12 -0
  58. package/plugins/design/dev-server/use-tool-mode.tsx +12 -0
  59. package/plugins/design/templates/_shell.html +15 -5
  60. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +1 -0
@@ -3,43 +3,25 @@ var __getProtoOf = Object.getPrototypeOf;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- function __accessProp(key) {
7
- return this[key];
8
- }
9
- var __toESMCache_node;
10
- var __toESMCache_esm;
11
6
  var __toESM = (mod, isNodeMode, target) => {
12
- var canCache = mod != null && typeof mod === "object";
13
- if (canCache) {
14
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
15
- var cached = cache.get(mod);
16
- if (cached)
17
- return cached;
18
- }
19
7
  target = mod != null ? __create(__getProtoOf(mod)) : {};
20
8
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
21
9
  for (let key of __getOwnPropNames(mod))
22
10
  if (!__hasOwnProp.call(to, key))
23
11
  __defProp(to, key, {
24
- get: __accessProp.bind(mod, key),
12
+ get: () => mod[key],
25
13
  enumerable: true
26
14
  });
27
- if (canCache)
28
- cache.set(mod, to);
29
15
  return to;
30
16
  };
31
17
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
32
- var __returnValue = (v) => v;
33
- function __exportSetter(name, newValue) {
34
- this[name] = __returnValue.bind(null, newValue);
35
- }
36
18
  var __export = (target, all) => {
37
19
  for (var name in all)
38
20
  __defProp(target, name, {
39
21
  get: all[name],
40
22
  enumerable: true,
41
23
  configurable: true,
42
- set: __exportSetter.bind(all, name)
24
+ set: (newValue) => all[name] = () => newValue
43
25
  });
44
26
  };
45
27
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -208,10 +190,10 @@ var require_react_dom = __commonJS((exports, module) => {
208
190
  if (true) {
209
191
  checkDCE();
210
192
  module.exports = exports_react_dom_production;
211
- }
193
+ } else {}
212
194
  });
213
195
 
214
- // synth:/home/runner/work/maude/maude/plugins/design/dev-server/.runtime-bundle-react-dom-entry.tsx
196
+ // synth:/Users/iagh/git/claude-design/plugins/design/dev-server/.runtime-bundle-react-dom-entry.tsx
215
197
  var __mod__ = __toESM(require_react_dom(), 1);
216
198
  var {
217
199
  __DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
@@ -3,43 +3,25 @@ var __getProtoOf = Object.getPrototypeOf;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- function __accessProp(key) {
7
- return this[key];
8
- }
9
- var __toESMCache_node;
10
- var __toESMCache_esm;
11
6
  var __toESM = (mod, isNodeMode, target) => {
12
- var canCache = mod != null && typeof mod === "object";
13
- if (canCache) {
14
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
15
- var cached = cache.get(mod);
16
- if (cached)
17
- return cached;
18
- }
19
7
  target = mod != null ? __create(__getProtoOf(mod)) : {};
20
8
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
21
9
  for (let key of __getOwnPropNames(mod))
22
10
  if (!__hasOwnProp.call(to, key))
23
11
  __defProp(to, key, {
24
- get: __accessProp.bind(mod, key),
12
+ get: () => mod[key],
25
13
  enumerable: true
26
14
  });
27
- if (canCache)
28
- cache.set(mod, to);
29
15
  return to;
30
16
  };
31
17
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
32
- var __returnValue = (v) => v;
33
- function __exportSetter(name, newValue) {
34
- this[name] = __returnValue.bind(null, newValue);
35
- }
36
18
  var __export = (target, all) => {
37
19
  for (var name in all)
38
20
  __defProp(target, name, {
39
21
  get: all[name],
40
22
  enumerable: true,
41
23
  configurable: true,
42
- set: __exportSetter.bind(all, name)
24
+ set: (newValue) => all[name] = () => newValue
43
25
  });
44
26
  };
45
27
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -309,7 +291,7 @@ var require_scheduler = __commonJS((exports, module) => {
309
291
  init_scheduler_production();
310
292
  if (true) {
311
293
  module.exports = exports_scheduler_production;
312
- }
294
+ } else {}
313
295
  });
314
296
 
315
297
  // ../../../node_modules/.pnpm/react-dom@19.2.6_react@19.2.6/node_modules/react-dom/cjs/react-dom-client.production.js
@@ -10356,10 +10338,10 @@ var require_client = __commonJS((exports, module) => {
10356
10338
  if (true) {
10357
10339
  checkDCE();
10358
10340
  module.exports = exports_react_dom_client_production;
10359
- }
10341
+ } else {}
10360
10342
  });
10361
10343
 
10362
- // synth:/home/runner/work/maude/maude/plugins/design/dev-server/.runtime-bundle-react-dom_client-entry.tsx
10344
+ // synth:/Users/iagh/git/claude-design/plugins/design/dev-server/.runtime-bundle-react-dom_client-entry.tsx
10363
10345
  var __mod__ = __toESM(require_client(), 1);
10364
10346
  var {
10365
10347
  createRoot,
@@ -3,43 +3,25 @@ var __getProtoOf = Object.getPrototypeOf;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- function __accessProp(key) {
7
- return this[key];
8
- }
9
- var __toESMCache_node;
10
- var __toESMCache_esm;
11
6
  var __toESM = (mod, isNodeMode, target) => {
12
- var canCache = mod != null && typeof mod === "object";
13
- if (canCache) {
14
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
15
- var cached = cache.get(mod);
16
- if (cached)
17
- return cached;
18
- }
19
7
  target = mod != null ? __create(__getProtoOf(mod)) : {};
20
8
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
21
9
  for (let key of __getOwnPropNames(mod))
22
10
  if (!__hasOwnProp.call(to, key))
23
11
  __defProp(to, key, {
24
- get: __accessProp.bind(mod, key),
12
+ get: () => mod[key],
25
13
  enumerable: true
26
14
  });
27
- if (canCache)
28
- cache.set(mod, to);
29
15
  return to;
30
16
  };
31
17
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
32
- var __returnValue = (v) => v;
33
- function __exportSetter(name, newValue) {
34
- this[name] = __returnValue.bind(null, newValue);
35
- }
36
18
  var __export = (target, all) => {
37
19
  for (var name in all)
38
20
  __defProp(target, name, {
39
21
  get: all[name],
40
22
  enumerable: true,
41
23
  configurable: true,
42
- set: __exportSetter.bind(all, name)
24
+ set: (newValue) => all[name] = () => newValue
43
25
  });
44
26
  };
45
27
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -452,10 +434,10 @@ var require_react = __commonJS((exports, module) => {
452
434
  init_react_production();
453
435
  if (true) {
454
436
  module.exports = exports_react_production;
455
- }
437
+ } else {}
456
438
  });
457
439
 
458
- // synth:/home/runner/work/maude/maude/plugins/design/dev-server/.runtime-bundle-react-entry.tsx
440
+ // synth:/Users/iagh/git/claude-design/plugins/design/dev-server/.runtime-bundle-react-entry.tsx
459
441
  var __mod__ = __toESM(require_react(), 1);
460
442
  var {
461
443
  Activity,
@@ -3,43 +3,25 @@ var __getProtoOf = Object.getPrototypeOf;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- function __accessProp(key) {
7
- return this[key];
8
- }
9
- var __toESMCache_node;
10
- var __toESMCache_esm;
11
6
  var __toESM = (mod, isNodeMode, target) => {
12
- var canCache = mod != null && typeof mod === "object";
13
- if (canCache) {
14
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
15
- var cached = cache.get(mod);
16
- if (cached)
17
- return cached;
18
- }
19
7
  target = mod != null ? __create(__getProtoOf(mod)) : {};
20
8
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
21
9
  for (let key of __getOwnPropNames(mod))
22
10
  if (!__hasOwnProp.call(to, key))
23
11
  __defProp(to, key, {
24
- get: __accessProp.bind(mod, key),
12
+ get: () => mod[key],
25
13
  enumerable: true
26
14
  });
27
- if (canCache)
28
- cache.set(mod, to);
29
15
  return to;
30
16
  };
31
17
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
32
- var __returnValue = (v) => v;
33
- function __exportSetter(name, newValue) {
34
- this[name] = __returnValue.bind(null, newValue);
35
- }
36
18
  var __export = (target, all) => {
37
19
  for (var name in all)
38
20
  __defProp(target, name, {
39
21
  get: all[name],
40
22
  enumerable: true,
41
23
  configurable: true,
42
- set: __exportSetter.bind(all, name)
24
+ set: (newValue) => all[name] = () => newValue
43
25
  });
44
26
  };
45
27
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -61,10 +43,10 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
61
43
  init_react_jsx_dev_runtime_production();
62
44
  if (true) {
63
45
  module.exports = exports_react_jsx_dev_runtime_production;
64
- }
46
+ } else {}
65
47
  });
66
48
 
67
- // synth:/home/runner/work/maude/maude/plugins/design/dev-server/.runtime-bundle-react_jsx-dev-runtime-entry.tsx
49
+ // synth:/Users/iagh/git/claude-design/plugins/design/dev-server/.runtime-bundle-react_jsx-dev-runtime-entry.tsx
68
50
  var __mod__ = __toESM(require_jsx_dev_runtime(), 1);
69
51
  var {
70
52
  Fragment,
@@ -3,43 +3,25 @@ var __getProtoOf = Object.getPrototypeOf;
3
3
  var __defProp = Object.defineProperty;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- function __accessProp(key) {
7
- return this[key];
8
- }
9
- var __toESMCache_node;
10
- var __toESMCache_esm;
11
6
  var __toESM = (mod, isNodeMode, target) => {
12
- var canCache = mod != null && typeof mod === "object";
13
- if (canCache) {
14
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
15
- var cached = cache.get(mod);
16
- if (cached)
17
- return cached;
18
- }
19
7
  target = mod != null ? __create(__getProtoOf(mod)) : {};
20
8
  const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
21
9
  for (let key of __getOwnPropNames(mod))
22
10
  if (!__hasOwnProp.call(to, key))
23
11
  __defProp(to, key, {
24
- get: __accessProp.bind(mod, key),
12
+ get: () => mod[key],
25
13
  enumerable: true
26
14
  });
27
- if (canCache)
28
- cache.set(mod, to);
29
15
  return to;
30
16
  };
31
17
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
32
- var __returnValue = (v) => v;
33
- function __exportSetter(name, newValue) {
34
- this[name] = __returnValue.bind(null, newValue);
35
- }
36
18
  var __export = (target, all) => {
37
19
  for (var name in all)
38
20
  __defProp(target, name, {
39
21
  get: all[name],
40
22
  enumerable: true,
41
23
  configurable: true,
42
- set: __exportSetter.bind(all, name)
24
+ set: (newValue) => all[name] = () => newValue
43
25
  });
44
26
  };
45
27
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -84,10 +66,10 @@ var require_jsx_runtime = __commonJS((exports, module) => {
84
66
  init_react_jsx_runtime_production();
85
67
  if (true) {
86
68
  module.exports = exports_react_jsx_runtime_production;
87
- }
69
+ } else {}
88
70
  });
89
71
 
90
- // synth:/home/runner/work/maude/maude/plugins/design/dev-server/.runtime-bundle-react_jsx-runtime-entry.tsx
72
+ // synth:/Users/iagh/git/claude-design/plugins/design/dev-server/.runtime-bundle-react_jsx-runtime-entry.tsx
91
73
  var __mod__ = __toESM(require_jsx_runtime(), 1);
92
74
  var {
93
75
  Fragment,
@@ -1,15 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __returnValue = (v) => v;
3
- function __exportSetter(name, newValue) {
4
- this[name] = __returnValue.bind(null, newValue);
5
- }
6
2
  var __export = (target, all) => {
7
3
  for (var name in all)
8
4
  __defProp(target, name, {
9
5
  get: all[name],
10
6
  enumerable: true,
11
7
  configurable: true,
12
- set: __exportSetter.bind(all, name)
8
+ set: (newValue) => all[name] = () => newValue
13
9
  });
14
10
  };
15
11
 
@@ -359,7 +355,7 @@ var applyAwarenessUpdate = (awareness, update, origin) => {
359
355
  }
360
356
  };
361
357
 
362
- // synth:/home/runner/work/maude/maude/plugins/design/dev-server/.runtime-bundle-y-protocols_awareness-entry.tsx
358
+ // synth:/Users/iagh/git/claude-design/plugins/design/dev-server/.runtime-bundle-y-protocols_awareness-entry.tsx
363
359
  var {
364
360
  Awareness: Awareness2,
365
361
  applyAwarenessUpdate: applyAwarenessUpdate2,
@@ -1,15 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __returnValue = (v) => v;
3
- function __exportSetter(name, newValue) {
4
- this[name] = __returnValue.bind(null, newValue);
5
- }
6
2
  var __export = (target, all) => {
7
3
  for (var name in all)
8
4
  __defProp(target, name, {
9
5
  get: all[name],
10
6
  enumerable: true,
11
7
  configurable: true,
12
- set: __exportSetter.bind(all, name)
8
+ set: (newValue) => all[name] = () => newValue
13
9
  });
14
10
  };
15
11
 
@@ -75,7 +71,7 @@ var readSyncMessage = (decoder, encoder, doc, transactionOrigin, errorHandler) =
75
71
  return messageType;
76
72
  };
77
73
 
78
- // synth:/home/runner/work/maude/maude/plugins/design/dev-server/.runtime-bundle-y-protocols_sync-entry.tsx
74
+ // synth:/Users/iagh/git/claude-design/plugins/design/dev-server/.runtime-bundle-y-protocols_sync-entry.tsx
79
75
  var {
80
76
  messageYjsSyncStep1: messageYjsSyncStep12,
81
77
  messageYjsSyncStep2: messageYjsSyncStep22,
@@ -1,15 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
- var __returnValue = (v) => v;
3
- function __exportSetter(name, newValue) {
4
- this[name] = __returnValue.bind(null, newValue);
5
- }
6
2
  var __export = (target, all) => {
7
3
  for (var name in all)
8
4
  __defProp(target, name, {
9
5
  get: all[name],
10
6
  enumerable: true,
11
7
  configurable: true,
12
- set: __exportSetter.bind(all, name)
8
+ set: (newValue) => all[name] = () => newValue
13
9
  });
14
10
  };
15
11
 
@@ -6476,7 +6472,7 @@ if (glo[importIdentifier] === true) {
6476
6472
  }
6477
6473
  glo[importIdentifier] = true;
6478
6474
 
6479
- // synth:/home/runner/work/maude/maude/plugins/design/dev-server/.runtime-bundle-yjs-entry.tsx
6475
+ // synth:/Users/iagh/git/claude-design/plugins/design/dev-server/.runtime-bundle-yjs-entry.tsx
6480
6476
  var {
6481
6477
  AbsolutePosition: AbsolutePosition2,
6482
6478
  AbstractConnector: AbstractConnector2,
@@ -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
+ }
@@ -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
- const rel = filename.replace(/\\/g, '/');
68
- const ext = path.extname(rel).toLowerCase();
69
- const version = Date.now();
70
- if (rel.startsWith('_lib/')) {
71
- return { type: 'canvas-hmr', mode: 'hard', scope: 'lib', version };
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
+ }