@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
@@ -1,350 +0,0 @@
1
- // Cloud Phase 27 D2 — one writer at a time on the workspace checkout.
2
- //
3
- // The lock is the thing standing between "two processes over one working tree"
4
- // and the preserved dissent's 3 a.m. event, so the properties asserted here are
5
- // the ones that make it a lock rather than a suggestion: mutual exclusion,
6
- // release on throw, and a stale holder that unwedges itself without a human.
7
-
8
- import { describe, expect, test } from 'bun:test';
9
- import {
10
- mkdirSync,
11
- mkdtempSync,
12
- readdirSync,
13
- readFileSync,
14
- rmSync,
15
- utimesSync,
16
- writeFileSync,
17
- } from 'node:fs';
18
- import { tmpdir } from 'node:os';
19
- import { join } from 'node:path';
20
-
21
- import { HEARTBEAT_MS, REPO_LOCK_FILE, STALE_LOCK_MS, withRepoLock } from '../git/repo-lock.ts';
22
-
23
- function makeRepo({ git = true } = {}): string {
24
- const root = mkdtempSync(join(tmpdir(), 'maude-repo-lock-'));
25
- if (git) mkdirSync(join(root, '.git'), { recursive: true });
26
- return root;
27
- }
28
-
29
- const lockFile = (root: string) => join(root, '.git', REPO_LOCK_FILE);
30
- const sleep = (ms: number) => new Promise<void>((r) => setTimeout(r, ms));
31
-
32
- describe('repo lock', () => {
33
- test('two holders never overlap — the loser waits rather than failing', async () => {
34
- const root = makeRepo();
35
- try {
36
- const order: string[] = [];
37
- let concurrent = 0;
38
- let maxConcurrent = 0;
39
-
40
- const contend = (name: string) =>
41
- withRepoLock(root, name, async () => {
42
- concurrent += 1;
43
- maxConcurrent = Math.max(maxConcurrent, concurrent);
44
- order.push(`${name}:in`);
45
- await sleep(30);
46
- order.push(`${name}:out`);
47
- concurrent -= 1;
48
- });
49
-
50
- await Promise.all([contend('hub:autocommit'), contend('studio:checkout')]);
51
-
52
- // The point of the lock, stated twice: never two inside, and never an
53
- // interleaved in/in/out/out — the second entry follows the first exit.
54
- expect(maxConcurrent).toBe(1);
55
- expect(order[1]).toBe(order[0].replace(':in', ':out'));
56
- } finally {
57
- rmSync(root, { recursive: true, force: true });
58
- }
59
- });
60
-
61
- test('the lock is released when the operation throws', async () => {
62
- const root = makeRepo();
63
- try {
64
- await expect(
65
- withRepoLock(root, 'studio:commit', async () => {
66
- throw new Error('git exploded');
67
- })
68
- ).rejects.toThrow('git exploded');
69
-
70
- // Not merely "the file is gone" — the next caller gets in, which is the
71
- // property that matters. A lock leaked by a failed commit would stop
72
- // every autosave after it.
73
- let ran = false;
74
- await withRepoLock(root, 'hub:autocommit', async () => {
75
- ran = true;
76
- });
77
- expect(ran).toBe(true);
78
- expect(() => readFileSync(lockFile(root))).toThrow();
79
- } finally {
80
- rmSync(root, { recursive: true, force: true });
81
- }
82
- });
83
-
84
- test('a repo with no .git runs unlocked instead of creating one', async () => {
85
- const root = makeRepo({ git: false });
86
- try {
87
- let ran = false;
88
- await withRepoLock(root, 'hub:seed', async () => {
89
- ran = true;
90
- });
91
- expect(ran).toBe(true);
92
- // It must not have become a repo initializer on the way past.
93
- expect(() => readFileSync(join(root, '.git', REPO_LOCK_FILE))).toThrow();
94
- } finally {
95
- rmSync(root, { recursive: true, force: true });
96
- }
97
- });
98
-
99
- test('a stale lock is stolen — a cell that crashed mid-commit unwedges itself', async () => {
100
- const root = makeRepo();
101
- try {
102
- // A holder that still EXISTS as a process (this one) so the steal can only
103
- // be attributed to the age, not to the liveness check.
104
- writeFileSync(
105
- lockFile(root),
106
- JSON.stringify({ pid: process.pid, holder: 'hub:autocommit', at: 0, token: 'old' })
107
- );
108
-
109
- let ran = false;
110
- const warned: string[] = [];
111
- await withRepoLock(
112
- root,
113
- 'studio:commit',
114
- async () => {
115
- ran = true;
116
- },
117
- {
118
- waitMs: 1000,
119
- // Age it past the window without waiting for it.
120
- now: () => Date.now() + STALE_LOCK_MS + 5_000,
121
- log: { warn: (m: string) => warned.push(m), log() {} },
122
- }
123
- );
124
- expect(ran).toBe(true);
125
- expect(warned.join(' ')).toContain('hub:autocommit');
126
- } finally {
127
- rmSync(root, { recursive: true, force: true });
128
- }
129
- });
130
-
131
- test('a lock whose process is gone is stolen without waiting out the window', async () => {
132
- const root = makeRepo();
133
- try {
134
- // pid 1 is tini in a cell and always alive, so a pid that cannot exist is
135
- // the honest fixture: 2^22 is above every default pid_max.
136
- writeFileSync(
137
- lockFile(root),
138
- JSON.stringify({ pid: 4194304, holder: 'studio:checkout', at: Date.now(), token: 'dead' })
139
- );
140
-
141
- let ran = false;
142
- await withRepoLock(
143
- root,
144
- 'hub:autocommit',
145
- async () => {
146
- ran = true;
147
- },
148
- { waitMs: 500, log: { warn() {}, log() {} } }
149
- );
150
- expect(ran).toBe(true);
151
- } finally {
152
- rmSync(root, { recursive: true, force: true });
153
- }
154
- });
155
-
156
- test('giving up names the holder rather than hanging', async () => {
157
- const root = makeRepo();
158
- try {
159
- writeFileSync(
160
- lockFile(root),
161
- JSON.stringify({ pid: process.pid, holder: 'studio:fold', at: Date.now(), token: 'live' })
162
- );
163
- await expect(
164
- withRepoLock(root, 'hub:autocommit', async () => undefined, { waitMs: 120 })
165
- ).rejects.toThrow(/studio:fold/);
166
- } finally {
167
- rmSync(root, { recursive: true, force: true });
168
- }
169
- });
170
-
171
- test('releasing never unlinks a lock that was stolen from us', async () => {
172
- // The one way a lock can be worse than no lock: a slow holder finishes,
173
- // unlinks, and pulls the lock out from under whoever legitimately took it
174
- // after the steal.
175
- const root = makeRepo();
176
- try {
177
- const finished = withRepoLock(root, 'hub:autocommit', async () => {
178
- // Simulate the steal: somebody reaped us and took it.
179
- writeFileSync(
180
- lockFile(root),
181
- JSON.stringify({
182
- pid: process.pid,
183
- holder: 'studio:checkout',
184
- at: Date.now(),
185
- token: 'theirs',
186
- })
187
- );
188
- });
189
- await finished;
190
- const after = JSON.parse(readFileSync(lockFile(root), 'utf8'));
191
- expect(after.holder).toBe('studio:checkout');
192
- } finally {
193
- rmSync(root, { recursive: true, force: true });
194
- }
195
- });
196
- test("a lost steal race does not unlink the winner's fresh lock", async () => {
197
- // THE MUTUAL-EXCLUSION FAILURE THIS LOCK EXISTS TO PREVENT, REINTRODUCED BY
198
- // ITS OWN RECOVERY PATH. Two waiters both see holder A as stale; the first
199
- // unlinks and acquires; the second — still acting on its read of A —
200
- // unlinks the FIRST's fresh lock and acquires too. Both then run their
201
- // critical section over one git index.
202
- //
203
- // The fixture is aged through its MTIME rather than through a fake clock,
204
- // and that distinction is the test: a fake `now` makes every lock look
205
- // ancient, including the winner's brand-new one, which is not a state the
206
- // real world can produce and would assert something nobody needs.
207
- const root = makeRepo();
208
- try {
209
- writeFileSync(
210
- lockFile(root),
211
- JSON.stringify({ pid: process.pid, holder: 'hub:autocommit', at: 0, token: 'stale' })
212
- );
213
- const long_ago = new Date(Date.now() - STALE_LOCK_MS * 4);
214
- utimesSync(lockFile(root), long_ago, long_ago);
215
-
216
- let concurrent = 0;
217
- let maxConcurrent = 0;
218
- const contend = (name: string) =>
219
- withRepoLock(
220
- root,
221
- name,
222
- async () => {
223
- concurrent += 1;
224
- maxConcurrent = Math.max(maxConcurrent, concurrent);
225
- await sleep(60);
226
- concurrent -= 1;
227
- },
228
- { waitMs: 5000, log: { warn() {}, log() {} } }
229
- );
230
-
231
- await Promise.all([contend('one'), contend('two'), contend('three')]);
232
- expect(maxConcurrent).toBe(1);
233
- } finally {
234
- rmSync(root, { recursive: true, force: true });
235
- }
236
- }, 20_000);
237
-
238
- test('a long-but-live holder is not stolen from — the heartbeat says it is alive', async () => {
239
- // `pull`, `fold` and `resolve` came under this lock in D2, and they do
240
- // NETWORK I/O. Without a heartbeat the staleness test cannot tell "held for
241
- // 30 s" from "the holder died 30 s ago", so the next waiter steals the lock
242
- // out from under a running merge.
243
- const root = makeRepo();
244
- try {
245
- const order: string[] = [];
246
- const slow = withRepoLock(root, 'studio:pull', async () => {
247
- order.push('slow:in');
248
- await sleep(HEARTBEAT_MS + 400);
249
- order.push('slow:out');
250
- });
251
- await sleep(50);
252
- // A waiter whose clock is PAST the staleness window. The heartbeat has
253
- // refreshed the mtime, so the lock is not stale by age and the holder is
254
- // alive — it must wait, not steal.
255
- const waiter = withRepoLock(
256
- root,
257
- 'hub:autocommit',
258
- async () => {
259
- order.push('waiter:in');
260
- },
261
- { waitMs: 20_000, log: { warn() {}, log() {} } }
262
- );
263
- await Promise.all([slow, waiter]);
264
- expect(order).toEqual(['slow:in', 'slow:out', 'waiter:in']);
265
- } finally {
266
- rmSync(root, { recursive: true, force: true });
267
- }
268
- }, 30_000);
269
-
270
- test('a hostile holder label cannot forge a log line', async () => {
271
- const root = makeRepo();
272
- try {
273
- writeFileSync(
274
- lockFile(root),
275
- JSON.stringify({
276
- pid: process.pid,
277
- holder: 'evil\n[repo-lock] PASS: nothing to see here',
278
- at: 0,
279
- token: 'x',
280
- })
281
- );
282
- const long_ago = new Date(Date.now() - STALE_LOCK_MS * 4);
283
- utimesSync(lockFile(root), long_ago, long_ago);
284
- const warned: string[] = [];
285
- await withRepoLock(root, 'hub:autocommit', async () => undefined, {
286
- waitMs: 2000,
287
- log: { warn: (m: string) => warned.push(m), log() {} },
288
- });
289
- expect(warned.join(' ')).not.toContain('\n');
290
- expect(warned.join(' ')).not.toContain('nothing to see here');
291
- } finally {
292
- rmSync(root, { recursive: true, force: true });
293
- }
294
- });
295
- test('exactly ONE waiter can steal a dead lock — the steal is a compare-and-swap', async () => {
296
- // The `unlink` this replaced could not say no: two waiters both judged the
297
- // holder stale, the first unlinked and acquired, and the second unlinked
298
- // the FIRST's fresh lock. `rename` fails with ENOENT when the source is
299
- // already gone, so only one waiter moves the file aside and the rest go
300
- // back around the loop.
301
- //
302
- // WHAT THIS TEST DOES AND DOES NOT PROVE, because the difference matters.
303
- // It asserts the OBSERVABLE contract — one steal, no leftover sidecar,
304
- // never two holders. It does NOT reproduce the interleaving that made the
305
- // unlink unsafe: that race is between the hub PROCESS and the studio
306
- // PROCESS, and inside one bun test the whole read → steal → acquire
307
- // sequence runs synchronously, so a single-process harness reaches the same
308
- // verdict with either implementation (measured, not assumed). The
309
- // correctness argument for `rename` is its atomicity — exactly one caller
310
- // can move a given file — and this test guards the contract around it.
311
- const root = makeRepo();
312
- try {
313
- writeFileSync(
314
- lockFile(root),
315
- JSON.stringify({ pid: 4194304, holder: 'hub:autocommit', at: 0, token: 'dead' })
316
- );
317
- const long_ago = new Date(Date.now() - STALE_LOCK_MS * 4);
318
- utimesSync(lockFile(root), long_ago, long_ago);
319
-
320
- const steals: string[] = [];
321
- let concurrent = 0;
322
- let maxConcurrent = 0;
323
- const contend = (name: string) =>
324
- withRepoLock(
325
- root,
326
- name,
327
- async () => {
328
- concurrent += 1;
329
- maxConcurrent = Math.max(maxConcurrent, concurrent);
330
- await sleep(40);
331
- concurrent -= 1;
332
- },
333
- {
334
- waitMs: 5000,
335
- log: { warn: (m: string) => steals.push(m), log() {} },
336
- }
337
- );
338
-
339
- await Promise.all([contend('a'), contend('b'), contend('c'), contend('d')]);
340
-
341
- expect(maxConcurrent).toBe(1);
342
- // One steal, not four: the other three found the file already gone.
343
- expect(steals.filter((m) => m.includes('stole a')).length).toBe(1);
344
- // And no sidecar left behind.
345
- expect(readdirSync(join(root, '.git')).filter((f) => f.includes('.stale-'))).toEqual([]);
346
- } finally {
347
- rmSync(root, { recursive: true, force: true });
348
- }
349
- }, 20_000);
350
- });
@@ -1,125 +0,0 @@
1
- // Regression test for issue-report-a-bug-http-500 (RCA:
2
- // .ai/logs/rca/issue-report-a-bug-http-500.md). `/_api/report` proxies the
3
- // dialog's multipart body to `MAUDE_REPORT_URL`. A bare `fetch(endpoint, …)`
4
- // call inside http.ts silently resolved to this module's OWN same-named
5
- // fall-through handler instead of the global `fetch` — the request never left
6
- // the machine, and the route mirrored a bogus local 500 back to the client.
7
- // These tests boot a real server pointed at a real stub upstream, so a
8
- // reintroduced shadow (or any other break in the proxy hop) fails loudly
9
- // instead of only being caught by a mocked-upstream unit test.
10
-
11
- import { describe, expect, test } from 'bun:test';
12
- import { readFileSync } from 'node:fs';
13
-
14
- import { bootServer, killProc, makeSandbox, nextPort } from './_helpers.ts';
15
-
16
- async function bootStubUpstream(
17
- handler: (req: Request) => Response | Promise<Response>
18
- ): Promise<{ url: string; stop: () => void; received: () => number }> {
19
- let received = 0;
20
- const server = Bun.serve({
21
- port: 0,
22
- fetch: async (req) => {
23
- received += 1;
24
- return handler(req);
25
- },
26
- });
27
- return {
28
- url: `http://127.0.0.1:${server.port}`,
29
- stop: () => server.stop(true),
30
- received: () => received,
31
- };
32
- }
33
-
34
- describe('/_api/report proxy hop', () => {
35
- test('forwards the multipart body verbatim and mirrors a successful upstream response', async () => {
36
- const { root } = makeSandbox();
37
- let capturedBytes = 0;
38
- let capturedHasReportPart = false;
39
- const upstream = await bootStubUpstream(async (req) => {
40
- const form = await req.formData();
41
- capturedHasReportPart = typeof form.get('report') === 'string';
42
- // Multipart parsing consumed the body — approximate "did real bytes
43
- // arrive" by re-serializing what FormData kept.
44
- capturedBytes = [...form.values()].reduce(
45
- (n, v) => n + (typeof v === 'string' ? v.length : (v as Blob).size),
46
- 0
47
- );
48
- return Response.json({ ok: true, issueNumber: 1, issueUrl: 'https://example.com/issues/1' });
49
- });
50
-
51
- const port = nextPort();
52
- const proc = await bootServer(root, port, { MAUDE_REPORT_URL: `${upstream.url}/report` });
53
- try {
54
- const form = new FormData();
55
- form.set(
56
- 'report',
57
- JSON.stringify({ schema: 'maude-report/v1', description: 'test', reportId: 'r-0123abcd' })
58
- );
59
- form.append('screenshot', new Blob([new Uint8Array([1, 2, 3, 4])]), 'shot.png');
60
-
61
- const res = await fetch(`http://localhost:${port}/_api/report`, {
62
- method: 'POST',
63
- body: form,
64
- });
65
- expect(res.status).toBe(200);
66
- const json = await res.json();
67
- expect(json).toEqual({ ok: true, issueNumber: 1, issueUrl: 'https://example.com/issues/1' });
68
-
69
- expect(upstream.received()).toBe(1);
70
- expect(capturedHasReportPart).toBe(true);
71
- expect(capturedBytes).toBeGreaterThan(0);
72
- } finally {
73
- await killProc(proc);
74
- upstream.stop();
75
- }
76
- });
77
-
78
- test('an unreachable upstream yields 502 with a JSON error, not a bare 500', async () => {
79
- const { root } = makeSandbox();
80
- const port = nextPort();
81
- // Nothing listens on this port — the fetch itself must fail/timeout.
82
- const proc = await bootServer(root, port, { MAUDE_REPORT_URL: 'http://127.0.0.1:1/report' });
83
- try {
84
- const form = new FormData();
85
- form.set(
86
- 'report',
87
- JSON.stringify({ schema: 'maude-report/v1', description: 'test', reportId: 'r-0123abcd' })
88
- );
89
- const res = await fetch(`http://localhost:${port}/_api/report`, {
90
- method: 'POST',
91
- body: form,
92
- });
93
- expect(res.status).toBe(502);
94
- const json = await res.json();
95
- expect(json).toEqual({ error: 'report service unreachable' });
96
- } finally {
97
- await killProc(proc);
98
- }
99
- });
100
- });
101
-
102
- describe('no bare `fetch(` call sites in http.ts', () => {
103
- test('every fetch call is either the global fetch (qualified) or a local handler name', () => {
104
- const src = readFileSync(new URL('../http.ts', import.meta.url), 'utf8');
105
- // A bare `fetch(` immediately preceded by a word-boundary and NOT preceded
106
- // by a `.` (member access) or `function ` (declaration) would silently
107
- // resolve to this file's own same-named fall-through handler instead of
108
- // the global — exactly the bug this test guards against. `handleFallthrough`
109
- // is the current, unambiguous name; this assertion also fails if the
110
- // handler is ever renamed back to something call-site-ambiguous.
111
- const bareFetchCalls = [...src.matchAll(/(?<![.\w])fetch\(/g)];
112
- const lines = src.split('\n');
113
- // Skip: comment lines (mentioning `fetch(` in prose is fine) and the one
114
- // legitimate bare occurrence — the type-signature parameter name
115
- // `fetch(req: Request): Promise<Response>;` in the `Api`-shaped interface,
116
- // which is a declaration, not a call.
117
- const offendingLines = bareFetchCalls
118
- .map((m) => src.slice(0, m.index).split('\n').length)
119
- .map((line) => ({ line, text: lines[line - 1] ?? '' }))
120
- .filter(({ text }) => !/^\s*(\/\/|\*)/.test(text))
121
- .filter(({ text }) => !/^\s*fetch\(req: Request\): Promise<Response>;/.test(text));
122
-
123
- expect(offendingLines).toEqual([]);
124
- });
125
- });
@@ -1,197 +0,0 @@
1
- // ripple.test.ts — feature-enhanced-video-editing Phase 0 (Task 2). Golden-file
2
- // fixtures for the pure ripple engine: literal shifts, const-arithmetic
3
- // trailing-term merges, the TOTAL-follows-const double-shift guard, series
4
- // TOTAL-only behavior, and the loud structured refusals.
5
-
6
- import { describe, expect, test } from 'bun:test';
7
-
8
- import {
9
- applyRippleAfterClip,
10
- collectConsts,
11
- RippleError,
12
- resolveNum,
13
- shiftExpression,
14
- } from '../ripple.ts';
15
-
16
- const CANVAS = '/abs/Canvas.tsx';
17
-
18
- describe('shiftExpression', () => {
19
- test('literal rewrites in place', () => {
20
- expect(shiftExpression('90', 12)).toBe('102');
21
- expect(shiftExpression('90', -30)).toBe('60');
22
- });
23
- test('trailing integer term merges', () => {
24
- expect(shiftExpression('A + B - 20', 12)).toBe('A + B - 8');
25
- expect(shiftExpression('A + B - 20', 20)).toBe('A + B');
26
- expect(shiftExpression('A + 10', -22)).toBe('A - 12');
27
- });
28
- test('no trailing term → appends', () => {
29
- expect(shiftExpression('A', 12)).toBe('A + 12');
30
- expect(shiftExpression('A + B', -12)).toBe('A + B - 12');
31
- });
32
- test('trailing int that is an operand of * stays untouched (appends instead)', () => {
33
- expect(shiftExpression('A * 2', 12)).toBe('A * 2 + 12');
34
- });
35
- test('unsafe charset → null (a call slips the charset but fails resolveNum — the engine refuses there)', () => {
36
- expect(shiftExpression('a ? 1 : 2', 12)).toBe(null);
37
- expect(resolveNum('fn(A)', { A: 1 })).toBe(null);
38
- });
39
- });
40
-
41
- describe('collectConsts + resolveNum', () => {
42
- const SRC = 'const A = 40;\nconst B = 50;\nconst TOTAL = A + B - 20;\n';
43
- test('derived consts resolve', () => {
44
- const c = collectConsts(SRC);
45
- expect(c.TOTAL).toBe(70);
46
- expect(resolveNum('TOTAL - 10', c)).toBe(60);
47
- expect(resolveNum('mystery + 1', c)).toBe(null);
48
- });
49
- });
50
-
51
- // ---------------------------------------------------------------------------
52
- // Standalone-Sequence comp (overlay/audio band shape) — literal + expression.
53
-
54
- const STANDALONE = [
55
- 'const A = 90;',
56
- 'const TOTAL = 300;',
57
- 'const Comp = () => (',
58
- ' <>',
59
- ' <Sequence name="one" from={0} durationInFrames={A}><Video src="a.mp4" /></Sequence>',
60
- ' <Sequence name="two" from={90} durationInFrames={100}><Video src="b.mp4" /></Sequence>',
61
- ' <Sequence name="three" from={A + 100} durationInFrames={110}><Video src="c.mp4" /></Sequence>',
62
- ' </>',
63
- ');',
64
- 'function Canvas() {',
65
- ' return (',
66
- ' <DCArtboard id="a"><VideoComp component={Comp} durationInFrames={TOTAL} fps={30} /></DCArtboard>',
67
- ' );',
68
- '}',
69
- ].join('\n');
70
-
71
- describe('applyRippleAfterClip — standalone sequences', () => {
72
- test('shifts literal and expression froms, bumps the TOTAL const', () => {
73
- const r = applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:one', 12);
74
- expect(r.totalEdited).toBe(true);
75
- expect(r.shifted).toEqual(['name:two', 'name:three']);
76
- expect(r.source).toContain('const TOTAL = 312;');
77
- expect(r.source).toContain('from={102}');
78
- expect(r.source).toContain('from={A + 112}');
79
- // upstream untouched
80
- expect(r.source).toContain('from={0}');
81
- });
82
-
83
- test('negative delta merges the trailing term back down', () => {
84
- const r = applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:one', -30);
85
- expect(r.source).toContain('const TOTAL = 270;');
86
- expect(r.source).toContain('from={60}');
87
- expect(r.source).toContain('from={A + 70}');
88
- });
89
-
90
- test('delta 0 is a no-op', () => {
91
- const r = applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:one', 0);
92
- expect(r.source).toBe(STANDALONE);
93
- expect(r.shifted).toEqual([]);
94
- });
95
-
96
- test('only clips AFTER the target shift', () => {
97
- const r = applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:two', 12);
98
- expect(r.shifted).toEqual(['name:three']);
99
- expect(r.source).toContain('from={90}'); // "two" itself untouched
100
- expect(r.source).toContain('from={A + 112}');
101
- });
102
- });
103
-
104
- // ---------------------------------------------------------------------------
105
- // The double-shift guard — an end card at `from={TOTAL - 60}` already follows
106
- // the bumped const and must be SKIPPED, not shifted twice.
107
-
108
- const COUPLED = [
109
- 'const TOTAL = 300;',
110
- 'const Comp = () => (',
111
- ' <>',
112
- ' <Sequence name="body" from={0} durationInFrames={240}><Video src="a.mp4" /></Sequence>',
113
- ' <Sequence name="end" from={TOTAL - 60} durationInFrames={60}><Video src="b.mp4" /></Sequence>',
114
- ' </>',
115
- ');',
116
- 'function Canvas() {',
117
- ' return (',
118
- ' <DCArtboard id="a"><VideoComp component={Comp} durationInFrames={TOTAL} fps={30} /></DCArtboard>',
119
- ' );',
120
- '}',
121
- ].join('\n');
122
-
123
- describe('applyRippleAfterClip — const-coupled froms', () => {
124
- test('a from that follows the bumped TOTAL is skipped (no double shift)', () => {
125
- const r = applyRippleAfterClip(CANVAS, COUPLED, 'a', 'name:body', 30);
126
- expect(r.source).toContain('const TOTAL = 330;');
127
- expect(r.source).toContain('from={TOTAL - 60}'); // untouched — follows the const
128
- expect(r.shifted).toEqual([]);
129
- });
130
- });
131
-
132
- // ---------------------------------------------------------------------------
133
- // TransitionSeries storyline — series clips get NO from; TOTAL still moves.
134
-
135
- const SERIES = [
136
- "import { TransitionSeries } from '@remotion/transitions';",
137
- 'const S1 = 90;',
138
- 'const S2 = 120;',
139
- 'const XF = 15;',
140
- 'const TOTAL = S1 + S2 - XF;',
141
- 'const Comp = () => (',
142
- ' <TransitionSeries>',
143
- ' <TransitionSeries.Sequence name="s1" durationInFrames={S1}><Video src="a.mp4" /></TransitionSeries.Sequence>',
144
- ' <TransitionSeries.Transition presentation={fade()} timing={linearTiming({ durationInFrames: XF })} />',
145
- ' <TransitionSeries.Sequence name="s2" durationInFrames={S2}><Video src="b.mp4" /></TransitionSeries.Sequence>',
146
- ' </TransitionSeries>',
147
- ');',
148
- 'function Canvas() {',
149
- ' return (',
150
- ' <DCArtboard id="a"><VideoComp component={Comp} durationInFrames={TOTAL} fps={30} /></DCArtboard>',
151
- ' );',
152
- '}',
153
- ].join('\n');
154
-
155
- describe('applyRippleAfterClip — series storyline', () => {
156
- test('series clips get no from shift; TOTAL const merges the delta', () => {
157
- const r = applyRippleAfterClip(CANVAS, SERIES, 'a', 'name:s1', 12);
158
- expect(r.totalEdited).toBe(true);
159
- expect(r.shifted).toEqual([]);
160
- expect(r.source).toContain('const TOTAL = S1 + S2 - XF + 12;');
161
- // clip tags untouched
162
- expect(r.source).toContain('durationInFrames={S1}');
163
- expect(r.source).toContain('durationInFrames={S2}');
164
- });
165
- });
166
-
167
- // ---------------------------------------------------------------------------
168
- // Refusals — loud, structured, never a silent mis-shift.
169
-
170
- describe('applyRippleAfterClip — refusals', () => {
171
- test('unresolvable downstream from refuses with the clip named', () => {
172
- const SRC = STANDALONE.replace('from={A + 100}', 'from={computeStart()}');
173
- let err: unknown;
174
- try {
175
- applyRippleAfterClip(CANVAS, SRC, 'a', 'name:one', 12);
176
- } catch (e) {
177
- err = e;
178
- }
179
- expect(err).toBeInstanceOf(RippleError);
180
- expect(String((err as Error).message)).toContain('name:three');
181
- });
182
-
183
- test('unresolvable TOTAL const refuses', () => {
184
- const SRC = STANDALONE.replace('const TOTAL = 300;', 'const TOTAL = computeTotal();');
185
- expect(() => applyRippleAfterClip(CANVAS, SRC, 'a', 'name:one', 12)).toThrow(RippleError);
186
- });
187
-
188
- test('a shift below frame 0 refuses', () => {
189
- expect(() => applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:one', -120)).toThrow(
190
- RippleError
191
- );
192
- });
193
-
194
- test('unknown clip refuses', () => {
195
- expect(() => applyRippleAfterClip(CANVAS, STANDALONE, 'a', 'name:nope', 12)).toThrow();
196
- });
197
- });