@1agh/maude 1.0.9 → 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/probe.ts +268 -16
  3. package/apps/studio/acp/transcript-io.ts +394 -0
  4. package/apps/studio/acp/transcript.ts +103 -38
  5. package/apps/studio/api.ts +139 -26
  6. package/apps/studio/bin/_import-figma.mjs +57 -11
  7. package/apps/studio/bin/_seed-fixture.mjs +163 -0
  8. package/apps/studio/canvas-imports.ts +89 -0
  9. package/apps/studio/client/app.jsx +257 -7
  10. package/apps/studio/client/export-center.jsx +15 -7
  11. package/apps/studio/client/panels/ReadinessList.jsx +7 -1
  12. package/apps/studio/client/panels/SyncPanel.jsx +125 -4
  13. package/apps/studio/client/report-bug.jsx +19 -1
  14. package/apps/studio/client/styles/3-shell-maude.css +34 -0
  15. package/apps/studio/client/styles/4-components.css +26 -0
  16. package/apps/studio/collab/persistence.ts +132 -16
  17. package/apps/studio/collab/registry.ts +10 -18
  18. package/apps/studio/debug-bundle.ts +13 -0
  19. package/apps/studio/dist/client.bundle.js +1128 -1128
  20. package/apps/studio/dist/styles.css +1 -1
  21. package/apps/studio/export-dialog.tsx +54 -0
  22. package/apps/studio/exporters/degraded.ts +19 -3
  23. package/apps/studio/exporters/ghostscript.ts +194 -0
  24. package/apps/studio/exporters/pdf-fonts.ts +261 -0
  25. package/apps/studio/exporters/pdf.ts +152 -3
  26. package/apps/studio/exporters/remote.ts +36 -1
  27. package/apps/studio/fs-watch.ts +45 -1
  28. package/apps/studio/http.ts +7 -1
  29. package/apps/studio/mem.ts +45 -9
  30. package/apps/studio/readiness.ts +42 -7
  31. package/apps/studio/server.ts +11 -4
  32. package/apps/studio/sync/agent.ts +136 -29
  33. package/apps/studio/sync/asset-push.ts +1 -47
  34. package/apps/studio/sync/codec.ts +122 -31
  35. package/apps/studio/sync/cold-start-apply.ts +7 -0
  36. package/apps/studio/sync/cold-start.ts +118 -22
  37. package/apps/studio/sync/comment-identity.ts +68 -0
  38. package/apps/studio/sync/connection-state.ts +15 -1
  39. package/apps/studio/sync/file-ledger.ts +117 -0
  40. package/apps/studio/sync/file-plane.ts +623 -27
  41. package/apps/studio/sync/index.ts +711 -20
  42. package/apps/studio/sync/journal-client.ts +13 -1
  43. package/apps/studio/sync/migrate-seed.ts +40 -3
  44. package/apps/studio/sync/presentation.ts +26 -3
  45. package/apps/studio/sync/retry-after.ts +169 -0
  46. package/apps/studio/sync/seed-progress.ts +218 -0
  47. package/apps/studio/sync/status.ts +122 -5
  48. package/apps/studio/sync/transport-error.ts +111 -0
  49. package/apps/studio/whats-new.json +91 -0
  50. package/cli/bin/maude.mjs +2 -0
  51. package/cli/commands/codex.mjs +191 -0
  52. package/cli/commands/harness.mjs +1051 -0
  53. package/cli/commands/help.mjs +13 -0
  54. package/cli/lib/design-link.mjs +190 -2
  55. package/cli/lib/harness/capabilities.mjs +302 -0
  56. package/cli/lib/harness/codex-runtime.mjs +889 -0
  57. package/cli/lib/harness/compatibility.mjs +27 -0
  58. package/cli/lib/harness/discover-claude.mjs +1393 -0
  59. package/cli/lib/harness/managed-state.mjs +1296 -0
  60. package/cli/lib/harness/model.mjs +86 -0
  61. package/cli/lib/harness/secrets.mjs +390 -0
  62. package/cli/lib/harness/targets/codex.mjs +1141 -0
  63. package/cli/lib/harness/targets/opencode.mjs +807 -0
  64. package/cli/lib/harness/transaction.mjs +1099 -0
  65. package/cli/lib/hubs-config.mjs +35 -8
  66. package/cli/templates/harness/opencode/maude-projector.ts +58 -0
  67. package/docs/harness-capability-matrix.md +125 -0
  68. package/docs/harness-environment-projection.md +95 -0
  69. package/package.json +32 -9
  70. package/plugins/flow/.claude-plugin/config.schema.json +8 -0
  71. package/apps/studio/.ai/cache/_stats.json +0 -7
  72. package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
  73. package/apps/studio/bin/_curl-local.test.mjs +0 -280
  74. package/apps/studio/bin/_pw-launch.test.mjs +0 -90
  75. package/apps/studio/bin/_smart-frames.test.mjs +0 -195
  76. package/apps/studio/bin/_transcribe.test.mjs +0 -80
  77. package/apps/studio/bun.lock +0 -928
  78. package/apps/studio/draw/test/animate.test.ts +0 -172
  79. package/apps/studio/draw/test/brush.test.ts +0 -213
  80. package/apps/studio/draw/test/composition.test.ts +0 -141
  81. package/apps/studio/draw/test/geometry.test.ts +0 -199
  82. package/apps/studio/draw/test/gradient.test.ts +0 -167
  83. package/apps/studio/draw/test/layout.test.ts +0 -120
  84. package/apps/studio/draw/test/morph.test.ts +0 -107
  85. package/apps/studio/draw/test/optimize.test.ts +0 -40
  86. package/apps/studio/draw/test/palette.test.ts +0 -123
  87. package/apps/studio/draw/test/primitives.test.ts +0 -129
  88. package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
  89. package/apps/studio/draw/test/serialize.test.ts +0 -105
  90. package/apps/studio/figma/assets.test.ts +0 -464
  91. package/apps/studio/figma/client.test.ts +0 -395
  92. package/apps/studio/figma/codegen-client.test.ts +0 -276
  93. package/apps/studio/figma/codegen-fonts.test.ts +0 -103
  94. package/apps/studio/figma/codegen-values.test.ts +0 -179
  95. package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
  96. package/apps/studio/figma/fig-decode.test.ts +0 -788
  97. package/apps/studio/figma/fig-differential.test.ts +0 -182
  98. package/apps/studio/figma/fig-translator.test.ts +0 -192
  99. package/apps/studio/figma/fig-vector.test.ts +0 -113
  100. package/apps/studio/figma/from-codegen.test.ts +0 -408
  101. package/apps/studio/figma/sanitize.test.ts +0 -325
  102. package/apps/studio/figma/tailwind-map.test.ts +0 -142
  103. package/apps/studio/figma/to-artboard.test.ts +0 -808
  104. package/apps/studio/figma/to-render.test.ts +0 -180
  105. package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
  106. package/apps/studio/figma/to-strokes.test.ts +0 -705
  107. package/apps/studio/figma/to-tokens.test.ts +0 -321
  108. package/apps/studio/figma/url.test.ts +0 -167
  109. package/apps/studio/footage/schema.test.ts +0 -287
  110. package/apps/studio/generation/adapters/groq.test.ts +0 -80
  111. package/apps/studio/generation/audio-library.test.ts +0 -95
  112. package/apps/studio/generation/captions.test.ts +0 -78
  113. package/apps/studio/generation/elevenlabs.test.ts +0 -223
  114. package/apps/studio/generation/gemini.test.ts +0 -350
  115. package/apps/studio/generation/jobs.test.ts +0 -128
  116. package/apps/studio/generation/keys.test.ts +0 -75
  117. package/apps/studio/generation/prefs.test.ts +0 -95
  118. package/apps/studio/generation/types.test.ts +0 -123
  119. package/apps/studio/generation/whisper-models.test.ts +0 -179
  120. package/apps/studio/test/_helpers.ts +0 -108
  121. package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
  122. package/apps/studio/test/acp-activity.test.ts +0 -154
  123. package/apps/studio/test/acp-ai-activity.test.ts +0 -182
  124. package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
  125. package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
  126. package/apps/studio/test/acp-branch-guard.test.ts +0 -123
  127. package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
  128. package/apps/studio/test/acp-bridge.test.ts +0 -301
  129. package/apps/studio/test/acp-capabilities.test.ts +0 -123
  130. package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
  131. package/apps/studio/test/acp-commands.test.ts +0 -119
  132. package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
  133. package/apps/studio/test/acp-elicitation.test.ts +0 -251
  134. package/apps/studio/test/acp-env.test.ts +0 -95
  135. package/apps/studio/test/acp-mode-banner.test.ts +0 -45
  136. package/apps/studio/test/acp-origin-gate.test.ts +0 -160
  137. package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
  138. package/apps/studio/test/acp-permission.test.ts +0 -278
  139. package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
  140. package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
  141. package/apps/studio/test/acp-session-plugins.test.ts +0 -209
  142. package/apps/studio/test/acp-toolgroup.test.ts +0 -76
  143. package/apps/studio/test/acp-transcript-view.test.ts +0 -71
  144. package/apps/studio/test/acp-transcript.test.ts +0 -238
  145. package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
  146. package/apps/studio/test/acp-usage.test.ts +0 -143
  147. package/apps/studio/test/acp-write-gate.test.ts +0 -323
  148. package/apps/studio/test/acp-write-scope.test.ts +0 -533
  149. package/apps/studio/test/active-state.test.ts +0 -186
  150. package/apps/studio/test/activity.test.ts +0 -189
  151. package/apps/studio/test/ai-activity.test.ts +0 -113
  152. package/apps/studio/test/annotate-write.test.ts +0 -1074
  153. package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
  154. package/apps/studio/test/annotations-align.test.ts +0 -88
  155. package/apps/studio/test/annotations-api.test.ts +0 -145
  156. package/apps/studio/test/annotations-bindings.test.ts +0 -262
  157. package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
  158. package/apps/studio/test/annotations-groups.test.ts +0 -231
  159. package/apps/studio/test/annotations-layer.test.ts +0 -1011
  160. package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
  161. package/apps/studio/test/annotations-snap.test.ts +0 -135
  162. package/apps/studio/test/annotations-text-format.test.ts +0 -396
  163. package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
  164. package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
  165. package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
  166. package/apps/studio/test/artboard-kinds.test.tsx +0 -83
  167. package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
  168. package/apps/studio/test/asset-api.test.ts +0 -149
  169. package/apps/studio/test/assets-s3.test.ts +0 -249
  170. package/apps/studio/test/binary-smoke.test.ts +0 -47
  171. package/apps/studio/test/boot-self-heal.test.ts +0 -87
  172. package/apps/studio/test/browse-posture.test.tsx +0 -107
  173. package/apps/studio/test/bundle-smoke.test.ts +0 -46
  174. package/apps/studio/test/camera-reveal.test.tsx +0 -173
  175. package/apps/studio/test/canvas-artifacts.test.ts +0 -125
  176. package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
  177. package/apps/studio/test/canvas-build.test.ts +0 -141
  178. package/apps/studio/test/canvas-create-api.test.ts +0 -676
  179. package/apps/studio/test/canvas-cursors.test.ts +0 -164
  180. package/apps/studio/test/canvas-edit.test.ts +0 -470
  181. package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
  182. package/apps/studio/test/canvas-header.test.ts +0 -127
  183. package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
  184. package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
  185. package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
  186. package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
  187. package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
  188. package/apps/studio/test/canvas-list-watch.test.ts +0 -388
  189. package/apps/studio/test/canvas-media-drop.test.ts +0 -187
  190. package/apps/studio/test/canvas-meta-api.test.ts +0 -707
  191. package/apps/studio/test/canvas-move-api.test.ts +0 -587
  192. package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
  193. package/apps/studio/test/canvas-pipeline.test.ts +0 -180
  194. package/apps/studio/test/canvas-rects.test.ts +0 -198
  195. package/apps/studio/test/canvas-reorder.test.ts +0 -211
  196. package/apps/studio/test/canvas-route.test.ts +0 -179
  197. package/apps/studio/test/canvas-shell-base.test.ts +0 -107
  198. package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
  199. package/apps/studio/test/canvas-url.test.ts +0 -138
  200. package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
  201. package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
  202. package/apps/studio/test/capture-core.test.ts +0 -32
  203. package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
  204. package/apps/studio/test/chat-attachments.test.ts +0 -117
  205. package/apps/studio/test/chat-context.test.ts +0 -129
  206. package/apps/studio/test/chat-markdown.test.tsx +0 -58
  207. package/apps/studio/test/clip-addressing.test.ts +0 -738
  208. package/apps/studio/test/clip-ops.test.ts +0 -922
  209. package/apps/studio/test/cloud-build.test.ts +0 -95
  210. package/apps/studio/test/cloud-connect-note.test.ts +0 -135
  211. package/apps/studio/test/cloud-endpoints.test.ts +0 -590
  212. package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
  213. package/apps/studio/test/cloud-history-posture.test.ts +0 -230
  214. package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
  215. package/apps/studio/test/cloud-renew.test.ts +0 -205
  216. package/apps/studio/test/cloud-session-role.test.ts +0 -161
  217. package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
  218. package/apps/studio/test/cold-start-apply.test.ts +0 -303
  219. package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
  220. package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
  221. package/apps/studio/test/collab-bridge.test.ts +0 -79
  222. package/apps/studio/test/collab-loopback.test.ts +0 -63
  223. package/apps/studio/test/collab-origin-gate.test.ts +0 -323
  224. package/apps/studio/test/collab-protocol.test.ts +0 -146
  225. package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
  226. package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
  227. package/apps/studio/test/collab-room.test.ts +0 -188
  228. package/apps/studio/test/collab-session-survival.test.tsx +0 -176
  229. package/apps/studio/test/collab-stress.test.ts +0 -129
  230. package/apps/studio/test/comment-mount.test.ts +0 -97
  231. package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
  232. package/apps/studio/test/comments-api.test.ts +0 -277
  233. package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
  234. package/apps/studio/test/comments-overlay.test.ts +0 -117
  235. package/apps/studio/test/compile-entry.test.ts +0 -133
  236. package/apps/studio/test/component-map.test.ts +0 -48
  237. package/apps/studio/test/config-projection.test.ts +0 -104
  238. package/apps/studio/test/config-reload.test.ts +0 -230
  239. package/apps/studio/test/config-version.test.ts +0 -88
  240. package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
  241. package/apps/studio/test/convert-to-absolute.test.ts +0 -333
  242. package/apps/studio/test/credential-grammar.test.ts +0 -107
  243. package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
  244. package/apps/studio/test/csrf-write-guard.test.ts +0 -141
  245. package/apps/studio/test/debug-bundle.test.ts +0 -105
  246. package/apps/studio/test/design-setup-readiness.test.ts +0 -155
  247. package/apps/studio/test/detach-component.test.ts +0 -94
  248. package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
  249. package/apps/studio/test/dom-selection.test.ts +0 -198
  250. package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
  251. package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
  252. package/apps/studio/test/edit-persistence.test.ts +0 -91
  253. package/apps/studio/test/edit-scope-api.test.ts +0 -119
  254. package/apps/studio/test/edit-source-command.test.ts +0 -142
  255. package/apps/studio/test/edit-suppress.test.ts +0 -170
  256. package/apps/studio/test/editing-presence.test.ts +0 -103
  257. package/apps/studio/test/element-resize.test.ts +0 -136
  258. package/apps/studio/test/element-structural-api.test.ts +0 -434
  259. package/apps/studio/test/element-structural-edit.test.ts +0 -750
  260. package/apps/studio/test/ensure-browser.test.ts +0 -92
  261. package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
  262. package/apps/studio/test/export-assemble.test.ts +0 -53
  263. package/apps/studio/test/export-browser-lane.test.ts +0 -126
  264. package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
  265. package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
  266. package/apps/studio/test/export-center.test.tsx +0 -287
  267. package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
  268. package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
  269. package/apps/studio/test/export-lane.test.ts +0 -245
  270. package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
  271. package/apps/studio/test/exporters/canva.test.ts +0 -63
  272. package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
  273. package/apps/studio/test/exporters/endpoint.test.ts +0 -121
  274. package/apps/studio/test/exporters/history.test.ts +0 -107
  275. package/apps/studio/test/exporters/html.test.ts +0 -26
  276. package/apps/studio/test/exporters/jobs.test.ts +0 -342
  277. package/apps/studio/test/exporters/pdf.test.ts +0 -85
  278. package/apps/studio/test/exporters/png.test.ts +0 -103
  279. package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
  280. package/apps/studio/test/exporters/pptx.test.ts +0 -31
  281. package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
  282. package/apps/studio/test/exporters/runtime.test.ts +0 -59
  283. package/apps/studio/test/exporters/scope.test.ts +0 -292
  284. package/apps/studio/test/exporters/svg.test.ts +0 -29
  285. package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
  286. package/apps/studio/test/exporters/zip.test.ts +0 -104
  287. package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
  288. package/apps/studio/test/figjam-v3-model.test.ts +0 -342
  289. package/apps/studio/test/figma-explode.test.ts +0 -438
  290. package/apps/studio/test/figma-provenance.test.ts +0 -108
  291. package/apps/studio/test/figma-routes.test.ts +0 -294
  292. package/apps/studio/test/file-lock.test.ts +0 -84
  293. package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -13
  294. package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
  295. package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
  296. package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
  297. package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
  298. package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
  299. package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
  300. package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
  301. package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
  302. package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
  303. package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
  304. package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
  305. package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
  306. package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
  307. package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
  308. package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
  309. package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
  310. package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
  311. package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
  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 -226
  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,97 @@
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
+ },
76
+ {
77
+ "id": "canvas-media-plays-on-linux",
78
+ "version": "1.0.10",
79
+ "date": "2026-08-26",
80
+ "kind": "fix",
81
+ "title": "Canvas video and audio work on Linux",
82
+ "summary": "Opening a canvas with video or audio on Linux could take the whole view down with no message — once because the GStreamer plugins it needs are optional on most distros, and again because the GPU video path faults on AMD graphics. The .deb now pulls those plugins in, Maude steers around the AMD fault by itself, and if the plugins are missing anyway it tells you, with the exact install command for your distro.",
83
+ "learnMore": "https://maude.sh/docs/desktop#media-playback-on-linux",
84
+ "surface": "design-ui"
85
+ },
86
+ {
87
+ "id": "a-stopped-server-says-so",
88
+ "version": "1.0.10",
89
+ "date": "2026-08-26",
90
+ "kind": "fix",
91
+ "title": "A stopped server now says so",
92
+ "summary": "If the server behind your project stopped for good, Maude used to leave the window sitting there — nothing loading, nothing saving, and no reason given. Now it tells you which project stopped, confirms your files on disk are untouched, and offers Try again. A one-off crash hours into a session also no longer counts against the give-up limit the way a genuine restart loop does.",
93
+ "surface": "design-ui"
94
+ },
4
95
  {
5
96
  "id": "exports-match-what-you-see",
6
97
  "version": "1.0.8",
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
+ }