@1agh/maude 1.0.11 → 1.3.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 (567) hide show
  1. package/README.md +3 -1
  2. package/apps/studio/acp/bridge.ts +18 -13
  3. package/apps/studio/acp/index.ts +43 -7
  4. package/apps/studio/acp/transcript-io.ts +394 -0
  5. package/apps/studio/acp/transcript.ts +103 -38
  6. package/apps/studio/annotations-layer.tsx +191 -116
  7. package/apps/studio/annotations-model.ts +39 -0
  8. package/apps/studio/annotations-sync.ts +50 -0
  9. package/apps/studio/api.ts +821 -55
  10. package/apps/studio/bin/_import-figma.mjs +57 -11
  11. package/apps/studio/bin/_seed-fixture.mjs +163 -0
  12. package/apps/studio/bin/annotate.mjs +3 -1
  13. package/apps/studio/bin/server-up.sh +17 -2
  14. package/apps/studio/canvas-edit.ts +391 -7
  15. package/apps/studio/canvas-imports.ts +89 -0
  16. package/apps/studio/canvas-lib.tsx +82 -18
  17. package/apps/studio/canvas-notice-message.ts +16 -0
  18. package/apps/studio/canvas-notifications.tsx +19 -0
  19. package/apps/studio/canvas-shell.tsx +57 -6
  20. package/apps/studio/client/app.jsx +943 -248
  21. package/apps/studio/client/apply-edit-request.ts +55 -0
  22. package/apps/studio/client/export-center.jsx +125 -67
  23. package/apps/studio/client/github.js +38 -1
  24. package/apps/studio/client/index-loader.ts +76 -0
  25. package/apps/studio/client/panels/CloudBar.jsx +52 -6
  26. package/apps/studio/client/panels/GitPanel.jsx +103 -0
  27. package/apps/studio/client/panels/OnboardingWizard.jsx +36 -7
  28. package/apps/studio/client/panels/RepoBranchSwitcher.jsx +92 -27
  29. package/apps/studio/client/panels/SourceConflictPanel.jsx +166 -0
  30. package/apps/studio/client/panels/SyncPanel.jsx +238 -4
  31. package/apps/studio/client/panels/TeamProjects.jsx +376 -0
  32. package/apps/studio/client/panels/file-deep-link-dialog.jsx +54 -0
  33. package/apps/studio/client/photo-knobs.jsx +3 -0
  34. package/apps/studio/client/report-bug.jsx +19 -1
  35. package/apps/studio/client/share-dialog.jsx +91 -0
  36. package/apps/studio/client/share-link.js +94 -0
  37. package/apps/studio/client/styles/3-shell-maude.css +69 -18
  38. package/apps/studio/client/styles/4-components.css +44 -1
  39. package/apps/studio/client/tree-row-menu.jsx +48 -3
  40. package/apps/studio/client/whats-new.jsx +26 -32
  41. package/apps/studio/cloud/endpoints.ts +132 -0
  42. package/apps/studio/collab/index.ts +4 -0
  43. package/apps/studio/collab/persistence.ts +137 -17
  44. package/apps/studio/collab/registry.ts +19 -21
  45. package/apps/studio/collab/room.ts +13 -2
  46. package/apps/studio/context.ts +59 -0
  47. package/apps/studio/debug-bundle.ts +13 -0
  48. package/apps/studio/dist/client.bundle.js +1911 -1879
  49. package/apps/studio/dist/runtime/.min-sizes.json +1 -0
  50. package/apps/studio/dist/runtime/sonner.js +1 -0
  51. package/apps/studio/dist/styles.css +1 -1
  52. package/apps/studio/export-dialog.tsx +54 -0
  53. package/apps/studio/exporters/degraded.ts +19 -3
  54. package/apps/studio/exporters/ghostscript.ts +194 -0
  55. package/apps/studio/exporters/pdf-fonts.ts +261 -0
  56. package/apps/studio/exporters/pdf.ts +152 -3
  57. package/apps/studio/exporters/remote.ts +36 -1
  58. package/apps/studio/fs-watch.ts +45 -1
  59. package/apps/studio/http.ts +296 -19
  60. package/apps/studio/inspect.ts +19 -0
  61. package/apps/studio/managed-projects.ts +150 -0
  62. package/apps/studio/mem.ts +45 -9
  63. package/apps/studio/notifications.tsx +292 -0
  64. package/apps/studio/runtime-bundle.ts +2 -0
  65. package/apps/studio/server.ts +69 -6
  66. package/apps/studio/sync/accepted-cold-start.ts +225 -0
  67. package/apps/studio/sync/accepted-link.ts +320 -0
  68. package/apps/studio/sync/action-stage.ts +343 -0
  69. package/apps/studio/sync/agent.ts +104 -45
  70. package/apps/studio/sync/asset-push.ts +1 -47
  71. package/apps/studio/sync/cell-file-events.ts +3 -0
  72. package/apps/studio/sync/codec.ts +192 -34
  73. package/apps/studio/sync/cold-start-apply.ts +7 -0
  74. package/apps/studio/sync/cold-start.ts +118 -22
  75. package/apps/studio/sync/comment-identity.ts +68 -0
  76. package/apps/studio/sync/connection-state.ts +15 -1
  77. package/apps/studio/sync/ctl-provider.ts +15 -2
  78. package/apps/studio/sync/document-discovery.ts +81 -0
  79. package/apps/studio/sync/file-ledger.ts +117 -0
  80. package/apps/studio/sync/file-membership.ts +10 -2
  81. package/apps/studio/sync/file-plane.ts +926 -47
  82. package/apps/studio/sync/index.ts +1786 -36
  83. package/apps/studio/sync/journal-client.ts +13 -1
  84. package/apps/studio/sync/migrate-seed.ts +150 -22
  85. package/apps/studio/sync/poke.ts +4 -2
  86. package/apps/studio/sync/presentation.ts +311 -5
  87. package/apps/studio/sync/projection.ts +820 -23
  88. package/apps/studio/sync/repeated-module.ts +73 -0
  89. package/apps/studio/sync/retry-after.ts +169 -0
  90. package/apps/studio/sync/revision-barrier.ts +129 -0
  91. package/apps/studio/sync/seed-progress.ts +218 -0
  92. package/apps/studio/sync/seed-repair.ts +46 -0
  93. package/apps/studio/sync/source-merge.ts +100 -0
  94. package/apps/studio/sync/source-ops.ts +289 -0
  95. package/apps/studio/sync/source-recovery.ts +70 -0
  96. package/apps/studio/sync/source-validation.ts +56 -0
  97. package/apps/studio/sync/status.ts +201 -6
  98. package/apps/studio/sync/transaction-client.ts +460 -0
  99. package/apps/studio/sync/transport-error.ts +111 -0
  100. package/apps/studio/sync/writer-registry.ts +236 -0
  101. package/apps/studio/text-caret.ts +35 -0
  102. package/apps/studio/undo-hud.tsx +9 -87
  103. package/apps/studio/use-canvas-media-drop.tsx +4 -39
  104. package/apps/studio/whats-new.json +173 -0
  105. package/cli/bin/maude.mjs +2 -0
  106. package/cli/commands/codex.mjs +191 -0
  107. package/cli/commands/harness.mjs +1051 -0
  108. package/cli/commands/help.mjs +13 -0
  109. package/cli/lib/design-link.mjs +190 -2
  110. package/cli/lib/harness/capabilities.mjs +302 -0
  111. package/cli/lib/harness/codex-runtime.mjs +892 -0
  112. package/cli/lib/harness/compatibility.mjs +27 -0
  113. package/cli/lib/harness/discover-claude.mjs +1393 -0
  114. package/cli/lib/harness/managed-state.mjs +1296 -0
  115. package/cli/lib/harness/model.mjs +86 -0
  116. package/cli/lib/harness/secrets.mjs +390 -0
  117. package/cli/lib/harness/targets/codex.mjs +1141 -0
  118. package/cli/lib/harness/targets/opencode.mjs +807 -0
  119. package/cli/lib/harness/transaction.mjs +1099 -0
  120. package/cli/lib/hubs-config.mjs +35 -8
  121. package/cli/templates/harness/opencode/maude-projector.ts +58 -0
  122. package/docs/harness-capability-matrix.md +125 -0
  123. package/docs/harness-environment-projection.md +95 -0
  124. package/package.json +33 -9
  125. package/plugins/design/dependencies.json +3 -3
  126. package/plugins/design/templates/_shell.html +47 -0
  127. package/plugins/flow/.claude-plugin/config.schema.json +8 -0
  128. package/plugins/flow/dependencies.json +3 -3
  129. package/apps/studio/.ai/cache/_stats.json +0 -7
  130. package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
  131. package/apps/studio/bin/_curl-local.test.mjs +0 -280
  132. package/apps/studio/bin/_pw-launch.test.mjs +0 -90
  133. package/apps/studio/bin/_smart-frames.test.mjs +0 -195
  134. package/apps/studio/bin/_transcribe.test.mjs +0 -80
  135. package/apps/studio/bun.lock +0 -928
  136. package/apps/studio/draw/test/animate.test.ts +0 -172
  137. package/apps/studio/draw/test/brush.test.ts +0 -213
  138. package/apps/studio/draw/test/composition.test.ts +0 -141
  139. package/apps/studio/draw/test/geometry.test.ts +0 -199
  140. package/apps/studio/draw/test/gradient.test.ts +0 -167
  141. package/apps/studio/draw/test/layout.test.ts +0 -120
  142. package/apps/studio/draw/test/morph.test.ts +0 -107
  143. package/apps/studio/draw/test/optimize.test.ts +0 -40
  144. package/apps/studio/draw/test/palette.test.ts +0 -123
  145. package/apps/studio/draw/test/primitives.test.ts +0 -129
  146. package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
  147. package/apps/studio/draw/test/serialize.test.ts +0 -105
  148. package/apps/studio/figma/assets.test.ts +0 -464
  149. package/apps/studio/figma/client.test.ts +0 -395
  150. package/apps/studio/figma/codegen-client.test.ts +0 -276
  151. package/apps/studio/figma/codegen-fonts.test.ts +0 -103
  152. package/apps/studio/figma/codegen-values.test.ts +0 -179
  153. package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
  154. package/apps/studio/figma/fig-decode.test.ts +0 -788
  155. package/apps/studio/figma/fig-differential.test.ts +0 -182
  156. package/apps/studio/figma/fig-translator.test.ts +0 -192
  157. package/apps/studio/figma/fig-vector.test.ts +0 -113
  158. package/apps/studio/figma/from-codegen.test.ts +0 -408
  159. package/apps/studio/figma/sanitize.test.ts +0 -325
  160. package/apps/studio/figma/tailwind-map.test.ts +0 -142
  161. package/apps/studio/figma/to-artboard.test.ts +0 -808
  162. package/apps/studio/figma/to-render.test.ts +0 -180
  163. package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
  164. package/apps/studio/figma/to-strokes.test.ts +0 -705
  165. package/apps/studio/figma/to-tokens.test.ts +0 -321
  166. package/apps/studio/figma/url.test.ts +0 -167
  167. package/apps/studio/footage/schema.test.ts +0 -287
  168. package/apps/studio/generation/adapters/groq.test.ts +0 -80
  169. package/apps/studio/generation/audio-library.test.ts +0 -95
  170. package/apps/studio/generation/captions.test.ts +0 -78
  171. package/apps/studio/generation/elevenlabs.test.ts +0 -223
  172. package/apps/studio/generation/gemini.test.ts +0 -350
  173. package/apps/studio/generation/jobs.test.ts +0 -128
  174. package/apps/studio/generation/keys.test.ts +0 -75
  175. package/apps/studio/generation/prefs.test.ts +0 -95
  176. package/apps/studio/generation/types.test.ts +0 -123
  177. package/apps/studio/generation/whisper-models.test.ts +0 -179
  178. package/apps/studio/test/_helpers.ts +0 -108
  179. package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
  180. package/apps/studio/test/acp-activity.test.ts +0 -154
  181. package/apps/studio/test/acp-ai-activity.test.ts +0 -182
  182. package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
  183. package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
  184. package/apps/studio/test/acp-branch-guard.test.ts +0 -123
  185. package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
  186. package/apps/studio/test/acp-bridge.test.ts +0 -545
  187. package/apps/studio/test/acp-capabilities.test.ts +0 -123
  188. package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
  189. package/apps/studio/test/acp-commands.test.ts +0 -119
  190. package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
  191. package/apps/studio/test/acp-elicitation.test.ts +0 -251
  192. package/apps/studio/test/acp-env.test.ts +0 -95
  193. package/apps/studio/test/acp-mode-banner.test.ts +0 -45
  194. package/apps/studio/test/acp-origin-gate.test.ts +0 -160
  195. package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
  196. package/apps/studio/test/acp-permission.test.ts +0 -278
  197. package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
  198. package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
  199. package/apps/studio/test/acp-session-plugins.test.ts +0 -209
  200. package/apps/studio/test/acp-toolgroup.test.ts +0 -76
  201. package/apps/studio/test/acp-transcript-view.test.ts +0 -71
  202. package/apps/studio/test/acp-transcript.test.ts +0 -238
  203. package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
  204. package/apps/studio/test/acp-usage.test.ts +0 -143
  205. package/apps/studio/test/acp-write-gate.test.ts +0 -323
  206. package/apps/studio/test/acp-write-scope.test.ts +0 -533
  207. package/apps/studio/test/active-state.test.ts +0 -186
  208. package/apps/studio/test/activity.test.ts +0 -189
  209. package/apps/studio/test/ai-activity.test.ts +0 -113
  210. package/apps/studio/test/annotate-write.test.ts +0 -1074
  211. package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
  212. package/apps/studio/test/annotations-align.test.ts +0 -88
  213. package/apps/studio/test/annotations-api.test.ts +0 -145
  214. package/apps/studio/test/annotations-bindings.test.ts +0 -262
  215. package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
  216. package/apps/studio/test/annotations-groups.test.ts +0 -231
  217. package/apps/studio/test/annotations-layer.test.ts +0 -1011
  218. package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
  219. package/apps/studio/test/annotations-snap.test.ts +0 -135
  220. package/apps/studio/test/annotations-text-format.test.ts +0 -396
  221. package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
  222. package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
  223. package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
  224. package/apps/studio/test/artboard-kinds.test.tsx +0 -83
  225. package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
  226. package/apps/studio/test/asset-api.test.ts +0 -149
  227. package/apps/studio/test/assets-s3.test.ts +0 -249
  228. package/apps/studio/test/binary-smoke.test.ts +0 -47
  229. package/apps/studio/test/boot-self-heal.test.ts +0 -87
  230. package/apps/studio/test/browse-posture.test.tsx +0 -107
  231. package/apps/studio/test/bundle-smoke.test.ts +0 -46
  232. package/apps/studio/test/camera-reveal.test.tsx +0 -173
  233. package/apps/studio/test/canvas-artifacts.test.ts +0 -125
  234. package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
  235. package/apps/studio/test/canvas-build.test.ts +0 -141
  236. package/apps/studio/test/canvas-create-api.test.ts +0 -676
  237. package/apps/studio/test/canvas-cursors.test.ts +0 -164
  238. package/apps/studio/test/canvas-edit.test.ts +0 -470
  239. package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
  240. package/apps/studio/test/canvas-header.test.ts +0 -127
  241. package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
  242. package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
  243. package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
  244. package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
  245. package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
  246. package/apps/studio/test/canvas-list-watch.test.ts +0 -388
  247. package/apps/studio/test/canvas-media-drop.test.ts +0 -187
  248. package/apps/studio/test/canvas-meta-api.test.ts +0 -707
  249. package/apps/studio/test/canvas-move-api.test.ts +0 -587
  250. package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
  251. package/apps/studio/test/canvas-pipeline.test.ts +0 -180
  252. package/apps/studio/test/canvas-rects.test.ts +0 -198
  253. package/apps/studio/test/canvas-reorder.test.ts +0 -211
  254. package/apps/studio/test/canvas-route.test.ts +0 -179
  255. package/apps/studio/test/canvas-shell-base.test.ts +0 -107
  256. package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
  257. package/apps/studio/test/canvas-url.test.ts +0 -138
  258. package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
  259. package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
  260. package/apps/studio/test/capture-core.test.ts +0 -32
  261. package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
  262. package/apps/studio/test/chat-attachments.test.ts +0 -117
  263. package/apps/studio/test/chat-context.test.ts +0 -129
  264. package/apps/studio/test/chat-markdown.test.tsx +0 -58
  265. package/apps/studio/test/clip-addressing.test.ts +0 -738
  266. package/apps/studio/test/clip-ops.test.ts +0 -922
  267. package/apps/studio/test/cloud-build.test.ts +0 -95
  268. package/apps/studio/test/cloud-connect-note.test.ts +0 -135
  269. package/apps/studio/test/cloud-endpoints.test.ts +0 -590
  270. package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
  271. package/apps/studio/test/cloud-history-posture.test.ts +0 -230
  272. package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
  273. package/apps/studio/test/cloud-renew.test.ts +0 -205
  274. package/apps/studio/test/cloud-session-role.test.ts +0 -161
  275. package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
  276. package/apps/studio/test/cold-start-apply.test.ts +0 -303
  277. package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
  278. package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
  279. package/apps/studio/test/collab-bridge.test.ts +0 -79
  280. package/apps/studio/test/collab-loopback.test.ts +0 -63
  281. package/apps/studio/test/collab-origin-gate.test.ts +0 -323
  282. package/apps/studio/test/collab-protocol.test.ts +0 -146
  283. package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
  284. package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
  285. package/apps/studio/test/collab-room.test.ts +0 -188
  286. package/apps/studio/test/collab-session-survival.test.tsx +0 -176
  287. package/apps/studio/test/collab-stress.test.ts +0 -129
  288. package/apps/studio/test/comment-mount.test.ts +0 -97
  289. package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
  290. package/apps/studio/test/comments-api.test.ts +0 -277
  291. package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
  292. package/apps/studio/test/comments-overlay.test.ts +0 -117
  293. package/apps/studio/test/compile-entry.test.ts +0 -133
  294. package/apps/studio/test/component-map.test.ts +0 -48
  295. package/apps/studio/test/config-projection.test.ts +0 -104
  296. package/apps/studio/test/config-reload.test.ts +0 -230
  297. package/apps/studio/test/config-version.test.ts +0 -88
  298. package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
  299. package/apps/studio/test/convert-to-absolute.test.ts +0 -333
  300. package/apps/studio/test/credential-grammar.test.ts +0 -107
  301. package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
  302. package/apps/studio/test/csrf-write-guard.test.ts +0 -141
  303. package/apps/studio/test/debug-bundle.test.ts +0 -105
  304. package/apps/studio/test/design-setup-readiness.test.ts +0 -155
  305. package/apps/studio/test/detach-component.test.ts +0 -94
  306. package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
  307. package/apps/studio/test/dom-selection.test.ts +0 -198
  308. package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
  309. package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
  310. package/apps/studio/test/edit-persistence.test.ts +0 -91
  311. package/apps/studio/test/edit-scope-api.test.ts +0 -119
  312. package/apps/studio/test/edit-source-command.test.ts +0 -142
  313. package/apps/studio/test/edit-suppress.test.ts +0 -170
  314. package/apps/studio/test/editing-presence.test.ts +0 -103
  315. package/apps/studio/test/element-resize.test.ts +0 -136
  316. package/apps/studio/test/element-structural-api.test.ts +0 -434
  317. package/apps/studio/test/element-structural-edit.test.ts +0 -750
  318. package/apps/studio/test/ensure-browser.test.ts +0 -92
  319. package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
  320. package/apps/studio/test/export-assemble.test.ts +0 -53
  321. package/apps/studio/test/export-browser-lane.test.ts +0 -126
  322. package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
  323. package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
  324. package/apps/studio/test/export-center.test.tsx +0 -287
  325. package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
  326. package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
  327. package/apps/studio/test/export-lane.test.ts +0 -245
  328. package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
  329. package/apps/studio/test/exporters/canva.test.ts +0 -63
  330. package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
  331. package/apps/studio/test/exporters/endpoint.test.ts +0 -121
  332. package/apps/studio/test/exporters/history.test.ts +0 -107
  333. package/apps/studio/test/exporters/html.test.ts +0 -26
  334. package/apps/studio/test/exporters/jobs.test.ts +0 -342
  335. package/apps/studio/test/exporters/pdf.test.ts +0 -85
  336. package/apps/studio/test/exporters/png.test.ts +0 -103
  337. package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
  338. package/apps/studio/test/exporters/pptx.test.ts +0 -31
  339. package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
  340. package/apps/studio/test/exporters/runtime.test.ts +0 -59
  341. package/apps/studio/test/exporters/scope.test.ts +0 -292
  342. package/apps/studio/test/exporters/svg.test.ts +0 -29
  343. package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
  344. package/apps/studio/test/exporters/zip.test.ts +0 -104
  345. package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
  346. package/apps/studio/test/figjam-v3-model.test.ts +0 -342
  347. package/apps/studio/test/figma-explode.test.ts +0 -438
  348. package/apps/studio/test/figma-provenance.test.ts +0 -108
  349. package/apps/studio/test/figma-routes.test.ts +0 -294
  350. package/apps/studio/test/file-lock.test.ts +0 -84
  351. package/apps/studio/test/fixtures/backgrounding-claude-auth.sh +0 -12
  352. package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
  353. package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
  354. package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
  355. package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
  356. package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
  357. package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
  358. package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
  359. package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
  360. package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
  361. package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
  362. package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
  363. package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
  364. package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
  365. package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
  366. package/apps/studio/test/fixtures/noisy-claude-auth.mjs +0 -27
  367. package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
  368. package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
  369. package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
  370. package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
  371. package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
  372. package/apps/studio/test/fixtures/wedged-claude-auth.sh +0 -11
  373. package/apps/studio/test/footage-store.test.ts +0 -100
  374. package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
  375. package/apps/studio/test/fs-watch.test.ts +0 -41
  376. package/apps/studio/test/gemma-models.test.ts +0 -245
  377. package/apps/studio/test/generate-route.test.ts +0 -106
  378. package/apps/studio/test/git-api.test.ts +0 -0
  379. package/apps/studio/test/git-branches.test.ts +0 -341
  380. package/apps/studio/test/git-cloud-posture.test.ts +0 -59
  381. package/apps/studio/test/git-grouping.test.ts +0 -106
  382. package/apps/studio/test/git-lifecycle.test.ts +0 -100
  383. package/apps/studio/test/git-log-format.test.ts +0 -95
  384. package/apps/studio/test/git-safe-rel.test.ts +0 -132
  385. package/apps/studio/test/git-system-engine.test.ts +0 -135
  386. package/apps/studio/test/git-watch.test.ts +0 -97
  387. package/apps/studio/test/github-api.test.ts +0 -577
  388. package/apps/studio/test/grid-track-handles.test.ts +0 -160
  389. package/apps/studio/test/handoff-static-frames.test.ts +0 -215
  390. package/apps/studio/test/handoff.test.ts +0 -329
  391. package/apps/studio/test/history-rollback.test.ts +0 -88
  392. package/apps/studio/test/hmr-broadcast.test.ts +0 -236
  393. package/apps/studio/test/hmr-classify.test.ts +0 -87
  394. package/apps/studio/test/hub-link.test.ts +0 -80
  395. package/apps/studio/test/identity-cache.test.ts +0 -83
  396. package/apps/studio/test/import-asset-browser.test.ts +0 -64
  397. package/apps/studio/test/import-asset-route.test.ts +0 -71
  398. package/apps/studio/test/import-asset.test.ts +0 -427
  399. package/apps/studio/test/import-brand-browser.test.ts +0 -88
  400. package/apps/studio/test/import-brand-route.test.ts +0 -120
  401. package/apps/studio/test/import-brand.test.ts +0 -206
  402. package/apps/studio/test/import-figma.test.ts +0 -667
  403. package/apps/studio/test/import-tokens.test.ts +0 -722
  404. package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
  405. package/apps/studio/test/input-router.test.ts +0 -757
  406. package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
  407. package/apps/studio/test/inspect-selections.test.ts +0 -219
  408. package/apps/studio/test/knob-props-authored.test.ts +0 -87
  409. package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
  410. package/apps/studio/test/locator.test.ts +0 -214
  411. package/apps/studio/test/marquee-overlay.test.ts +0 -94
  412. package/apps/studio/test/media-commit-chain.test.ts +0 -210
  413. package/apps/studio/test/mode-toggle.test.tsx +0 -226
  414. package/apps/studio/test/motion-sample.test.ts +0 -35
  415. package/apps/studio/test/move-artboards-command.test.ts +0 -108
  416. package/apps/studio/test/participants-chrome.test.ts +0 -65
  417. package/apps/studio/test/paths.test.ts +0 -88
  418. package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
  419. package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
  420. package/apps/studio/test/perf-harness.ts +0 -193
  421. package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
  422. package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
  423. package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
  424. package/apps/studio/test/photo-edit-api.test.ts +0 -201
  425. package/apps/studio/test/photo-filters.test.ts +0 -149
  426. package/apps/studio/test/photo-pipeline.test.ts +0 -106
  427. package/apps/studio/test/photo-taxonomy.test.ts +0 -96
  428. package/apps/studio/test/print-marks.test.ts +0 -113
  429. package/apps/studio/test/print-units.test.ts +0 -173
  430. package/apps/studio/test/read-annotations.test.ts +0 -727
  431. package/apps/studio/test/read-only-gate.test.ts +0 -188
  432. package/apps/studio/test/read-only-role.test.ts +0 -95
  433. package/apps/studio/test/readiness.test.ts +0 -337
  434. package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
  435. package/apps/studio/test/reorder-api.test.ts +0 -210
  436. package/apps/studio/test/repo-concurrency.test.ts +0 -146
  437. package/apps/studio/test/repo-lock.test.ts +0 -350
  438. package/apps/studio/test/report-proxy.test.ts +0 -125
  439. package/apps/studio/test/ripple.test.ts +0 -197
  440. package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
  441. package/apps/studio/test/runtime-bundle.test.ts +0 -69
  442. package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
  443. package/apps/studio/test/scaffold-design.test.ts +0 -122
  444. package/apps/studio/test/server-lifecycle.test.ts +0 -38
  445. package/apps/studio/test/session-runtime-state.test.ts +0 -226
  446. package/apps/studio/test/session-scope.test.ts +0 -100
  447. package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
  448. package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
  449. package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
  450. package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
  451. package/apps/studio/test/shared-doc-projection.test.ts +0 -238
  452. package/apps/studio/test/shell-importmap.test.ts +0 -56
  453. package/apps/studio/test/sizing-mode.test.ts +0 -102
  454. package/apps/studio/test/slash-commands.test.ts +0 -117
  455. package/apps/studio/test/snap-distance-pill.test.ts +0 -68
  456. package/apps/studio/test/spacing-handles.test.ts +0 -138
  457. package/apps/studio/test/specimen-select.test.ts +0 -88
  458. package/apps/studio/test/sync-agent.test.ts +0 -660
  459. package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
  460. package/apps/studio/test/sync-asset-push.test.ts +0 -931
  461. package/apps/studio/test/sync-atomic-write.test.ts +0 -62
  462. package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
  463. package/apps/studio/test/sync-autocommit.test.ts +0 -537
  464. package/apps/studio/test/sync-canvas-path.test.ts +0 -200
  465. package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
  466. package/apps/studio/test/sync-codec.test.ts +0 -230
  467. package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
  468. package/apps/studio/test/sync-cold-start.test.ts +0 -304
  469. package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
  470. package/apps/studio/test/sync-connection-state.test.ts +0 -337
  471. package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
  472. package/apps/studio/test/sync-decide-file.test.ts +0 -420
  473. package/apps/studio/test/sync-doc-name.test.ts +0 -281
  474. package/apps/studio/test/sync-echo-guard.test.ts +0 -96
  475. package/apps/studio/test/sync-file-ledger.test.ts +0 -334
  476. package/apps/studio/test/sync-file-membership.test.ts +0 -347
  477. package/apps/studio/test/sync-file-plane.test.ts +0 -976
  478. package/apps/studio/test/sync-file-pull.test.ts +0 -333
  479. package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
  480. package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
  481. package/apps/studio/test/sync-hardening.test.ts +0 -586
  482. package/apps/studio/test/sync-hub-listing.test.ts +0 -46
  483. package/apps/studio/test/sync-hubs-config.test.ts +0 -133
  484. package/apps/studio/test/sync-incident-replay.test.ts +0 -211
  485. package/apps/studio/test/sync-journal.test.ts +0 -176
  486. package/apps/studio/test/sync-meta-codec.test.ts +0 -199
  487. package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
  488. package/apps/studio/test/sync-move-retirement.test.ts +0 -231
  489. package/apps/studio/test/sync-panel-surface.test.ts +0 -153
  490. package/apps/studio/test/sync-path-pull.test.ts +0 -531
  491. package/apps/studio/test/sync-presentation.test.ts +0 -262
  492. package/apps/studio/test/sync-pull-budget.test.ts +0 -169
  493. package/apps/studio/test/sync-remote-docs.test.ts +0 -333
  494. package/apps/studio/test/sync-resync-routes.test.ts +0 -168
  495. package/apps/studio/test/sync-runtime.test.ts +0 -1675
  496. package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
  497. package/apps/studio/test/sync-settings-routes.test.ts +0 -195
  498. package/apps/studio/test/sync-settings.test.ts +0 -151
  499. package/apps/studio/test/sync-status.test.ts +0 -261
  500. package/apps/studio/test/sync-supervisor.test.ts +0 -262
  501. package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
  502. package/apps/studio/test/sync-trash.test.ts +0 -132
  503. package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
  504. package/apps/studio/test/sync-untrusted.test.ts +0 -103
  505. package/apps/studio/test/system-endpoint.test.ts +0 -144
  506. package/apps/studio/test/text-editability-stamp.test.ts +0 -131
  507. package/apps/studio/test/timeline-comp-target.test.ts +0 -139
  508. package/apps/studio/test/timeline-media-cache.test.ts +0 -41
  509. package/apps/studio/test/timeline-parse.test.ts +0 -184
  510. package/apps/studio/test/timeline-scale.test.ts +0 -65
  511. package/apps/studio/test/timeline-snap.test.ts +0 -85
  512. package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
  513. package/apps/studio/test/tool-palette.test.tsx +0 -55
  514. package/apps/studio/test/tour-overlay.test.tsx +0 -90
  515. package/apps/studio/test/trusted-request-host.test.ts +0 -66
  516. package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
  517. package/apps/studio/test/undo-stack.test.ts +0 -244
  518. package/apps/studio/test/use-agent-presence.test.tsx +0 -117
  519. package/apps/studio/test/use-annotation-resize.test.ts +0 -402
  520. package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
  521. package/apps/studio/test/use-artboard-drag.test.ts +0 -324
  522. package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
  523. package/apps/studio/test/use-collab.test.ts +0 -0
  524. package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
  525. package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
  526. package/apps/studio/test/use-selection-set.test.tsx +0 -166
  527. package/apps/studio/test/use-snap-guides.test.ts +0 -271
  528. package/apps/studio/test/use-tool-mode.test.tsx +0 -186
  529. package/apps/studio/test/use-undo-stack.test.tsx +0 -223
  530. package/apps/studio/test/video-asset.test.ts +0 -163
  531. package/apps/studio/test/video-comp-fixture.test.ts +0 -50
  532. package/apps/studio/test/video-comp.test.ts +0 -270
  533. package/apps/studio/test/video-encode-lib.test.ts +0 -63
  534. package/apps/studio/test/video-render-bridge.test.ts +0 -171
  535. package/apps/studio/test/whats-new.test.ts +0 -113
  536. package/apps/studio/test/whisper-setup.test.ts +0 -97
  537. package/apps/studio/test/workspace-containment.test.ts +0 -382
  538. package/apps/studio/test/workspace-signin.test.ts +0 -292
  539. package/apps/studio/test/ws-handshake.test.ts +0 -33
  540. package/cli/commands/cache.test.mjs +0 -166
  541. package/cli/commands/design-link.test.mjs +0 -419
  542. package/cli/commands/design.test.mjs +0 -240
  543. package/cli/commands/doctor.test.mjs +0 -237
  544. package/cli/commands/hub-workspace.test.mjs +0 -275
  545. package/cli/commands/hub.test.mjs +0 -225
  546. package/cli/commands/kg.test.mjs +0 -142
  547. package/cli/commands/preflight.test.mjs +0 -46
  548. package/cli/lib/argv.test.mjs +0 -46
  549. package/cli/lib/cache.test.mjs +0 -303
  550. package/cli/lib/cell-plan.test.mjs +0 -225
  551. package/cli/lib/config-lint.test.mjs +0 -117
  552. package/cli/lib/ddr-to-kgai.test.mjs +0 -118
  553. package/cli/lib/design-ownership.test.mjs +0 -329
  554. package/cli/lib/fetch-asset.test.mjs +0 -213
  555. package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
  556. package/cli/lib/figma-import-controls.test.mjs +0 -70
  557. package/cli/lib/flow-design-integration.test.mjs +0 -165
  558. package/cli/lib/gitignore-block.test.mjs +0 -136
  559. package/cli/lib/gitignore-drift.test.mjs +0 -156
  560. package/cli/lib/hubs-config.test.mjs +0 -165
  561. package/cli/lib/pkg-root.test.mjs +0 -111
  562. package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
  563. package/cli/lib/plugin-name-namespace.test.mjs +0 -71
  564. package/cli/lib/reconstruct-toolset.test.mjs +0 -194
  565. package/cli/lib/stack-detect.test.mjs +0 -121
  566. package/cli/lib/update-check.test.mjs +0 -32
  567. package/cli/lib/workspace-plan.test.mjs +0 -699
@@ -0,0 +1,55 @@
1
+ // The shell half of an inline-edit undo/redo (`dgn:'apply-edit'`). The canvas
2
+ // iframe's edit-source command cannot reach the main-origin `/_api/edit-*`
3
+ // routes (DDR-054), so it posts the re-application here and the shell builds
4
+ // the request.
5
+ //
6
+ // `from` is the value the command expects the source to hold right now (the
7
+ // side it replaces FROM). For css/attr it becomes the route's `expected`
8
+ // precondition, so an undo cannot overwrite a newer value a teammate wrote
9
+ // (audit 2026-09-13 P1 #5). For text it keeps its existing job: targeting a
10
+ // `{variable}` edit at the right source string.
11
+
12
+ export interface ApplyEditMessage {
13
+ op: unknown;
14
+ canvas?: unknown;
15
+ id?: unknown;
16
+ key?: unknown;
17
+ value?: unknown;
18
+ from?: unknown;
19
+ occurrence?: unknown;
20
+ }
21
+
22
+ export interface ApplyEditRequest {
23
+ op: 'css' | 'text' | 'attr';
24
+ url: string;
25
+ body: Record<string, unknown>;
26
+ }
27
+
28
+ export function applyEditRequest(m: ApplyEditMessage): ApplyEditRequest | null {
29
+ if (m.op !== 'css' && m.op !== 'text' && m.op !== 'attr') return null;
30
+ if (typeof m.id !== 'string' || !m.id) return null;
31
+ const value = typeof m.value === 'string' ? m.value : null;
32
+ // Older canvas bundles send no `from`; they keep the legacy unconditional write.
33
+ const expected =
34
+ typeof m.from === 'string' || m.from === null ? { expected: m.from } : ({} as object);
35
+ if (m.op === 'css') {
36
+ const target = value == null ? { reset: true } : { value };
37
+ return {
38
+ op: 'css',
39
+ url: '/_api/edit-css',
40
+ body: { canvas: m.canvas, id: m.id, property: m.key, ...target, ...expected },
41
+ };
42
+ }
43
+ if (m.op === 'attr') {
44
+ const target = value == null ? { reset: true } : { value };
45
+ return {
46
+ op: 'attr',
47
+ url: '/_api/edit-attr',
48
+ body: { canvas: m.canvas, id: m.id, attr: m.key, ...target, ...expected },
49
+ };
50
+ }
51
+ const body: Record<string, unknown> = { canvas: m.canvas, id: m.id, text: value ?? '' };
52
+ if (typeof m.occurrence === 'number') body.occurrence = m.occurrence;
53
+ if (typeof m.from === 'string') body.before = m.from;
54
+ return { op: 'text', url: '/_api/edit-text', body };
55
+ }
@@ -12,6 +12,7 @@
12
12
  // (`.st-exports`) and 4-components.css (`.st-export-*`).
13
13
 
14
14
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
15
+ import { dismissNotice, notify, summarizeNoticeText } from '../notifications.tsx';
15
16
  import { isNativeApp, saveExport } from './github.js';
16
17
 
17
18
  const FORMAT_LABELS = {
@@ -75,6 +76,7 @@ async function saveNative(id, filename) {
75
76
  export function useExportCenter({ enabled = true } = {}) {
76
77
  const [jobs, setJobs] = useState(() => new Map());
77
78
  const [panelOpen, setPanelOpen] = useState(false);
79
+ const [selectedJobId, setSelectedJobId] = useState(null);
78
80
  const [toastQueue, setToastQueue] = useState([]);
79
81
  const [savingIds, setSavingIds] = useState(() => new Set());
80
82
  const prevStatusRef = useRef(new Map());
@@ -149,7 +151,7 @@ export function useExportCenter({ enabled = true } = {}) {
149
151
  if (toSurface.length) {
150
152
  setToastQueue((q) => {
151
153
  const merged = q.filter((id) => !toSurface.includes(id));
152
- return [...toSurface, ...merged].slice(0, 8);
154
+ return [...toSurface, ...merged];
153
155
  });
154
156
  }
155
157
 
@@ -169,14 +171,22 @@ export function useExportCenter({ enabled = true } = {}) {
169
171
 
170
172
  const list = useMemo(
171
173
  () =>
172
- Array.from(jobs.values()).sort((a, b) => (b.createdAt || '').localeCompare(a.createdAt || '')),
174
+ Array.from(jobs.values()).sort((a, b) =>
175
+ (b.createdAt || '').localeCompare(a.createdAt || '')
176
+ ),
173
177
  [jobs]
174
178
  );
175
179
  const runningCount = list.filter((j) => j.status === 'running').length;
176
180
  const queuedCount = list.filter((j) => j.status === 'queued').length;
177
181
 
178
- const dismissToast = useCallback(() => setToastQueue((q) => q.slice(1)), []);
179
- const openPanel = useCallback(() => setPanelOpen(true), []);
182
+ const dismissToast = useCallback(
183
+ (id) => setToastQueue((q) => q.filter((item) => item !== (id ?? q[0]))),
184
+ []
185
+ );
186
+ const openPanel = useCallback((jobId) => {
187
+ setSelectedJobId(typeof jobId === 'string' ? jobId : null);
188
+ setPanelOpen(true);
189
+ }, []);
180
190
  const closePanel = useCallback(() => setPanelOpen(false), []);
181
191
 
182
192
  const withSaving = useCallback(async (job, fn) => {
@@ -221,6 +231,8 @@ export function useExportCenter({ enabled = true } = {}) {
221
231
  queuedCount,
222
232
  busyCount: runningCount + queuedCount,
223
233
  panelOpen,
234
+ selectedJobId,
235
+ toastJobs: toastQueue.map((id) => jobs.get(id)).filter(Boolean),
224
236
  openPanel,
225
237
  closePanel,
226
238
  showToast: !!toastJobId,
@@ -248,7 +260,9 @@ function saveActionLabel(saving) {
248
260
 
249
261
  function ProgressBar({ progress }) {
250
262
  if (!progress || !progress.total) {
251
- return <div className="st-export-progress st-export-progress--indeterminate" aria-hidden="true" />;
263
+ return (
264
+ <div className="st-export-progress st-export-progress--indeterminate" aria-hidden="true" />
265
+ );
252
266
  }
253
267
  const pct = Math.max(0, Math.min(100, Math.round((progress.current / progress.total) * 100)));
254
268
  return (
@@ -307,9 +321,11 @@ function StatusPill({ job }) {
307
321
  : job.status === 'done'
308
322
  ? degraded && job.degraded.audioDropped
309
323
  ? 'Ready · no audio'
310
- : degraded
311
- ? 'Ready · degraded'
312
- : 'Ready'
324
+ : degraded && job.degraded.fontsNotEmbedded?.length
325
+ ? 'Ready · font warning'
326
+ : degraded
327
+ ? 'Ready · degraded'
328
+ : 'Ready'
313
329
  : 'Failed';
314
330
  const eta = job.status === 'running' ? etaLabel(job) : null;
315
331
  return (
@@ -329,15 +345,25 @@ function StatusPill({ job }) {
329
345
  * back degraded — in the panel row and in the completion toast, because the
330
346
  * whole failure mode of this bug was that neither surface said anything.
331
347
  */
332
- function DegradedNote({ degraded }) {
348
+ function DegradedNote({ degraded, compact = false }) {
333
349
  if (!degraded) return null;
350
+ // The font case names the fonts, because "reduced fidelity" is useless to
351
+ // someone about to send this to a printer — what they need is which faces
352
+ // came out unprintable (issue #116).
353
+ const fonts = degraded.fontsNotEmbedded ?? [];
354
+ const headline = degraded.audioDropped
355
+ ? 'Exported without audio.'
356
+ : fonts.length
357
+ ? `Fonts a print shop will reject: ${fonts.join(', ')}.`
358
+ : 'Exported with reduced fidelity.';
334
359
  return (
335
360
  <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.'}
340
- {degraded.remedy ? <div className="st-export-degraded__fix">{degraded.remedy}</div> : null}
361
+ <span aria-hidden="true">!</span> {compact ? summarizeNoticeText(headline) : headline}
362
+ {degraded.remedy ? (
363
+ <div className="st-export-degraded__fix">
364
+ {compact ? summarizeNoticeText(degraded.remedy) : degraded.remedy}
365
+ </div>
366
+ ) : null}
341
367
  </div>
342
368
  );
343
369
  }
@@ -364,65 +390,88 @@ export function ExportBadge({ center }) {
364
390
  );
365
391
  }
366
392
 
367
- export function ExportToast({ center }) {
368
- if (!center.showToast || !center.toastJob) return null;
369
- const job = center.toastJob;
393
+ function ExportJobNotice({ job, center }) {
370
394
  const pending = job.status === 'queued' || job.status === 'running';
371
395
  const ok = job.status === 'done';
372
396
  const saving = center.savingIds.has(job.id);
373
- const finalize = () => {
374
- if (isNativeApp()) void center.save(job);
375
- else void center.download(job);
376
- };
377
- return (
378
- <div className="st-toast" role="status" aria-live="polite">
379
- <button type="button" className="st-toast-close" aria-label="Dismiss" onClick={center.dismissToast}>
380
- ×
381
- </button>
382
- <div className="st-toast-hd">
383
- <span aria-hidden="true">{pending ? '⋯' : ok && !job.degraded ? '⬇' : '⚠'}</span>
384
- {pending
385
- ? 'Exporting…'
386
- : ok
387
- ? job.degraded
388
- ? 'Export ready — with a problem'
389
- : 'Export ready'
390
- : 'Export failed'}
391
- </div>
392
- <div className="st-toast-title">{jobLabel(job)}</div>
393
- {pending ? (
397
+ const id = `export-${job.id}`;
398
+ useEffect(() => {
399
+ notify({
400
+ id,
401
+ group: 'exports',
402
+ title: pending
403
+ ? 'Exporting…'
404
+ : ok
405
+ ? job.degraded
406
+ ? 'Export ready — with a problem'
407
+ : 'Export ready'
408
+ : 'Export failed',
409
+ description: pending
410
+ ? 'Running in the background — you can keep working.'
411
+ : ok
412
+ ? (job.filename ?? 'Ready to download.')
413
+ : (job.error ?? 'Something went wrong.'),
414
+ kind: pending ? 'info' : ok ? (job.degraded ? 'warning' : 'success') : 'error',
415
+ duration: pending ? Infinity : undefined,
416
+ timerKey: pending ? 'active' : job.status,
417
+ paused: saving,
418
+ onDismiss: () => center.dismissToast(job.id),
419
+ content: (
394
420
  <>
395
- <div className="st-toast-txt">Running in the background — you can keep working.</div>
396
- <ProgressBar progress={job.progress} />
421
+ <div className="maude-notice-summary">{jobLabel(job)}</div>
422
+ {pending && <ProgressBar progress={job.progress} />}
423
+ {ok && <DegradedNote degraded={job.degraded} compact />}
424
+ <div className="st-toast-actions">
425
+ {ok && (
426
+ <button
427
+ type="button"
428
+ className="btn btn--primary btn--sm"
429
+ disabled={saving}
430
+ onClick={() => (isNativeApp() ? void center.save(job) : void center.download(job))}
431
+ >
432
+ {saving && <span className="st-btn-spin" aria-hidden="true" />}
433
+ {saveActionLabel(saving)}
434
+ </button>
435
+ )}
436
+ <button
437
+ type="button"
438
+ className="btn btn--ghost btn--sm"
439
+ onClick={() => center.openPanel(job.id)}
440
+ >
441
+ View in history
442
+ </button>
443
+ </div>
397
444
  </>
398
- ) : (
399
- <div className="st-toast-txt">
400
- {ok ? (job.filename ?? 'Ready to download.') : (job.error ?? 'Something went wrong.')}
401
- </div>
402
- )}
403
- {ok && <DegradedNote degraded={job.degraded} />}
404
- {ok && (
405
- <div className="st-toast-actions">
406
- <button
407
- type="button"
408
- className="btn btn--primary btn--sm"
409
- onClick={finalize}
410
- disabled={saving}
411
- >
412
- {saving && <span className="st-btn-spin" aria-hidden="true" />}
413
- {saveActionLabel(saving)}
414
- </button>
415
- <button type="button" className="btn btn--ghost btn--sm" onClick={center.dismissToast}>
416
- Dismiss
417
- </button>
418
- </div>
419
- )}
420
- </div>
421
- );
445
+ ),
446
+ });
447
+ }, [
448
+ id,
449
+ job,
450
+ pending,
451
+ ok,
452
+ saving,
453
+ center.dismissToast,
454
+ center.openPanel,
455
+ center.save,
456
+ center.download,
457
+ ]);
458
+ useEffect(() => () => dismissNotice(id), [id]);
459
+ return null;
460
+ }
461
+
462
+ export function ExportToast({ center }) {
463
+ return center.toastJobs.map((job) => <ExportJobNotice key={job.id} job={job} center={center} />);
422
464
  }
423
465
 
424
466
  export function ExportPanel({ center }) {
425
- const { panelOpen, closePanel } = center;
467
+ const { panelOpen, closePanel, selectedJobId } = center;
468
+ const selectedRow = useRef(null);
469
+ useEffect(() => {
470
+ if (panelOpen && selectedJobId) {
471
+ selectedRow.current?.scrollIntoView?.({ block: 'nearest' });
472
+ selectedRow.current?.focus();
473
+ }
474
+ }, [panelOpen, selectedJobId]);
426
475
  useEffect(() => {
427
476
  if (!panelOpen) return;
428
477
  function onKey(e) {
@@ -466,7 +515,13 @@ export function ExportPanel({ center }) {
466
515
  ) : (
467
516
  <ul className="st-export-list">
468
517
  {center.jobs.map((job) => (
469
- <li key={job.id} className="st-export-item">
518
+ <li
519
+ key={job.id}
520
+ className="st-export-item"
521
+ data-testid={`export-job-${job.id}`}
522
+ ref={job.id === selectedJobId ? selectedRow : null}
523
+ tabIndex={-1}
524
+ >
470
525
  <div className="st-export-item__hd">
471
526
  <span className="st-export-item__label">{jobLabel(job)}</span>
472
527
  <StatusPill job={job} />
@@ -493,7 +548,10 @@ export function ExportPanel({ center }) {
493
548
  )}
494
549
  {job.status === 'failed' && (
495
550
  <div className="st-export-item__ft">
496
- <span className="st-export-item__error">{job.error ?? 'Export failed.'}</span>
551
+ <details className="st-export-item__error">
552
+ <summary>Show error details</summary>
553
+ <pre>{job.error ?? 'Export failed.'}</pre>
554
+ </details>
497
555
  </div>
498
556
  )}
499
557
  </li>
@@ -86,6 +86,42 @@ export const initDesign = (dir) => api('/_api/design/init', { method: 'POST', bo
86
86
  /** Phase 29 (E4) Door C — connect to a team hub (saves the global hub credential). */
87
87
  export const hubLink = (body) => api('/_api/hub/link', { method: 'POST', body: JSON.stringify(body) });
88
88
 
89
+ // ── managed team projects (plan T21/T22) ───────────────────────────────────────
90
+ /**
91
+ * Sign in / hold the credential and describe a team project, so its own copy can
92
+ * be created without choosing a folder. body: { kind: 'cloud', projectId } |
93
+ * { kind: 'handoff', code, claimedProject } | { kind: 'hub', url, email, password }
94
+ * | { kind: 'known-hub', url }.
95
+ */
96
+ export const prepareProject = (body) => api('/_api/projects/prepare', { method: 'POST', body: JSON.stringify(body) });
97
+ /** Create (first time) or reuse the project's managed copy, then switch to it. */
98
+ export const managedProjectOpen = (p) =>
99
+ invoke('managed_project_open', {
100
+ serverUrl: p.serverUrl,
101
+ projectId: p.projectId,
102
+ name: p.name || p.projectId,
103
+ canvasGroups: p.canvasGroups || [],
104
+ });
105
+ /** The team projects this computer keeps a copy of, most recent first. */
106
+ export const managedProjectsList = () => invoke('managed_projects_list');
107
+
108
+ /**
109
+ * Prepare, then open. Resolves `{ ok: true, path }` once the switch has started
110
+ * (the webview reloads onto the project), or `{ ok: false, error }`.
111
+ */
112
+ export async function openTeamProject(input) {
113
+ const r = await prepareProject(input);
114
+ if (!r.ok || !r.json?.ok) {
115
+ return { ok: false, error: r.json?.error || 'The project could not be opened.', status: r.status };
116
+ }
117
+ try {
118
+ const path = await managedProjectOpen(r.json);
119
+ return { ok: true, path, project: r.json };
120
+ } catch (e) {
121
+ return { ok: false, error: String(e?.message || e || 'The project could not be opened.') };
122
+ }
123
+ }
124
+
89
125
  // ── Tauri shell: app-state (first-run / last-project / recent) — Phase 29 (E4) ──
90
126
  // In a plain browser these throw via tauri(); the wizard only mounts in the native
91
127
  // app (isNativeApp()), so call sites guard accordingly.
@@ -99,7 +135,8 @@ export const appRecentProjects = () => invoke('app_recent_projects');
99
135
  /** Native folder picker → chosen parent dir, or null if cancelled. */
100
136
  export const pickDirectory = () => invoke('pick_directory');
101
137
  /** Switch the app to a local project folder (the freshly cloned copy). */
102
- export const openLocalProject = (path) => invoke('open_local_project', { path });
138
+ export const openLocalProject = (path, open) => invoke('open_local_project', { path, open });
139
+ export const resolveProjectForLink = (project) => invoke('resolve_project_for_link', { project });
103
140
  /**
104
141
  * Native "Save As…" for an export — opens an OS save dialog seeded with
105
142
  * `filename`, then has the Rust side fetch the finished `jobId`'s bytes
@@ -0,0 +1,76 @@
1
+ /** One project-index request at a time, with automatic transient recovery. */
2
+ export function createIndexLoader<T>({
3
+ read,
4
+ apply,
5
+ onError,
6
+ retryDelayMs = 500,
7
+ maxRetryDelayMs = 5000,
8
+ timeoutMs = 10000,
9
+ }: {
10
+ read: (signal: AbortSignal) => Promise<T>;
11
+ apply: (value: T) => void;
12
+ onError: (error: unknown) => void;
13
+ retryDelayMs?: number;
14
+ maxRetryDelayMs?: number;
15
+ timeoutMs?: number;
16
+ }) {
17
+ let disposed = false;
18
+ let dirty = false;
19
+ let failures = 0;
20
+ let pending: Promise<void> | null = null;
21
+ let retryTimer: ReturnType<typeof setTimeout> | undefined;
22
+ let controller: AbortController | undefined;
23
+ const cancelRetry = () => {
24
+ clearTimeout(retryTimer);
25
+ retryTimer = undefined;
26
+ };
27
+ async function drain() {
28
+ while (dirty && !disposed) {
29
+ dirty = false;
30
+ cancelRetry();
31
+ const current = new AbortController();
32
+ controller = current;
33
+ const timeout = setTimeout(() => current.abort(), timeoutMs);
34
+ try {
35
+ const value = await read(current.signal);
36
+ // A mutation/refresh requested a newer index during this read. Do not
37
+ // briefly restore the older tree; the next request serves all waiters.
38
+ if (!disposed && !dirty) apply(value);
39
+ failures = 0;
40
+ } catch (error) {
41
+ if (disposed) return;
42
+ onError(error);
43
+ const status = (error as { status?: number } | null)?.status;
44
+ if (status == null || status === 408 || status === 429 || status >= 500) {
45
+ const delay = Math.min(maxRetryDelayMs, retryDelayMs * 2 ** Math.min(failures++, 10));
46
+ retryTimer = setTimeout(() => { void reload(); }, delay);
47
+ }
48
+ } finally {
49
+ clearTimeout(timeout);
50
+ if (controller === current) controller = undefined;
51
+ }
52
+ }
53
+ }
54
+ function reload(): Promise<void> {
55
+ if (disposed) return Promise.resolve();
56
+ dirty = true;
57
+ cancelRetry();
58
+ if (!pending) {
59
+ pending = drain().finally(() => {
60
+ pending = null;
61
+ // Cover a reload requested between drain's return and this microtask.
62
+ if (dirty && !disposed) return reload();
63
+ });
64
+ }
65
+ return pending;
66
+ }
67
+ return {
68
+ reload,
69
+ dispose() {
70
+ disposed = true;
71
+ dirty = false;
72
+ cancelRetry();
73
+ controller?.abort();
74
+ },
75
+ };
76
+ }
@@ -14,8 +14,10 @@
14
14
 
15
15
  import { useEffect, useRef, useState } from 'react';
16
16
 
17
+ import { localIdentityMatches, parseFileDeepLink } from '../share-link.js';
18
+ import FileDeepLinkDialog from './file-deep-link-dialog.jsx';
17
19
  import { safeName, syncPresentation } from '../../sync/presentation.ts';
18
- import { invoke, isNativeApp, listen, openCloudUrl } from '../github.js';
20
+ import { invoke, isNativeApp, listen, openCloudUrl, openTeamProject } from '../github.js';
19
21
 
20
22
  const api = async (path, init) => {
21
23
  const res = await fetch(path, init);
@@ -80,7 +82,7 @@ export function isDisplayableUrl(url, cloudUrl) {
80
82
  * browser didn't open, go to:" teaches the person to copy the address into a
81
83
  * browser the lock cannot see (B4). Callers surface the failure instead.
82
84
  */
83
- function openExternal(url) {
85
+ export function openExternal(url) {
84
86
  if (!url) return Promise.resolve('failed');
85
87
  if (isNativeApp()) {
86
88
  return openCloudUrl(url).then(
@@ -118,7 +120,9 @@ export function parseDeepLink(url) {
118
120
  // without this, arbitrary attacker text renders in the dialog's heading, prose
119
121
  // and aria-label. Nothing longer can name a real project anyway.
120
122
  if (m[1].length > 40) return null;
121
- const code = new URLSearchParams(m[2]).get('code') ?? '';
123
+ const params = new URLSearchParams(m[2]);
124
+ if (params.has('open') || params.getAll('code').length !== 1) return null;
125
+ const code = params.get('code') ?? '';
122
126
  if (!/^mhc_[0-9a-f]{16,128}$/.test(code)) return null;
123
127
  return { project: m[1], code };
124
128
  }
@@ -402,7 +406,7 @@ function Icon({ name, size = 15 }) {
402
406
  // GitPanel's cloud-managed posture turns on the same `linkedHub.credentialed`
403
407
  // fact this panel's Connected row does, and CloudBar owns every action that
404
408
  // changes it (status resolve, attach, detach) — so it reports, the app decides.
405
- export default function CloudBar({ syncStatus, onLinkedHub }) {
409
+ export default function CloudBar({ syncStatus, onLinkedHub, onLocalProject, onOpenFile, filesReady }) {
406
410
  const [state, setState] = useState('loading'); // loading | out | in
407
411
  const [email, setEmail] = useState(null);
408
412
  const [menuOpen, setMenuOpen] = useState(false);
@@ -451,6 +455,7 @@ export default function CloudBar({ syncStatus, onLinkedHub }) {
451
455
  resolved: true,
452
456
  });
453
457
  onLinkedHub?.(r.json?.linkedHub ?? null);
458
+ onLocalProject?.(r.json?.project ?? null);
454
459
  if (r.ok && r.json?.connected) {
455
460
  setEmail(r.json.email);
456
461
  setState('in');
@@ -474,7 +479,9 @@ export default function CloudBar({ syncStatus, onLinkedHub }) {
474
479
  useEffect(() => {
475
480
  if (!isNativeApp()) return undefined;
476
481
  const consume = (url) => {
477
- const parsed = parseDeepLink(url);
482
+ const connect = parseDeepLink(url);
483
+ const file = connect ? null : parseFileDeepLink(url);
484
+ const parsed = connect ? { ...connect, kind: 'connect' } : file ? { ...file, kind: 'file' } : null;
478
485
  if (!parsed) return;
479
486
  // NEVER replace a link the person is already looking at. The dialog is a
480
487
  // single slot, so an overwrite would swap the code out from under a
@@ -493,6 +500,13 @@ export default function CloudBar({ syncStatus, onLinkedHub }) {
493
500
  return () => unlisten?.();
494
501
  }, []);
495
502
 
503
+ const fileMatches = pending?.kind === 'file' && local.resolved && localIdentityMatches(local, pending.project);
504
+ useEffect(() => {
505
+ if (!fileMatches || !filesReady || !onOpenFile) return;
506
+ onOpenFile(pending.rel);
507
+ setPending(null);
508
+ }, [fileMatches, filesReady, pending, onOpenFile]);
509
+
496
510
  async function connectPending() {
497
511
  if (!pending) return;
498
512
  setBusy('deeplink');
@@ -516,6 +530,21 @@ export default function CloudBar({ syncStatus, onLinkedHub }) {
516
530
  }
517
531
  }
518
532
 
533
+ // Plan T22: the link's project opened as ITS OWN copy — the designer's
534
+ // default, and the one answer that can't sync the wrong folder anywhere. The
535
+ // server still checks the claimed name against what the code opens.
536
+ async function openPendingManaged() {
537
+ if (!pending) return;
538
+ setBusy('deeplink');
539
+ setError('');
540
+ const r = await openTeamProject({ kind: 'handoff', code: pending.code, claimedProject: pending.project });
541
+ setPending(null);
542
+ if (!r.ok) {
543
+ setBusy('');
544
+ setError(r.error || 'The project could not be opened.');
545
+ }
546
+ }
547
+
519
548
  // A successful attach IS a credentialed link — mirror it into `local` so the
520
549
  // project list flips to Connected without a second status fetch (the server
521
550
  // just wrote both halves: linkedHub in config.json + the hub credential).
@@ -677,7 +706,8 @@ export default function CloudBar({ syncStatus, onLinkedHub }) {
677
706
  status fetch, and a modal rendered without the local half showed no
678
707
  warning and a primary Connect — failing OPEN in the one scenario with
679
708
  the least context (attacker pass B3). */}
680
- {pending && local.resolved && (
709
+ {pending?.kind === 'file' && local.resolved && !fileMatches && <FileDeepLinkDialog key={pending.project + pending.rel} pending={pending} local={local} cloudUrl={cloudUrl} onClose={() => setPending(null)} />}
710
+ {pending?.kind === 'connect' && local.resolved && (
681
711
  <div
682
712
  className="gi-modal"
683
713
  role="dialog"
@@ -751,6 +781,22 @@ export default function CloudBar({ syncStatus, onLinkedHub }) {
751
781
  </span>
752
782
  </div>
753
783
  )}
784
+ {isNativeApp() && (
785
+ <div className="gi-dl-open">
786
+ <button
787
+ type="button"
788
+ className="btn btn--primary"
789
+ disabled={busy === 'deeplink'}
790
+ onClick={openPendingManaged}
791
+ data-testid="cloud-deeplink-open"
792
+ >
793
+ {busy === 'deeplink' ? 'Opening…' : `Open ${pending.project}`}
794
+ </button>
795
+ <span className="gi-dc-foot-note">
796
+ Opens the project in its own copy on this computer. Nothing in this folder changes.
797
+ </span>
798
+ </div>
799
+ )}
754
800
  <div className="gi-dc-foot">
755
801
  {/* On a mismatch the safe action leads and connecting is the one you
756
802
  have to mean. No project switcher lives here on purpose: the