@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
@@ -1218,6 +1218,37 @@ const PDF_DPI_OPTIONS = [
1218
1218
  ];
1219
1219
  const PDF_DPI_DEFAULT = 'auto';
1220
1220
 
1221
+ // issue #116 — how the export treats text. Mirrors export-dialog.tsx's
1222
+ // PDF_TEXT_OPTIONS (same mirror obligation as PDF_DPI_OPTIONS above).
1223
+ //
1224
+ // The list is FIXED regardless of whether Ghostscript is present on this
1225
+ // machine. Hiding "Convert to outlines" when gs is missing would need a new
1226
+ // capability route, and would answer the user's question ("can I send this to
1227
+ // a printer?") by making the answer invisible; a refusal that names the
1228
+ // one-line install is more honest and reaches them at the moment they care.
1229
+ const PDF_TEXT_OPTIONS = [
1230
+ {
1231
+ id: 'keep',
1232
+ label: 'Keep selectable (default)',
1233
+ description:
1234
+ 'Text stays selectable and searchable. You are warned if a font came out unprintable.',
1235
+ },
1236
+ {
1237
+ id: 'embed',
1238
+ label: 'Verify fonts embedded',
1239
+ description:
1240
+ 'Same file, but the export fails instead of shipping a font a print shop would reject.',
1241
+ },
1242
+ {
1243
+ id: 'outline',
1244
+ label: 'Convert to outlines (print-safe)',
1245
+ description:
1246
+ 'Every glyph becomes a vector path — no fonts left to break. Text is no longer selectable ' +
1247
+ 'and the file can grow a lot. Needs Ghostscript installed locally; cloud workspaces have it.',
1248
+ },
1249
+ ];
1250
+ const PDF_TEXT_DEFAULT = 'keep';
1251
+
1221
1252
  // feature-bulk-media-insert — best-effort MIME guess from a filename extension.
1222
1253
  // Only used to classify a natively-picked file (Rust returns `{name, bytes}`,
1223
1254
  // no type) for the destination-toggle's image/video/audio split; the actual
@@ -1602,6 +1633,7 @@ function ExportDialog({
1602
1633
  const [pdfMarksCrop, setPdfMarksCrop] = useState(false);
1603
1634
  const [pdfMarksRegistration, setPdfMarksRegistration] = useState(false);
1604
1635
  const [pdfDpiId, setPdfDpiId] = useState(PDF_DPI_DEFAULT);
1636
+ const [pdfTextId, setPdfTextId] = useState(PDF_TEXT_DEFAULT);
1605
1637
  // DDR-148 addendum — mp4/webm of a registered video-comp render through
1606
1638
  // renderMediaOnWeb, which produces real audio (Remotion owns the
1607
1639
  // TransitionSeries/volume-closure timeline). gif has no audio track at all
@@ -1714,6 +1746,10 @@ function ExportDialog({
1714
1746
  // itself stays vector regardless of this.
1715
1747
  const pdfDpi = PDF_DPI_OPTIONS.find((d) => d.id === pdfDpiId)?.value;
1716
1748
  if (pdfDpi !== undefined) options.dpi = pdfDpi;
1749
+ // issue #116 — omitted for `keep` rather than sent explicitly: the
1750
+ // adapter's default IS keep, so an untouched dialog produces the exact
1751
+ // request body it did before this feature existed.
1752
+ if (pdfTextId !== 'keep') options.text = pdfTextId;
1717
1753
  }
1718
1754
  // Scope targeting hints (resolveScope reads these): `artboardId` makes
1719
1755
  // "Active artboard" export the right screen instead of `:first-of-type`;
@@ -2022,6 +2058,27 @@ function ExportDialog({
2022
2058
  The page stays vector; this only sets the capture density for raster content on it
2023
2059
  (dropped photos, large-format art).
2024
2060
  </div>
2061
+ <div className="st-dialog-row">
2062
+ <label className="st-dialog-lbl" htmlFor="st-export-pdf-text">
2063
+ Text
2064
+ </label>
2065
+ <select
2066
+ id="st-export-pdf-text"
2067
+ className="st-select"
2068
+ data-testid="export-pdf-text"
2069
+ value={pdfTextId}
2070
+ onChange={(e) => setPdfTextId(e.target.value)}
2071
+ >
2072
+ {PDF_TEXT_OPTIONS.map((t) => (
2073
+ <option key={t.id} value={t.id}>
2074
+ {t.label}
2075
+ </option>
2076
+ ))}
2077
+ </select>
2078
+ </div>
2079
+ <div className="st-mono" style={{ fontSize: 11, color: 'var(--fg-3)' }}>
2080
+ {PDF_TEXT_OPTIONS.find((t) => t.id === pdfTextId)?.description}
2081
+ </div>
2025
2082
  <div className="st-dialog-row">
2026
2083
  <label
2027
2084
  className="st-dialog-lbl"
@@ -4548,6 +4605,10 @@ function Viewport({
4548
4605
  cfg,
4549
4606
  loadingPath,
4550
4607
  onIframeLoad,
4608
+ canvasError,
4609
+ canvasReloadNonce,
4610
+ onRetryCanvasLoad,
4611
+ loadedPath,
4551
4612
  showQuickSetup,
4552
4613
  onStartQuickSetup,
4553
4614
  previewPath,
@@ -4564,8 +4625,21 @@ function Viewport({
4564
4625
  useEffect(() => {
4565
4626
  if (previewPath) previewRef.current?.focus();
4566
4627
  }, [previewPath]);
4628
+ // One observable word for "what is the canvas pane actually doing" — the
4629
+ // top-frame signal the #115 E2E scenario waits on, and the only place these
4630
+ // three states are named together. `ready` is `dgn:'loaded'`-backed, so it
4631
+ // cannot be faked by a timer expiring (which is exactly how the bug used to
4632
+ // look successful: skeleton gone, pane white).
4633
+ const canvasState =
4634
+ canvasError && canvasError.path === activePath
4635
+ ? 'error'
4636
+ : activePath && activePath !== SYSTEM_TAB && loadedPath === activePath
4637
+ ? 'ready'
4638
+ : loadingPath && loadingPath === activePath
4639
+ ? 'loading'
4640
+ : 'idle';
4567
4641
  return (
4568
- <div className="viewport st-stage" data-tour="viewport">
4642
+ <div className="viewport st-stage" data-tour="viewport" data-canvas-state={canvasState}>
4569
4643
  {previewPath && (
4570
4644
  // feature-studio-file-preview — an overlay, not a tab: the canvas
4571
4645
  // iframe (if any) stays mounted underneath so switching back to it
@@ -4638,7 +4712,10 @@ function Viewport({
4638
4712
  }
4639
4713
  return (
4640
4714
  <iframe
4641
- key={t.path}
4715
+ // The nonce is part of the key ONLY so the #115 Retry can force a
4716
+ // remount; it is otherwise constant, so normal switching keys on the
4717
+ // path exactly as before.
4718
+ key={`${t.path}#${canvasReloadNonce}`}
4642
4719
  ref={(el) => registerIframe(t.path, el)}
4643
4720
  src={canvasUrl(t.path, cfg)}
4644
4721
  className={t.path === activePath ? 'active' : ''}
@@ -4669,6 +4746,45 @@ function Viewport({
4669
4746
  </div>
4670
4747
  </div>
4671
4748
  )}
4749
+ {canvasError && canvasError.path === activePath && (
4750
+ // issue #115 — what the blank pane used to be. Names which of the two
4751
+ // failures happened and offers the recovery that used to require
4752
+ // quitting the app (or knowing that switching projects and back
4753
+ // re-reads /_config).
4754
+ <div className="st-canvas-error" role="alert" data-testid="canvas-load-error">
4755
+ <div className="st-canvas-error-card">
4756
+ <div className="st-canvas-error-title">
4757
+ {canvasError.kind === 'server'
4758
+ ? "Maude's server isn't responding"
4759
+ : "This canvas didn't finish loading"}
4760
+ </div>
4761
+ <div className="st-canvas-error-body">
4762
+ {canvasError.kind === 'server' ? (
4763
+ <>
4764
+ The canvas couldn't be reached. This usually means Maude's
4765
+ server restarted underneath this window — your files on disk
4766
+ are untouched.
4767
+ </>
4768
+ ) : (
4769
+ <>
4770
+ The server is up, but{' '}
4771
+ <code>{sanitizeDisplayText(basename(canvasError.path))}</code>{' '}
4772
+ never finished compiling. Its own error, if it has one, is in
4773
+ the canvas frame.
4774
+ </>
4775
+ )}
4776
+ </div>
4777
+ <button
4778
+ type="button"
4779
+ className="btn btn--primary"
4780
+ data-testid="canvas-load-retry"
4781
+ onClick={() => onRetryCanvasLoad?.(canvasError.path)}
4782
+ >
4783
+ Reload canvas
4784
+ </button>
4785
+ </div>
4786
+ </div>
4787
+ )}
4672
4788
  </div>
4673
4789
  );
4674
4790
  }
@@ -9763,6 +9879,47 @@ function App() {
9763
9879
  // Phase 9 Task 8 — hub-down offline mode banner. Driven by the 'sync:status'
9764
9880
  // WS message the linked-mode sync runtime emits. null in solo mode.
9765
9881
  const [syncStatus, setSyncStatus] = useState(null);
9882
+ // feature-large-project-seed — the NATIVE half of seed progress.
9883
+ //
9884
+ // A seed against a large project is minutes to hours of work, and the target
9885
+ // user (DDR-177) never opens a terminal. The Sync panel is the detail view;
9886
+ // this is what reaches them when the panel is not on screen: the window
9887
+ // title while it runs, and ONE notification per phase transition.
9888
+ //
9889
+ // Per TRANSITION, never per file. A 2 961-file seed must produce two
9890
+ // notifications, not 2 961 — `send_notification` throttles on the Rust side
9891
+ // too, but a client that leans on that is a client that would spam a plain
9892
+ // browser tab through the Web fallback.
9893
+ const seedPhaseRef = useRef(null);
9894
+ useEffect(() => {
9895
+ const p = syncStatus?.files?.progress;
9896
+ if (!p) return;
9897
+ const prev = seedPhaseRef.current;
9898
+ seedPhaseRef.current = p.phase;
9899
+ if (prev === null || prev === p.phase) return;
9900
+ if (p.phase === 'converged' && (prev === 'seeding' || prev === 'paused')) {
9901
+ notifyDesktop('Project synced', `${p.delivered} files are up to date.`);
9902
+ } else if (p.phase === 'blocked') {
9903
+ const total = (p.blocked ?? []).reduce((n, b) => n + b.count, 0);
9904
+ notifyDesktop(
9905
+ 'Sync needs a decision',
9906
+ `${total} file${total === 1 ? '' : 's'} could not be sent. Open the Sync panel for the reason.`
9907
+ );
9908
+ }
9909
+ }, [syncStatus?.files?.progress]);
9910
+ // The title carries the count while a seed runs, and is restored afterwards —
9911
+ // it is the one surface visible without opening anything.
9912
+ useEffect(() => {
9913
+ if (typeof document === 'undefined') return undefined;
9914
+ const p = syncStatus?.files?.progress;
9915
+ const base = document.title.replace(/\s+—\s+syncing\s+\d+\/\d+$/, '');
9916
+ if (p && (p.phase === 'seeding' || p.phase === 'paused') && p.tracked > 0) {
9917
+ document.title = `${base} — syncing ${p.delivered}/${p.tracked}`;
9918
+ } else {
9919
+ document.title = base;
9920
+ }
9921
+ return undefined;
9922
+ }, [syncStatus?.files?.progress]);
9766
9923
  // DDR-218 — the folder's cloud link ({url, credentialed} | null), lifted from
9767
9924
  // CloudBar (status resolve / attach / detach). Gates the GitPanel's
9768
9925
  // cloud-managed posture; linked-but-uncredentialed keeps the full panel.
@@ -9790,6 +9947,24 @@ function App() {
9790
9947
  // Canvas-compile skeleton (single-canvas model → one path at a time).
9791
9948
  const [loadingPath, setLoadingPath] = useState(null);
9792
9949
  const loadFallbackTimer = useRef(null);
9950
+ // issue #115 — a canvas that never reported `loaded`. `{ path, kind }`, where
9951
+ // kind is 'server' (the canvas origin did not answer /_health — a dead or
9952
+ // respawned sidecar) or 'compile' (the origin is up, the canvas itself never
9953
+ // mounted). Rendered instead of the blank pane the cap used to leave behind.
9954
+ const [canvasError, setCanvasError] = useState(null);
9955
+ // Bumped by the error panel's Retry. Folded into the iframe `key` so a retry
9956
+ // REMOUNTS the frame — re-setting `src` to the identical URL would not
9957
+ // re-navigate, and after a respawn the URL is identical in every case except
9958
+ // the port that `loadServerConfig()` is about to correct.
9959
+ const [canvasReloadNonce, setCanvasReloadNonce] = useState(0);
9960
+ // The canvas whose SHELL DOCUMENT has actually loaded, per its `dgn:'loaded'`
9961
+ // post. Distinct from `!loadingPath`, which the 2.5 s onLoad fallback and the
9962
+ // 15 s cap also clear — this one only ever means "the shell ran". That makes
9963
+ // it the honest signal for `data-canvas-state` below, and the one the #115 E2E
9964
+ // scenario asserts: the shell's inline script posts it whether or not the
9965
+ // canvas TSX went on to build, so it isolates "the origin was reachable" from
9966
+ // "the canvas compiled" — exactly the axis #115 is about.
9967
+ const [loadedPath, setLoadedPath] = useState(null);
9793
9968
  // Resizable side panels (DS components-resize-panels) + the active drag side.
9794
9969
  const sbSize = usePanelSize('maude-sb-w', { min: 200, max: 420, def: 252 });
9795
9970
  const rpSize = usePanelSize('maude-rp-w', { min: 260, max: 480, def: 304 });
@@ -9825,11 +10000,6 @@ function App() {
9825
10000
  setLoadingPath((p) => (p === path ? null : p));
9826
10001
  }, 2500);
9827
10002
  }, []);
9828
- useEffect(() => {
9829
- if (!loadingPath) return;
9830
- const cap = setTimeout(() => setLoadingPath(null), 15000);
9831
- return () => clearTimeout(cap);
9832
- }, [loadingPath]);
9833
10003
  // Loaded at boot from /_config and re-fetched on the server's
9834
10004
  // `config-updated` push (config.json hot-reload — /design:setup-ds rewrites
9835
10005
  // it mid-session) — informs canvasUrl() so TSX iframes can pass the right
@@ -9846,6 +10016,44 @@ function App() {
9846
10016
  // for one frame, each fired the request its shell exists to refuse, and then
9847
10017
  // unmounted. Nothing was broken and everything looked broken.
9848
10018
  const [cfg, setCfg] = useState({ designRel: '.design', cloud: undefined });
10019
+ // THE CAP MUST NOT CLEAR TO WHITE — issue #115. This used to be a bare
10020
+ // `setLoadingPath(null)`, which is the most useless thing it could do: when
10021
+ // the canvas origin is unreachable the shell HTML never loads, so the shell's
10022
+ // own `#canvas-mount-error` surface (templates/_shell.html) does not exist to
10023
+ // report anything, and dropping the skeleton left a blank pane with no error,
10024
+ // no reason and no way back inside the app. Probe the canvas origin to tell
10025
+ // the two failures apart — a dead server (the #115 stale-origin case, and any
10026
+ // sidecar crash) versus a canvas that genuinely never finished compiling —
10027
+ // and hand the user the recovery in both cases.
10028
+ //
10029
+ // DECLARED AFTER `cfg`, ON PURPOSE — the same rule the block below states for
10030
+ // the git flags, and this effect is what taught us it is not decorative. Its
10031
+ // dependency array reads `cfg?.canvasOrigin`, and A DEPENDENCY ARRAY IS
10032
+ // EVALUATED DURING RENDER: sitting above the `useState` above, it hit `cfg` in
10033
+ // its temporal dead zone, so `App` threw `Cannot access 'cfg' before
10034
+ // initialization` on its very first render and the WHOLE STUDIO mounted
10035
+ // nothing — a white page for every user, not only the desktop shell the fix
10036
+ // was written for. It reached the branch because the PR carried a merge
10037
+ // conflict, which stops GitHub from building a merge ref, which means the
10038
+ // client-boot gate never ran on it (issue #112 close-out).
10039
+ useEffect(() => {
10040
+ if (!loadingPath) return;
10041
+ const path = loadingPath;
10042
+ const cap = setTimeout(async () => {
10043
+ let kind = 'compile';
10044
+ try {
10045
+ const r = await fetch(`${cfg?.canvasOrigin || ''}/_health`, {
10046
+ cache: 'no-store',
10047
+ });
10048
+ if (!r.ok) kind = 'server';
10049
+ } catch {
10050
+ kind = 'server';
10051
+ }
10052
+ setCanvasError({ path, kind });
10053
+ setLoadingPath((p) => (p === path ? null : p));
10054
+ }, 15000);
10055
+ return () => clearTimeout(cap);
10056
+ }, [loadingPath, cfg?.canvasOrigin]);
9849
10057
  // WHO IS SAVING THIS PROJECT — one expression, read by every surface that
9850
10058
  // would otherwise offer to save it, poll for it, or badge it (DDR-218, widened
9851
10059
  // by feature-cloud-managed-git-posture).
@@ -11471,12 +11679,30 @@ function App() {
11471
11679
  // 25 s keeps the socket active (Bun resets the idle timer on any frame,
11472
11680
  // inbound OR outbound); the server ignores an unknown message type.
11473
11681
  let pingTimer = null;
11682
+ // A RECONNECT MEANS THE PEER MAY BE A DIFFERENT PROCESS — issue #115.
11683
+ // The desktop supervisor respawns the sidecar when it dies (DDR-235's Bun
11684
+ // fault is a recurring one), and everything this page cached from `/_config`
11685
+ // was published by the process that just went away. `canvasOrigin` is the
11686
+ // one that bites: it is an OS-assigned ephemeral port (server.ts's
11687
+ // `startCanvasServer(0)`), so it is DIFFERENT in every process, while the
11688
+ // main origin walks a deterministic ladder from 4399 and usually reclaims
11689
+ // its old port. The result was a half-live page — socket back up, status bar
11690
+ // `live`, tree and panels fine — whose every canvas iframe navigated to a
11691
+ // dead port. Since a canvas switch mounts a FRESH iframe (single-canvas
11692
+ // model, keyed by path), the already-open canvas kept rendering and only
11693
+ // switching went white, silently and permanently.
11694
+ //
11695
+ // So: re-derive the config from whoever is answering now. Only on a
11696
+ // RE-connect — the boot effect above already did the first fetch.
11697
+ let everConnected = false;
11474
11698
  function connect() {
11475
11699
  const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
11476
11700
  const ws = new WebSocket(proto + '//' + location.host + '/_ws');
11477
11701
  wsRef.current = ws;
11478
11702
  ws.addEventListener('open', () => {
11479
11703
  setWsConnected(true);
11704
+ if (everConnected) loadServerConfig();
11705
+ everConnected = true;
11480
11706
  clearInterval(pingTimer);
11481
11707
  pingTimer = setInterval(() => {
11482
11708
  if (ws.readyState === WebSocket.OPEN) ws.send('{"type":"ping"}');
@@ -11826,11 +12052,27 @@ function App() {
11826
12052
  setActivePath(path);
11827
12053
  setFocusedCommentId(null);
11828
12054
  setPreviewPath(null);
12055
+ setCanvasError(null);
12056
+ setLoadedPath(null);
11829
12057
  // Canvas-compile skeleton — cleared by the iframe's dgn:'loaded' message,
11830
12058
  // the onLoad fallback timer (legacy .html), or a hard 15s cap.
11831
12059
  if (path !== SYSTEM_TAB) setLoadingPath(path);
11832
12060
  }, []);
11833
12061
 
12062
+ // Retry from the #115 error panel: re-read /_config FIRST (the stale
12063
+ // `canvasOrigin` is the likeliest reason we are here at all), then remount the
12064
+ // iframe via the nonce so it navigates against the corrected origin.
12065
+ const retryCanvasLoad = useCallback(
12066
+ (path) => {
12067
+ setCanvasError(null);
12068
+ setLoadedPath(null);
12069
+ loadServerConfig();
12070
+ setCanvasReloadNonce((n) => n + 1);
12071
+ if (path && path !== SYSTEM_TAB) setLoadingPath(path);
12072
+ },
12073
+ [loadServerConfig]
12074
+ );
12075
+
11834
12076
  /**
11835
12077
  * First open lands on a rendered canvas — Cloud Phase 27 C3.
11836
12078
  *
@@ -13132,6 +13374,10 @@ function App() {
13132
13374
  // iframe finished loading — drop the compile skeleton, push current
13133
13375
  // comments + carry over focused pin if any
13134
13376
  setLoadingPath((p) => (p === m.file ? null : p));
13377
+ setLoadedPath(m.file);
13378
+ // …and retire any #115 error panel for this canvas: it just proved it
13379
+ // can load (a late load, or a successful Retry).
13380
+ setCanvasError((e) => (e && e.path === m.file ? null : e));
13135
13381
  // Presentation Mode suppresses comment pins (same gate as the push
13136
13382
  // effect above), so a canvas opened while presenting starts pin-free.
13137
13383
  const list = presentMode ? [] : commentsByFile[m.file] || [];
@@ -15259,6 +15505,10 @@ function App() {
15259
15505
  cfg={cfg}
15260
15506
  loadingPath={loadingPath}
15261
15507
  onIframeLoad={onIframeLoad}
15508
+ canvasError={canvasError}
15509
+ canvasReloadNonce={canvasReloadNonce}
15510
+ onRetryCanvasLoad={retryCanvasLoad}
15511
+ loadedPath={loadedPath}
15262
15512
  showQuickSetup={
15263
15513
  isNativeApp() && !viewerMode && !!setupReadiness && !setupReadiness.ready
15264
15514
  }
@@ -307,9 +307,11 @@ function StatusPill({ job }) {
307
307
  : job.status === 'done'
308
308
  ? degraded && job.degraded.audioDropped
309
309
  ? 'Ready · no audio'
310
- : degraded
311
- ? 'Ready · degraded'
312
- : 'Ready'
310
+ : degraded && job.degraded.fontsNotEmbedded?.length
311
+ ? 'Ready · font warning'
312
+ : degraded
313
+ ? 'Ready · degraded'
314
+ : 'Ready'
313
315
  : 'Failed';
314
316
  const eta = job.status === 'running' ? etaLabel(job) : null;
315
317
  return (
@@ -331,12 +333,18 @@ function StatusPill({ job }) {
331
333
  */
332
334
  function DegradedNote({ degraded }) {
333
335
  if (!degraded) return null;
336
+ // The font case names the fonts, because "reduced fidelity" is useless to
337
+ // someone about to send this to a printer — what they need is which faces
338
+ // came out unprintable (issue #116).
339
+ const fonts = degraded.fontsNotEmbedded ?? [];
340
+ const headline = degraded.audioDropped
341
+ ? 'Exported without audio.'
342
+ : fonts.length
343
+ ? `Fonts a print shop will reject: ${fonts.join(', ')}.`
344
+ : 'Exported with reduced fidelity.';
334
345
  return (
335
346
  <div className="st-export-degraded" data-testid="export-degraded-note">
336
- <span aria-hidden="true">⚠</span>{' '}
337
- {degraded.audioDropped
338
- ? 'Exported without audio.'
339
- : 'Exported with reduced fidelity.'}
347
+ <span aria-hidden="true">⚠</span> {headline}
340
348
  {degraded.remedy ? <div className="st-export-degraded__fix">{degraded.remedy}</div> : null}
341
349
  </div>
342
350
  );
@@ -292,7 +292,13 @@ function Row({ item, refresh }) {
292
292
  ) : null}
293
293
  </span>
294
294
  ) : null}
295
- {item.action === 'signin' && item.resolvedPath ? (
295
+ {/* Shown whenever a path resolved — NOT only in the signin state. DDR-166
296
+ Decision 3 calls this binding ("so a pre-existing PATH-hijacked `claude`
297
+ isn't invisible to the one human capable of noticing it's wrong"), and
298
+ gating it on `action === 'signin'` hid it in the one state whose most
299
+ likely cause IS something unexpected fronting the binary (issue #107,
300
+ attacker finding F4). */}
301
+ {item.resolvedPath ? (
296
302
  <span className="rdy-fix-tx rdy-resolved-path">
297
303
  {item.resolvedViaMaude ? 'Installed by Maude: ' : 'Found on your PATH: '}
298
304
  <code>{item.resolvedPath}</code>
@@ -98,6 +98,39 @@ function readFiles(raw) {
98
98
  return raw;
99
99
  }
100
100
 
101
+ /**
102
+ * The seed progress model, or null when absent/untrustworthy.
103
+ *
104
+ * Absent is the norm on an older dev-server, and this panel must render
105
+ * exactly as it did before when it is — every field here is additive.
106
+ */
107
+ function readProgress(raw) {
108
+ if (!raw || typeof raw !== 'object') return null;
109
+ if (![raw.tracked, raw.delivered, raw.remaining].every(isCount)) return null;
110
+ if (typeof raw.phase !== 'string') return null;
111
+ const blocked = Array.isArray(raw.blocked)
112
+ ? raw.blocked.filter((b) => b && typeof b.class === 'string' && isCount(b.count))
113
+ : [];
114
+ return { ...raw, blocked };
115
+ }
116
+
117
+ /** One sentence per phase, in the user's terms — never the machine's. */
118
+ const PHASE_TEXT = {
119
+ scanning: 'Looking through the project…',
120
+ seeding: null, // the counts say it better than a sentence would
121
+ paused: 'Paused — nothing is lost; this resumes by itself.',
122
+ blocked: 'Nothing is moving on its own — the files below need a decision.',
123
+ converged: 'Everything is up to date.',
124
+ };
125
+
126
+ /** What each wall means, and what a person can do about it. */
127
+ const BLOCKED_TEXT = {
128
+ 'too-large': 'too big for this workspace',
129
+ quota: "this hour's upload allowance is used up",
130
+ unreachable: 'the workspace could not be reached',
131
+ refused: 'the workspace would not take them',
132
+ };
133
+
101
134
  /** Consent-class notices (feature-before-first-external-users Task 1), or []
102
135
  * when the shape can't be trusted. Absent is the norm — the payload carries
103
136
  * `notices` only when a boot raised one (shared-doc ON, TSX bodies). */
@@ -279,6 +312,7 @@ export default function SyncPanel({
279
312
  const files = readFiles(status?.files);
280
313
 
281
314
  const delivery = useMemo(() => readDelivery(status?.files?.delivery), [status?.files?.delivery]);
315
+ const progress = useMemo(() => readProgress(status?.files?.progress), [status?.files?.progress]);
282
316
 
283
317
  // Sync settings (Task 2) — fetched once on mount; `settings` stays null when
284
318
  // no hub is linked, which is also the render gate for the whole section.
@@ -648,17 +682,98 @@ export default function SyncPanel({
648
682
  {files && (
649
683
  <section aria-label="Project files" data-testid="sync-files">
650
684
  <div className="gp-sect-label">
651
- project files <span className="gp-group-count">{files.synced}</span>
652
- </div>
653
- <div className="sp-assets-line">
654
- {`${files.synced} synced` + (files.pulled > 0 ? ` · ${files.pulled} pulled` : '')}
685
+ project files{' '}
686
+ <span className="gp-group-count">{progress ? progress.tracked : files.synced}</span>
655
687
  </div>
688
+ {progress ? (
689
+ <>
690
+ <div className="sp-progress-track" data-testid="sync-files-progress">
691
+ <div
692
+ className="sp-progress-fill"
693
+ style={{
694
+ width: `${
695
+ progress.tracked > 0
696
+ ? Math.min(100, Math.round((progress.delivered / progress.tracked) * 100))
697
+ : 100
698
+ }%`,
699
+ }}
700
+ />
701
+ </div>
702
+ <div className="sp-assets-line" data-testid="sync-files-remaining">
703
+ {`${progress.delivered} of ${progress.tracked} delivered`}
704
+ {progress.remaining > 0 ? ` · ${progress.remaining} waiting` : ''}
705
+ </div>
706
+ {PHASE_TEXT[progress.phase] && (
707
+ <div className="sp-assets-line" data-testid="sync-files-phase">
708
+ {PHASE_TEXT[progress.phase]}
709
+ </div>
710
+ )}
711
+ {progress.passCapped && progress.remaining > 0 && (
712
+ <div className="sp-assets-line">
713
+ More is on the way — the last pass reached its limit and picks up where it
714
+ left off.
715
+ </div>
716
+ )}
717
+ {progress.blocked.map((b) => (
718
+ <div
719
+ key={b.class}
720
+ className="sp-assets-retry"
721
+ data-testid={`sync-blocked-${b.class}`}
722
+ >
723
+ {b.count} file{b.count === 1 ? '' : 's'} —{' '}
724
+ {BLOCKED_TEXT[b.class] ?? 'the workspace would not take them'}.
725
+ </div>
726
+ ))}
727
+ {/*
728
+ THE RAW COUNTERS STAY. A panel derived from the same source it
729
+ displays cannot be cross-checked, and DDR-214's whole lesson is
730
+ that a status surface has to be falsifiable — so the numbers the
731
+ passes actually reported remain here to disagree with.
732
+ */}
733
+ <div className="sp-assets-line sp-raw-counters">
734
+ {`raw: ${files.synced} synced`}
735
+ {files.pushed ? ` · ${files.pushed} pushed` : ''}
736
+ {files.pulled > 0 ? ` · ${files.pulled} pulled` : ''}
737
+ </div>
738
+ </>
739
+ ) : (
740
+ <div className="sp-assets-line">
741
+ {`${files.synced} synced` + (files.pulled > 0 ? ` · ${files.pulled} pulled` : '')}
742
+ </div>
743
+ )}
656
744
  {files.conflicts > 0 && (
657
745
  <div className="sp-assets-retry" data-testid="sync-files-conflicts">
658
746
  {files.conflicts} conflict{files.conflicts === 1 ? '' : 's'} — the older copies are
659
747
  kept in Trash, below.
660
748
  </div>
661
749
  )}
750
+ {/*
751
+ A PAUSE IS NOT A FAILURE, and it gets its own words. The
752
+ workspace asked us to slow down; nothing is wrong, nothing is
753
+ lost, and the only correct action is to wait — which is exactly
754
+ what a person cannot infer from a count of failures (issue #109).
755
+ It comes FIRST because it explains the failure line below it.
756
+ */}
757
+ {files.rateLimited && (
758
+ <div className="sp-assets-retry" data-testid="sync-files-rate-limited">
759
+ <strong>syncing paused for a moment.</strong> The workspace asked this machine to
760
+ slow down.{' '}
761
+ {files.rateLimited.waiting > 0
762
+ ? `${files.rateLimited.waiting} file${files.rateLimited.waiting === 1 ? '' : 's'} still on the way — nothing is lost, they arrive when the pause lifts.`
763
+ : 'It resumes by itself.'}
764
+ </div>
765
+ )}
766
+ {/*
767
+ AND A FAILURE IS NOT A SYNC. This had no field at all, so a pass
768
+ that refused every file rendered as "N synced" and a person saw
769
+ broken images with the cause only in a terminal they never open.
770
+ */}
771
+ {files.failed > 0 && !files.rateLimited && (
772
+ <div className="sp-assets-retry" data-testid="sync-files-failed">
773
+ {files.failed} file{files.failed === 1 ? '' : 's'} did not come through on the last
774
+ check — sync retries them by itself. The list is below.
775
+ </div>
776
+ )}
662
777
  {/*
663
778
  A HELD BREAKER, said out loud. These used to exist only as a
664
779
  console.warn, which on a machine whose user never opens a
@@ -717,6 +832,12 @@ export default function SyncPanel({
717
832
  </li>
718
833
  ))}
719
834
  {delivery.fine.length > 200 && <li>…and {delivery.fine.length - 200} more</li>}
835
+ {files.deliveryTruncated > 0 && (
836
+ <li className="sp-truncated">
837
+ …and {files.deliveryTruncated} more not listed here — the list is capped so
838
+ the files that need attention always fit.
839
+ </li>
840
+ )}
720
841
  </ul>
721
842
  </details>
722
843
  )}
@@ -321,6 +321,7 @@ export function ReportBugDialog({ open, onClose, activeCanvas = null }) {
321
321
  attachments: {
322
322
  screenshots: shots.length,
323
323
  serverLogTail: consent.logTail && !!bundle?.logs?.serverLogTail,
324
+ processStats: consent.logTail && !!bundle?.process,
324
325
  crashLogs: consent.crashLogs ? crashLogs.length : 0,
325
326
  },
326
327
  logs: {},
@@ -330,6 +331,19 @@ export function ReportBugDialog({ open, onClose, activeCanvas = null }) {
330
331
  if (consent.logTail && bundle?.logs?.serverLogTail) {
331
332
  report.logs.serverLogTail = bundle.logs.serverLogTail;
332
333
  }
334
+ // Server process memory — three integers, rides the SAME consent as the log
335
+ // tail because it is diagnostics of the same kind (and, like the tail, is
336
+ // only meaningful alongside it). Issue #119 arrived as a RAM figure the
337
+ // reporter read off Activity Monitor, with nothing in the report to confirm
338
+ // it; the memory watch that should have logged the problem was measuring a
339
+ // counter that does not move under Bun, so the log tail was silent too.
340
+ if (consent.logTail && bundle?.process) {
341
+ report.process = {
342
+ rssBytes: bundle.process.rssBytes,
343
+ heapUsedBytes: bundle.process.heapUsedBytes,
344
+ uptimeSeconds: bundle.process.uptimeSeconds,
345
+ };
346
+ }
333
347
  if (consent.crashLogs && crashLogs.length && isNativeApp()) {
334
348
  report.logs.crashLogs = [];
335
349
  for (const log of crashLogs) {
@@ -568,7 +582,11 @@ export function ReportBugDialog({ open, onClose, activeCanvas = null }) {
568
582
  />
569
583
  <span>
570
584
  Recent server log ({bundle.logs.serverLogTail.split('\n').length} lines,
571
- paths &amp; secrets scrubbed) —{' '}
585
+ paths &amp; secrets scrubbed)
586
+ {bundle?.process
587
+ ? ` + server memory (${Math.round(bundle.process.rssBytes / 1048576)} MB)`
588
+ : ''}{' '}
589
+ —{' '}
572
590
  <details className="rb-details">
573
591
  <summary>view</summary>
574
592
  <pre className="rb-log">{bundle.logs.serverLogTail}</pre>