@1agh/maude 1.0.11 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (506) hide show
  1. package/apps/studio/acp/bridge.ts +18 -13
  2. package/apps/studio/acp/transcript-io.ts +394 -0
  3. package/apps/studio/acp/transcript.ts +103 -38
  4. package/apps/studio/api.ts +139 -26
  5. package/apps/studio/bin/_import-figma.mjs +57 -11
  6. package/apps/studio/bin/_seed-fixture.mjs +163 -0
  7. package/apps/studio/canvas-imports.ts +89 -0
  8. package/apps/studio/client/app.jsx +257 -7
  9. package/apps/studio/client/export-center.jsx +15 -7
  10. package/apps/studio/client/panels/SyncPanel.jsx +125 -4
  11. package/apps/studio/client/report-bug.jsx +19 -1
  12. package/apps/studio/client/styles/3-shell-maude.css +34 -0
  13. package/apps/studio/client/styles/4-components.css +26 -0
  14. package/apps/studio/collab/persistence.ts +132 -16
  15. package/apps/studio/collab/registry.ts +10 -18
  16. package/apps/studio/debug-bundle.ts +13 -0
  17. package/apps/studio/dist/client.bundle.js +1128 -1128
  18. package/apps/studio/dist/styles.css +1 -1
  19. package/apps/studio/export-dialog.tsx +54 -0
  20. package/apps/studio/exporters/degraded.ts +19 -3
  21. package/apps/studio/exporters/ghostscript.ts +194 -0
  22. package/apps/studio/exporters/pdf-fonts.ts +261 -0
  23. package/apps/studio/exporters/pdf.ts +152 -3
  24. package/apps/studio/exporters/remote.ts +36 -1
  25. package/apps/studio/fs-watch.ts +45 -1
  26. package/apps/studio/http.ts +7 -1
  27. package/apps/studio/mem.ts +45 -9
  28. package/apps/studio/server.ts +11 -4
  29. package/apps/studio/sync/agent.ts +136 -29
  30. package/apps/studio/sync/asset-push.ts +1 -47
  31. package/apps/studio/sync/codec.ts +122 -31
  32. package/apps/studio/sync/cold-start-apply.ts +7 -0
  33. package/apps/studio/sync/cold-start.ts +118 -22
  34. package/apps/studio/sync/comment-identity.ts +68 -0
  35. package/apps/studio/sync/connection-state.ts +15 -1
  36. package/apps/studio/sync/file-ledger.ts +117 -0
  37. package/apps/studio/sync/file-plane.ts +623 -27
  38. package/apps/studio/sync/index.ts +711 -20
  39. package/apps/studio/sync/journal-client.ts +13 -1
  40. package/apps/studio/sync/migrate-seed.ts +40 -3
  41. package/apps/studio/sync/presentation.ts +26 -3
  42. package/apps/studio/sync/retry-after.ts +169 -0
  43. package/apps/studio/sync/seed-progress.ts +218 -0
  44. package/apps/studio/sync/status.ts +122 -5
  45. package/apps/studio/sync/transport-error.ts +111 -0
  46. package/apps/studio/whats-new.json +72 -0
  47. package/cli/bin/maude.mjs +2 -0
  48. package/cli/commands/codex.mjs +191 -0
  49. package/cli/commands/harness.mjs +1051 -0
  50. package/cli/commands/help.mjs +13 -0
  51. package/cli/lib/design-link.mjs +190 -2
  52. package/cli/lib/harness/capabilities.mjs +302 -0
  53. package/cli/lib/harness/codex-runtime.mjs +889 -0
  54. package/cli/lib/harness/compatibility.mjs +27 -0
  55. package/cli/lib/harness/discover-claude.mjs +1393 -0
  56. package/cli/lib/harness/managed-state.mjs +1296 -0
  57. package/cli/lib/harness/model.mjs +86 -0
  58. package/cli/lib/harness/secrets.mjs +390 -0
  59. package/cli/lib/harness/targets/codex.mjs +1141 -0
  60. package/cli/lib/harness/targets/opencode.mjs +807 -0
  61. package/cli/lib/harness/transaction.mjs +1099 -0
  62. package/cli/lib/hubs-config.mjs +35 -8
  63. package/cli/templates/harness/opencode/maude-projector.ts +58 -0
  64. package/docs/harness-capability-matrix.md +125 -0
  65. package/docs/harness-environment-projection.md +95 -0
  66. package/package.json +32 -9
  67. package/plugins/flow/.claude-plugin/config.schema.json +8 -0
  68. package/apps/studio/.ai/cache/_stats.json +0 -7
  69. package/apps/studio/bin/_agent-browser-safe.test.mjs +0 -165
  70. package/apps/studio/bin/_curl-local.test.mjs +0 -280
  71. package/apps/studio/bin/_pw-launch.test.mjs +0 -90
  72. package/apps/studio/bin/_smart-frames.test.mjs +0 -195
  73. package/apps/studio/bin/_transcribe.test.mjs +0 -80
  74. package/apps/studio/bun.lock +0 -928
  75. package/apps/studio/draw/test/animate.test.ts +0 -172
  76. package/apps/studio/draw/test/brush.test.ts +0 -213
  77. package/apps/studio/draw/test/composition.test.ts +0 -141
  78. package/apps/studio/draw/test/geometry.test.ts +0 -199
  79. package/apps/studio/draw/test/gradient.test.ts +0 -167
  80. package/apps/studio/draw/test/layout.test.ts +0 -120
  81. package/apps/studio/draw/test/morph.test.ts +0 -107
  82. package/apps/studio/draw/test/optimize.test.ts +0 -40
  83. package/apps/studio/draw/test/palette.test.ts +0 -123
  84. package/apps/studio/draw/test/primitives.test.ts +0 -129
  85. package/apps/studio/draw/test/serialize-animate.test.ts +0 -142
  86. package/apps/studio/draw/test/serialize.test.ts +0 -105
  87. package/apps/studio/figma/assets.test.ts +0 -464
  88. package/apps/studio/figma/client.test.ts +0 -395
  89. package/apps/studio/figma/codegen-client.test.ts +0 -276
  90. package/apps/studio/figma/codegen-fonts.test.ts +0 -103
  91. package/apps/studio/figma/codegen-values.test.ts +0 -179
  92. package/apps/studio/figma/comments-to-strokes.test.ts +0 -194
  93. package/apps/studio/figma/fig-decode.test.ts +0 -788
  94. package/apps/studio/figma/fig-differential.test.ts +0 -182
  95. package/apps/studio/figma/fig-translator.test.ts +0 -192
  96. package/apps/studio/figma/fig-vector.test.ts +0 -113
  97. package/apps/studio/figma/from-codegen.test.ts +0 -408
  98. package/apps/studio/figma/sanitize.test.ts +0 -325
  99. package/apps/studio/figma/tailwind-map.test.ts +0 -142
  100. package/apps/studio/figma/to-artboard.test.ts +0 -808
  101. package/apps/studio/figma/to-render.test.ts +0 -180
  102. package/apps/studio/figma/to-strokes-roundtrip.test.ts +0 -152
  103. package/apps/studio/figma/to-strokes.test.ts +0 -705
  104. package/apps/studio/figma/to-tokens.test.ts +0 -321
  105. package/apps/studio/figma/url.test.ts +0 -167
  106. package/apps/studio/footage/schema.test.ts +0 -287
  107. package/apps/studio/generation/adapters/groq.test.ts +0 -80
  108. package/apps/studio/generation/audio-library.test.ts +0 -95
  109. package/apps/studio/generation/captions.test.ts +0 -78
  110. package/apps/studio/generation/elevenlabs.test.ts +0 -223
  111. package/apps/studio/generation/gemini.test.ts +0 -350
  112. package/apps/studio/generation/jobs.test.ts +0 -128
  113. package/apps/studio/generation/keys.test.ts +0 -75
  114. package/apps/studio/generation/prefs.test.ts +0 -95
  115. package/apps/studio/generation/types.test.ts +0 -123
  116. package/apps/studio/generation/whisper-models.test.ts +0 -179
  117. package/apps/studio/test/_helpers.ts +0 -108
  118. package/apps/studio/test/acp-activity-endpoint.test.ts +0 -183
  119. package/apps/studio/test/acp-activity.test.ts +0 -154
  120. package/apps/studio/test/acp-ai-activity.test.ts +0 -182
  121. package/apps/studio/test/acp-attachment-serve.test.ts +0 -74
  122. package/apps/studio/test/acp-bootstrap-brief.test.ts +0 -178
  123. package/apps/studio/test/acp-branch-guard.test.ts +0 -123
  124. package/apps/studio/test/acp-bridge-lifetime.test.ts +0 -475
  125. package/apps/studio/test/acp-bridge.test.ts +0 -545
  126. package/apps/studio/test/acp-capabilities.test.ts +0 -123
  127. package/apps/studio/test/acp-caps-bridge.test.ts +0 -279
  128. package/apps/studio/test/acp-commands.test.ts +0 -119
  129. package/apps/studio/test/acp-elicitation-bridge.test.ts +0 -480
  130. package/apps/studio/test/acp-elicitation.test.ts +0 -251
  131. package/apps/studio/test/acp-env.test.ts +0 -95
  132. package/apps/studio/test/acp-mode-banner.test.ts +0 -45
  133. package/apps/studio/test/acp-origin-gate.test.ts +0 -160
  134. package/apps/studio/test/acp-permission-prompt.test.ts +0 -251
  135. package/apps/studio/test/acp-permission.test.ts +0 -278
  136. package/apps/studio/test/acp-plugin-bootstrap.test.ts +0 -89
  137. package/apps/studio/test/acp-session-allowed-tools.test.ts +0 -235
  138. package/apps/studio/test/acp-session-plugins.test.ts +0 -209
  139. package/apps/studio/test/acp-toolgroup.test.ts +0 -76
  140. package/apps/studio/test/acp-transcript-view.test.ts +0 -71
  141. package/apps/studio/test/acp-transcript.test.ts +0 -238
  142. package/apps/studio/test/acp-usage-bridge.test.ts +0 -141
  143. package/apps/studio/test/acp-usage.test.ts +0 -143
  144. package/apps/studio/test/acp-write-gate.test.ts +0 -323
  145. package/apps/studio/test/acp-write-scope.test.ts +0 -533
  146. package/apps/studio/test/active-state.test.ts +0 -186
  147. package/apps/studio/test/activity.test.ts +0 -189
  148. package/apps/studio/test/ai-activity.test.ts +0 -113
  149. package/apps/studio/test/annotate-write.test.ts +0 -1074
  150. package/apps/studio/test/annotation-strokes-command.test.ts +0 -166
  151. package/apps/studio/test/annotations-align.test.ts +0 -88
  152. package/apps/studio/test/annotations-api.test.ts +0 -145
  153. package/apps/studio/test/annotations-bindings.test.ts +0 -262
  154. package/apps/studio/test/annotations-draw-modifiers.test.ts +0 -206
  155. package/apps/studio/test/annotations-groups.test.ts +0 -231
  156. package/apps/studio/test/annotations-layer.test.ts +0 -1011
  157. package/apps/studio/test/annotations-roundtrip.test.ts +0 -663
  158. package/apps/studio/test/annotations-snap.test.ts +0 -135
  159. package/apps/studio/test/annotations-text-format.test.ts +0 -396
  160. package/apps/studio/test/artboard-activity-overlay.test.tsx +0 -56
  161. package/apps/studio/test/artboard-guides-overlay.test.tsx +0 -152
  162. package/apps/studio/test/artboard-hug-commit.test.ts +0 -36
  163. package/apps/studio/test/artboard-kinds.test.tsx +0 -83
  164. package/apps/studio/test/artboard-selection-attrs.test.ts +0 -69
  165. package/apps/studio/test/asset-api.test.ts +0 -149
  166. package/apps/studio/test/assets-s3.test.ts +0 -249
  167. package/apps/studio/test/binary-smoke.test.ts +0 -47
  168. package/apps/studio/test/boot-self-heal.test.ts +0 -87
  169. package/apps/studio/test/browse-posture.test.tsx +0 -107
  170. package/apps/studio/test/bundle-smoke.test.ts +0 -46
  171. package/apps/studio/test/camera-reveal.test.tsx +0 -173
  172. package/apps/studio/test/canvas-artifacts.test.ts +0 -125
  173. package/apps/studio/test/canvas-build-sandbox.test.ts +0 -69
  174. package/apps/studio/test/canvas-build.test.ts +0 -141
  175. package/apps/studio/test/canvas-create-api.test.ts +0 -676
  176. package/apps/studio/test/canvas-cursors.test.ts +0 -164
  177. package/apps/studio/test/canvas-edit.test.ts +0 -470
  178. package/apps/studio/test/canvas-freshness-deps.test.ts +0 -64
  179. package/apps/studio/test/canvas-header.test.ts +0 -127
  180. package/apps/studio/test/canvas-hide-chrome.test.ts +0 -88
  181. package/apps/studio/test/canvas-hmr-runtime.test.tsx +0 -114
  182. package/apps/studio/test/canvas-lib-inline.test.ts +0 -146
  183. package/apps/studio/test/canvas-lib-motion.test.ts +0 -131
  184. package/apps/studio/test/canvas-lib-resolver.test.ts +0 -111
  185. package/apps/studio/test/canvas-list-watch.test.ts +0 -388
  186. package/apps/studio/test/canvas-media-drop.test.ts +0 -187
  187. package/apps/studio/test/canvas-meta-api.test.ts +0 -707
  188. package/apps/studio/test/canvas-move-api.test.ts +0 -587
  189. package/apps/studio/test/canvas-origin-gate.test.ts +0 -417
  190. package/apps/studio/test/canvas-pipeline.test.ts +0 -180
  191. package/apps/studio/test/canvas-rects.test.ts +0 -198
  192. package/apps/studio/test/canvas-reorder.test.ts +0 -211
  193. package/apps/studio/test/canvas-route.test.ts +0 -179
  194. package/apps/studio/test/canvas-shell-base.test.ts +0 -107
  195. package/apps/studio/test/canvas-shell-build-error.test.ts +0 -52
  196. package/apps/studio/test/canvas-url.test.ts +0 -138
  197. package/apps/studio/test/canvas-wheel-delta.test.ts +0 -42
  198. package/apps/studio/test/canvas-zoom-floor.test.ts +0 -73
  199. package/apps/studio/test/capture-core.test.ts +0 -32
  200. package/apps/studio/test/capture-determinism-shape.test.ts +0 -135
  201. package/apps/studio/test/chat-attachments.test.ts +0 -117
  202. package/apps/studio/test/chat-context.test.ts +0 -129
  203. package/apps/studio/test/chat-markdown.test.tsx +0 -58
  204. package/apps/studio/test/clip-addressing.test.ts +0 -738
  205. package/apps/studio/test/clip-ops.test.ts +0 -922
  206. package/apps/studio/test/cloud-build.test.ts +0 -95
  207. package/apps/studio/test/cloud-connect-note.test.ts +0 -135
  208. package/apps/studio/test/cloud-endpoints.test.ts +0 -590
  209. package/apps/studio/test/cloud-history-hardening.test.ts +0 -165
  210. package/apps/studio/test/cloud-history-posture.test.ts +0 -230
  211. package/apps/studio/test/cloud-managed-save-surfaces.test.ts +0 -90
  212. package/apps/studio/test/cloud-renew.test.ts +0 -205
  213. package/apps/studio/test/cloud-session-role.test.ts +0 -161
  214. package/apps/studio/test/cloud-shell-surfaces.test.ts +0 -170
  215. package/apps/studio/test/cold-start-apply.test.ts +0 -303
  216. package/apps/studio/test/collab-annotations-bridge.test.ts +0 -55
  217. package/apps/studio/test/collab-awareness-bridge.test.ts +0 -223
  218. package/apps/studio/test/collab-bridge.test.ts +0 -79
  219. package/apps/studio/test/collab-loopback.test.ts +0 -63
  220. package/apps/studio/test/collab-origin-gate.test.ts +0 -323
  221. package/apps/studio/test/collab-protocol.test.ts +0 -146
  222. package/apps/studio/test/collab-readonly-gate.test.ts +0 -194
  223. package/apps/studio/test/collab-reseed-guard.test.ts +0 -78
  224. package/apps/studio/test/collab-room.test.ts +0 -188
  225. package/apps/studio/test/collab-session-survival.test.tsx +0 -176
  226. package/apps/studio/test/collab-stress.test.ts +0 -129
  227. package/apps/studio/test/comment-mount.test.ts +0 -97
  228. package/apps/studio/test/comment-relay-origin-gate.test.ts +0 -117
  229. package/apps/studio/test/comments-api.test.ts +0 -277
  230. package/apps/studio/test/comments-fs-rebroadcast.test.ts +0 -155
  231. package/apps/studio/test/comments-overlay.test.ts +0 -117
  232. package/apps/studio/test/compile-entry.test.ts +0 -133
  233. package/apps/studio/test/component-map.test.ts +0 -48
  234. package/apps/studio/test/config-projection.test.ts +0 -104
  235. package/apps/studio/test/config-reload.test.ts +0 -230
  236. package/apps/studio/test/config-version.test.ts +0 -88
  237. package/apps/studio/test/context-resolve-tokens.test.ts +0 -97
  238. package/apps/studio/test/convert-to-absolute.test.ts +0 -333
  239. package/apps/studio/test/credential-grammar.test.ts +0 -107
  240. package/apps/studio/test/csp-canvas-shell.test.ts +0 -62
  241. package/apps/studio/test/csrf-write-guard.test.ts +0 -141
  242. package/apps/studio/test/debug-bundle.test.ts +0 -105
  243. package/apps/studio/test/design-setup-readiness.test.ts +0 -155
  244. package/apps/studio/test/detach-component.test.ts +0 -94
  245. package/apps/studio/test/dns-rebinding-guard.test.ts +0 -101
  246. package/apps/studio/test/dom-selection.test.ts +0 -198
  247. package/apps/studio/test/dynamic-text-edit.test.ts +0 -162
  248. package/apps/studio/test/edit-css-occurrence.test.ts +0 -81
  249. package/apps/studio/test/edit-persistence.test.ts +0 -91
  250. package/apps/studio/test/edit-scope-api.test.ts +0 -119
  251. package/apps/studio/test/edit-source-command.test.ts +0 -142
  252. package/apps/studio/test/edit-suppress.test.ts +0 -170
  253. package/apps/studio/test/editing-presence.test.ts +0 -103
  254. package/apps/studio/test/element-resize.test.ts +0 -136
  255. package/apps/studio/test/element-structural-api.test.ts +0 -434
  256. package/apps/studio/test/element-structural-edit.test.ts +0 -750
  257. package/apps/studio/test/ensure-browser.test.ts +0 -92
  258. package/apps/studio/test/equal-spacing-detector.test.ts +0 -257
  259. package/apps/studio/test/export-assemble.test.ts +0 -53
  260. package/apps/studio/test/export-browser-lane.test.ts +0 -126
  261. package/apps/studio/test/export-capture-fidelity.test.ts +0 -146
  262. package/apps/studio/test/export-capture-hygiene.test.ts +0 -675
  263. package/apps/studio/test/export-center.test.tsx +0 -287
  264. package/apps/studio/test/export-e2e-lanes.test.ts +0 -857
  265. package/apps/studio/test/export-format-scope-coherence.test.ts +0 -89
  266. package/apps/studio/test/export-lane.test.ts +0 -245
  267. package/apps/studio/test/export-shim-multi-capture.test.ts +0 -83
  268. package/apps/studio/test/exporters/canva.test.ts +0 -63
  269. package/apps/studio/test/exporters/degraded-propagation.test.ts +0 -123
  270. package/apps/studio/test/exporters/endpoint.test.ts +0 -121
  271. package/apps/studio/test/exporters/history.test.ts +0 -107
  272. package/apps/studio/test/exporters/html.test.ts +0 -26
  273. package/apps/studio/test/exporters/jobs.test.ts +0 -342
  274. package/apps/studio/test/exporters/pdf.test.ts +0 -85
  275. package/apps/studio/test/exporters/png.test.ts +0 -103
  276. package/apps/studio/test/exporters/pptx-deck.test.ts +0 -112
  277. package/apps/studio/test/exporters/pptx.test.ts +0 -31
  278. package/apps/studio/test/exporters/pw-launch.test.ts +0 -49
  279. package/apps/studio/test/exporters/runtime.test.ts +0 -59
  280. package/apps/studio/test/exporters/scope.test.ts +0 -292
  281. package/apps/studio/test/exporters/svg.test.ts +0 -29
  282. package/apps/studio/test/exporters/unsupported-media.test.ts +0 -123
  283. package/apps/studio/test/exporters/zip.test.ts +0 -104
  284. package/apps/studio/test/fetch-asset-gate.test.ts +0 -189
  285. package/apps/studio/test/figjam-v3-model.test.ts +0 -342
  286. package/apps/studio/test/figma-explode.test.ts +0 -438
  287. package/apps/studio/test/figma-provenance.test.ts +0 -108
  288. package/apps/studio/test/figma-routes.test.ts +0 -294
  289. package/apps/studio/test/file-lock.test.ts +0 -84
  290. package/apps/studio/test/fixtures/backgrounding-claude-auth.sh +0 -12
  291. package/apps/studio/test/fixtures/fake-claude-auth.mjs +0 -23
  292. package/apps/studio/test/fixtures/figjam-v3-groups-bindings.svg +0 -1
  293. package/apps/studio/test/fixtures/mock-acp-agent-caps.mjs +0 -158
  294. package/apps/studio/test/fixtures/mock-acp-agent-commands.mjs +0 -44
  295. package/apps/studio/test/fixtures/mock-acp-agent-elicit-flood.mjs +0 -46
  296. package/apps/studio/test/fixtures/mock-acp-agent-elicit-url.mjs +0 -42
  297. package/apps/studio/test/fixtures/mock-acp-agent-elicit.mjs +0 -63
  298. package/apps/studio/test/fixtures/mock-acp-agent-permission-flood.mjs +0 -51
  299. package/apps/studio/test/fixtures/mock-acp-agent-permission.mjs +0 -50
  300. package/apps/studio/test/fixtures/mock-acp-agent-slow.mjs +0 -45
  301. package/apps/studio/test/fixtures/mock-acp-agent-usage.mjs +0 -56
  302. package/apps/studio/test/fixtures/mock-acp-agent-wedged.mjs +0 -37
  303. package/apps/studio/test/fixtures/mock-acp-agent-write.mjs +0 -118
  304. package/apps/studio/test/fixtures/mock-acp-agent.mjs +0 -69
  305. package/apps/studio/test/fixtures/noisy-claude-auth.mjs +0 -27
  306. package/apps/studio/test/fixtures/perf-canvas.mjs +0 -201
  307. package/apps/studio/test/fixtures/phase-20-annotations.svg +0 -1
  308. package/apps/studio/test/fixtures/phase-21-annotations.svg +0 -1
  309. package/apps/studio/test/fixtures/tiny-subset.woff2 +0 -0
  310. package/apps/studio/test/fixtures/video-comp-fixture.tsx +0 -82
  311. package/apps/studio/test/fixtures/wedged-claude-auth.sh +0 -11
  312. package/apps/studio/test/footage-store.test.ts +0 -100
  313. package/apps/studio/test/fs-mkdir-api.test.ts +0 -248
  314. package/apps/studio/test/fs-watch.test.ts +0 -41
  315. package/apps/studio/test/gemma-models.test.ts +0 -245
  316. package/apps/studio/test/generate-route.test.ts +0 -106
  317. package/apps/studio/test/git-api.test.ts +0 -0
  318. package/apps/studio/test/git-branches.test.ts +0 -341
  319. package/apps/studio/test/git-cloud-posture.test.ts +0 -59
  320. package/apps/studio/test/git-grouping.test.ts +0 -106
  321. package/apps/studio/test/git-lifecycle.test.ts +0 -100
  322. package/apps/studio/test/git-log-format.test.ts +0 -95
  323. package/apps/studio/test/git-safe-rel.test.ts +0 -132
  324. package/apps/studio/test/git-system-engine.test.ts +0 -135
  325. package/apps/studio/test/git-watch.test.ts +0 -97
  326. package/apps/studio/test/github-api.test.ts +0 -577
  327. package/apps/studio/test/grid-track-handles.test.ts +0 -160
  328. package/apps/studio/test/handoff-static-frames.test.ts +0 -215
  329. package/apps/studio/test/handoff.test.ts +0 -329
  330. package/apps/studio/test/history-rollback.test.ts +0 -88
  331. package/apps/studio/test/hmr-broadcast.test.ts +0 -236
  332. package/apps/studio/test/hmr-classify.test.ts +0 -87
  333. package/apps/studio/test/hub-link.test.ts +0 -80
  334. package/apps/studio/test/identity-cache.test.ts +0 -83
  335. package/apps/studio/test/import-asset-browser.test.ts +0 -64
  336. package/apps/studio/test/import-asset-route.test.ts +0 -71
  337. package/apps/studio/test/import-asset.test.ts +0 -427
  338. package/apps/studio/test/import-brand-browser.test.ts +0 -88
  339. package/apps/studio/test/import-brand-route.test.ts +0 -120
  340. package/apps/studio/test/import-brand.test.ts +0 -206
  341. package/apps/studio/test/import-figma.test.ts +0 -667
  342. package/apps/studio/test/import-tokens.test.ts +0 -722
  343. package/apps/studio/test/index-data-canvas-ds.test.ts +0 -97
  344. package/apps/studio/test/input-router.test.ts +0 -757
  345. package/apps/studio/test/inspect-script-syntax.test.ts +0 -53
  346. package/apps/studio/test/inspect-selections.test.ts +0 -219
  347. package/apps/studio/test/knob-props-authored.test.ts +0 -87
  348. package/apps/studio/test/layers-synthetic-groups.test.ts +0 -96
  349. package/apps/studio/test/locator.test.ts +0 -214
  350. package/apps/studio/test/marquee-overlay.test.ts +0 -94
  351. package/apps/studio/test/media-commit-chain.test.ts +0 -210
  352. package/apps/studio/test/mode-toggle.test.tsx +0 -226
  353. package/apps/studio/test/motion-sample.test.ts +0 -35
  354. package/apps/studio/test/move-artboards-command.test.ts +0 -108
  355. package/apps/studio/test/participants-chrome.test.ts +0 -65
  356. package/apps/studio/test/paths.test.ts +0 -88
  357. package/apps/studio/test/pdf-print-boxes.test.ts +0 -359
  358. package/apps/studio/test/peer-selection-follows-camera.test.tsx +0 -131
  359. package/apps/studio/test/perf-harness.ts +0 -193
  360. package/apps/studio/test/phase-3.6-smoke.test.ts +0 -79
  361. package/apps/studio/test/photo-bg-remove-validation.test.ts +0 -71
  362. package/apps/studio/test/photo-canvas-bundle.test.ts +0 -79
  363. package/apps/studio/test/photo-edit-api.test.ts +0 -201
  364. package/apps/studio/test/photo-filters.test.ts +0 -149
  365. package/apps/studio/test/photo-pipeline.test.ts +0 -106
  366. package/apps/studio/test/photo-taxonomy.test.ts +0 -96
  367. package/apps/studio/test/print-marks.test.ts +0 -113
  368. package/apps/studio/test/print-units.test.ts +0 -173
  369. package/apps/studio/test/read-annotations.test.ts +0 -727
  370. package/apps/studio/test/read-only-gate.test.ts +0 -188
  371. package/apps/studio/test/read-only-role.test.ts +0 -95
  372. package/apps/studio/test/readiness.test.ts +0 -337
  373. package/apps/studio/test/remote-ahead-behind-cache.test.ts +0 -89
  374. package/apps/studio/test/reorder-api.test.ts +0 -210
  375. package/apps/studio/test/repo-concurrency.test.ts +0 -146
  376. package/apps/studio/test/repo-lock.test.ts +0 -350
  377. package/apps/studio/test/report-proxy.test.ts +0 -125
  378. package/apps/studio/test/ripple.test.ts +0 -197
  379. package/apps/studio/test/runtime-bundle-error-mapping.test.ts +0 -43
  380. package/apps/studio/test/runtime-bundle.test.ts +0 -69
  381. package/apps/studio/test/sanitize-annotation-svg.test.ts +0 -238
  382. package/apps/studio/test/scaffold-design.test.ts +0 -122
  383. package/apps/studio/test/server-lifecycle.test.ts +0 -38
  384. package/apps/studio/test/session-runtime-state.test.ts +0 -226
  385. package/apps/studio/test/session-scope.test.ts +0 -100
  386. package/apps/studio/test/shared-doc-cell-pairing.test.ts +0 -642
  387. package/apps/studio/test/shared-doc-convergence.test.ts +0 -265
  388. package/apps/studio/test/shared-doc-foundation.test.ts +0 -63
  389. package/apps/studio/test/shared-doc-migrate.test.ts +0 -369
  390. package/apps/studio/test/shared-doc-projection.test.ts +0 -238
  391. package/apps/studio/test/shell-importmap.test.ts +0 -56
  392. package/apps/studio/test/sizing-mode.test.ts +0 -102
  393. package/apps/studio/test/slash-commands.test.ts +0 -117
  394. package/apps/studio/test/snap-distance-pill.test.ts +0 -68
  395. package/apps/studio/test/spacing-handles.test.ts +0 -138
  396. package/apps/studio/test/specimen-select.test.ts +0 -88
  397. package/apps/studio/test/sync-agent.test.ts +0 -660
  398. package/apps/studio/test/sync-annotations-cold-start.test.ts +0 -302
  399. package/apps/studio/test/sync-asset-push.test.ts +0 -931
  400. package/apps/studio/test/sync-atomic-write.test.ts +0 -62
  401. package/apps/studio/test/sync-attach-incremental.test.ts +0 -527
  402. package/apps/studio/test/sync-autocommit.test.ts +0 -537
  403. package/apps/studio/test/sync-canvas-path.test.ts +0 -200
  404. package/apps/studio/test/sync-cell-write-nudge.test.ts +0 -346
  405. package/apps/studio/test/sync-codec.test.ts +0 -230
  406. package/apps/studio/test/sync-cold-seed-dedup.test.ts +0 -187
  407. package/apps/studio/test/sync-cold-start.test.ts +0 -304
  408. package/apps/studio/test/sync-connect-honesty.test.ts +0 -114
  409. package/apps/studio/test/sync-connection-state.test.ts +0 -337
  410. package/apps/studio/test/sync-ctl-channel.test.ts +0 -508
  411. package/apps/studio/test/sync-decide-file.test.ts +0 -420
  412. package/apps/studio/test/sync-doc-name.test.ts +0 -281
  413. package/apps/studio/test/sync-echo-guard.test.ts +0 -96
  414. package/apps/studio/test/sync-file-ledger.test.ts +0 -334
  415. package/apps/studio/test/sync-file-membership.test.ts +0 -347
  416. package/apps/studio/test/sync-file-plane.test.ts +0 -976
  417. package/apps/studio/test/sync-file-pull.test.ts +0 -333
  418. package/apps/studio/test/sync-fresh-link-parity.test.ts +0 -267
  419. package/apps/studio/test/sync-fs-mirror.test.ts +0 -181
  420. package/apps/studio/test/sync-hardening.test.ts +0 -586
  421. package/apps/studio/test/sync-hub-listing.test.ts +0 -46
  422. package/apps/studio/test/sync-hubs-config.test.ts +0 -133
  423. package/apps/studio/test/sync-incident-replay.test.ts +0 -211
  424. package/apps/studio/test/sync-journal.test.ts +0 -176
  425. package/apps/studio/test/sync-meta-codec.test.ts +0 -199
  426. package/apps/studio/test/sync-migrate-flat-fallback.test.ts +0 -98
  427. package/apps/studio/test/sync-move-retirement.test.ts +0 -231
  428. package/apps/studio/test/sync-panel-surface.test.ts +0 -153
  429. package/apps/studio/test/sync-path-pull.test.ts +0 -531
  430. package/apps/studio/test/sync-presentation.test.ts +0 -262
  431. package/apps/studio/test/sync-pull-budget.test.ts +0 -169
  432. package/apps/studio/test/sync-remote-docs.test.ts +0 -333
  433. package/apps/studio/test/sync-resync-routes.test.ts +0 -168
  434. package/apps/studio/test/sync-runtime.test.ts +0 -1675
  435. package/apps/studio/test/sync-seed-defers-to-hub.test.ts +0 -83
  436. package/apps/studio/test/sync-settings-routes.test.ts +0 -195
  437. package/apps/studio/test/sync-settings.test.ts +0 -151
  438. package/apps/studio/test/sync-status.test.ts +0 -261
  439. package/apps/studio/test/sync-supervisor.test.ts +0 -262
  440. package/apps/studio/test/sync-tombstone-apply.test.ts +0 -111
  441. package/apps/studio/test/sync-trash.test.ts +0 -132
  442. package/apps/studio/test/sync-two-peer-discovery.test.ts +0 -343
  443. package/apps/studio/test/sync-untrusted.test.ts +0 -103
  444. package/apps/studio/test/system-endpoint.test.ts +0 -144
  445. package/apps/studio/test/text-editability-stamp.test.ts +0 -131
  446. package/apps/studio/test/timeline-comp-target.test.ts +0 -139
  447. package/apps/studio/test/timeline-media-cache.test.ts +0 -41
  448. package/apps/studio/test/timeline-parse.test.ts +0 -184
  449. package/apps/studio/test/timeline-scale.test.ts +0 -65
  450. package/apps/studio/test/timeline-snap.test.ts +0 -85
  451. package/apps/studio/test/tool-palette-insert-anchor.test.ts +0 -86
  452. package/apps/studio/test/tool-palette.test.tsx +0 -55
  453. package/apps/studio/test/tour-overlay.test.tsx +0 -90
  454. package/apps/studio/test/trusted-request-host.test.ts +0 -66
  455. package/apps/studio/test/undo-sequence-byte-compare.test.ts +0 -211
  456. package/apps/studio/test/undo-stack.test.ts +0 -244
  457. package/apps/studio/test/use-agent-presence.test.tsx +0 -117
  458. package/apps/studio/test/use-annotation-resize.test.ts +0 -402
  459. package/apps/studio/test/use-annotation-selection.test.tsx +0 -77
  460. package/apps/studio/test/use-artboard-drag.test.ts +0 -324
  461. package/apps/studio/test/use-canvas-activity.test.tsx +0 -206
  462. package/apps/studio/test/use-collab.test.ts +0 -0
  463. package/apps/studio/test/use-cursor-modifiers.test.ts +0 -59
  464. package/apps/studio/test/use-keyboard-discipline.test.ts +0 -27
  465. package/apps/studio/test/use-selection-set.test.tsx +0 -166
  466. package/apps/studio/test/use-snap-guides.test.ts +0 -271
  467. package/apps/studio/test/use-tool-mode.test.tsx +0 -186
  468. package/apps/studio/test/use-undo-stack.test.tsx +0 -223
  469. package/apps/studio/test/video-asset.test.ts +0 -163
  470. package/apps/studio/test/video-comp-fixture.test.ts +0 -50
  471. package/apps/studio/test/video-comp.test.ts +0 -270
  472. package/apps/studio/test/video-encode-lib.test.ts +0 -63
  473. package/apps/studio/test/video-render-bridge.test.ts +0 -171
  474. package/apps/studio/test/whats-new.test.ts +0 -113
  475. package/apps/studio/test/whisper-setup.test.ts +0 -97
  476. package/apps/studio/test/workspace-containment.test.ts +0 -382
  477. package/apps/studio/test/workspace-signin.test.ts +0 -292
  478. package/apps/studio/test/ws-handshake.test.ts +0 -33
  479. package/cli/commands/cache.test.mjs +0 -166
  480. package/cli/commands/design-link.test.mjs +0 -419
  481. package/cli/commands/design.test.mjs +0 -240
  482. package/cli/commands/doctor.test.mjs +0 -237
  483. package/cli/commands/hub-workspace.test.mjs +0 -275
  484. package/cli/commands/hub.test.mjs +0 -225
  485. package/cli/commands/kg.test.mjs +0 -142
  486. package/cli/commands/preflight.test.mjs +0 -46
  487. package/cli/lib/argv.test.mjs +0 -46
  488. package/cli/lib/cache.test.mjs +0 -303
  489. package/cli/lib/cell-plan.test.mjs +0 -225
  490. package/cli/lib/config-lint.test.mjs +0 -117
  491. package/cli/lib/ddr-to-kgai.test.mjs +0 -118
  492. package/cli/lib/design-ownership.test.mjs +0 -329
  493. package/cli/lib/fetch-asset.test.mjs +0 -213
  494. package/cli/lib/figma-codegen-reachability.test.mjs +0 -104
  495. package/cli/lib/figma-import-controls.test.mjs +0 -70
  496. package/cli/lib/flow-design-integration.test.mjs +0 -165
  497. package/cli/lib/gitignore-block.test.mjs +0 -136
  498. package/cli/lib/gitignore-drift.test.mjs +0 -156
  499. package/cli/lib/hubs-config.test.mjs +0 -165
  500. package/cli/lib/pkg-root.test.mjs +0 -111
  501. package/cli/lib/plugin-cli-reachability.test.mjs +0 -57
  502. package/cli/lib/plugin-name-namespace.test.mjs +0 -71
  503. package/cli/lib/reconstruct-toolset.test.mjs +0 -194
  504. package/cli/lib/stack-detect.test.mjs +0 -121
  505. package/cli/lib/update-check.test.mjs +0 -32
  506. package/cli/lib/workspace-plan.test.mjs +0 -699
@@ -1,667 +0,0 @@
1
- // `maude design import-figma --board` — the write path (DDR-216 T6).
2
- //
3
- // The translation itself is covered by `figma/to-strokes.test.ts`. What this
4
- // file owns is the VERB's own contract:
5
- //
6
- // • the output goes through the canonical serializer + `sanitizeAnnotationSvg`
7
- // (D6's annotation row) and lands at a code-computed slug;
8
- // • assets/artifacts stage OUTSIDE the design root and are promoted only on
9
- // success, so a failure leaves nothing in a versioned, Syncthing-replicated
10
- // directory (D5 — "gitignored" is NOT "not replicated");
11
- // • the write is realpath-contained;
12
- // • the summary is enum + node ids ONLY, never node text, because this verb
13
- // is run BY an agent and everything it prints is model input (D10).
14
-
15
- import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test';
16
- import {
17
- existsSync,
18
- mkdirSync,
19
- mkdtempSync,
20
- readdirSync,
21
- readFileSync,
22
- rmSync,
23
- writeFileSync,
24
- } from 'node:fs';
25
- import { tmpdir } from 'node:os';
26
- import { join } from 'node:path';
27
-
28
- import {
29
- formatSummary,
30
- ImportFigmaError,
31
- importBoard,
32
- importFrames,
33
- importPages,
34
- importTokens,
35
- withSansFallback,
36
- } from '../bin/_import-figma.mjs';
37
-
38
- const TOKEN = 'figd_VERBCANARY_0123456789abcdef';
39
- const BOARD_URL = 'https://www.figma.com/board/Em6NOwaOFTYV7NlQT4NK8l/Analyza';
40
-
41
- let sandbox: string;
42
- let keysDir: string;
43
- let realFetch: typeof fetch;
44
-
45
- const box = (x: number, y: number, width: number, height: number) => ({ x, y, width, height });
46
-
47
- /** A board carrying a hostile layer name, a hidden node and an unmappable one. */
48
- const DOC = {
49
- document: {
50
- id: '0:0',
51
- name: 'Document',
52
- type: 'CANVAS',
53
- children: [
54
- {
55
- id: '1:8',
56
- name: 'Sticky',
57
- type: 'STICKY',
58
- absoluteBoundingBox: box(-3244, -6272, 240, 240),
59
- characters: 'Persona A',
60
- },
61
- {
62
- id: '2:17',
63
- name: 'Příliš žluťoučký — "test" / <b> & \'x\'',
64
- type: 'SHAPE_WITH_TEXT',
65
- shapeType: 'SQUARE',
66
- absoluteBoundingBox: box(0, 0, 200, 200),
67
- characters: 'SECRET_BODY_TEXT',
68
- },
69
- {
70
- id: '2:40',
71
- name: 'Parallelogram',
72
- type: 'SHAPE_WITH_TEXT',
73
- shapeType: 'PARALLELOGRAM_RIGHT',
74
- absoluteBoundingBox: box(400, 0, 200, 200),
75
- },
76
- {
77
- id: '9:1',
78
- name: 'hidden',
79
- type: 'STICKY',
80
- visible: false,
81
- characters: 'you cannot see me',
82
- absoluteBoundingBox: box(800, 0, 240, 240),
83
- },
84
- ],
85
- },
86
- };
87
-
88
- beforeEach(() => {
89
- sandbox = mkdtempSync(join(tmpdir(), 'maude-figma-verb-'));
90
- mkdirSync(join(sandbox, '.design'), { recursive: true });
91
- keysDir = mkdtempSync(join(tmpdir(), 'maude-figma-verb-keys-'));
92
- const keysPath = join(keysDir, 'keys.json');
93
- writeFileSync(keysPath, JSON.stringify({ keys: { figma: TOKEN } }), { mode: 0o600 });
94
- process.env.MAUDE_GEN_KEYS_PATH = keysPath;
95
- realFetch = globalThis.fetch;
96
- globalThis.fetch = mock(
97
- async () =>
98
- new Response(JSON.stringify(DOC), {
99
- status: 200,
100
- headers: { 'Content-Type': 'application/json' },
101
- })
102
- ) as unknown as typeof fetch;
103
- });
104
-
105
- afterEach(() => {
106
- globalThis.fetch = realFetch;
107
- rmSync(sandbox, { recursive: true, force: true });
108
- rmSync(keysDir, { recursive: true, force: true });
109
- delete process.env.MAUDE_GEN_KEYS_PATH;
110
- });
111
-
112
- describe('a rendered SVG never falls back to a SERIF (live-migration report)', () => {
113
- // Measured on the StudyFi cover page: Figma renders text as
114
- // `font-family="Inter"` and nothing else. An SVG behind `<img src>` renders
115
- // in an isolated document — the page CSS, its @font-face rules and the DS
116
- // webfonts do not reach inside — so an uninstalled family lands on the
117
- // browser default, which is a serif. A sans-serif product design arrived in
118
- // Times, and every count-based check called it a success.
119
- test('a bare family gains a generic sans fallback', () => {
120
- expect(withSansFallback('<text font-family="Inter">x</text>')).toContain(
121
- 'font-family="Inter, sans-serif"'
122
- );
123
- expect(withSansFallback('<text style="font-family: SF Pro Text">x</text>')).toContain(
124
- 'font-family:SF Pro Text, sans-serif'
125
- );
126
- });
127
-
128
- test('a stack that already ends in a generic is left alone', () => {
129
- for (const already of [
130
- '<text font-family="Inter, sans-serif">x</text>',
131
- '<text font-family="Georgia, serif">x</text>',
132
- '<text font-family="ui-monospace, monospace">x</text>',
133
- '<text font-family="system-ui">x</text>',
134
- ]) {
135
- expect(withSansFallback(already)).toBe(already);
136
- }
137
- });
138
-
139
- test('it is a FALLBACK, not a substitution — the requested family still wins', () => {
140
- const out = withSansFallback('<text font-family="Hanken Grotesk">x</text>');
141
- expect(out.indexOf('Hanken Grotesk')).toBeLessThan(out.indexOf('sans-serif'));
142
- });
143
-
144
- test('it rewrites nothing but font-family', () => {
145
- const svg = '<svg><rect fill="#fff"/><text font-size="12" font-family="Inter">hi</text></svg>';
146
- const out = withSansFallback(svg);
147
- expect(out).toContain('fill="#fff"');
148
- expect(out).toContain('font-size="12"');
149
- expect(out).toContain('>hi<');
150
- });
151
-
152
- test('an unterminated / oversized attribute cannot run away', () => {
153
- const huge = `<text font-family="${'A'.repeat(500)}">x</text>`;
154
- expect(withSansFallback(huge)).toBe(huge);
155
- });
156
- });
157
-
158
- describe('--pages contains a page failure instead of losing the rest of the file', () => {
159
- // Measured on the first live migration: a fault entering page 4 of 6 cost
160
- // pages 4, 5 and 6 — twice in a row — because the loop caught `too_large`,
161
- // empty pages and comment failures but NOT a page fetch failure. There is no
162
- // resume, so the retry re-fetched and re-rendered the three that had already
163
- // succeeded.
164
- const PAGES = {
165
- document: {
166
- id: '0:0',
167
- type: 'DOCUMENT',
168
- children: [
169
- { id: '1:1', name: 'One', type: 'CANVAS' },
170
- { id: '2:2', name: 'Two', type: 'CANVAS' },
171
- { id: '3:3', name: 'Three', type: 'CANVAS' },
172
- ],
173
- },
174
- };
175
- const PAGE_BODY = (id: string) => ({
176
- nodes: {
177
- [id]: {
178
- document: {
179
- id,
180
- name: 'P',
181
- type: 'CANVAS',
182
- children: [
183
- {
184
- id: `${id}0`,
185
- name: 'Frame',
186
- type: 'FRAME',
187
- visible: true,
188
- absoluteBoundingBox: box(0, 0, 100, 100),
189
- },
190
- ],
191
- },
192
- },
193
- },
194
- });
195
-
196
- /** Page two's document fetch drops the connection; one and three are fine. */
197
- function stubWithPageTwoDown() {
198
- globalThis.fetch = mock(async (input: unknown) => {
199
- const url = String(input);
200
- if (url.includes('/images'))
201
- return new Response(JSON.stringify({ images: {} }), { status: 200 });
202
- if (url.includes('/comments'))
203
- return new Response(JSON.stringify({ comments: [] }), { status: 200 });
204
- if (
205
- url.includes('depth=1') ||
206
- /\/files\/[^/]+\?/.test(url) ||
207
- url.endsWith('/files/2H6a9YUgPAu0AGdEiwP895')
208
- ) {
209
- return new Response(JSON.stringify(PAGES), { status: 200 });
210
- }
211
- const m = /ids=([^&]+)/.exec(url);
212
- const id = m ? decodeURIComponent(m[1]) : '';
213
- if (id.startsWith('2:2')) throw new TypeError('fetch failed');
214
- if (id) return new Response(JSON.stringify(PAGE_BODY(id)), { status: 200 });
215
- return new Response(JSON.stringify(PAGES), { status: 200 });
216
- }) as unknown as typeof fetch;
217
- }
218
-
219
- test('the pages either side of the fault still import, and the fault is REPORTED', async () => {
220
- stubWithPageTwoDown();
221
- const r = await importPages({
222
- url: 'https://www.figma.com/design/2H6a9YUgPAu0AGdEiwP895/x',
223
- root: sandbox,
224
- folder: 'f',
225
- });
226
- // Page three is the one the old behaviour lost: it comes AFTER the fault.
227
- expect(r.written.length).toBeGreaterThanOrEqual(2);
228
- const failed = r.skipped.find((x: { page?: string }) => x.page === '2:2');
229
- expect(failed).toBeDefined();
230
- // Reported by node id + a code-owned reason, never document text (D10).
231
- expect(String(failed?.why)).toMatch(/^(network|bad_response|failed \(\w+\))$/);
232
- });
233
-
234
- test('a MISCONFIGURED request stays fatal — a partial folder must not look complete', async () => {
235
- stubWithPageTwoDown();
236
- await expect(
237
- importPages({
238
- url: 'https://www.figma.com/design/2H6a9YUgPAu0AGdEiwP895/x',
239
- root: sandbox,
240
- folder: 'NOT A SLUG',
241
- })
242
- ).rejects.toBeInstanceOf(ImportFigmaError);
243
- });
244
- });
245
-
246
- describe('--board writes a sanitized annotation layer', () => {
247
- test('lands at a code-computed slug under the design root', async () => {
248
- const result = await importBoard({ url: BOARD_URL, root: sandbox });
249
- // The slug is the HOST CANVAS's slug — an annotation layer named anything
250
- // else has nothing to render it (found on the first live board import).
251
- expect(result.slug).toMatch(/^ui-[a-z0-9_]+$/);
252
- expect(result.path?.startsWith(join(sandbox, '.design'))).toBe(true);
253
- expect(existsSync(result.path as string)).toBe(true);
254
- });
255
-
256
- test('the slug is NEVER derived from a Figma string', async () => {
257
- const result = await importBoard({ url: BOARD_URL, root: sandbox });
258
- expect(result.slug).not.toContain('žlu');
259
- expect(result.slug).not.toContain('<');
260
- expect(result.slug).toMatch(/^[a-z0-9_-]{1,64}$/);
261
- });
262
-
263
- test('the written SVG is sanitizer-clean — no markup from the hostile name', async () => {
264
- const result = await importBoard({ url: BOARD_URL, root: sandbox });
265
- const svg = readFileSync(result.path as string, 'utf8');
266
- expect(svg.startsWith('<svg')).toBe(true);
267
- expect(svg).not.toContain('<b>');
268
- expect(svg).not.toContain('<script');
269
- expect(svg).not.toMatch(/\son[a-z]+=/i);
270
- });
271
-
272
- test('a hidden node is not written', async () => {
273
- const result = await importBoard({ url: BOARD_URL, root: sandbox });
274
- const svg = readFileSync(result.path as string, 'utf8');
275
- expect(svg).not.toContain('you cannot see me');
276
- });
277
-
278
- // A FigJam board is not a screen, and framing it as one was wrong twice: the
279
- // artboard drew screen chrome around whiteboard content, and it took the DS
280
- // surface colour — which paints a white board near-black on a dark-default
281
- // design system. Reported from a live migration into `studyfi-design`.
282
- test('the host canvas has NO artboard — the board is not a screen', async () => {
283
- const result = await importBoard({ url: BOARD_URL, root: sandbox });
284
- const tsx = readFileSync(join(sandbox, '.design', result.canvas as string), 'utf8');
285
- expect(tsx).not.toContain('<DCArtboard');
286
- expect(tsx).not.toContain('DCArtboard,');
287
- expect(tsx).toContain('<DesignCanvas />');
288
- });
289
-
290
- test('the board gets an OPAQUE light paper — a section is only a 6% tint', async () => {
291
- // `annotations-model.ts` paints a section at a hardcoded fill-opacity of
292
- // 0.06, so it cannot be the ground: white-at-6% over a dark-default DS is
293
- // still dark. Measured on the first migration into `studyfi-design`.
294
- const result = await importBoard({ url: BOARD_URL, root: sandbox });
295
- const svg = readFileSync(result.path as string, 'utf8');
296
- expect(svg).toContain('data-id="figma-board-paper"');
297
- expect(svg.toLowerCase()).toContain('fill="#ffffff"');
298
- });
299
-
300
- test('the region is a labelled SECTION, on the annotation layer', async () => {
301
- const result = await importBoard({ url: BOARD_URL, root: sandbox });
302
- const svg = readFileSync(result.path as string, 'utf8');
303
- expect(svg).toContain('data-tool="section"');
304
- expect(svg).toContain('data-id="figma-board-region"');
305
- });
306
-
307
- test('paint order is paper -> region -> content; either one later would veil the board', async () => {
308
- const result = await importBoard({ url: BOARD_URL, root: sandbox });
309
- const svg = readFileSync(result.path as string, 'utf8');
310
- const paper = svg.indexOf('data-id="figma-board-paper"');
311
- const region = svg.indexOf('data-id="figma-board-region"');
312
- const firstContent = svg.search(/data-tool="(sticky|image|text|ellipse|arrow)"/);
313
- expect(paper).toBeGreaterThan(-1);
314
- expect(paper).toBeLessThan(region);
315
- if (firstContent > -1) expect(region).toBeLessThan(firstContent);
316
- });
317
-
318
- test('an explicit --slug is honoured when it is a valid slug', async () => {
319
- const result = await importBoard({ url: BOARD_URL, root: sandbox, slug: 'retro-q3' });
320
- // `--slug` names the CANVAS; the annotation layer follows from it.
321
- expect(result.canvas).toBe('ui/Retro Q3.tsx');
322
- expect(result.slug).toBe('ui-retro_q3');
323
- expect(result.path?.endsWith('ui-retro_q3.annotations.svg')).toBe(true);
324
- });
325
-
326
- test.each([
327
- ['traversal', '../../etc/passwd'],
328
- ['absolute', '/etc/passwd'],
329
- ['uppercase + spaces', 'My Board'],
330
- ['too long', 'a'.repeat(65)],
331
- ])('a hostile --slug is rejected (%s), nothing is written', async (_label, slug) => {
332
- await expect(importBoard({ url: BOARD_URL, root: sandbox, slug })).rejects.toBeInstanceOf(
333
- ImportFigmaError
334
- );
335
- expect(readdirSync(join(sandbox, '.design'))).toEqual([]);
336
- });
337
- });
338
-
339
- describe('staging is outside the design root, and leaves nothing behind', () => {
340
- test('a successful run leaves the layer + its host canvas, and no staging residue', async () => {
341
- const result = await importBoard({ url: BOARD_URL, root: sandbox });
342
- const entries = readdirSync(join(sandbox, '.design'));
343
- expect(entries.sort()).toEqual(['ui', `${result.slug}.annotations.svg`]);
344
- // The host canvas — without it the strokes are on disk and invisible.
345
- expect(readdirSync(join(sandbox, '.design', 'ui')).sort()).toEqual([
346
- 'Figjam Em6nowao.meta.json',
347
- 'Figjam Em6nowao.tsx',
348
- ]);
349
- // No `.tmp-*` / staging directory anywhere under the design root.
350
- expect(entries.some((e) => e.startsWith('.tmp') || e.startsWith('maude-figma-'))).toBe(false);
351
- });
352
-
353
- test('a dry run writes NOTHING but still reports', async () => {
354
- const result = await importBoard({ url: BOARD_URL, root: sandbox, dryRun: true });
355
- expect(result.path).toBeUndefined();
356
- expect(result.strokeCount).toBeGreaterThan(0);
357
- expect(readdirSync(join(sandbox, '.design'))).toEqual([]);
358
- });
359
- });
360
-
361
- describe('the summary is model input — enum + node ids only (D10)', () => {
362
- test('names and body text never appear in the summary', async () => {
363
- const result = await importBoard({ url: BOARD_URL, root: sandbox, dryRun: true });
364
- const summary = formatSummary(result.report);
365
- expect(summary).not.toContain('žluťoučký');
366
- expect(summary).not.toContain('SECRET_BODY_TEXT');
367
- expect(summary).not.toContain('you cannot see me');
368
- expect(summary).not.toContain('Persona A');
369
- });
370
-
371
- test('every non-clean disposition is named by node id and a fixed reason', async () => {
372
- const result = await importBoard({ url: BOARD_URL, root: sandbox, dryRun: true });
373
- const summary = formatSummary(result.report);
374
- // The unmappable shape is reported, not silently dropped.
375
- expect(summary).toContain('2:40');
376
- expect(summary).toContain('unmappable-shape');
377
- // The hidden node is reported too.
378
- expect(summary).toContain('9:1');
379
- expect(summary).toContain('hidden-node-skipped');
380
- });
381
-
382
- test('the origin shift is reported so a huge negative-space board is explicable', async () => {
383
- const result = await importBoard({ url: BOARD_URL, root: sandbox, dryRun: true });
384
- expect(result.origin).toEqual({ x: -3244, y: -6272 });
385
- });
386
- });
387
-
388
- // ── --frames (Phase 3) ──────────────────────────────────────────────────────
389
-
390
- const DESIGN_DOC = {
391
- document: {
392
- id: '0:1',
393
- name: 'Page 1',
394
- type: 'CANVAS',
395
- children: [
396
- {
397
- id: '1:2',
398
- name: 'AL Horizontal (-> flex row)',
399
- type: 'FRAME',
400
- layoutMode: 'HORIZONTAL',
401
- itemSpacing: 16,
402
- absoluteBoundingBox: box(0, 0, 400, 100),
403
- children: [
404
- {
405
- id: '1:3',
406
- name: 'Chip One',
407
- type: 'RECTANGLE',
408
- absoluteBoundingBox: box(0, 0, 80, 40),
409
- },
410
- ],
411
- },
412
- {
413
- id: '2:23',
414
- name: 'Karta — "uvozovky" / <script> & {curly}',
415
- type: 'FRAME',
416
- absoluteBoundingBox: box(500, 0, 400, 300),
417
- children: [
418
- {
419
- id: '2:20',
420
- name: 'H',
421
- type: 'TEXT',
422
- characters: 'Nadpis',
423
- style: { fontSize: 32 },
424
- absoluteBoundingBox: box(520, 20, 200, 40),
425
- },
426
- ],
427
- },
428
- // A loose non-frame node at page level — must not become a canvas.
429
- { id: '3:1', name: 'stray', type: 'RECTANGLE', absoluteBoundingBox: box(0, 400, 10, 10) },
430
- ],
431
- },
432
- };
433
-
434
- describe('--frames writes one canvas per top-level frame', () => {
435
- function stubDesign() {
436
- globalThis.fetch = mock(
437
- async () =>
438
- new Response(JSON.stringify(DESIGN_DOC), {
439
- status: 200,
440
- headers: { 'Content-Type': 'application/json' },
441
- })
442
- ) as unknown as typeof fetch;
443
- }
444
-
445
- const URL_D = 'https://www.figma.com/design/dGNzRC2kmrmGnOxaBa0RI7/Fixtures';
446
-
447
- test('emits a .tsx + .meta.json pair per frame, and skips loose nodes', async () => {
448
- stubDesign();
449
- const r = await importFrames({ url: URL_D, root: sandbox });
450
- expect(r.frameCount).toBe(2);
451
- const files = readdirSync(join(sandbox, '.design', 'ui')).sort();
452
- expect(files.length).toBe(4);
453
- expect(files.filter((f) => f.endsWith('.tsx')).length).toBe(2);
454
- expect(files.filter((f) => f.endsWith('.meta.json')).length).toBe(2);
455
- });
456
-
457
- test('the meta stamps kind + a real importedAt', async () => {
458
- stubDesign();
459
- const r = await importFrames({ url: URL_D, root: sandbox });
460
- const metaPath = (r.written[0].path as string).replace(/\.tsx$/, '.meta.json');
461
- const meta = JSON.parse(readFileSync(metaPath, 'utf8'));
462
- expect(meta.kind).toBe('imported-figma');
463
- expect(meta.source.nodeId).toMatch(/^[0-9]+:[0-9]+$/);
464
- expect(Number.isNaN(Date.parse(meta.source.importedAt))).toBe(false);
465
- });
466
-
467
- test('a hostile frame name never reaches a FILENAME', async () => {
468
- stubDesign();
469
- await importFrames({ url: URL_D, root: sandbox });
470
- for (const f of readdirSync(join(sandbox, '.design', 'ui'))) {
471
- expect(f).toMatch(/^[a-z0-9-]+\.(tsx|meta\.json)$/);
472
- }
473
- });
474
-
475
- test('a dry run writes nothing but reports metrics', async () => {
476
- stubDesign();
477
- const r = await importFrames({ url: URL_D, root: sandbox, dryRun: true });
478
- expect(r.written[0].metrics.bytes).toBeGreaterThan(0);
479
- expect(existsSync(join(sandbox, '.design', 'ui'))).toBe(false);
480
- });
481
-
482
- test('a document with no frame refuses rather than writing an empty canvas', async () => {
483
- globalThis.fetch = mock(
484
- async () =>
485
- new Response(
486
- JSON.stringify({ document: { id: '0:1', name: 'P', type: 'CANVAS', children: [] } }),
487
- { status: 200, headers: { 'Content-Type': 'application/json' } }
488
- )
489
- ) as unknown as typeof fetch;
490
- await expect(importFrames({ url: URL_D, root: sandbox })).rejects.toBeInstanceOf(
491
- ImportFigmaError
492
- );
493
- });
494
- });
495
-
496
- // ── --tokens (Phase 4) ──────────────────────────────────────────────────────
497
-
498
- describe('--tokens degrades from Variables to styles without surfacing an error', () => {
499
- const URL_D = 'https://www.figma.com/design/dGNzRC2kmrmGnOxaBa0RI7/Fixtures';
500
-
501
- test('a 403 on the Enterprise-gated Variables endpoint falls back to styles', async () => {
502
- // This is the COMMON case, not an error state — the dogfood account is Pro.
503
- let call = 0;
504
- globalThis.fetch = mock(async (input: RequestInfo | URL) => {
505
- call += 1;
506
- const url = String(input);
507
- if (url.includes('/variables/local')) {
508
- return new Response(JSON.stringify({ err: 'plan' }), { status: 403 });
509
- }
510
- if (url.includes('/styles')) {
511
- return new Response(
512
- JSON.stringify({
513
- meta: {
514
- styles: [{ key: 'k', name: 'Brand/Primary', style_type: 'FILL', node_id: '1:1' }],
515
- },
516
- }),
517
- { status: 200, headers: { 'Content-Type': 'application/json' } }
518
- );
519
- }
520
- return new Response(
521
- JSON.stringify({
522
- nodes: {
523
- '1:1': {
524
- document: {
525
- id: '1:1',
526
- name: 'swatch',
527
- type: 'RECTANGLE',
528
- fills: [{ type: 'SOLID', visible: true, color: { r: 1, g: 0, b: 0, a: 1 } }],
529
- },
530
- },
531
- },
532
- }),
533
- { status: 200, headers: { 'Content-Type': 'application/json' } }
534
- );
535
- }) as unknown as typeof fetch;
536
-
537
- const r = await importTokens({ url: URL_D, root: sandbox, dryRun: true });
538
- expect(r.source).toBe('styles');
539
- expect(r.count).toBe(1);
540
- expect(r.tokens).toEqual({ brand: { primary: { $type: 'color', $value: '#ff0000' } } });
541
- expect(call).toBeGreaterThan(1);
542
- });
543
-
544
- test('the emitted file is plain JSON, staged outside then promoted', async () => {
545
- globalThis.fetch = mock(async (input: RequestInfo | URL) => {
546
- const url = String(input);
547
- if (url.includes('/variables/local')) return new Response('{}', { status: 403 });
548
- if (url.includes('/styles')) {
549
- return new Response(JSON.stringify({ meta: { styles: [] } }), {
550
- status: 200,
551
- headers: { 'Content-Type': 'application/json' },
552
- });
553
- }
554
- return new Response('{}', { status: 200 });
555
- }) as unknown as typeof fetch;
556
- const r = await importTokens({ url: URL_D, root: sandbox });
557
- expect(r.path).toContain('_history');
558
- expect(() => JSON.parse(readFileSync(r.path as string, 'utf8'))).not.toThrow();
559
- });
560
- });
561
-
562
- describe('the asset pipeline is actually WIRED into --frames', () => {
563
- const URL_D = 'https://www.figma.com/design/dGNzRC2kmrmGnOxaBa0RI7/Fixtures';
564
-
565
- const VECTOR_DOC = {
566
- document: {
567
- id: '0:1',
568
- name: 'Page 1',
569
- type: 'CANVAS',
570
- children: [
571
- {
572
- id: '1:1',
573
- name: 'Frame with a mark',
574
- type: 'FRAME',
575
- absoluteBoundingBox: box(0, 0, 400, 300),
576
- children: [
577
- {
578
- id: '2:1',
579
- name: 'Mark wrapper',
580
- type: 'GROUP',
581
- absoluteBoundingBox: box(0, 0, 90, 72),
582
- children: [
583
- { id: '2:2', name: 'v', type: 'VECTOR', absoluteBoundingBox: box(0, 0, 20, 20) },
584
- { id: '2:3', name: 'v', type: 'VECTOR', absoluteBoundingBox: box(20, 0, 20, 20) },
585
- ],
586
- },
587
- ],
588
- },
589
- ],
590
- },
591
- };
592
-
593
- /** Answer the document, and decline to render anything. */
594
- function stubDeclineRender() {
595
- globalThis.fetch = mock(async (input: RequestInfo | URL) => {
596
- const url = String(input);
597
- if (url.includes('/v1/images/')) {
598
- return new Response(JSON.stringify({ images: { '2:1': null } }), {
599
- status: 200,
600
- headers: { 'Content-Type': 'application/json' },
601
- });
602
- }
603
- return new Response(JSON.stringify(VECTOR_DOC), {
604
- status: 200,
605
- headers: { 'Content-Type': 'application/json' },
606
- });
607
- }) as unknown as typeof fetch;
608
- }
609
-
610
- test('a collapsed cluster reaches the image endpoint as ONE batched request', async () => {
611
- const seen: string[] = [];
612
- globalThis.fetch = mock(async (input: RequestInfo | URL) => {
613
- const url = String(input);
614
- seen.push(url);
615
- if (url.includes('/v1/images/')) {
616
- return new Response(JSON.stringify({ images: { '2:1': null } }), {
617
- status: 200,
618
- headers: { 'Content-Type': 'application/json' },
619
- });
620
- }
621
- return new Response(JSON.stringify(VECTOR_DOC), {
622
- status: 200,
623
- headers: { 'Content-Type': 'application/json' },
624
- });
625
- }) as unknown as typeof fetch;
626
-
627
- const r = await importFrames({ url: URL_D, root: sandbox });
628
- const imageCalls = seen.filter((u) => u.includes('/v1/images/'));
629
- const byFormat = (f: string) =>
630
- imageCalls.filter((u) => new URL(u).searchParams.get('format') === f);
631
-
632
- // ONE call for the whole cluster — the collapse is what keeps this at 1
633
- // instead of 2 (or, on a real logo, 14). Asserted PER FORMAT, because this
634
- // stub returns `{ '2:1': null }`, i.e. Figma DECLINING to render, which the
635
- // later SVG→PNG degradation path answers with a second, legitimate request.
636
- // The original `imageCalls.length === 1` conflated "the collapse batched the
637
- // cluster" with "nothing retried", and went red in main when the degradation
638
- // landed — the same incomplete change that left `asset-degraded` outside the
639
- // Disposition union (DDR-219 D9). The collapse claim is what this test owns.
640
- expect(byFormat('svg')).toHaveLength(1);
641
- expect(new URL(byFormat('svg')[0]).searchParams.get('ids')).toBe('2:1');
642
- // The degradation is intended behaviour, so it is asserted rather than
643
- // tolerated: one retry, for the same collapsed id, never one per leaf.
644
- expect(byFormat('png')).toHaveLength(1);
645
- expect(new URL(byFormat('png')[0]).searchParams.get('ids')).toBe('2:1');
646
- expect(r.pendingExports).toBe(1);
647
- });
648
-
649
- test('an unresolved asset is REPORTED and its placeholder survives in the source', async () => {
650
- stubDeclineRender();
651
- const r = await importFrames({ url: URL_D, root: sandbox });
652
- expect(r.resolvedAssets).toBe(0);
653
- const tsx = readFileSync(r.written[0].path as string, 'utf8');
654
- // A visibly broken image beats a silently missing element.
655
- expect(tsx).toContain('/assets/pending-');
656
- const dispositions = r.reports.flatMap((rep) => rep.entries.map((e) => e.disposition));
657
- expect(dispositions).toContain('asset-skipped');
658
- });
659
-
660
- test('the emitted canvas NEVER references a figma.com URL, resolved or not', async () => {
661
- stubDeclineRender();
662
- const r = await importFrames({ url: URL_D, root: sandbox });
663
- const tsx = readFileSync(r.written[0].path as string, 'utf8');
664
- expect(tsx).not.toContain('figma.com');
665
- expect(tsx).not.toMatch(/src="https?:/);
666
- });
667
- });