@1agh/maude 1.0.11 → 1.2.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 (506) hide show
  1. package/apps/studio/acp/bridge.ts +18 -13
  2. package/apps/studio/acp/transcript-io.ts +394 -0
  3. package/apps/studio/acp/transcript.ts +103 -38
  4. package/apps/studio/api.ts +139 -26
  5. package/apps/studio/bin/_import-figma.mjs +57 -11
  6. package/apps/studio/bin/_seed-fixture.mjs +163 -0
  7. package/apps/studio/canvas-imports.ts +89 -0
  8. package/apps/studio/client/app.jsx +257 -7
  9. package/apps/studio/client/export-center.jsx +15 -7
  10. package/apps/studio/client/panels/SyncPanel.jsx +125 -4
  11. package/apps/studio/client/report-bug.jsx +19 -1
  12. package/apps/studio/client/styles/3-shell-maude.css +34 -0
  13. package/apps/studio/client/styles/4-components.css +26 -0
  14. package/apps/studio/collab/persistence.ts +132 -16
  15. package/apps/studio/collab/registry.ts +10 -18
  16. package/apps/studio/debug-bundle.ts +13 -0
  17. package/apps/studio/dist/client.bundle.js +1128 -1128
  18. package/apps/studio/dist/styles.css +1 -1
  19. package/apps/studio/export-dialog.tsx +54 -0
  20. package/apps/studio/exporters/degraded.ts +19 -3
  21. package/apps/studio/exporters/ghostscript.ts +194 -0
  22. package/apps/studio/exporters/pdf-fonts.ts +261 -0
  23. package/apps/studio/exporters/pdf.ts +152 -3
  24. package/apps/studio/exporters/remote.ts +36 -1
  25. package/apps/studio/fs-watch.ts +45 -1
  26. package/apps/studio/http.ts +7 -1
  27. package/apps/studio/mem.ts +45 -9
  28. package/apps/studio/server.ts +11 -4
  29. package/apps/studio/sync/agent.ts +136 -29
  30. package/apps/studio/sync/asset-push.ts +1 -47
  31. package/apps/studio/sync/codec.ts +122 -31
  32. package/apps/studio/sync/cold-start-apply.ts +7 -0
  33. package/apps/studio/sync/cold-start.ts +118 -22
  34. package/apps/studio/sync/comment-identity.ts +68 -0
  35. package/apps/studio/sync/connection-state.ts +15 -1
  36. package/apps/studio/sync/file-ledger.ts +117 -0
  37. package/apps/studio/sync/file-plane.ts +623 -27
  38. package/apps/studio/sync/index.ts +711 -20
  39. package/apps/studio/sync/journal-client.ts +13 -1
  40. package/apps/studio/sync/migrate-seed.ts +40 -3
  41. package/apps/studio/sync/presentation.ts +26 -3
  42. package/apps/studio/sync/retry-after.ts +169 -0
  43. package/apps/studio/sync/seed-progress.ts +218 -0
  44. package/apps/studio/sync/status.ts +122 -5
  45. package/apps/studio/sync/transport-error.ts +111 -0
  46. package/apps/studio/whats-new.json +72 -0
  47. package/cli/bin/maude.mjs +2 -0
  48. package/cli/commands/codex.mjs +191 -0
  49. package/cli/commands/harness.mjs +1051 -0
  50. package/cli/commands/help.mjs +13 -0
  51. package/cli/lib/design-link.mjs +190 -2
  52. package/cli/lib/harness/capabilities.mjs +302 -0
  53. package/cli/lib/harness/codex-runtime.mjs +889 -0
  54. package/cli/lib/harness/compatibility.mjs +27 -0
  55. package/cli/lib/harness/discover-claude.mjs +1393 -0
  56. package/cli/lib/harness/managed-state.mjs +1296 -0
  57. package/cli/lib/harness/model.mjs +86 -0
  58. package/cli/lib/harness/secrets.mjs +390 -0
  59. package/cli/lib/harness/targets/codex.mjs +1141 -0
  60. package/cli/lib/harness/targets/opencode.mjs +807 -0
  61. package/cli/lib/harness/transaction.mjs +1099 -0
  62. package/cli/lib/hubs-config.mjs +35 -8
  63. package/cli/templates/harness/opencode/maude-projector.ts +58 -0
  64. package/docs/harness-capability-matrix.md +125 -0
  65. package/docs/harness-environment-projection.md +95 -0
  66. package/package.json +32 -9
  67. package/plugins/flow/.claude-plugin/config.schema.json +8 -0
  68. package/apps/studio/.ai/cache/_stats.json +0 -7
  69. package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
  70. package/apps/studio/bin/_curl-local.test.mjs +0 -280
  71. package/apps/studio/bin/_pw-launch.test.mjs +0 -90
  72. package/apps/studio/bin/_smart-frames.test.mjs +0 -195
  73. package/apps/studio/bin/_transcribe.test.mjs +0 -80
  74. package/apps/studio/bun.lock +0 -928
  75. package/apps/studio/draw/test/animate.test.ts +0 -172
  76. package/apps/studio/draw/test/brush.test.ts +0 -213
  77. package/apps/studio/draw/test/composition.test.ts +0 -141
  78. package/apps/studio/draw/test/geometry.test.ts +0 -199
  79. package/apps/studio/draw/test/gradient.test.ts +0 -167
  80. package/apps/studio/draw/test/layout.test.ts +0 -120
  81. package/apps/studio/draw/test/morph.test.ts +0 -107
  82. package/apps/studio/draw/test/optimize.test.ts +0 -40
  83. package/apps/studio/draw/test/palette.test.ts +0 -123
  84. package/apps/studio/draw/test/primitives.test.ts +0 -129
  85. package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
  86. package/apps/studio/draw/test/serialize.test.ts +0 -105
  87. package/apps/studio/figma/assets.test.ts +0 -464
  88. package/apps/studio/figma/client.test.ts +0 -395
  89. package/apps/studio/figma/codegen-client.test.ts +0 -276
  90. package/apps/studio/figma/codegen-fonts.test.ts +0 -103
  91. package/apps/studio/figma/codegen-values.test.ts +0 -179
  92. package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
  93. package/apps/studio/figma/fig-decode.test.ts +0 -788
  94. package/apps/studio/figma/fig-differential.test.ts +0 -182
  95. package/apps/studio/figma/fig-translator.test.ts +0 -192
  96. package/apps/studio/figma/fig-vector.test.ts +0 -113
  97. package/apps/studio/figma/from-codegen.test.ts +0 -408
  98. package/apps/studio/figma/sanitize.test.ts +0 -325
  99. package/apps/studio/figma/tailwind-map.test.ts +0 -142
  100. package/apps/studio/figma/to-artboard.test.ts +0 -808
  101. package/apps/studio/figma/to-render.test.ts +0 -180
  102. package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
  103. package/apps/studio/figma/to-strokes.test.ts +0 -705
  104. package/apps/studio/figma/to-tokens.test.ts +0 -321
  105. package/apps/studio/figma/url.test.ts +0 -167
  106. package/apps/studio/footage/schema.test.ts +0 -287
  107. package/apps/studio/generation/adapters/groq.test.ts +0 -80
  108. package/apps/studio/generation/audio-library.test.ts +0 -95
  109. package/apps/studio/generation/captions.test.ts +0 -78
  110. package/apps/studio/generation/elevenlabs.test.ts +0 -223
  111. package/apps/studio/generation/gemini.test.ts +0 -350
  112. package/apps/studio/generation/jobs.test.ts +0 -128
  113. package/apps/studio/generation/keys.test.ts +0 -75
  114. package/apps/studio/generation/prefs.test.ts +0 -95
  115. package/apps/studio/generation/types.test.ts +0 -123
  116. package/apps/studio/generation/whisper-models.test.ts +0 -179
  117. package/apps/studio/test/_helpers.ts +0 -108
  118. package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
  119. package/apps/studio/test/acp-activity.test.ts +0 -154
  120. package/apps/studio/test/acp-ai-activity.test.ts +0 -182
  121. package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
  122. package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
  123. package/apps/studio/test/acp-branch-guard.test.ts +0 -123
  124. package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
  125. package/apps/studio/test/acp-bridge.test.ts +0 -545
  126. package/apps/studio/test/acp-capabilities.test.ts +0 -123
  127. package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
  128. package/apps/studio/test/acp-commands.test.ts +0 -119
  129. package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
  130. package/apps/studio/test/acp-elicitation.test.ts +0 -251
  131. package/apps/studio/test/acp-env.test.ts +0 -95
  132. package/apps/studio/test/acp-mode-banner.test.ts +0 -45
  133. package/apps/studio/test/acp-origin-gate.test.ts +0 -160
  134. package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
  135. package/apps/studio/test/acp-permission.test.ts +0 -278
  136. package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
  137. package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
  138. package/apps/studio/test/acp-session-plugins.test.ts +0 -209
  139. package/apps/studio/test/acp-toolgroup.test.ts +0 -76
  140. package/apps/studio/test/acp-transcript-view.test.ts +0 -71
  141. package/apps/studio/test/acp-transcript.test.ts +0 -238
  142. package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
  143. package/apps/studio/test/acp-usage.test.ts +0 -143
  144. package/apps/studio/test/acp-write-gate.test.ts +0 -323
  145. package/apps/studio/test/acp-write-scope.test.ts +0 -533
  146. package/apps/studio/test/active-state.test.ts +0 -186
  147. package/apps/studio/test/activity.test.ts +0 -189
  148. package/apps/studio/test/ai-activity.test.ts +0 -113
  149. package/apps/studio/test/annotate-write.test.ts +0 -1074
  150. package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
  151. package/apps/studio/test/annotations-align.test.ts +0 -88
  152. package/apps/studio/test/annotations-api.test.ts +0 -145
  153. package/apps/studio/test/annotations-bindings.test.ts +0 -262
  154. package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
  155. package/apps/studio/test/annotations-groups.test.ts +0 -231
  156. package/apps/studio/test/annotations-layer.test.ts +0 -1011
  157. package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
  158. package/apps/studio/test/annotations-snap.test.ts +0 -135
  159. package/apps/studio/test/annotations-text-format.test.ts +0 -396
  160. package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
  161. package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
  162. package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
  163. package/apps/studio/test/artboard-kinds.test.tsx +0 -83
  164. package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
  165. package/apps/studio/test/asset-api.test.ts +0 -149
  166. package/apps/studio/test/assets-s3.test.ts +0 -249
  167. package/apps/studio/test/binary-smoke.test.ts +0 -47
  168. package/apps/studio/test/boot-self-heal.test.ts +0 -87
  169. package/apps/studio/test/browse-posture.test.tsx +0 -107
  170. package/apps/studio/test/bundle-smoke.test.ts +0 -46
  171. package/apps/studio/test/camera-reveal.test.tsx +0 -173
  172. package/apps/studio/test/canvas-artifacts.test.ts +0 -125
  173. package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
  174. package/apps/studio/test/canvas-build.test.ts +0 -141
  175. package/apps/studio/test/canvas-create-api.test.ts +0 -676
  176. package/apps/studio/test/canvas-cursors.test.ts +0 -164
  177. package/apps/studio/test/canvas-edit.test.ts +0 -470
  178. package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
  179. package/apps/studio/test/canvas-header.test.ts +0 -127
  180. package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
  181. package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
  182. package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
  183. package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
  184. package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
  185. package/apps/studio/test/canvas-list-watch.test.ts +0 -388
  186. package/apps/studio/test/canvas-media-drop.test.ts +0 -187
  187. package/apps/studio/test/canvas-meta-api.test.ts +0 -707
  188. package/apps/studio/test/canvas-move-api.test.ts +0 -587
  189. package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
  190. package/apps/studio/test/canvas-pipeline.test.ts +0 -180
  191. package/apps/studio/test/canvas-rects.test.ts +0 -198
  192. package/apps/studio/test/canvas-reorder.test.ts +0 -211
  193. package/apps/studio/test/canvas-route.test.ts +0 -179
  194. package/apps/studio/test/canvas-shell-base.test.ts +0 -107
  195. package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
  196. package/apps/studio/test/canvas-url.test.ts +0 -138
  197. package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
  198. package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
  199. package/apps/studio/test/capture-core.test.ts +0 -32
  200. package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
  201. package/apps/studio/test/chat-attachments.test.ts +0 -117
  202. package/apps/studio/test/chat-context.test.ts +0 -129
  203. package/apps/studio/test/chat-markdown.test.tsx +0 -58
  204. package/apps/studio/test/clip-addressing.test.ts +0 -738
  205. package/apps/studio/test/clip-ops.test.ts +0 -922
  206. package/apps/studio/test/cloud-build.test.ts +0 -95
  207. package/apps/studio/test/cloud-connect-note.test.ts +0 -135
  208. package/apps/studio/test/cloud-endpoints.test.ts +0 -590
  209. package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
  210. package/apps/studio/test/cloud-history-posture.test.ts +0 -230
  211. package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
  212. package/apps/studio/test/cloud-renew.test.ts +0 -205
  213. package/apps/studio/test/cloud-session-role.test.ts +0 -161
  214. package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
  215. package/apps/studio/test/cold-start-apply.test.ts +0 -303
  216. package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
  217. package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
  218. package/apps/studio/test/collab-bridge.test.ts +0 -79
  219. package/apps/studio/test/collab-loopback.test.ts +0 -63
  220. package/apps/studio/test/collab-origin-gate.test.ts +0 -323
  221. package/apps/studio/test/collab-protocol.test.ts +0 -146
  222. package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
  223. package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
  224. package/apps/studio/test/collab-room.test.ts +0 -188
  225. package/apps/studio/test/collab-session-survival.test.tsx +0 -176
  226. package/apps/studio/test/collab-stress.test.ts +0 -129
  227. package/apps/studio/test/comment-mount.test.ts +0 -97
  228. package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
  229. package/apps/studio/test/comments-api.test.ts +0 -277
  230. package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
  231. package/apps/studio/test/comments-overlay.test.ts +0 -117
  232. package/apps/studio/test/compile-entry.test.ts +0 -133
  233. package/apps/studio/test/component-map.test.ts +0 -48
  234. package/apps/studio/test/config-projection.test.ts +0 -104
  235. package/apps/studio/test/config-reload.test.ts +0 -230
  236. package/apps/studio/test/config-version.test.ts +0 -88
  237. package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
  238. package/apps/studio/test/convert-to-absolute.test.ts +0 -333
  239. package/apps/studio/test/credential-grammar.test.ts +0 -107
  240. package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
  241. package/apps/studio/test/csrf-write-guard.test.ts +0 -141
  242. package/apps/studio/test/debug-bundle.test.ts +0 -105
  243. package/apps/studio/test/design-setup-readiness.test.ts +0 -155
  244. package/apps/studio/test/detach-component.test.ts +0 -94
  245. package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
  246. package/apps/studio/test/dom-selection.test.ts +0 -198
  247. package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
  248. package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
  249. package/apps/studio/test/edit-persistence.test.ts +0 -91
  250. package/apps/studio/test/edit-scope-api.test.ts +0 -119
  251. package/apps/studio/test/edit-source-command.test.ts +0 -142
  252. package/apps/studio/test/edit-suppress.test.ts +0 -170
  253. package/apps/studio/test/editing-presence.test.ts +0 -103
  254. package/apps/studio/test/element-resize.test.ts +0 -136
  255. package/apps/studio/test/element-structural-api.test.ts +0 -434
  256. package/apps/studio/test/element-structural-edit.test.ts +0 -750
  257. package/apps/studio/test/ensure-browser.test.ts +0 -92
  258. package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
  259. package/apps/studio/test/export-assemble.test.ts +0 -53
  260. package/apps/studio/test/export-browser-lane.test.ts +0 -126
  261. package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
  262. package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
  263. package/apps/studio/test/export-center.test.tsx +0 -287
  264. package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
  265. package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
  266. package/apps/studio/test/export-lane.test.ts +0 -245
  267. package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
  268. package/apps/studio/test/exporters/canva.test.ts +0 -63
  269. package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
  270. package/apps/studio/test/exporters/endpoint.test.ts +0 -121
  271. package/apps/studio/test/exporters/history.test.ts +0 -107
  272. package/apps/studio/test/exporters/html.test.ts +0 -26
  273. package/apps/studio/test/exporters/jobs.test.ts +0 -342
  274. package/apps/studio/test/exporters/pdf.test.ts +0 -85
  275. package/apps/studio/test/exporters/png.test.ts +0 -103
  276. package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
  277. package/apps/studio/test/exporters/pptx.test.ts +0 -31
  278. package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
  279. package/apps/studio/test/exporters/runtime.test.ts +0 -59
  280. package/apps/studio/test/exporters/scope.test.ts +0 -292
  281. package/apps/studio/test/exporters/svg.test.ts +0 -29
  282. package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
  283. package/apps/studio/test/exporters/zip.test.ts +0 -104
  284. package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
  285. package/apps/studio/test/figjam-v3-model.test.ts +0 -342
  286. package/apps/studio/test/figma-explode.test.ts +0 -438
  287. package/apps/studio/test/figma-provenance.test.ts +0 -108
  288. package/apps/studio/test/figma-routes.test.ts +0 -294
  289. package/apps/studio/test/file-lock.test.ts +0 -84
  290. package/apps/studio/test/fixtures/backgrounding-claude-auth.sh +0 -12
  291. package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
  292. package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
  293. package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
  294. package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
  295. package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
  296. package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
  297. package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
  298. package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
  299. package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
  300. package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
  301. package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
  302. package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
  303. package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
  304. package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
  305. package/apps/studio/test/fixtures/noisy-claude-auth.mjs +0 -27
  306. package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
  307. package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
  308. package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
  309. package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
  310. package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
  311. package/apps/studio/test/fixtures/wedged-claude-auth.sh +0 -11
  312. package/apps/studio/test/footage-store.test.ts +0 -100
  313. package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
  314. package/apps/studio/test/fs-watch.test.ts +0 -41
  315. package/apps/studio/test/gemma-models.test.ts +0 -245
  316. package/apps/studio/test/generate-route.test.ts +0 -106
  317. package/apps/studio/test/git-api.test.ts +0 -0
  318. package/apps/studio/test/git-branches.test.ts +0 -341
  319. package/apps/studio/test/git-cloud-posture.test.ts +0 -59
  320. package/apps/studio/test/git-grouping.test.ts +0 -106
  321. package/apps/studio/test/git-lifecycle.test.ts +0 -100
  322. package/apps/studio/test/git-log-format.test.ts +0 -95
  323. package/apps/studio/test/git-safe-rel.test.ts +0 -132
  324. package/apps/studio/test/git-system-engine.test.ts +0 -135
  325. package/apps/studio/test/git-watch.test.ts +0 -97
  326. package/apps/studio/test/github-api.test.ts +0 -577
  327. package/apps/studio/test/grid-track-handles.test.ts +0 -160
  328. package/apps/studio/test/handoff-static-frames.test.ts +0 -215
  329. package/apps/studio/test/handoff.test.ts +0 -329
  330. package/apps/studio/test/history-rollback.test.ts +0 -88
  331. package/apps/studio/test/hmr-broadcast.test.ts +0 -236
  332. package/apps/studio/test/hmr-classify.test.ts +0 -87
  333. package/apps/studio/test/hub-link.test.ts +0 -80
  334. package/apps/studio/test/identity-cache.test.ts +0 -83
  335. package/apps/studio/test/import-asset-browser.test.ts +0 -64
  336. package/apps/studio/test/import-asset-route.test.ts +0 -71
  337. package/apps/studio/test/import-asset.test.ts +0 -427
  338. package/apps/studio/test/import-brand-browser.test.ts +0 -88
  339. package/apps/studio/test/import-brand-route.test.ts +0 -120
  340. package/apps/studio/test/import-brand.test.ts +0 -206
  341. package/apps/studio/test/import-figma.test.ts +0 -667
  342. package/apps/studio/test/import-tokens.test.ts +0 -722
  343. package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
  344. package/apps/studio/test/input-router.test.ts +0 -757
  345. package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
  346. package/apps/studio/test/inspect-selections.test.ts +0 -219
  347. package/apps/studio/test/knob-props-authored.test.ts +0 -87
  348. package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
  349. package/apps/studio/test/locator.test.ts +0 -214
  350. package/apps/studio/test/marquee-overlay.test.ts +0 -94
  351. package/apps/studio/test/media-commit-chain.test.ts +0 -210
  352. package/apps/studio/test/mode-toggle.test.tsx +0 -226
  353. package/apps/studio/test/motion-sample.test.ts +0 -35
  354. package/apps/studio/test/move-artboards-command.test.ts +0 -108
  355. package/apps/studio/test/participants-chrome.test.ts +0 -65
  356. package/apps/studio/test/paths.test.ts +0 -88
  357. package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
  358. package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
  359. package/apps/studio/test/perf-harness.ts +0 -193
  360. package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
  361. package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
  362. package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
  363. package/apps/studio/test/photo-edit-api.test.ts +0 -201
  364. package/apps/studio/test/photo-filters.test.ts +0 -149
  365. package/apps/studio/test/photo-pipeline.test.ts +0 -106
  366. package/apps/studio/test/photo-taxonomy.test.ts +0 -96
  367. package/apps/studio/test/print-marks.test.ts +0 -113
  368. package/apps/studio/test/print-units.test.ts +0 -173
  369. package/apps/studio/test/read-annotations.test.ts +0 -727
  370. package/apps/studio/test/read-only-gate.test.ts +0 -188
  371. package/apps/studio/test/read-only-role.test.ts +0 -95
  372. package/apps/studio/test/readiness.test.ts +0 -337
  373. package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
  374. package/apps/studio/test/reorder-api.test.ts +0 -210
  375. package/apps/studio/test/repo-concurrency.test.ts +0 -146
  376. package/apps/studio/test/repo-lock.test.ts +0 -350
  377. package/apps/studio/test/report-proxy.test.ts +0 -125
  378. package/apps/studio/test/ripple.test.ts +0 -197
  379. package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
  380. package/apps/studio/test/runtime-bundle.test.ts +0 -69
  381. package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
  382. package/apps/studio/test/scaffold-design.test.ts +0 -122
  383. package/apps/studio/test/server-lifecycle.test.ts +0 -38
  384. package/apps/studio/test/session-runtime-state.test.ts +0 -226
  385. package/apps/studio/test/session-scope.test.ts +0 -100
  386. package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
  387. package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
  388. package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
  389. package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
  390. package/apps/studio/test/shared-doc-projection.test.ts +0 -238
  391. package/apps/studio/test/shell-importmap.test.ts +0 -56
  392. package/apps/studio/test/sizing-mode.test.ts +0 -102
  393. package/apps/studio/test/slash-commands.test.ts +0 -117
  394. package/apps/studio/test/snap-distance-pill.test.ts +0 -68
  395. package/apps/studio/test/spacing-handles.test.ts +0 -138
  396. package/apps/studio/test/specimen-select.test.ts +0 -88
  397. package/apps/studio/test/sync-agent.test.ts +0 -660
  398. package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
  399. package/apps/studio/test/sync-asset-push.test.ts +0 -931
  400. package/apps/studio/test/sync-atomic-write.test.ts +0 -62
  401. package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
  402. package/apps/studio/test/sync-autocommit.test.ts +0 -537
  403. package/apps/studio/test/sync-canvas-path.test.ts +0 -200
  404. package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
  405. package/apps/studio/test/sync-codec.test.ts +0 -230
  406. package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
  407. package/apps/studio/test/sync-cold-start.test.ts +0 -304
  408. package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
  409. package/apps/studio/test/sync-connection-state.test.ts +0 -337
  410. package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
  411. package/apps/studio/test/sync-decide-file.test.ts +0 -420
  412. package/apps/studio/test/sync-doc-name.test.ts +0 -281
  413. package/apps/studio/test/sync-echo-guard.test.ts +0 -96
  414. package/apps/studio/test/sync-file-ledger.test.ts +0 -334
  415. package/apps/studio/test/sync-file-membership.test.ts +0 -347
  416. package/apps/studio/test/sync-file-plane.test.ts +0 -976
  417. package/apps/studio/test/sync-file-pull.test.ts +0 -333
  418. package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
  419. package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
  420. package/apps/studio/test/sync-hardening.test.ts +0 -586
  421. package/apps/studio/test/sync-hub-listing.test.ts +0 -46
  422. package/apps/studio/test/sync-hubs-config.test.ts +0 -133
  423. package/apps/studio/test/sync-incident-replay.test.ts +0 -211
  424. package/apps/studio/test/sync-journal.test.ts +0 -176
  425. package/apps/studio/test/sync-meta-codec.test.ts +0 -199
  426. package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
  427. package/apps/studio/test/sync-move-retirement.test.ts +0 -231
  428. package/apps/studio/test/sync-panel-surface.test.ts +0 -153
  429. package/apps/studio/test/sync-path-pull.test.ts +0 -531
  430. package/apps/studio/test/sync-presentation.test.ts +0 -262
  431. package/apps/studio/test/sync-pull-budget.test.ts +0 -169
  432. package/apps/studio/test/sync-remote-docs.test.ts +0 -333
  433. package/apps/studio/test/sync-resync-routes.test.ts +0 -168
  434. package/apps/studio/test/sync-runtime.test.ts +0 -1675
  435. package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
  436. package/apps/studio/test/sync-settings-routes.test.ts +0 -195
  437. package/apps/studio/test/sync-settings.test.ts +0 -151
  438. package/apps/studio/test/sync-status.test.ts +0 -261
  439. package/apps/studio/test/sync-supervisor.test.ts +0 -262
  440. package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
  441. package/apps/studio/test/sync-trash.test.ts +0 -132
  442. package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
  443. package/apps/studio/test/sync-untrusted.test.ts +0 -103
  444. package/apps/studio/test/system-endpoint.test.ts +0 -144
  445. package/apps/studio/test/text-editability-stamp.test.ts +0 -131
  446. package/apps/studio/test/timeline-comp-target.test.ts +0 -139
  447. package/apps/studio/test/timeline-media-cache.test.ts +0 -41
  448. package/apps/studio/test/timeline-parse.test.ts +0 -184
  449. package/apps/studio/test/timeline-scale.test.ts +0 -65
  450. package/apps/studio/test/timeline-snap.test.ts +0 -85
  451. package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
  452. package/apps/studio/test/tool-palette.test.tsx +0 -55
  453. package/apps/studio/test/tour-overlay.test.tsx +0 -90
  454. package/apps/studio/test/trusted-request-host.test.ts +0 -66
  455. package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
  456. package/apps/studio/test/undo-stack.test.ts +0 -244
  457. package/apps/studio/test/use-agent-presence.test.tsx +0 -117
  458. package/apps/studio/test/use-annotation-resize.test.ts +0 -402
  459. package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
  460. package/apps/studio/test/use-artboard-drag.test.ts +0 -324
  461. package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
  462. package/apps/studio/test/use-collab.test.ts +0 -0
  463. package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
  464. package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
  465. package/apps/studio/test/use-selection-set.test.tsx +0 -166
  466. package/apps/studio/test/use-snap-guides.test.ts +0 -271
  467. package/apps/studio/test/use-tool-mode.test.tsx +0 -186
  468. package/apps/studio/test/use-undo-stack.test.tsx +0 -223
  469. package/apps/studio/test/video-asset.test.ts +0 -163
  470. package/apps/studio/test/video-comp-fixture.test.ts +0 -50
  471. package/apps/studio/test/video-comp.test.ts +0 -270
  472. package/apps/studio/test/video-encode-lib.test.ts +0 -63
  473. package/apps/studio/test/video-render-bridge.test.ts +0 -171
  474. package/apps/studio/test/whats-new.test.ts +0 -113
  475. package/apps/studio/test/whisper-setup.test.ts +0 -97
  476. package/apps/studio/test/workspace-containment.test.ts +0 -382
  477. package/apps/studio/test/workspace-signin.test.ts +0 -292
  478. package/apps/studio/test/ws-handshake.test.ts +0 -33
  479. package/cli/commands/cache.test.mjs +0 -166
  480. package/cli/commands/design-link.test.mjs +0 -419
  481. package/cli/commands/design.test.mjs +0 -240
  482. package/cli/commands/doctor.test.mjs +0 -237
  483. package/cli/commands/hub-workspace.test.mjs +0 -275
  484. package/cli/commands/hub.test.mjs +0 -225
  485. package/cli/commands/kg.test.mjs +0 -142
  486. package/cli/commands/preflight.test.mjs +0 -46
  487. package/cli/lib/argv.test.mjs +0 -46
  488. package/cli/lib/cache.test.mjs +0 -303
  489. package/cli/lib/cell-plan.test.mjs +0 -225
  490. package/cli/lib/config-lint.test.mjs +0 -117
  491. package/cli/lib/ddr-to-kgai.test.mjs +0 -118
  492. package/cli/lib/design-ownership.test.mjs +0 -329
  493. package/cli/lib/fetch-asset.test.mjs +0 -213
  494. package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
  495. package/cli/lib/figma-import-controls.test.mjs +0 -70
  496. package/cli/lib/flow-design-integration.test.mjs +0 -165
  497. package/cli/lib/gitignore-block.test.mjs +0 -136
  498. package/cli/lib/gitignore-drift.test.mjs +0 -156
  499. package/cli/lib/hubs-config.test.mjs +0 -165
  500. package/cli/lib/pkg-root.test.mjs +0 -111
  501. package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
  502. package/cli/lib/plugin-name-namespace.test.mjs +0 -71
  503. package/cli/lib/reconstruct-toolset.test.mjs +0 -194
  504. package/cli/lib/stack-detect.test.mjs +0 -121
  505. package/cli/lib/update-check.test.mjs +0 -32
  506. package/cli/lib/workspace-plan.test.mjs +0 -699
@@ -14,6 +14,7 @@
14
14
 
15
15
  import type { AssetPushProgress } from './asset-push.ts';
16
16
  import type { SyncStatusSnapshot } from './connection-state.ts';
17
+ import type { SeedProgress } from './seed-progress.ts';
17
18
 
18
19
  // `cold-start-hub-wins` stays in the union for OLD payload readers (additive
19
20
  // evolution — the NoSyncablePayload discriminator pattern); new conflicts are
@@ -41,6 +42,11 @@ export interface SyncStatusPayload extends SyncStatusSnapshot {
41
42
  canvases: number;
42
43
  /** Recent conflict notifications (most-recent-last, capped). */
43
44
  conflicts: SyncConflict[];
45
+ /**
46
+ * When the ConnectionMonitor last spoke, as distinct from when this payload
47
+ * was built (`updatedAt`). Additive — see the note in `payload()`.
48
+ */
49
+ connectionUpdatedAt?: number;
44
50
  /**
45
51
  * Phase 9.2 (DDR-064) — true when the unified single-shared-doc model is
46
52
  * active (MAUDE_SHARED_DOC). Lets `maude design status` + the browser banner
@@ -112,6 +118,25 @@ export interface FilePlaneStatus {
112
118
  /** One sentence, in the user's terms, about what is waiting and why. */
113
119
  detail: string;
114
120
  }[];
121
+ /**
122
+ * Files this lane could NOT move on the last pass — issue #109.
123
+ *
124
+ * `held` covers the breakers, which are deliberate refusals this machine
125
+ * made. This covers the other half: a transfer the hub refused, or a write
126
+ * that failed. It had no field at all, so ~200 refused assets rendered as a
127
+ * status bar reading `synced` and a person seeing broken images with no
128
+ * cause anywhere but a terminal they never open. Same disease `held` was
129
+ * added to cure, one lane over.
130
+ */
131
+ failed?: number;
132
+ /**
133
+ * The hub asked us to slow down and the WHOLE LANE is paused until `until`.
134
+ *
135
+ * Distinguished from `failed` because the answer is different: nothing is
136
+ * wrong, nothing is lost, and there is nothing to do but wait — which is
137
+ * exactly what a person cannot infer from a count of failures.
138
+ */
139
+ rateLimited?: { until: number; waiting: number };
115
140
  /**
116
141
  * THE DORUČENKA (DDR-226 §7) — per-path delivery state.
117
142
  *
@@ -126,6 +151,26 @@ export interface FilePlaneStatus {
126
151
  * as the thing to disagree with.
127
152
  */
128
153
  delivery?: Record<string, string>;
154
+ /**
155
+ * How many delivery rows were dropped from `delivery` by its cap.
156
+ *
157
+ * The map used to be unbounded — one entry per ledger row, so 2 961 keys for
158
+ * a real project, re-serialized into `_sync.json` AND broadcast over the
159
+ * WebSocket on EVERY status change, including 200 ms-throttled asset-progress
160
+ * emits. Same bounding shape `items`/`itemsTruncated` already uses.
161
+ */
162
+ deliveryTruncated?: number;
163
+ /**
164
+ * THE DENOMINATOR (feature-large-project-seed).
165
+ *
166
+ * Derived from the LEDGER rather than from per-pass results — see
167
+ * `seed-progress.ts` for why the per-pass counters read zero for twenty
168
+ * minutes while 2 961 rows changed underneath them. The raw counters above
169
+ * stay beside it deliberately: a panel derived from the same source it
170
+ * displays cannot be cross-checked, and DDR-214's lesson is that a status
171
+ * surface has to be falsifiable.
172
+ */
173
+ progress?: SeedProgress;
129
174
  }
130
175
 
131
176
  export interface SyncStatusStoreOptions {
@@ -139,6 +184,8 @@ export interface SyncStatusStoreOptions {
139
184
  broadcast?: (payload: SyncStatusPayload) => void;
140
185
  /** Max conflict notifications retained. Default 20. */
141
186
  maxConflicts?: number;
187
+ /** Floor between two writes/broadcasts. 0 disables coalescing (tests). */
188
+ flushIntervalMs?: number;
142
189
  now?: () => number;
143
190
  }
144
191
 
@@ -164,9 +211,17 @@ export interface SyncStatusStore {
164
211
 
165
212
  const DEFAULT_MAX_CONFLICTS = 20;
166
213
 
214
+ /** Floor between two status writes/broadcasts. Matches `asset-push.ts`'s own
215
+ * `PROGRESS_INTERVAL_MS` — the lane that emits fastest sets the pace. */
216
+ export const STATUS_FLUSH_MS = 200;
217
+
218
+ /** Delivery rows one payload carries, actionable-first. See `deliveryTruncated`. */
219
+ export const MAX_DELIVERY_ROWS = 300;
220
+
167
221
  export function createSyncStatusStore(opts: SyncStatusStoreOptions): SyncStatusStore {
168
222
  const now = opts.now ?? Date.now;
169
223
  const maxConflicts = opts.maxConflicts ?? DEFAULT_MAX_CONFLICTS;
224
+ const flushIntervalMs = opts.flushIntervalMs ?? STATUS_FLUSH_MS;
170
225
  const conflicts: SyncConflict[] = [];
171
226
 
172
227
  let snapshot: SyncStatusSnapshot = {
@@ -188,6 +243,18 @@ export function createSyncStatusStore(opts: SyncStatusStoreOptions): SyncStatusS
188
243
  function payload(): SyncStatusPayload {
189
244
  return {
190
245
  ...snapshot,
246
+ // WHEN THIS PAYLOAD WAS BUILT, not when the connection monitor last
247
+ // spoke. `updatedAt` arrived here by spreading the monitor's snapshot,
248
+ // which only `update()` refreshes — so `updateFiles()` and
249
+ // `updateAssets()` flushed payloads carrying a stale timestamp ABOUT
250
+ // THEMSELVES. Measured at 130 s stale against a ledger that was current
251
+ // to within a second, on a file being rewritten continuously. A
252
+ // freshness stamp that lies is worse than none: it is what a person (and
253
+ // `maude design status`) uses to decide whether sync is alive at all.
254
+ updatedAt: now(),
255
+ // The monitor's own stamp, preserved under a name that says what it is,
256
+ // so nothing that depended on the old meaning silently changes meaning.
257
+ connectionUpdatedAt: snapshot.updatedAt,
191
258
  url: opts.url,
192
259
  canvases: opts.canvases,
193
260
  conflicts: conflicts.slice(),
@@ -198,7 +265,23 @@ export function createSyncStatusStore(opts: SyncStatusStoreOptions): SyncStatusS
198
265
  };
199
266
  }
200
267
 
201
- function flush(): void {
268
+ /**
269
+ * Write + broadcast, COALESCED.
270
+ *
271
+ * Every emit used to re-serialize the whole payload — including an unbounded
272
+ * per-path delivery map — to disk and to every open tab. During a seed the
273
+ * asset lane alone emits every 200 ms, and the file lane emits per pass, so
274
+ * the cost was paid continuously for a payload nobody could read that fast.
275
+ *
276
+ * `immediate` is not an optimisation escape hatch: conflicts, notices and
277
+ * terminal transitions are the events a person is waiting on, and delaying
278
+ * those by even a debounce window is the class of lie this file exists to
279
+ * avoid.
280
+ */
281
+ let flushTimer: ReturnType<typeof setTimeout> | null = null;
282
+ let pendingFlush = false;
283
+
284
+ function emit(): void {
202
285
  const p = payload();
203
286
  try {
204
287
  opts.write(p);
@@ -212,15 +295,47 @@ export function createSyncStatusStore(opts: SyncStatusStoreOptions): SyncStatusS
212
295
  }
213
296
  }
214
297
 
298
+ function flush(immediate = false): void {
299
+ if (flushIntervalMs <= 0) {
300
+ emit();
301
+ return;
302
+ }
303
+ if (!immediate && flushTimer !== null) {
304
+ pendingFlush = true;
305
+ return;
306
+ }
307
+ // An immediate flush jumps the queue but still OPENS the window — without
308
+ // that, every urgent write was followed by a free un-coalesced one, so a
309
+ // conflict during a seed re-admitted the whole burst it was jumping ahead of.
310
+ if (flushTimer !== null) {
311
+ clearTimeout(flushTimer);
312
+ flushTimer = null;
313
+ }
314
+ pendingFlush = false;
315
+ emit();
316
+ flushTimer = setTimeout(() => {
317
+ flushTimer = null;
318
+ // Only if something arrived while we were waiting — an idle store must
319
+ // not keep a timer alive forever.
320
+ if (pendingFlush) {
321
+ pendingFlush = false;
322
+ flush();
323
+ }
324
+ }, flushIntervalMs);
325
+ flushTimer.unref?.();
326
+ }
327
+
215
328
  return {
216
329
  update(next) {
330
+ const changed = snapshot.state !== next.state;
217
331
  snapshot = next;
218
- flush();
332
+ // A connection state CHANGE is the headline; a heartbeat is not.
333
+ flush(changed);
219
334
  },
220
335
  addConflict(conflict) {
221
336
  conflicts.push({ ...conflict, at: now() });
222
337
  if (conflicts.length > maxConflicts) conflicts.splice(0, conflicts.length - maxConflicts);
223
- flush();
338
+ flush(true);
224
339
  },
225
340
  updateAssets(progress) {
226
341
  assets = progress;
@@ -228,12 +343,14 @@ export function createSyncStatusStore(opts: SyncStatusStoreOptions): SyncStatusS
228
343
  },
229
344
  updateFiles(next) {
230
345
  files = next;
231
- flush();
346
+ // A finished or stalled seed is what a person is waiting to see; a
347
+ // mid-seed tick is not.
348
+ flush(next.progress?.phase === 'converged' || next.progress?.phase === 'blocked');
232
349
  },
233
350
  notice(next) {
234
351
  if (notices.some((n) => n.id === next.id)) return;
235
352
  notices.push({ ...next, at: now() });
236
- flush();
353
+ flush(true);
237
354
  },
238
355
  get: payload,
239
356
  };
@@ -0,0 +1,111 @@
1
+ // What a THROWN fetch error means, in words a person can act on.
2
+ //
3
+ // THE BUG THIS EXISTS TO FIX. Every `catch` in `file-plane.ts` stored
4
+ // `(err as Error).message` directly into the per-path delivery reason a person
5
+ // reads in the Sync panel. Those messages come from Bun's runtime, not from us:
6
+ //
7
+ // $ bun -e "try{await fetch('http://127.0.0.1:1/x')}catch(e){console.log(e.message)}"
8
+ // Unable to connect. Is the computer able to access the url?
9
+ //
10
+ // On 2026-09-03, 484 of 803 undelivered files in an 8.8 GB project carried
11
+ // exactly those two Bun strings — because the cell they were being pushed to
12
+ // was restarting in a loop. So a person looking for "why is my project not
13
+ // syncing" was told, 135 times, to check their URL for a typo. It also cost a
14
+ // debugging session: the strings appear nowhere in this repo, so grepping for
15
+ // them found nothing and the reason class looked like a mystery.
16
+ //
17
+ // `failureReason()` in `retry-after.ts` is the sibling for RESPONSE refusals —
18
+ // a request that arrived and was declined. This module is for the requests that
19
+ // never arrived at all.
20
+
21
+ /** What kind of transport failure this was. */
22
+ export type TransportErrorClass = 'unreachable' | 'timeout' | 'aborted' | 'other';
23
+
24
+ export interface ClassifiedTransportError {
25
+ class: TransportErrorClass;
26
+ /** What a person reads. Ours, never the runtime's. */
27
+ text: string;
28
+ /** The runtime's own words — for the console and `detail` fields ONLY. */
29
+ raw: string;
30
+ }
31
+
32
+ /** How much of a raw message we keep. Bounded: it reaches logs, and a hostile
33
+ * peer can influence some of what a fetch throws. */
34
+ const MAX_RAW_CHARS = 200;
35
+
36
+ /**
37
+ * Bun's connect-failure wording, as of Bun 1.x.
38
+ *
39
+ * MATCHED AS A FALLBACK, NOT AS THE PRIMARY SIGNAL. Error *names* are stable
40
+ * API; these sentences are not, and a Bun upgrade that rewords them must
41
+ * degrade this to `other` with a neutral sentence rather than start leaking the
42
+ * new wording. That is why `other` has a real message of its own instead of
43
+ * falling through to `raw`.
44
+ */
45
+ const BUN_CONNECT_HINTS = [
46
+ 'unable to connect',
47
+ 'was there a typo in the url or port',
48
+ 'connectionrefused',
49
+ 'connection refused',
50
+ 'failed to connect',
51
+ 'econnrefused',
52
+ 'enotfound',
53
+ 'eai_again',
54
+ 'dns',
55
+ 'socket connection was closed unexpectedly',
56
+ ];
57
+
58
+ const BUN_TIMEOUT_HINTS = ['timeout', 'timed out', 'etimedout'];
59
+
60
+ /**
61
+ * Classify a caught `fetch` rejection.
62
+ *
63
+ * Total: every input produces a class and a sentence, including a non-Error
64
+ * throw. A classifier that can return "nothing" would just reintroduce the
65
+ * `err.message` fall-through it replaces.
66
+ */
67
+ export function classifyTransportError(err: unknown): ClassifiedTransportError {
68
+ const raw = rawTextOf(err).slice(0, MAX_RAW_CHARS);
69
+ const name = typeof (err as { name?: unknown })?.name === 'string' ? (err as Error).name : '';
70
+ const haystack = `${name} ${raw}`.toLowerCase();
71
+
72
+ // Names first — they are the stable half of the contract.
73
+ if (name === 'TimeoutError' || (name === 'AbortError' && haystack.includes('timeout'))) {
74
+ return {
75
+ class: 'timeout',
76
+ text: 'The workspace took too long to answer',
77
+ raw,
78
+ };
79
+ }
80
+ if (name === 'AbortError') {
81
+ return { class: 'aborted', text: 'Stopped before it finished', raw };
82
+ }
83
+ if (BUN_TIMEOUT_HINTS.some((h) => haystack.includes(h))) {
84
+ return { class: 'timeout', text: 'The workspace took too long to answer', raw };
85
+ }
86
+ if (BUN_CONNECT_HINTS.some((h) => haystack.includes(h))) {
87
+ return { class: 'unreachable', text: 'Could not reach the workspace', raw };
88
+ }
89
+ // NEUTRAL, never the raw string. An unrecognised failure is still a failure
90
+ // we own the wording of.
91
+ return { class: 'other', text: 'The transfer did not complete', raw };
92
+ }
93
+
94
+ /**
95
+ * The user-facing sentence alone — the common call site, since almost every
96
+ * `catch` wants exactly this and nothing else.
97
+ */
98
+ export function transportErrorText(err: unknown): string {
99
+ return classifyTransportError(err).text;
100
+ }
101
+
102
+ function rawTextOf(err: unknown): string {
103
+ if (typeof err === 'string') return err;
104
+ const message = (err as { message?: unknown })?.message;
105
+ if (typeof message === 'string') return message;
106
+ try {
107
+ return String(err);
108
+ } catch {
109
+ return 'unknown error';
110
+ }
111
+ }
@@ -1,6 +1,78 @@
1
1
  {
2
2
  "$schema": "./whats-new.schema.json",
3
3
  "entries": [
4
+ {
5
+ "id": "pdf-export-print-safe-text",
6
+ "version": "1.2.0",
7
+ "date": "2026-09-04",
8
+ "kind": "feature",
9
+ "title": "PDF exports a print shop will actually accept",
10
+ "summary": "A colour font or a faux-italic could come out of a PDF export in a form printers reject — the file looked perfect on screen, and you only found out when the print shop refused it. Maude now checks the fonts on every PDF export and warns you, by name, when one of them would fail preflight. Under Text you can also ask it to verify the fonts are properly embedded, or convert all text to curves so there is nothing left to break — bleed, crop marks and photo resolution all come through untouched.",
11
+ "surface": "design-ui"
12
+ },
13
+ {
14
+ "id": "seed-progress-you-can-watch",
15
+ "version": "1.2.0",
16
+ "date": "2026-09-04",
17
+ "kind": "improvement",
18
+ "title": "You can watch a large project sync",
19
+ "summary": "Linking a big project used to be a black box: the Sync panel counted files it had moved but never how many there were, so \"1 412\" looked the same whether it was a quarter done or finished — and `maude design status` reported only canvases, so a project with hundreds of files still on their way could read as fully synced. Now every surface shows the same thing: how many of how many have arrived, what is waiting, and what is stuck and why. The panel has a progress bar, `maude design status` has a files line (and a `--watch` mode), and the desktop app puts the count in the window title and tells you once when it finishes. Files the workspace will never take — something over its size limit, or an hourly upload allowance that is used up — now say so by name instead of retrying forever.",
20
+ "surface": "design-ui"
21
+ },
22
+ {
23
+ "id": "images-arrive-again",
24
+ "version": "1.2.0",
25
+ "date": "2026-09-04",
26
+ "kind": "fix",
27
+ "title": "Images arrive again on linked projects",
28
+ "summary": "On a project synced with a workspace, images and other project files could quietly never arrive — broken placeholders on the canvas while the Sync panel still said everything was synced. When a workspace asks Maude to slow down, it now waits as long as it was asked instead of retrying straight into the same wall, and the Sync panel says so in plain words: syncing is paused for a moment, nothing is lost, the rest arrives when the pause lifts.",
29
+ "surface": "design-ui"
30
+ },
31
+ {
32
+ "id": "comments-stop-disappearing",
33
+ "version": "1.2.0",
34
+ "date": "2026-09-04",
35
+ "kind": "fix",
36
+ "title": "Comments stop disappearing",
37
+ "summary": "On a project linked to a hub, a comment you had just added could vanish a few seconds later, leaving only the first one on the canvas — with nothing to undo and no sign anything had gone wrong. A new comment now reaches the shared project before it is written to disk, and Maude will not overwrite a comment it has not seen yet, so what you write stays put.",
38
+ "surface": "design-ui"
39
+ },
40
+ {
41
+ "id": "comments-stop-duplicating",
42
+ "version": "1.2.0",
43
+ "date": "2026-09-04",
44
+ "kind": "fix",
45
+ "title": "Comments stop duplicating, and resolve works",
46
+ "summary": "On a project linked to a hub, a comment could come back two, four, then eight times, and clicking resolve appeared to do nothing — every copy had to be dealt with separately, and new ones kept arriving. Maude now recognises the copies as one comment, so they collapse back to a single pin, and resolving or deleting one clears all of it. A project that already filled up with duplicates repairs itself the next time you open it.",
47
+ "surface": "design-ui"
48
+ },
49
+ {
50
+ "id": "synced-canvases-stop-duplicating",
51
+ "version": "1.2.0",
52
+ "date": "2026-09-04",
53
+ "kind": "fix",
54
+ "title": "Synced canvases stop duplicating themselves",
55
+ "summary": "On a project linked to a hub, a canvas could come back with its whole contents pasted onto itself — twice, sometimes more — which broke the build and left the canvas unopenable. It happened when two people (or your machine and the hub) first picked up the same canvas at the same moment. Maude now recognises that exact shape and collapses it back to one copy, on the styles as well as the body. Moving a canvas into another folder also fixes up its imports instead of breaking them.",
56
+ "surface": "design-ui"
57
+ },
58
+ {
59
+ "date": "2026-09-04",
60
+ "id": "sync-recovers-after-the-workspace-sleeps",
61
+ "kind": "fix",
62
+ "summary": "If you left Maude alone for a while, your cloud workspace went to sleep and syncing quietly stopped — the indicator still said it was connected, and only pressing Resync got it moving again. Maude now notices when the connection has gone quiet, brings it back on its own, and only reports canvases as synced once they really are.",
63
+ "surface": "design-ui",
64
+ "title": "Sync comes back on its own",
65
+ "version": "1.2.0"
66
+ },
67
+ {
68
+ "date": "2026-09-04",
69
+ "id": "long-chats-no-longer-freeze",
70
+ "kind": "fix",
71
+ "summary": "In a project with a long chat history, Maude could slow to a crawl and stop responding for many minutes while using enormous amounts of memory — often needing a force-quit. Screenshots the agent took were being stored inside the chat history at full size, and every chat you opened re-read all of it. Chats now stay small and are read in fixed-size pieces, so a long history costs the same as a short one.",
72
+ "surface": "design-ui",
73
+ "title": "Long chats no longer freeze Maude",
74
+ "version": "1.2.0"
75
+ },
4
76
  {
5
77
  "id": "canvas-media-plays-on-linux",
6
78
  "version": "1.0.10",
package/cli/bin/maude.mjs CHANGED
@@ -14,6 +14,7 @@ const COMMANDS = {
14
14
  init: () => import('../commands/init.mjs'),
15
15
  config: () => import('../commands/config.mjs'),
16
16
  cache: () => import('../commands/cache.mjs'),
17
+ codex: () => import('../commands/codex.mjs'),
17
18
  preflight: () => import('../commands/preflight.mjs'),
18
19
  design: () => import('../commands/design.mjs'),
19
20
  'scenario-report': () => import('../commands/scenario-report.mjs'),
@@ -21,6 +22,7 @@ const COMMANDS = {
21
22
  kg: () => import('../commands/kg.mjs'),
22
23
  help: () => import('../commands/help.mjs'),
23
24
  hub: () => import('../commands/hub.mjs'),
25
+ harness: () => import('../commands/harness.mjs'),
24
26
  version: () => import('../commands/version.mjs'),
25
27
  };
26
28
 
@@ -0,0 +1,191 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { access, realpath } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { delimiter, isAbsolute, join, relative, resolve, sep } from 'node:path';
5
+
6
+ import { parseArgs } from '../lib/argv.mjs';
7
+ import { assertCodexPermissionAuthority, syncCodexRuntime } from '../lib/harness/codex-runtime.mjs';
8
+
9
+ export async function run({ args, io = defaultIo() }) {
10
+ const syncOnly = args[0] === 'sync';
11
+ const { flags } = syncOnly ? parseArgs(args.slice(1), { booleans: ['json'] }) : { flags: {} };
12
+ const launchArgs = [...args];
13
+ if (!syncOnly) assertLocalCodexTarget(launchArgs);
14
+ let explicitExecutable = flags.real;
15
+ if (!syncOnly && launchArgs[0] === '--real') {
16
+ if (!launchArgs[1]) throw new Error('--real requires an executable path');
17
+ explicitExecutable = launchArgs[1];
18
+ launchArgs.splice(0, 2);
19
+ }
20
+ if (process.env.MAUDE_CODEX_BRIDGE_ACTIVE === '1') {
21
+ throw new Error(
22
+ 'refusing recursive maude codex launch; set MAUDE_CODEX_REAL to the real binary'
23
+ );
24
+ }
25
+ process.env.MAUDE_CODEX_BRIDGE_ACTIVE = '1';
26
+ const home = resolve(process.env.HOME || homedir());
27
+ const projectRoot = await realpath(
28
+ resolve(flags.project || codexProjectDirectory(launchArgs) || process.cwd())
29
+ );
30
+ const requestedCodexHome = resolve(process.env.CODEX_HOME || join(home, '.codex'));
31
+ if (isContained(requestedCodexHome, projectRoot)) {
32
+ throw new Error('CODEX_HOME must be outside the current project for maude codex');
33
+ }
34
+ const codexHome = await realpath(requestedCodexHome);
35
+ if (isContained(codexHome, projectRoot)) {
36
+ throw new Error('CODEX_HOME must resolve outside the current project for maude codex');
37
+ }
38
+ const executable = await resolveCodexExecutable(explicitExecutable);
39
+ const synced = await syncCodexRuntime({
40
+ codexExecutable: executable,
41
+ codexHome,
42
+ home,
43
+ projectRoot,
44
+ });
45
+ if (syncOnly) {
46
+ if (flags.json) io.stdout.write(`${JSON.stringify(synced.summary, null, 2)}\n`);
47
+ return synced.summary;
48
+ }
49
+ if (synced.permissionMode && hasCodexPermissionOverride(launchArgs)) {
50
+ throw new Error('refusing a Codex sandbox override that could disable projected Claude denies');
51
+ }
52
+ const canonicalLaunchArgs = canonicalizeCodexProjectArguments(launchArgs, projectRoot);
53
+ if (synced.permissionMode) {
54
+ await assertCodexPermissionAuthority({ codexHome, projectRoot });
55
+ }
56
+ const result = spawnSync(executable, [...synced.launchArguments, ...canonicalLaunchArgs], {
57
+ cwd: projectRoot,
58
+ env: {
59
+ ...process.env,
60
+ ...synced.launchEnvironment,
61
+ CODEX_HOME: codexHome,
62
+ MAUDE_CODEX_BRIDGE_ACTIVE: process.env.MAUDE_CODEX_BRIDGE_ACTIVE,
63
+ },
64
+ stdio: 'inherit',
65
+ });
66
+ if (result.error) throw result.error;
67
+ process.exitCode = result.status ?? 1;
68
+ return { ...synced.summary, exitCode: process.exitCode };
69
+ }
70
+
71
+ export function codexProjectDirectory(args) {
72
+ for (let index = 0; index < args.length; index += 1) {
73
+ const argument = args[index];
74
+ if (argument === '--') return null;
75
+ if (argument === '-C' || argument === '--cd') return args[index + 1] ?? null;
76
+ if (argument.startsWith('-C')) return argument.slice(2) || null;
77
+ if (argument.startsWith('--cd=')) return argument.slice('--cd='.length) || null;
78
+ }
79
+ return null;
80
+ }
81
+
82
+ export function hasCodexRemoteTarget(args) {
83
+ for (const argument of args) {
84
+ if (argument === '--') return false;
85
+ if (argument === '--remote' || argument.startsWith('--remote=')) return true;
86
+ if (argument === 'cloud') return true;
87
+ }
88
+ return false;
89
+ }
90
+
91
+ export function assertLocalCodexTarget(args) {
92
+ if (hasCodexRemoteTarget(args)) {
93
+ throw new Error('refusing to synchronize local Claude state into a remote Codex session');
94
+ }
95
+ }
96
+
97
+ export function canonicalizeCodexProjectArguments(args, projectRoot) {
98
+ const output = [];
99
+ let found = false;
100
+ for (let index = 0; index < args.length; index += 1) {
101
+ const argument = args[index];
102
+ if (argument === '--')
103
+ return [...(found ? ['-C', projectRoot] : []), ...output, ...args.slice(index)];
104
+ if (argument === '-C' || argument === '--cd') {
105
+ if (found) throw new Error('multiple Codex working-directory overrides are not supported');
106
+ if (args[index + 1] === undefined) throw new Error(`${argument} requires a directory`);
107
+ found = true;
108
+ index += 1;
109
+ continue;
110
+ }
111
+ if (argument.startsWith('-C')) {
112
+ if (found) throw new Error('multiple Codex working-directory overrides are not supported');
113
+ if (!argument.slice(2)) throw new Error('-C requires a directory');
114
+ found = true;
115
+ continue;
116
+ }
117
+ if (argument.startsWith('--cd=')) {
118
+ if (found) throw new Error('multiple Codex working-directory overrides are not supported');
119
+ if (!argument.slice('--cd='.length)) throw new Error('--cd requires a directory');
120
+ found = true;
121
+ continue;
122
+ }
123
+ output.push(argument);
124
+ }
125
+ return [...(found ? ['-C', projectRoot] : []), ...output];
126
+ }
127
+
128
+ export function hasCodexPermissionOverride(args) {
129
+ for (let index = 0; index < args.length; index += 1) {
130
+ const argument = args[index];
131
+ if (argument === '--') return false;
132
+ if (
133
+ argument === '-s' ||
134
+ /^-s.+/.test(argument) ||
135
+ argument === '--sandbox' ||
136
+ argument.startsWith('--sandbox=') ||
137
+ argument === '--dangerously-bypass-approvals-and-sandbox' ||
138
+ argument === '--yolo' ||
139
+ argument === '--approve-for-me' ||
140
+ argument === '-p' ||
141
+ /^-p.+/.test(argument) ||
142
+ argument === '--profile' ||
143
+ argument.startsWith('--profile=') ||
144
+ argument === '-P' ||
145
+ /^-P.+/.test(argument) ||
146
+ argument === '--permission-profile' ||
147
+ argument.startsWith('--permission-profile=')
148
+ ) {
149
+ return true;
150
+ }
151
+ if (
152
+ argument === '-c' ||
153
+ /^-c.+/.test(argument) ||
154
+ argument === '--config' ||
155
+ argument.startsWith('--config=')
156
+ ) {
157
+ return true;
158
+ }
159
+ }
160
+ return false;
161
+ }
162
+
163
+ function isContained(path, root) {
164
+ const rel = relative(root, path);
165
+ return rel === '' || (!rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel));
166
+ }
167
+
168
+ async function resolveCodexExecutable(explicit) {
169
+ if (explicit) return await canonicalExecutable(explicit);
170
+ if (process.env.MAUDE_CODEX_REAL) return await canonicalExecutable(process.env.MAUDE_CODEX_REAL);
171
+ for (const root of String(process.env.PATH || '').split(delimiter)) {
172
+ if (!root) continue;
173
+ const candidate = join(root, 'codex');
174
+ try {
175
+ await access(candidate);
176
+ return await realpath(candidate);
177
+ } catch (error) {
178
+ if (error.code !== 'ENOENT') throw error;
179
+ }
180
+ }
181
+ throw new Error('Codex executable not found; pass --real <path>');
182
+ }
183
+
184
+ async function canonicalExecutable(path) {
185
+ await access(path);
186
+ return await realpath(path);
187
+ }
188
+
189
+ function defaultIo() {
190
+ return { stderr: process.stderr, stdout: process.stdout };
191
+ }