@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.
Files changed (143) hide show
  1. package/cli/commands/design-link.test.mjs +46 -0
  2. package/cli/commands/design.mjs +69 -3
  3. package/cli/commands/design.test.mjs +97 -1
  4. package/cli/commands/doctor.mjs +110 -5
  5. package/cli/commands/doctor.test.mjs +52 -0
  6. package/cli/lib/cache.mjs +1 -1
  7. package/cli/lib/config-lint.mjs +1 -1
  8. package/cli/lib/copy-tree.mjs +1 -1
  9. package/cli/lib/design-link.mjs +39 -9
  10. package/cli/lib/flow-design-integration.test.mjs +1 -1
  11. package/cli/lib/gitignore-block.test.mjs +2 -2
  12. package/cli/lib/stack-detect.mjs +1 -1
  13. package/package.json +9 -9
  14. package/plugins/design/dev-server/activity.ts +256 -0
  15. package/plugins/design/dev-server/ai-banner.tsx +4 -0
  16. package/plugins/design/dev-server/annotations-context-toolbar.tsx +1 -1
  17. package/plugins/design/dev-server/annotations-layer.tsx +3 -3
  18. package/plugins/design/dev-server/api.ts +1 -1
  19. package/plugins/design/dev-server/artboard-activity-overlay.tsx +67 -0
  20. package/plugins/design/dev-server/bin/scenario-report.mjs +1 -1
  21. package/plugins/design/dev-server/bin/server-up.sh +102 -9
  22. package/plugins/design/dev-server/bin/visual-sanity.sh +19 -3
  23. package/plugins/design/dev-server/canvas-comment-mount.tsx +165 -10
  24. package/plugins/design/dev-server/canvas-cursors.ts +1 -1
  25. package/plugins/design/dev-server/canvas-lib.tsx +72 -36
  26. package/plugins/design/dev-server/canvas-shell.tsx +1 -1
  27. package/plugins/design/dev-server/collab/awareness-bridge.ts +1 -1
  28. package/plugins/design/dev-server/collab/index.ts +4 -4
  29. package/plugins/design/dev-server/collab/persistence.ts +1 -1
  30. package/plugins/design/dev-server/config.schema.json +2 -1
  31. package/plugins/design/dev-server/context-menu.tsx +1 -1
  32. package/plugins/design/dev-server/dist/client.bundle.js +19 -19
  33. package/plugins/design/dev-server/dist/comment-mount.js +84 -6
  34. package/plugins/design/dev-server/dist/runtime/lib0_decoding.js +1 -620
  35. package/plugins/design/dev-server/dist/runtime/lib0_encoding.js +1 -604
  36. package/plugins/design/dev-server/dist/runtime/motion.js +4 -4944
  37. package/plugins/design/dev-server/dist/runtime/motion_react.js +10 -10054
  38. package/plugins/design/dev-server/dist/runtime/pixi-js.js +2775 -52482
  39. package/plugins/design/dev-server/dist/runtime/react-dom.js +1 -231
  40. package/plugins/design/dev-server/dist/runtime/react-dom_client.js +9 -10357
  41. package/plugins/design/dev-server/dist/runtime/react.js +1 -535
  42. package/plugins/design/dev-server/dist/runtime/react_jsx-dev-runtime.js +1 -60
  43. package/plugins/design/dev-server/dist/runtime/react_jsx-runtime.js +1 -85
  44. package/plugins/design/dev-server/dist/runtime/y-protocols_awareness.js +1 -376
  45. package/plugins/design/dev-server/dist/runtime/y-protocols_sync.js +1 -100
  46. package/plugins/design/dev-server/dist/runtime/yjs.js +5 -6687
  47. package/plugins/design/dev-server/draw/brush.ts +3 -3
  48. package/plugins/design/dev-server/draw/index.ts +5 -5
  49. package/plugins/design/dev-server/draw/layout.ts +1 -1
  50. package/plugins/design/dev-server/draw/test/geometry.test.ts +1 -1
  51. package/plugins/design/dev-server/draw/test/layout.test.ts +1 -1
  52. package/plugins/design/dev-server/draw/test/primitives.test.ts +1 -1
  53. package/plugins/design/dev-server/draw/test/serialize.test.ts +1 -1
  54. package/plugins/design/dev-server/equal-spacing-handles.tsx +1 -1
  55. package/plugins/design/dev-server/examples/perf-100-artboards.tsx +1 -2
  56. package/plugins/design/dev-server/export-dialog.tsx +1 -1
  57. package/plugins/design/dev-server/exporters/html.ts +1 -1
  58. package/plugins/design/dev-server/exporters/index.ts +2 -2
  59. package/plugins/design/dev-server/exporters/pdf.ts +1 -1
  60. package/plugins/design/dev-server/exporters/png.ts +1 -1
  61. package/plugins/design/dev-server/exporters/pptx.ts +1 -1
  62. package/plugins/design/dev-server/exporters/svg.ts +1 -1
  63. package/plugins/design/dev-server/http.ts +1 -1
  64. package/plugins/design/dev-server/inspect.ts +31 -1
  65. package/plugins/design/dev-server/marquee-overlay.tsx +1 -1
  66. package/plugins/design/dev-server/participants-chrome.tsx +86 -1
  67. package/plugins/design/dev-server/server.ts +11 -2
  68. package/plugins/design/dev-server/sync/agent.ts +1 -1
  69. package/plugins/design/dev-server/sync/index.ts +29 -24
  70. package/plugins/design/dev-server/sync/materialize.ts +2 -2
  71. package/plugins/design/dev-server/sync/migrate-seed.ts +1 -1
  72. package/plugins/design/dev-server/sync/projection.ts +3 -3
  73. package/plugins/design/dev-server/test/activity.test.ts +195 -0
  74. package/plugins/design/dev-server/test/ai-activity.test.ts +1 -1
  75. package/plugins/design/dev-server/test/annotations-api.test.ts +1 -2
  76. package/plugins/design/dev-server/test/annotations-draw-modifiers.test.ts +2 -2
  77. package/plugins/design/dev-server/test/annotations-layer.test.ts +6 -6
  78. package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +1 -1
  79. package/plugins/design/dev-server/test/artboard-activity-overlay.test.tsx +56 -0
  80. package/plugins/design/dev-server/test/boot-self-heal.test.ts +1 -1
  81. package/plugins/design/dev-server/test/canvas-cursors.test.ts +1 -1
  82. package/plugins/design/dev-server/test/canvas-edit.test.ts +1 -1
  83. package/plugins/design/dev-server/test/canvas-header.test.ts +1 -1
  84. package/plugins/design/dev-server/test/canvas-hmr-runtime.test.tsx +114 -0
  85. package/plugins/design/dev-server/test/canvas-lib-inline.test.ts +1 -1
  86. package/plugins/design/dev-server/test/canvas-lib-resolver.test.ts +1 -2
  87. package/plugins/design/dev-server/test/canvas-meta-api.test.ts +1 -2
  88. package/plugins/design/dev-server/test/canvas-origin-gate.test.ts +1 -2
  89. package/plugins/design/dev-server/test/collab-annotations-bridge.test.ts +1 -1
  90. package/plugins/design/dev-server/test/collab-bridge.test.ts +1 -1
  91. package/plugins/design/dev-server/test/collab-protocol.test.ts +2 -2
  92. package/plugins/design/dev-server/test/collab-room.test.ts +1 -1
  93. package/plugins/design/dev-server/test/collab-stress.test.ts +1 -1
  94. package/plugins/design/dev-server/test/comments-api.test.ts +1 -2
  95. package/plugins/design/dev-server/test/compile-entry.test.ts +1 -2
  96. package/plugins/design/dev-server/test/exporters/canva.test.ts +1 -2
  97. package/plugins/design/dev-server/test/exporters/history.test.ts +1 -2
  98. package/plugins/design/dev-server/test/exporters/pw-launch.test.ts +1 -1
  99. package/plugins/design/dev-server/test/exporters/scope.test.ts +1 -2
  100. package/plugins/design/dev-server/test/exporters/zip.test.ts +1 -2
  101. package/plugins/design/dev-server/test/git-lifecycle.test.ts +1 -2
  102. package/plugins/design/dev-server/test/handoff-static-frames.test.ts +1 -1
  103. package/plugins/design/dev-server/test/handoff.test.ts +1 -1
  104. package/plugins/design/dev-server/test/hmr-broadcast.test.ts +1 -1
  105. package/plugins/design/dev-server/test/input-router.test.ts +2 -2
  106. package/plugins/design/dev-server/test/locator.test.ts +1 -1
  107. package/plugins/design/dev-server/test/runtime-bundle.test.ts +1 -1
  108. package/plugins/design/dev-server/test/shared-doc-convergence.test.ts +1 -1
  109. package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +2 -3
  110. package/plugins/design/dev-server/test/shared-doc-projection.test.ts +1 -1
  111. package/plugins/design/dev-server/test/snap-distance-pill.test.ts +1 -1
  112. package/plugins/design/dev-server/test/sync-agent.test.ts +2 -3
  113. package/plugins/design/dev-server/test/sync-atomic-write.test.ts +2 -3
  114. package/plugins/design/dev-server/test/sync-codec.test.ts +1 -1
  115. package/plugins/design/dev-server/test/sync-connection-state.test.ts +1 -1
  116. package/plugins/design/dev-server/test/sync-echo-guard.test.ts +1 -1
  117. package/plugins/design/dev-server/test/sync-fs-mirror.test.ts +1 -2
  118. package/plugins/design/dev-server/test/sync-hardening.test.ts +4 -5
  119. package/plugins/design/dev-server/test/sync-hubs-config.test.ts +1 -2
  120. package/plugins/design/dev-server/test/sync-meta-codec.test.ts +1 -1
  121. package/plugins/design/dev-server/test/sync-runtime.test.ts +37 -14
  122. package/plugins/design/dev-server/test/sync-status.test.ts +1 -1
  123. package/plugins/design/dev-server/test/sync-untrusted.test.ts +1 -2
  124. package/plugins/design/dev-server/test/undo-stack.test.ts +2 -2
  125. package/plugins/design/dev-server/test/use-agent-presence.test.tsx +117 -0
  126. package/plugins/design/dev-server/test/use-artboard-drag.test.ts +3 -3
  127. package/plugins/design/dev-server/test/use-canvas-activity.test.tsx +206 -0
  128. package/plugins/design/dev-server/test/use-selection-set.test.tsx +1 -1
  129. package/plugins/design/dev-server/test/use-snap-guides.test.ts +1 -1
  130. package/plugins/design/dev-server/test/use-undo-stack.test.tsx +2 -2
  131. package/plugins/design/dev-server/use-agent-presence.tsx +244 -0
  132. package/plugins/design/dev-server/use-annotation-resize.tsx +1 -5
  133. package/plugins/design/dev-server/use-annotation-selection.tsx +2 -6
  134. package/plugins/design/dev-server/use-annotations-visibility.tsx +1 -1
  135. package/plugins/design/dev-server/use-artboard-drag.tsx +1 -1
  136. package/plugins/design/dev-server/use-canvas-activity.tsx +252 -0
  137. package/plugins/design/dev-server/use-collab.tsx +3 -4
  138. package/plugins/design/dev-server/use-selection-set.tsx +1 -1
  139. package/plugins/design/dev-server/use-tool-mode.tsx +1 -1
  140. package/plugins/design/dev-server/use-undo-stack.tsx +4 -5
  141. package/plugins/design/dev-server/ws.ts +21 -2
  142. package/plugins/design/templates/_shell.html +108 -3
  143. package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +18 -3
@@ -23,15 +23,15 @@
23
23
 
24
24
  import type { Rect } from './geometry.ts';
25
25
  import {
26
- path,
26
+ circle,
27
27
  type DrawPrimitive,
28
28
  type DrawStyle,
29
- type Point,
30
- circle,
31
29
  fe,
32
30
  filter,
33
31
  group,
34
32
  line,
33
+ type Point,
34
+ path,
35
35
  } from './primitives.ts';
36
36
 
37
37
  function fmt(n: number): string {
@@ -18,11 +18,11 @@
18
18
  * React-free (DDR-067) — nothing here imports react / a `.tsx`.
19
19
  */
20
20
 
21
- export * from './primitives.ts';
22
- export * from './geometry.ts';
23
- export * from './palette.ts';
24
- export * from './composition.ts';
25
21
  export * from './brush.ts';
22
+ export * from './composition.ts';
23
+ export * from './geometry.ts';
26
24
  export * from './layout.ts';
27
- export * from './serialize.ts';
28
25
  export * from './optimize.ts';
26
+ export * from './palette.ts';
27
+ export * from './primitives.ts';
28
+ export * from './serialize.ts';
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import { type Rect, routeConnector } from './geometry.ts';
18
- import { type DrawPrimitive, type Point, group, polyline, rect, snap, text } from './primitives.ts';
18
+ import { type DrawPrimitive, group, type Point, polyline, rect, snap, text } from './primitives.ts';
19
19
 
20
20
  // ─────────────────────────────────────────────────────────────────────────────
21
21
  // Grid + modular scale
@@ -1,11 +1,11 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import {
3
- EQUAL_AREA_CIRCLE_SCALE,
4
3
  blobPath,
5
4
  centroid,
6
5
  centroidCenter,
7
6
  chamferCorners,
8
7
  convexHull,
8
+ EQUAL_AREA_CIRCLE_SCALE,
9
9
  equalWeightCircleDiameter,
10
10
  overshoot,
11
11
  pchipEval,
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import type { Rect } from '../geometry.ts';
3
- import { type LabelItem, diagram, modularScale, placeLabels, snapToGrid } from '../layout.ts';
3
+ import { diagram, type LabelItem, modularScale, placeLabels, snapToGrid } from '../layout.ts';
4
4
  import type { Point } from '../primitives.ts';
5
5
 
6
6
  describe('grid + modular scale', () => {
@@ -1,6 +1,5 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import {
3
- VIEWBOX,
4
3
  boxViewBox,
5
4
  circle,
6
5
  group,
@@ -13,6 +12,7 @@ import {
13
12
  text,
14
13
  transformString,
15
14
  use,
15
+ VIEWBOX,
16
16
  } from '../primitives.ts';
17
17
 
18
18
  describe('snap', () => {
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
- import { path, circle, group, line, rect, text } from '../primitives.ts';
3
2
  import type { DrawPrimitive } from '../primitives.ts';
3
+ import { circle, group, line, path, rect, text } from '../primitives.ts';
4
4
  import { primitivesToNodes, toJsx, toSvg } from '../serialize.ts';
5
5
 
6
6
  const SAMPLE: DrawPrimitive[] = [
@@ -15,9 +15,9 @@
15
15
  import { useEffect, useRef, useState } from 'react';
16
16
 
17
17
  import {
18
+ detectEqualSpacing,
18
19
  type EqualSpacingResult,
19
20
  type SpacingAxis,
20
- detectEqualSpacing,
21
21
  } from './equal-spacing-detector.ts';
22
22
  import { useSelectionSet } from './use-selection-set.tsx';
23
23
 
@@ -20,9 +20,8 @@
20
20
  * Results land in .ai/decisions/DDR-024 + .ai/logs/phase-4-perf-<date>.md.
21
21
  */
22
22
 
23
- import { useEffect } from 'react';
24
-
25
23
  import { DCArtboard, DCSection, DesignCanvas } from '@maude/canvas-lib';
24
+ import { useEffect } from 'react';
26
25
 
27
26
  const ARTBOARD_COUNT = 100;
28
27
  const NODES_PER_ARTBOARD = 30;
@@ -14,8 +14,8 @@
14
14
  */
15
15
 
16
16
  import {
17
- type ReactNode,
18
17
  createContext,
18
+ type ReactNode,
19
19
  useCallback,
20
20
  useContext,
21
21
  useEffect,
@@ -13,10 +13,10 @@ import path from 'node:path';
13
13
  import JSZip from 'jszip';
14
14
 
15
15
  import {
16
+ canvasShellUrl,
16
17
  type ExportContext,
17
18
  type ExportOptions,
18
19
  type ExportResult,
19
- canvasShellUrl,
20
20
  } from './index.ts';
21
21
  import type { Target } from './scope.ts';
22
22
 
@@ -13,7 +13,7 @@ import * as html from './html.ts';
13
13
  import * as pdf from './pdf.ts';
14
14
  import * as png from './png.ts';
15
15
  import * as pptx from './pptx.ts';
16
- import { type ResolveScopeArgs, type Scope, type Target, resolveScope } from './scope.ts';
16
+ import { type ResolveScopeArgs, resolveScope, type Scope, type Target } from './scope.ts';
17
17
  import * as svg from './svg.ts';
18
18
  import * as zip from './zip.ts';
19
19
 
@@ -100,8 +100,8 @@ export async function runExport(args: {
100
100
  return adapter.run(targets, args.options, args.ctx);
101
101
  }
102
102
 
103
- export { resolveScope };
104
103
  export type { Scope, Target };
104
+ export { resolveScope };
105
105
 
106
106
  /**
107
107
  * Build the `_canvas-shell.html?canvas=…&tokens=…&components=…` URL the
@@ -15,10 +15,10 @@ import path from 'node:path';
15
15
  import { PDFDocument } from 'pdf-lib';
16
16
 
17
17
  import {
18
+ canvasShellUrl,
18
19
  type ExportContext,
19
20
  type ExportOptions,
20
21
  type ExportResult,
21
- canvasShellUrl,
22
22
  } from './index.ts';
23
23
  import type { Target } from './scope.ts';
24
24
 
@@ -14,10 +14,10 @@ import path from 'node:path';
14
14
  import JSZip from 'jszip';
15
15
 
16
16
  import {
17
+ canvasShellUrl,
17
18
  type ExportContext,
18
19
  type ExportOptions,
19
20
  type ExportResult,
20
- canvasShellUrl,
21
21
  } from './index.ts';
22
22
  import type { Target } from './scope.ts';
23
23
 
@@ -28,10 +28,10 @@ import PptxGenJS from 'pptxgenjs';
28
28
 
29
29
  import { getBrowserBundle } from './_browser-bundles.ts';
30
30
  import {
31
+ canvasShellUrl,
31
32
  type ExportContext,
32
33
  type ExportOptions,
33
34
  type ExportResult,
34
- canvasShellUrl,
35
35
  } from './index.ts';
36
36
  import type { Target } from './scope.ts';
37
37
 
@@ -14,10 +14,10 @@ import JSZip from 'jszip';
14
14
 
15
15
  import { getBrowserBundle } from './_browser-bundles.ts';
16
16
  import {
17
+ canvasShellUrl,
17
18
  type ExportContext,
18
19
  type ExportOptions,
19
20
  type ExportResult,
20
- canvasShellUrl,
21
21
  } from './index.ts';
22
22
  import type { Target } from './scope.ts';
23
23
 
@@ -19,7 +19,7 @@ import type { ActiveJsonShape } from './exporters/scope.ts';
19
19
  import type { Inspect } from './inspect.ts';
20
20
  import { canvasSlug, writeLocator } from './locator.ts';
21
21
  import { DEV_SERVER_ROOT } from './paths.ts';
22
- import { RUNTIME_PACKAGES, getRuntimeBundle, packageForSlug, slugFor } from './runtime-bundle.ts';
22
+ import { getRuntimeBundle, packageForSlug, RUNTIME_PACKAGES, slugFor } from './runtime-bundle.ts';
23
23
 
24
24
  // Real disk install root — never the virtual `/$bunfs/root` of compiled bins.
25
25
  // See paths.ts for the resolution logic + Phase 19.1 / v0.18.1 rationale.
@@ -230,7 +230,37 @@ const INSPECTOR_SCRIPT = `
230
230
  '.dgn-pin { position: absolute; top: 0; left: 0; z-index: 2147483646; width: 22px; height: 22px; padding: 0; border: 0; border-radius: 999px 999px 999px 4px; background: #facc15; color: #1c1917; font: 600 11px/22px var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace); text-align: center; cursor: pointer; box-shadow: 0 2px 6px rgba(0,0,0,0.5), 0 0 0 1px rgba(0,0,0,0.4); transition: filter 120ms; transform-origin: bottom left; will-change: transform; }',
231
231
  '.dgn-pin:hover { filter: brightness(1.1); outline: 2px solid rgba(0,0,0,0.3); }',
232
232
  '.dgn-pin.resolved { background: #22c55e; color: #052e16; }',
233
- '.dgn-pin.focused { box-shadow: 0 4px 12px rgba(0,0,0,0.6), 0 0 0 2px #fff; outline: 2px solid #fff; }'
233
+ '.dgn-pin.focused { box-shadow: 0 4px 12px rgba(0,0,0,0.6), 0 0 0 2px #fff; outline: 2px solid #fff; }',
234
+ /* Phase 13 / DDR-029 — canvas activity overlay. Single injection point so the
235
+ canvas iframe stays self-contained. Scoped with html ... so canvas-page
236
+ stylesheets can't clobber it. The "editing" motion is a light scan beam
237
+ (Phase 13.3) sweeping the whole artboard top→bottom — transform-only
238
+ (compositor), unmounts when the file goes idle (infinite-with-control,
239
+ flow:motion-rules §5); reduced-motion drops the beam to a static rim. The
240
+ rim border + glow are static boundary chrome. */
241
+ 'html .dc-activity-rim { position: absolute; pointer-events: none; box-sizing: border-box; border: 2.5px solid var(--mdcc-activity, hsl(210 90% 58%)); border-radius: 5px; z-index: 6; opacity: 1; transition: opacity 200ms ease-out; }',
242
+ 'html .dc-activity-rim[data-fading="true"] { opacity: 0; }',
243
+ 'html .dc-activity-rim::after { content: ""; position: absolute; inset: -1px; border-radius: 6px; box-shadow: 0 0 0 1px var(--mdcc-activity, hsl(210 90% 58%)), 0 0 16px 2px var(--mdcc-activity, hsl(210 90% 58%)); opacity: 0.55; pointer-events: none; will-change: opacity; animation: dc-activity-glow var(--mdcc-activity-scan-ms, 2200ms) ease-in-out infinite; }',
244
+ 'html .dc-activity-badge { position: absolute; top: 4px; right: 4px; z-index: 2; padding: 2px 8px; border-radius: 4px; background: var(--mdcc-activity, hsl(210 90% 58%)); color: #fff; font: 600 11px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; white-space: nowrap; box-shadow: 0 1px 4px rgba(0,0,0,0.45); }',
245
+ /* Phase 13.3 — the AGENT BORDER is the primary "editing" indicator: a clear
246
+ 2.5px border in the agent color with a softly pulsing glow (::after,
247
+ opacity-only, compositor). Clipping lives on the wash (not the rim) so the
248
+ outward glow is not clipped. The dc-activity-scan wash is ONE full-artboard
249
+ wave: a single gradient with a SHARP bottom edge (full agent color) fading
250
+ up to transparent at the top (height = artboard). Its sharp edge enters at
251
+ the top and the whole wave travels straight down and off past the bottom
252
+ edge (translateY -100% → 100%), then the next wave enters from the top in a
253
+ loop. transform-only; linear so it flows at a steady pace. */
254
+ 'html .dc-activity-scan { position: absolute; inset: 0; overflow: hidden; border-radius: 4px; pointer-events: none; z-index: 1; }',
255
+ 'html .dc-activity-scan::after { content: ""; position: absolute; left: 0; right: 0; top: 0; height: 135%; will-change: transform; background: linear-gradient(to top, color-mix(in oklab, var(--mdcc-activity, hsl(210 90% 58%)) 20%, transparent) 0%, transparent 100%); animation: dc-activity-wave var(--mdcc-activity-scan-ms, 3800ms) linear infinite; }',
256
+ '@keyframes dc-activity-glow { 0%, 100% { opacity: 0.4; } 50% { opacity: 0.85; } }',
257
+ '@keyframes dc-activity-wave { from { transform: translateY(-100%); } to { transform: translateY(100%); } }',
258
+ '@media (prefers-reduced-motion: reduce) { html .dc-activity-rim { transition: none; } html .dc-activity-rim::after { animation: none; opacity: 0.55; } html .dc-activity-scan { display: none; } }',
259
+ /* Phase 13.1 / DDR-077 — "holding last good render" toast, shown when an
260
+ agent edit produced a broken intermediate (build/render error) and the
261
+ canvas is held instead of flashing white. Amber = warn, distinct from the
262
+ blue activity rim. pointer-events:none so it never blocks the canvas. */
263
+ 'html .dc-hmr-holding { position: fixed; left: 50%; bottom: 16px; transform: translateX(-50%); z-index: 2147483646; pointer-events: none; max-width: 90vw; padding: 6px 12px; border-radius: 6px; background: hsl(38 92% 50% / 0.96); color: #1c1207; font: 600 12px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-shadow: 0 2px 10px rgba(0,0,0,0.45); }'
234
264
  ].join('\\n');
235
265
  document.documentElement.appendChild(styleEl);
236
266
 
@@ -277,6 +277,6 @@ export function applyMarqueeMode(
277
277
  selSet.replace(next);
278
278
  }
279
279
 
280
+ export type { Mode };
280
281
  // Re-export for tests.
281
282
  export { modeOf };
282
- export type { Mode };
@@ -17,6 +17,7 @@
17
17
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
18
18
 
19
19
  import { useViewportControllerContext } from './canvas-lib.tsx';
20
+ import { type AgentPresence, useAgentPresence } from './use-agent-presence.tsx';
20
21
  import { type ForeignAwareness, useCollab, useForeignAwareness } from './use-collab.tsx';
21
22
 
22
23
  const CHROME_CSS = `
@@ -110,6 +111,34 @@ const CHROME_CSS = `
110
111
  @media (prefers-reduced-motion: reduce) {
111
112
  .dc-participant { transition: none; }
112
113
  }
114
+ /* Phase 13.2 / DDR-078 — agent presence avatar. Looks like a peer chip + a
115
+ subtle ✦ marker so an agent reads as distinct from a human collaborator. */
116
+ .dc-participant--agent { box-shadow: 0 1px 2px rgba(0,0,0,0.08), 0 0 0 1px rgba(0,0,0,0.04); }
117
+ .dc-participant-agent-marker {
118
+ position: absolute;
119
+ right: -3px;
120
+ bottom: -3px;
121
+ width: 13px;
122
+ height: 13px;
123
+ border-radius: 50%;
124
+ background: var(--maude-chrome-bg-0, #fff);
125
+ color: var(--maude-chrome-fg-0, #111);
126
+ display: inline-flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ font-size: 8px;
130
+ line-height: 1;
131
+ box-shadow: 0 0 0 1px rgba(0,0,0,0.14);
132
+ pointer-events: none;
133
+ }
134
+ .dc-participant-popover__sub {
135
+ font-size: 11px;
136
+ color: var(--maude-chrome-fg-2, #666);
137
+ margin-top: -4px;
138
+ margin-bottom: 6px;
139
+ white-space: normal;
140
+ max-width: 200px;
141
+ }
113
142
  `.trim();
114
143
 
115
144
  function ensureChromeStyles(): void {
@@ -192,10 +221,63 @@ function Avatar({ peer, isFollowing, onToggleFollow }: AvatarProps): JSX.Element
192
221
  );
193
222
  }
194
223
 
224
+ // Phase 13.2 / DDR-078 — an agent rendered as a presence peer. No "Follow"
225
+ // (an agent has no viewport to mirror); a ✦ marker + an "AI agent" popover
226
+ // subtitle distinguish it from a human collaborator.
227
+ function AgentAvatar({ agent }: { agent: AgentPresence }): JSX.Element {
228
+ const [open, setOpen] = useState(false);
229
+ const rootRef = useRef<HTMLDivElement | null>(null);
230
+
231
+ useEffect(() => {
232
+ if (!open) return;
233
+ const onDocClick = (e: MouseEvent) => {
234
+ if (!rootRef.current) return;
235
+ if (!rootRef.current.contains(e.target as Node)) setOpen(false);
236
+ };
237
+ document.addEventListener('mousedown', onDocClick);
238
+ return () => document.removeEventListener('mousedown', onDocClick);
239
+ }, [open]);
240
+
241
+ return (
242
+ <div
243
+ ref={rootRef}
244
+ className="dc-participant dc-participant--agent"
245
+ style={{ background: agent.color }}
246
+ onClick={() => setOpen((v) => !v)}
247
+ onKeyDown={(e) => {
248
+ if (e.key === 'Enter' || e.key === ' ') {
249
+ e.preventDefault();
250
+ setOpen((v) => !v);
251
+ }
252
+ }}
253
+ // biome-ignore lint/a11y/useSemanticElements: matches the peer Avatar chip pattern.
254
+ role="button"
255
+ tabIndex={0}
256
+ title={`${agent.name} (AI agent)`}
257
+ aria-label={`${agent.name}, AI agent`}
258
+ aria-expanded={open}
259
+ >
260
+ {initialsFor(agent.name)}
261
+ <span className="dc-participant-agent-marker" aria-hidden>
262
+
263
+ </span>
264
+ {open && (
265
+ // biome-ignore lint/a11y/useKeyWithClickEvents: stop-propagation wrapper; no actionable child.
266
+ // biome-ignore lint/a11y/useSemanticElements: popover wrapper carries no semantic role.
267
+ <div className="dc-participant-popover" onClick={(e) => e.stopPropagation()}>
268
+ <div className="dc-participant-popover__name">{agent.name}</div>
269
+ <div className="dc-participant-popover__sub">AI agent · {agent.author}</div>
270
+ </div>
271
+ )}
272
+ </div>
273
+ );
274
+ }
275
+
195
276
  export function ParticipantsChrome(): JSX.Element | null {
196
277
  ensureChromeStyles();
197
278
  const collab = useCollab();
198
279
  const peers = useForeignAwareness();
280
+ const agent = useAgentPresence();
199
281
  const controller = useViewportControllerContext();
200
282
 
201
283
  const [followTarget, setFollowTarget] = useState<number | null>(null);
@@ -242,7 +324,9 @@ export function ParticipantsChrome(): JSX.Element | null {
242
324
  controller.setViewport(v);
243
325
  }, [controller, followTarget, peers]);
244
326
 
245
- if (peers.length === 0) return null;
327
+ // Render whenever there's a human peer OR an active agent (so an agent shows
328
+ // even with no human collaborators connected).
329
+ if (peers.length === 0 && !agent) return null;
246
330
 
247
331
  return (
248
332
  <div className="dc-participants" aria-label="Active collaborators">
@@ -254,6 +338,7 @@ export function ParticipantsChrome(): JSX.Element | null {
254
338
  onToggleFollow={onToggleFollow}
255
339
  />
256
340
  ))}
341
+ {agent ? <AgentAvatar key={agent.id} agent={agent} /> : null}
257
342
  </div>
258
343
  );
259
344
  }
@@ -16,6 +16,7 @@
16
16
 
17
17
  import { spawn } from 'node:child_process';
18
18
 
19
+ import { createActivity } from './activity.ts';
19
20
  import { createApi } from './api.ts';
20
21
  import { bootSelfHeal } from './boot-self-heal.ts';
21
22
  import { createAiActivity } from './collab/ai-activity.ts';
@@ -27,7 +28,7 @@ import { createHttp } from './http.ts';
27
28
  import { createInspect } from './inspect.ts';
28
29
  import { startHeapWatch } from './mem.ts';
29
30
  import { createSyncRuntime } from './sync/index.ts';
30
- import { type WsData, createWs, isLoopbackHost, parseCollabSlug } from './ws.ts';
31
+ import { createWs, isLoopbackHost, parseCollabSlug, type WsData } from './ws.ts';
31
32
 
32
33
  // Phase 19 / DDR-044 — covers the marketplace-cache-install gap where
33
34
  // node_modules/ ships empty (git clone honors .gitignore). Auto-installs +
@@ -77,7 +78,10 @@ await inspect.load();
77
78
  collab = createCollab(ctx, api);
78
79
  const aiActivity = createAiActivity(ctx);
79
80
  const gitLifecycle = createGitLifecycle(ctx, collab.registry);
80
- const ws = createWs(ctx, api, inspect, collab);
81
+ // Phase 13 / DDR-029 fs-watch-driven canvas activity overlay. Subscribes to
82
+ // `fs:any` and emits `activity:change`; ws.ts forwards it to canvas iframes.
83
+ const activity = createActivity(ctx);
84
+ const ws = createWs(ctx, api, inspect, collab, activity);
81
85
  const http = createHttp(ctx, api, inspect, aiActivity);
82
86
  const fsWatch = createFsWatch(ctx);
83
87
 
@@ -346,6 +350,11 @@ if (!process.env.NO_OPEN) {
346
350
  async function shutdown() {
347
351
  console.log('\n Stopping…');
348
352
  fsWatch.stop();
353
+ try {
354
+ activity.stop();
355
+ } catch {
356
+ /* timer cleanup is best-effort */
357
+ }
349
358
  try {
350
359
  if (syncRuntime) await syncRuntime.stop();
351
360
  } catch {
@@ -33,7 +33,6 @@ import type * as Y from 'yjs';
33
33
 
34
34
  import { atomicWrite } from './atomic-write.ts';
35
35
  import {
36
- Y_SYNC_TYPES,
37
36
  annotationsFromDoc,
38
37
  applyAnnotationsToDoc,
39
38
  applyCommentsToDoc,
@@ -45,6 +44,7 @@ import {
45
44
  htmlFromDoc,
46
45
  mergeSharedMetaIntoLocal,
47
46
  metaFromDoc,
47
+ Y_SYNC_TYPES,
48
48
  } from './codec.ts';
49
49
  import { type EchoGuard, hashBytes } from './echo-guard.ts';
50
50
 
@@ -29,15 +29,15 @@ import type { Context } from '../context.ts';
29
29
  import { type CanvasSyncAgent, createCanvasSyncAgent } from './agent.ts';
30
30
  import {
31
31
  type ConnectionMonitor,
32
- type ProviderStatus,
33
32
  createConnectionMonitor,
33
+ type ProviderStatus,
34
34
  } from './connection-state.ts';
35
- import { type EchoGuard, createEchoGuard } from './echo-guard.ts';
36
- import { type FsReader, createFsReader } from './fs-mirror.ts';
35
+ import { createEchoGuard, type EchoGuard } from './echo-guard.ts';
36
+ import { createFsReader, type FsReader } from './fs-mirror.ts';
37
37
  import { getHubToken } from './hubs-config.ts';
38
38
  import { migrateSeed } from './migrate-seed.ts';
39
- import { type DocProjection, createDocProjection } from './projection.ts';
40
- import { type SyncStatusStore, createSyncStatusStore } from './status.ts';
39
+ import { createDocProjection, type DocProjection } from './projection.ts';
40
+ import { createSyncStatusStore, type SyncStatusStore } from './status.ts';
41
41
  import { writeUntrustedMarkers } from './untrusted.ts';
42
42
 
43
43
  /** A minimum-surface stand-in for the HocuspocusProvider's runtime API. */
@@ -246,16 +246,17 @@ export function createSyncRuntime(
246
246
  return;
247
247
  }
248
248
 
249
- // DDR-072loud boot banner when the project-level TSX opt-in is on against
250
- // a NON-loopback hub. Broadening from hand-picked canvases to "all .tsx"
251
- // also broadens the WebRTC/self-nav exfil residual (the sandbox contains
252
- // execution but not that lane) to every synced canvas, so this must never be
253
- // silent: a sneaky PR that flips `linkedHub.syncTsx` surfaces here on the
254
- // next `serve`. Loopback hubs (local dev) skip it — no remote exfil concern.
255
- if (linkedHub.syncTsx === true && !isLoopbackHubUrl(linkedHub.url)) {
256
- const tsxCount = canvases.filter((c) => c.html.toLowerCase().endsWith('.tsx')).length;
249
+ // DDR-079TSX sync defaults ON, so every linked non-loopback project that
250
+ // ships .tsx broadcasts the WebRTC/self-nav exfil residual (the sandbox
251
+ // contains execution but not that lane) to every synced canvas. The default
252
+ // traded a footgun (silent 0-syncable) for this surface, so the surface must
253
+ // be LOUD: a banner on every `serve` naming the count + the opt-outs. Fires
254
+ // unless explicitly opted out (`syncTsx: false`); loopback hubs (local dev)
255
+ // skip it no remote exfil concern.
256
+ const tsxBodyCount = canvases.filter((c) => c.html.toLowerCase().endsWith('.tsx')).length;
257
+ if (linkedHub.syncTsx !== false && tsxBodyCount > 0 && !isLoopbackHubUrl(linkedHub.url)) {
257
258
  console.warn(
258
- `[sync] syncTsx=true — ${tsxCount} TSX canvas BODIES will sync to ${linkedHub.url} (project-level opt-in, DDR-072). The sandbox contains execution, but the WebRTC/self-nav exfil residual now applies to ALL synced canvases — only for hubs you operate or fully trust. Set a canvas .meta.json "syncable": false to exclude it.`
259
+ `[sync] ${tsxBodyCount} TSX canvas BODIES will sync to ${linkedHub.url} (TSX sync is ON by default — DDR-079). The sandbox contains execution, but a WebRTC/self-nav exfil residual applies to every synced canvaslink only hubs you operate or trust. Opt out: linkedHub.syncTsx=false (whole project) or a canvas .meta.json "syncable": false (one canvas).`
259
260
  );
260
261
  }
261
262
 
@@ -612,12 +613,15 @@ export async function scanCanvases(ctx: Context): Promise<CanvasScan> {
612
613
  // `.tsx` syncs — the per-canvas opt-in is inert without the sandbox, and
613
614
  // decoupling them would re-open the CRITICAL F1 RCE (DDR-060, DDR-054 §F1).
614
615
  const splitActive = !!ctx.canvasOrigin;
615
- // DDR-072 — project-level TSX opt-in. When `linkedHub.syncTsx` is true, a
616
- // `.tsx` with no explicit sidecar verdict defaults to syncable (the per-canvas
617
- // sidecar still wins either way see resolveSyncable). Coupled with the
618
- // sandbox via `splitActive` exactly like the per-canvas opt-in, so the flag is
619
- // inert without the F1 containment (DDR-060 Lock-2 invariant).
620
- const projectSyncTsx = ctx.cfg.linkedHub?.syncTsx === true;
616
+ // DDR-079 (supersedes DDR-072) — TSX sync defaults ON for a linked project.
617
+ // Absence of the flag = ON: a freshly-linked peer sees the project's TSX
618
+ // without a hidden per-project opt-in (the recurring "I linked but my teammate
619
+ // sees nothing" footgun). `linkedHub.syncTsx: false` is the explicit
620
+ // project-wide opt-out; a per-canvas `.meta.json "syncable": false` still wins
621
+ // for one canvas (see resolveSyncable). The Lock-2 sandbox coupling
622
+ // (`splitActive`) is UNTOUCHED — a `.tsx` still syncs ONLY when the cross-origin
623
+ // containment is active; decoupling them would re-open the F1 RCE (DDR-060).
624
+ const projectSyncTsx = ctx.cfg.linkedHub?.syncTsx !== false;
621
625
  for (const group of ctx.cfg.canvasGroups) {
622
626
  const groupAbs = path.join(ctx.paths.designRoot, group.path);
623
627
  if (!existsSync(groupAbs)) continue;
@@ -636,7 +640,7 @@ export async function scanCanvases(ctx: Context): Promise<CanvasScan> {
636
640
  }
637
641
 
638
642
  /**
639
- * T3 (9.1-B) + DDR-072 — resolve whether a `.tsx` body is syncable.
643
+ * T3 (9.1-B) + DDR-079 (was DDR-072) — resolve whether a `.tsx` body is syncable.
640
644
  *
641
645
  * Tri-state precedence:
642
646
  * 1. The sibling `<name>.meta.json` `"syncable"` boolean ALWAYS wins when
@@ -644,9 +648,10 @@ export async function scanCanvases(ctx: Context): Promise<CanvasScan> {
644
648
  * sidecar; deliberately NOT in the untrusted `/_api/canvas-meta` PATCH
645
649
  * whitelist (api.ts), so a hostile canvas/hub cannot flip its own body into
646
650
  * or out of the sync set.
647
- * 2. Otherwise fall back to the project-level `linkedHub.syncTsx` default.
651
+ * 2. Otherwise fall back to `projectSyncTsx` — which now defaults to TRUE
652
+ * (DDR-079); `linkedHub.syncTsx: false` is the explicit project-wide opt-out.
648
653
  *
649
- * Missing sidecar / parse error → no explicit verdict → the project default.
654
+ * Missing sidecar / parse error → no explicit verdict → the project default (on).
650
655
  */
651
656
  function resolveSyncable(bodyAbs: string, projectSyncTsx: boolean): boolean {
652
657
  const metaAbs = bodyAbs.replace(/\.(tsx|html)$/i, '.meta.json');
@@ -752,7 +757,7 @@ export function buildNoSyncablePayload(
752
757
  ): NoSyncablePayload {
753
758
  const reason =
754
759
  tsxCount > 0
755
- ? `${tsxCount} TSX canvas(es) found but none are syncable — a TSX body syncs when the canvas opts in (.meta.json "syncable": true) OR the project opts in all of them (.design/config.json linkedHub.syncTsx: true, DDR-072). The canvas sandbox is on by default; MAUDE_CANVAS_ORIGIN_SPLIT=0 disables it (and TSX sync with it). See DDR-060.`
760
+ ? `${tsxCount} TSX canvas(es) found but none are syncable. TSX sync is ON by default (DDR-079), so this means it was opted OUT either project-wide (.design/config.json linkedHub.syncTsx: false) or per canvas (.meta.json "syncable": false) — OR the cross-origin sandbox is off (MAUDE_CANVAS_ORIGIN_SPLIT=0 disables it, and TSX sync with it — DDR-060). Remove the opt-out / re-enable the sandbox to sync.`
756
761
  : `no canvases found under ${designRoot}.`;
757
762
  return {
758
763
  linked: true,
@@ -21,7 +21,7 @@
21
21
 
22
22
  import type * as Y from 'yjs';
23
23
  import { Y_TYPES } from '../collab/persistence.ts';
24
- import { Y_SYNC_TYPES, annotationsFromDoc, commentsFromDoc } from './codec.ts';
24
+ import { annotationsFromDoc, commentsFromDoc, Y_SYNC_TYPES } from './codec.ts';
25
25
 
26
26
  export interface CanonicalMaterialization {
27
27
  html: string;
@@ -59,4 +59,4 @@ export function materializeCanonical(doc: Y.Doc): string {
59
59
  }
60
60
 
61
61
  // Re-export so a shadow-compare caller has the type names in one import.
62
- export { Y_TYPES, Y_SYNC_TYPES };
62
+ export { Y_SYNC_TYPES, Y_TYPES };
@@ -34,12 +34,12 @@ import type * as Y from 'yjs';
34
34
 
35
35
  import { Y_TYPES } from '../collab/persistence.ts';
36
36
  import {
37
- Y_SYNC_TYPES,
38
37
  applyAnnotationsToDoc,
39
38
  applyCommentsToDoc,
40
39
  applyCssToDoc,
41
40
  applyHtmlToDoc,
42
41
  applyMetaToDoc,
42
+ Y_SYNC_TYPES,
43
43
  } from './codec.ts';
44
44
  import { ORIGINS } from './origins.ts';
45
45
 
@@ -30,9 +30,6 @@ import type * as Y from 'yjs';
30
30
 
31
31
  import { atomicWrite } from './atomic-write.ts';
32
32
  import {
33
- MAX_CSS_BYTES,
34
- MAX_HTML_BYTES,
35
- MAX_META_BYTES,
36
33
  applyAnnotationsToDoc,
37
34
  applyCommentsToDoc,
38
35
  applyCssToDoc,
@@ -40,6 +37,9 @@ import {
40
37
  applyMetaToDoc,
41
38
  cssFromDoc,
42
39
  htmlFromDoc,
40
+ MAX_CSS_BYTES,
41
+ MAX_HTML_BYTES,
42
+ MAX_META_BYTES,
43
43
  mergeSharedMetaIntoLocal,
44
44
  metaFromDoc,
45
45
  } from './codec.ts';