@imgly/codesign-mcp 0.1.6 → 0.1.8

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 (448) hide show
  1. package/README.md +5 -10
  2. package/assets/docs/api/index.d.ts +9 -3
  3. package/assets/docs/guide/SKILL.md +2 -2
  4. package/assets/docs/guide/api/node/classes/creativeengine.md +13 -13
  5. package/assets/docs/guide/edit-image/transform/scale.md +1 -3
  6. package/assets/docs/guide/filters-and-effects/duotone.md +1 -1
  7. package/assets/docs/guide/get-started/bun.md +0 -2
  8. package/assets/docs/guide/get-started/deno.md +0 -1
  9. package/assets/docs/guide/get-started/vanilla.md +0 -2
  10. package/assets/docs/guide/import-media/default-assets.md +20 -24
  11. package/assets/docs/guide/plugins/print-ready-pdf.md +0 -2
  12. package/assets/docs/guide/serve-assets.md +80 -23
  13. package/assets/docs/handbook/SKILL.md +6 -7
  14. package/dist/assets/inline.generated.js +12 -12
  15. package/dist/assets/inline.generated.js.map +1 -1
  16. package/dist/cli/http.d.ts.map +1 -1
  17. package/dist/cli/http.js +29 -5
  18. package/dist/cli/http.js.map +1 -1
  19. package/dist/cli/stdio.d.ts.map +1 -1
  20. package/dist/cli/stdio.js +145 -48
  21. package/dist/cli/stdio.js.map +1 -1
  22. package/dist/cli/trial-license.generated.d.ts +1 -1
  23. package/dist/cli/trial-license.generated.js +1 -1
  24. package/dist/cli/trial-license.generated.js.map +1 -1
  25. package/dist/config.d.ts +17 -2
  26. package/dist/config.d.ts.map +1 -1
  27. package/dist/config.js +20 -1
  28. package/dist/config.js.map +1 -1
  29. package/dist/diagnostics/bundle.d.ts +3 -0
  30. package/dist/diagnostics/bundle.d.ts.map +1 -1
  31. package/dist/diagnostics/bundle.js +25 -22
  32. package/dist/diagnostics/bundle.js.map +1 -1
  33. package/dist/diagnostics/events.d.ts +6 -0
  34. package/dist/diagnostics/events.d.ts.map +1 -1
  35. package/dist/diagnostics/events.js.map +1 -1
  36. package/dist/diagnostics/prompt.d.ts +3 -0
  37. package/dist/diagnostics/prompt.d.ts.map +1 -0
  38. package/dist/diagnostics/prompt.js +21 -0
  39. package/dist/diagnostics/prompt.js.map +1 -0
  40. package/dist/diagnostics/router.d.ts +12 -0
  41. package/dist/diagnostics/router.d.ts.map +1 -0
  42. package/dist/diagnostics/router.js +64 -0
  43. package/dist/diagnostics/router.js.map +1 -0
  44. package/dist/diagnostics/spa-assets.generated.d.ts.map +1 -0
  45. package/dist/diagnostics/spa-assets.generated.js +1231 -0
  46. package/dist/diagnostics/spa-assets.generated.js.map +1 -0
  47. package/dist/diagnostics/status.d.ts +38 -0
  48. package/dist/diagnostics/status.d.ts.map +1 -0
  49. package/dist/diagnostics/status.js +53 -0
  50. package/dist/diagnostics/status.js.map +1 -0
  51. package/dist/diagnostics/summary.d.ts +48 -0
  52. package/dist/diagnostics/summary.d.ts.map +1 -0
  53. package/dist/diagnostics/summary.js +116 -0
  54. package/dist/diagnostics/summary.js.map +1 -0
  55. package/dist/diagnostics/tool.d.ts +16 -0
  56. package/dist/diagnostics/tool.d.ts.map +1 -0
  57. package/dist/diagnostics/tool.js +90 -0
  58. package/dist/diagnostics/tool.js.map +1 -0
  59. package/dist/engine/asset-sources.d.ts +8 -0
  60. package/dist/engine/asset-sources.d.ts.map +1 -1
  61. package/dist/engine/asset-sources.js +9 -2
  62. package/dist/engine/asset-sources.js.map +1 -1
  63. package/dist/engine/cesdk-adapter.d.ts +5 -1
  64. package/dist/engine/cesdk-adapter.d.ts.map +1 -1
  65. package/dist/engine/cesdk-adapter.js +8 -1
  66. package/dist/engine/cesdk-adapter.js.map +1 -1
  67. package/dist/engine/engine-capacity.d.ts +16 -0
  68. package/dist/engine/engine-capacity.d.ts.map +1 -0
  69. package/dist/engine/engine-capacity.js +48 -0
  70. package/dist/engine/engine-capacity.js.map +1 -0
  71. package/dist/engine/engine-slots.d.ts +48 -0
  72. package/dist/engine/engine-slots.d.ts.map +1 -0
  73. package/dist/engine/engine-slots.js +182 -0
  74. package/dist/engine/engine-slots.js.map +1 -0
  75. package/dist/engine/uri-resolver.js.map +1 -1
  76. package/dist/server.d.ts +25 -0
  77. package/dist/server.d.ts.map +1 -1
  78. package/dist/server.js +24 -4
  79. package/dist/server.js.map +1 -1
  80. package/dist/tools/engine.d.ts +92 -14
  81. package/dist/tools/engine.d.ts.map +1 -1
  82. package/dist/tools/engine.js +147 -20
  83. package/dist/tools/engine.js.map +1 -1
  84. package/dist/tools/export.d.ts.map +1 -1
  85. package/dist/tools/export.js +34 -4
  86. package/dist/tools/export.js.map +1 -1
  87. package/dist/tools/index.js +2 -2
  88. package/dist/tools/index.js.map +1 -1
  89. package/dist/tools/load.d.ts.map +1 -1
  90. package/dist/tools/load.js +26 -19
  91. package/dist/tools/load.js.map +1 -1
  92. package/dist/tools/preview.d.ts.map +1 -1
  93. package/dist/tools/preview.js +60 -40
  94. package/dist/tools/preview.js.map +1 -1
  95. package/dist/tools/{open-editor.d.ts → view.d.ts} +3 -3
  96. package/dist/tools/view.d.ts.map +1 -0
  97. package/dist/tools/{open-editor.js → view.js} +20 -18
  98. package/dist/tools/view.js.map +1 -0
  99. package/dist/viewer/listener.d.ts +2 -0
  100. package/dist/viewer/listener.d.ts.map +1 -1
  101. package/dist/viewer/listener.js +2 -0
  102. package/dist/viewer/listener.js.map +1 -1
  103. package/dist/viewer/presigned-r2-url-builder.d.ts +1 -1
  104. package/dist/viewer/presigned-r2-url-builder.js +1 -1
  105. package/dist/viewer/presigned-r2-url-builder.js.map +1 -1
  106. package/dist/viewer/router.d.ts.map +1 -1
  107. package/dist/viewer/router.js +8 -17
  108. package/dist/viewer/router.js.map +1 -1
  109. package/dist/viewer/session-mount.js.map +1 -1
  110. package/dist/viewer/spa-assets.generated.d.ts.map +1 -1
  111. package/dist/viewer/spa-assets.generated.js +750 -869
  112. package/dist/viewer/spa-assets.generated.js.map +1 -1
  113. package/dist/viewer/url-builder.d.ts +4 -3
  114. package/dist/viewer/url-builder.d.ts.map +1 -1
  115. package/dist/viewer/url-builder.js +4 -4
  116. package/dist/viewer/url-builder.js.map +1 -1
  117. package/package.json +7 -6
  118. package/dist/dashboard/router.d.ts +0 -3
  119. package/dist/dashboard/router.d.ts.map +0 -1
  120. package/dist/dashboard/router.js +0 -19
  121. package/dist/dashboard/router.js.map +0 -1
  122. package/dist/dashboard/spa-assets.generated.d.ts.map +0 -1
  123. package/dist/dashboard/spa-assets.generated.js +0 -220
  124. package/dist/dashboard/spa-assets.generated.js.map +0 -1
  125. package/dist/metrics/mount.d.ts +0 -11
  126. package/dist/metrics/mount.d.ts.map +0 -1
  127. package/dist/metrics/mount.js +0 -13
  128. package/dist/metrics/mount.js.map +0 -1
  129. package/dist/metrics/recorder.d.ts +0 -55
  130. package/dist/metrics/recorder.d.ts.map +0 -1
  131. package/dist/metrics/recorder.js +0 -97
  132. package/dist/metrics/recorder.js.map +0 -1
  133. package/dist/test/scripts/changelog-add.d.ts +0 -8
  134. package/dist/test/scripts/changelog-add.d.ts.map +0 -1
  135. package/dist/test/scripts/changelog-add.js +0 -50
  136. package/dist/test/scripts/changelog-add.js.map +0 -1
  137. package/dist/test/scripts/changelog-collate.d.ts +0 -38
  138. package/dist/test/scripts/changelog-collate.d.ts.map +0 -1
  139. package/dist/test/scripts/changelog-collate.js +0 -171
  140. package/dist/test/scripts/changelog-collate.js.map +0 -1
  141. package/dist/test/test/changelog.test.d.ts +0 -2
  142. package/dist/test/test/changelog.test.d.ts.map +0 -1
  143. package/dist/test/test/changelog.test.js +0 -293
  144. package/dist/test/test/changelog.test.js.map +0 -1
  145. package/dist/test/test/cli/dispatcher.test.d.ts +0 -2
  146. package/dist/test/test/cli/dispatcher.test.d.ts.map +0 -1
  147. package/dist/test/test/cli/dispatcher.test.js +0 -54
  148. package/dist/test/test/cli/dispatcher.test.js.map +0 -1
  149. package/dist/test/test/cli/redirect-console.test.d.ts +0 -2
  150. package/dist/test/test/cli/redirect-console.test.d.ts.map +0 -1
  151. package/dist/test/test/cli/redirect-console.test.js +0 -78
  152. package/dist/test/test/cli/redirect-console.test.js.map +0 -1
  153. package/dist/test/test/cli/resolve-license.test.d.ts +0 -2
  154. package/dist/test/test/cli/resolve-license.test.d.ts.map +0 -1
  155. package/dist/test/test/cli/resolve-license.test.js +0 -29
  156. package/dist/test/test/cli/resolve-license.test.js.map +0 -1
  157. package/dist/test/test/cli/stdio-diagnostics.test.d.ts +0 -2
  158. package/dist/test/test/cli/stdio-diagnostics.test.d.ts.map +0 -1
  159. package/dist/test/test/cli/stdio-diagnostics.test.js +0 -31
  160. package/dist/test/test/cli/stdio-diagnostics.test.js.map +0 -1
  161. package/dist/test/test/cli/stdio-license.test.d.ts +0 -2
  162. package/dist/test/test/cli/stdio-license.test.d.ts.map +0 -1
  163. package/dist/test/test/cli/stdio-license.test.js +0 -19
  164. package/dist/test/test/cli/stdio-license.test.js.map +0 -1
  165. package/dist/test/test/config.test.d.ts +0 -2
  166. package/dist/test/test/config.test.d.ts.map +0 -1
  167. package/dist/test/test/config.test.js +0 -95
  168. package/dist/test/test/config.test.js.map +0 -1
  169. package/dist/test/test/cross-ref.test.d.ts +0 -2
  170. package/dist/test/test/cross-ref.test.d.ts.map +0 -1
  171. package/dist/test/test/cross-ref.test.js +0 -39
  172. package/dist/test/test/cross-ref.test.js.map +0 -1
  173. package/dist/test/test/diagnostics/bundle.test.d.ts +0 -2
  174. package/dist/test/test/diagnostics/bundle.test.d.ts.map +0 -1
  175. package/dist/test/test/diagnostics/bundle.test.js +0 -53
  176. package/dist/test/test/diagnostics/bundle.test.js.map +0 -1
  177. package/dist/test/test/diagnostics/compose.test.d.ts +0 -2
  178. package/dist/test/test/diagnostics/compose.test.d.ts.map +0 -1
  179. package/dist/test/test/diagnostics/compose.test.js +0 -31
  180. package/dist/test/test/diagnostics/compose.test.js.map +0 -1
  181. package/dist/test/test/diagnostics/file-sink.test.d.ts +0 -2
  182. package/dist/test/test/diagnostics/file-sink.test.d.ts.map +0 -1
  183. package/dist/test/test/diagnostics/file-sink.test.js +0 -113
  184. package/dist/test/test/diagnostics/file-sink.test.js.map +0 -1
  185. package/dist/test/test/diagnostics/redact.test.d.ts +0 -2
  186. package/dist/test/test/diagnostics/redact.test.d.ts.map +0 -1
  187. package/dist/test/test/diagnostics/redact.test.js +0 -59
  188. package/dist/test/test/diagnostics/redact.test.js.map +0 -1
  189. package/dist/test/test/diagnostics/tool-call-adapter.test.d.ts +0 -2
  190. package/dist/test/test/diagnostics/tool-call-adapter.test.d.ts.map +0 -1
  191. package/dist/test/test/diagnostics/tool-call-adapter.test.js +0 -34
  192. package/dist/test/test/diagnostics/tool-call-adapter.test.js.map +0 -1
  193. package/dist/test/test/engine/asset-sources.test.d.ts +0 -2
  194. package/dist/test/test/engine/asset-sources.test.d.ts.map +0 -1
  195. package/dist/test/test/engine/asset-sources.test.js +0 -100
  196. package/dist/test/test/engine/asset-sources.test.js.map +0 -1
  197. package/dist/test/test/engine/cesdk-adapter.test.d.ts +0 -2
  198. package/dist/test/test/engine/cesdk-adapter.test.d.ts.map +0 -1
  199. package/dist/test/test/engine/cesdk-adapter.test.js +0 -32
  200. package/dist/test/test/engine/cesdk-adapter.test.js.map +0 -1
  201. package/dist/test/test/engine/mutex.test.d.ts +0 -2
  202. package/dist/test/test/engine/mutex.test.d.ts.map +0 -1
  203. package/dist/test/test/engine/mutex.test.js +0 -63
  204. package/dist/test/test/engine/mutex.test.js.map +0 -1
  205. package/dist/test/test/engine/session.test.d.ts +0 -2
  206. package/dist/test/test/engine/session.test.d.ts.map +0 -1
  207. package/dist/test/test/engine/session.test.js +0 -159
  208. package/dist/test/test/engine/session.test.js.map +0 -1
  209. package/dist/test/test/engine/unsplash-source.test.d.ts +0 -2
  210. package/dist/test/test/engine/unsplash-source.test.d.ts.map +0 -1
  211. package/dist/test/test/engine/unsplash-source.test.js +0 -129
  212. package/dist/test/test/engine/unsplash-source.test.js.map +0 -1
  213. package/dist/test/test/engine/uri-resolver.test.d.ts +0 -2
  214. package/dist/test/test/engine/uri-resolver.test.d.ts.map +0 -1
  215. package/dist/test/test/engine/uri-resolver.test.js +0 -38
  216. package/dist/test/test/engine/uri-resolver.test.js.map +0 -1
  217. package/dist/test/test/gateway/hosted-stub.test.d.ts +0 -2
  218. package/dist/test/test/gateway/hosted-stub.test.d.ts.map +0 -1
  219. package/dist/test/test/gateway/hosted-stub.test.js +0 -11
  220. package/dist/test/test/gateway/hosted-stub.test.js.map +0 -1
  221. package/dist/test/test/gateway/local.test.d.ts +0 -2
  222. package/dist/test/test/gateway/local.test.d.ts.map +0 -1
  223. package/dist/test/test/gateway/local.test.js +0 -29
  224. package/dist/test/test/gateway/local.test.js.map +0 -1
  225. package/dist/test/test/handbook-contract.test.d.ts +0 -2
  226. package/dist/test/test/handbook-contract.test.d.ts.map +0 -1
  227. package/dist/test/test/handbook-contract.test.js +0 -79
  228. package/dist/test/test/handbook-contract.test.js.map +0 -1
  229. package/dist/test/test/http/server.test.d.ts +0 -2
  230. package/dist/test/test/http/server.test.d.ts.map +0 -1
  231. package/dist/test/test/http/server.test.js +0 -338
  232. package/dist/test/test/http/server.test.js.map +0 -1
  233. package/dist/test/test/http/session-id-header.test.d.ts +0 -2
  234. package/dist/test/test/http/session-id-header.test.d.ts.map +0 -1
  235. package/dist/test/test/http/session-id-header.test.js +0 -76
  236. package/dist/test/test/http/session-id-header.test.js.map +0 -1
  237. package/dist/test/test/http/session-manager.test.d.ts +0 -2
  238. package/dist/test/test/http/session-manager.test.d.ts.map +0 -1
  239. package/dist/test/test/http/session-manager.test.js +0 -97
  240. package/dist/test/test/http/session-manager.test.js.map +0 -1
  241. package/dist/test/test/http/workspace-auth.test.d.ts +0 -2
  242. package/dist/test/test/http/workspace-auth.test.d.ts.map +0 -1
  243. package/dist/test/test/http/workspace-auth.test.js +0 -40
  244. package/dist/test/test/http/workspace-auth.test.js.map +0 -1
  245. package/dist/test/test/metrics/mount.test.d.ts +0 -2
  246. package/dist/test/test/metrics/mount.test.d.ts.map +0 -1
  247. package/dist/test/test/metrics/mount.test.js +0 -59
  248. package/dist/test/test/metrics/mount.test.js.map +0 -1
  249. package/dist/test/test/metrics/recorder.test.d.ts +0 -2
  250. package/dist/test/test/metrics/recorder.test.d.ts.map +0 -1
  251. package/dist/test/test/metrics/recorder.test.js +0 -118
  252. package/dist/test/test/metrics/recorder.test.js.map +0 -1
  253. package/dist/test/test/revisions/hash.test.d.ts +0 -2
  254. package/dist/test/test/revisions/hash.test.d.ts.map +0 -1
  255. package/dist/test/test/revisions/hash.test.js +0 -33
  256. package/dist/test/test/revisions/hash.test.js.map +0 -1
  257. package/dist/test/test/revisions/store.test.d.ts +0 -2
  258. package/dist/test/test/revisions/store.test.d.ts.map +0 -1
  259. package/dist/test/test/revisions/store.test.js +0 -315
  260. package/dist/test/test/revisions/store.test.js.map +0 -1
  261. package/dist/test/test/server.metrics.test.d.ts +0 -2
  262. package/dist/test/test/server.metrics.test.d.ts.map +0 -1
  263. package/dist/test/test/server.metrics.test.js +0 -87
  264. package/dist/test/test/server.metrics.test.js.map +0 -1
  265. package/dist/test/test/server.test.d.ts +0 -2
  266. package/dist/test/test/server.test.d.ts.map +0 -1
  267. package/dist/test/test/server.test.js +0 -146
  268. package/dist/test/test/server.test.js.map +0 -1
  269. package/dist/test/test/skill-tools.test.d.ts +0 -2
  270. package/dist/test/test/skill-tools.test.d.ts.map +0 -1
  271. package/dist/test/test/skill-tools.test.js +0 -84
  272. package/dist/test/test/skill-tools.test.js.map +0 -1
  273. package/dist/test/test/smoke.test.d.ts +0 -2
  274. package/dist/test/test/smoke.test.d.ts.map +0 -1
  275. package/dist/test/test/smoke.test.js +0 -8
  276. package/dist/test/test/smoke.test.js.map +0 -1
  277. package/dist/test/test/tools/asset-generate.test.d.ts +0 -2
  278. package/dist/test/test/tools/asset-generate.test.d.ts.map +0 -1
  279. package/dist/test/test/tools/asset-generate.test.js +0 -66
  280. package/dist/test/test/tools/asset-generate.test.js.map +0 -1
  281. package/dist/test/test/tools/asset-search.test.d.ts +0 -2
  282. package/dist/test/test/tools/asset-search.test.d.ts.map +0 -1
  283. package/dist/test/test/tools/asset-search.test.js +0 -121
  284. package/dist/test/test/tools/asset-search.test.js.map +0 -1
  285. package/dist/test/test/tools/edit-harness.d.ts +0 -14
  286. package/dist/test/test/tools/edit-harness.d.ts.map +0 -1
  287. package/dist/test/test/tools/edit-harness.js +0 -30
  288. package/dist/test/test/tools/edit-harness.js.map +0 -1
  289. package/dist/test/test/tools/edit.telemetry.test.d.ts +0 -2
  290. package/dist/test/test/tools/edit.telemetry.test.d.ts.map +0 -1
  291. package/dist/test/test/tools/edit.telemetry.test.js +0 -46
  292. package/dist/test/test/tools/edit.telemetry.test.js.map +0 -1
  293. package/dist/test/test/tools/edit.test.d.ts +0 -2
  294. package/dist/test/test/tools/edit.test.d.ts.map +0 -1
  295. package/dist/test/test/tools/edit.test.js +0 -267
  296. package/dist/test/test/tools/edit.test.js.map +0 -1
  297. package/dist/test/test/tools/export-harness.d.ts +0 -13
  298. package/dist/test/test/tools/export-harness.d.ts.map +0 -1
  299. package/dist/test/test/tools/export-harness.js +0 -27
  300. package/dist/test/test/tools/export-harness.js.map +0 -1
  301. package/dist/test/test/tools/export.telemetry.test.d.ts +0 -2
  302. package/dist/test/test/tools/export.telemetry.test.d.ts.map +0 -1
  303. package/dist/test/test/tools/export.telemetry.test.js +0 -65
  304. package/dist/test/test/tools/export.telemetry.test.js.map +0 -1
  305. package/dist/test/test/tools/export.test.d.ts +0 -2
  306. package/dist/test/test/tools/export.test.d.ts.map +0 -1
  307. package/dist/test/test/tools/export.test.js +0 -174
  308. package/dist/test/test/tools/export.test.js.map +0 -1
  309. package/dist/test/test/tools/history.test.d.ts +0 -2
  310. package/dist/test/test/tools/history.test.d.ts.map +0 -1
  311. package/dist/test/test/tools/history.test.js +0 -29
  312. package/dist/test/test/tools/history.test.js.map +0 -1
  313. package/dist/test/test/tools/import.test.d.ts +0 -2
  314. package/dist/test/test/tools/import.test.d.ts.map +0 -1
  315. package/dist/test/test/tools/import.test.js +0 -122
  316. package/dist/test/test/tools/import.test.js.map +0 -1
  317. package/dist/test/test/tools/index.test.d.ts +0 -2
  318. package/dist/test/test/tools/index.test.d.ts.map +0 -1
  319. package/dist/test/test/tools/index.test.js +0 -117
  320. package/dist/test/test/tools/index.test.js.map +0 -1
  321. package/dist/test/test/tools/inspect.test.d.ts +0 -2
  322. package/dist/test/test/tools/inspect.test.d.ts.map +0 -1
  323. package/dist/test/test/tools/inspect.test.js +0 -62
  324. package/dist/test/test/tools/inspect.test.js.map +0 -1
  325. package/dist/test/test/tools/list.test.d.ts +0 -2
  326. package/dist/test/test/tools/list.test.d.ts.map +0 -1
  327. package/dist/test/test/tools/list.test.js +0 -23
  328. package/dist/test/test/tools/list.test.js.map +0 -1
  329. package/dist/test/test/tools/load.integration.test.d.ts +0 -2
  330. package/dist/test/test/tools/load.integration.test.d.ts.map +0 -1
  331. package/dist/test/test/tools/load.integration.test.js +0 -165
  332. package/dist/test/test/tools/load.integration.test.js.map +0 -1
  333. package/dist/test/test/tools/load.test.d.ts +0 -2
  334. package/dist/test/test/tools/load.test.d.ts.map +0 -1
  335. package/dist/test/test/tools/load.test.js +0 -387
  336. package/dist/test/test/tools/load.test.js.map +0 -1
  337. package/dist/test/test/tools/metrics.test.d.ts +0 -2
  338. package/dist/test/test/tools/metrics.test.d.ts.map +0 -1
  339. package/dist/test/test/tools/metrics.test.js +0 -29
  340. package/dist/test/test/tools/metrics.test.js.map +0 -1
  341. package/dist/test/test/tools/open-editor.test.d.ts +0 -2
  342. package/dist/test/test/tools/open-editor.test.d.ts.map +0 -1
  343. package/dist/test/test/tools/open-editor.test.js +0 -53
  344. package/dist/test/test/tools/open-editor.test.js.map +0 -1
  345. package/dist/test/test/tools/preview-harness.d.ts +0 -9
  346. package/dist/test/test/tools/preview-harness.d.ts.map +0 -1
  347. package/dist/test/test/tools/preview-harness.js +0 -23
  348. package/dist/test/test/tools/preview-harness.js.map +0 -1
  349. package/dist/test/test/tools/preview.telemetry.test.d.ts +0 -2
  350. package/dist/test/test/tools/preview.telemetry.test.d.ts.map +0 -1
  351. package/dist/test/test/tools/preview.telemetry.test.js +0 -36
  352. package/dist/test/test/tools/preview.telemetry.test.js.map +0 -1
  353. package/dist/test/test/tools/preview.test.d.ts +0 -2
  354. package/dist/test/test/tools/preview.test.d.ts.map +0 -1
  355. package/dist/test/test/tools/preview.test.js +0 -75
  356. package/dist/test/test/tools/preview.test.js.map +0 -1
  357. package/dist/test/test/tools/rename.test.d.ts +0 -2
  358. package/dist/test/test/tools/rename.test.d.ts.map +0 -1
  359. package/dist/test/test/tools/rename.test.js +0 -16
  360. package/dist/test/test/tools/rename.test.js.map +0 -1
  361. package/dist/test/test/tools/save.test.d.ts +0 -2
  362. package/dist/test/test/tools/save.test.d.ts.map +0 -1
  363. package/dist/test/test/tools/save.test.js +0 -137
  364. package/dist/test/test/tools/save.test.js.map +0 -1
  365. package/dist/test/test/tools/schema-best-practices.d.ts +0 -13
  366. package/dist/test/test/tools/schema-best-practices.d.ts.map +0 -1
  367. package/dist/test/test/tools/schema-best-practices.js +0 -169
  368. package/dist/test/test/tools/schema-best-practices.js.map +0 -1
  369. package/dist/test/test/tools/schema-best-practices.test.d.ts +0 -3
  370. package/dist/test/test/tools/schema-best-practices.test.d.ts.map +0 -1
  371. package/dist/test/test/tools/schema-best-practices.test.js +0 -194
  372. package/dist/test/test/tools/schema-best-practices.test.js.map +0 -1
  373. package/dist/test/test/tools/spans.test.d.ts +0 -2
  374. package/dist/test/test/tools/spans.test.d.ts.map +0 -1
  375. package/dist/test/test/tools/spans.test.js +0 -84
  376. package/dist/test/test/tools/spans.test.js.map +0 -1
  377. package/dist/test/test/viewer/broadcaster.test.d.ts +0 -2
  378. package/dist/test/test/viewer/broadcaster.test.d.ts.map +0 -1
  379. package/dist/test/test/viewer/broadcaster.test.js +0 -53
  380. package/dist/test/test/viewer/broadcaster.test.js.map +0 -1
  381. package/dist/test/test/viewer/cesdk-cdn.test.d.ts +0 -2
  382. package/dist/test/test/viewer/cesdk-cdn.test.d.ts.map +0 -1
  383. package/dist/test/test/viewer/cesdk-cdn.test.js +0 -19
  384. package/dist/test/test/viewer/cesdk-cdn.test.js.map +0 -1
  385. package/dist/test/test/viewer/config-route.test.d.ts +0 -2
  386. package/dist/test/test/viewer/config-route.test.d.ts.map +0 -1
  387. package/dist/test/test/viewer/config-route.test.js +0 -28
  388. package/dist/test/test/viewer/config-route.test.js.map +0 -1
  389. package/dist/test/test/viewer/resource-route.test.d.ts +0 -2
  390. package/dist/test/test/viewer/resource-route.test.d.ts.map +0 -1
  391. package/dist/test/test/viewer/resource-route.test.js +0 -70
  392. package/dist/test/test/viewer/resource-route.test.js.map +0 -1
  393. package/dist/test/test/viewer/router.test.d.ts +0 -2
  394. package/dist/test/test/viewer/router.test.d.ts.map +0 -1
  395. package/dist/test/test/viewer/router.test.js +0 -227
  396. package/dist/test/test/viewer/router.test.js.map +0 -1
  397. package/dist/test/test/viewer/session-mount.test.d.ts +0 -2
  398. package/dist/test/test/viewer/session-mount.test.d.ts.map +0 -1
  399. package/dist/test/test/viewer/session-mount.test.js +0 -52
  400. package/dist/test/test/viewer/session-mount.test.js.map +0 -1
  401. package/dist/test/test/viewer/spa-version.test.d.ts +0 -2
  402. package/dist/test/test/viewer/spa-version.test.d.ts.map +0 -1
  403. package/dist/test/test/viewer/spa-version.test.js +0 -14
  404. package/dist/test/test/viewer/spa-version.test.js.map +0 -1
  405. package/dist/test/test/viewer/url-builder.test.d.ts +0 -2
  406. package/dist/test/test/viewer/url-builder.test.d.ts.map +0 -1
  407. package/dist/test/test/viewer/url-builder.test.js +0 -98
  408. package/dist/test/test/viewer/url-builder.test.js.map +0 -1
  409. package/dist/test/test/workspace/content-hash.test.d.ts +0 -2
  410. package/dist/test/test/workspace/content-hash.test.d.ts.map +0 -1
  411. package/dist/test/test/workspace/content-hash.test.js +0 -17
  412. package/dist/test/test/workspace/content-hash.test.js.map +0 -1
  413. package/dist/test/test/workspace/hosted-stub.test.d.ts +0 -2
  414. package/dist/test/test/workspace/hosted-stub.test.d.ts.map +0 -1
  415. package/dist/test/test/workspace/hosted-stub.test.js +0 -30
  416. package/dist/test/test/workspace/hosted-stub.test.js.map +0 -1
  417. package/dist/test/test/workspace/in-memory.test.d.ts +0 -2
  418. package/dist/test/test/workspace/in-memory.test.d.ts.map +0 -1
  419. package/dist/test/test/workspace/in-memory.test.js +0 -4
  420. package/dist/test/test/workspace/in-memory.test.js.map +0 -1
  421. package/dist/test/test/workspace/node-fs.test.d.ts +0 -2
  422. package/dist/test/test/workspace/node-fs.test.d.ts.map +0 -1
  423. package/dist/test/test/workspace/node-fs.test.js +0 -20
  424. package/dist/test/test/workspace/node-fs.test.js.map +0 -1
  425. package/dist/test/test/workspace/s3.test.d.ts +0 -2
  426. package/dist/test/test/workspace/s3.test.d.ts.map +0 -1
  427. package/dist/test/test/workspace/s3.test.js +0 -153
  428. package/dist/test/test/workspace/s3.test.js.map +0 -1
  429. package/dist/test/test/workspace/scoped.test.d.ts +0 -2
  430. package/dist/test/test/workspace/scoped.test.d.ts.map +0 -1
  431. package/dist/test/test/workspace/scoped.test.js +0 -59
  432. package/dist/test/test/workspace/scoped.test.js.map +0 -1
  433. package/dist/test/test/workspace/shared-contract.d.ts +0 -3
  434. package/dist/test/test/workspace/shared-contract.d.ts.map +0 -1
  435. package/dist/test/test/workspace/shared-contract.js +0 -92
  436. package/dist/test/test/workspace/shared-contract.js.map +0 -1
  437. package/dist/test/tsconfig.test.tsbuildinfo +0 -1
  438. package/dist/test/vitest.config.d.ts +0 -3
  439. package/dist/test/vitest.config.d.ts.map +0 -1
  440. package/dist/test/vitest.config.js +0 -8
  441. package/dist/test/vitest.config.js.map +0 -1
  442. package/dist/tools/metrics.d.ts +0 -11
  443. package/dist/tools/metrics.d.ts.map +0 -1
  444. package/dist/tools/metrics.js +0 -28
  445. package/dist/tools/metrics.js.map +0 -1
  446. package/dist/tools/open-editor.d.ts.map +0 -1
  447. package/dist/tools/open-editor.js.map +0 -1
  448. /package/dist/{dashboard → diagnostics}/spa-assets.generated.d.ts +0 -0
@@ -1,810 +1,158 @@
1
1
  // AUTO-GENERATED by scripts/generate-spa-assets.mjs. Do not edit by hand.
2
2
  export const SPA_ASSETS = {
3
- "index.html": `<!doctype html>
4
- <html lang="en">
5
- <head>
6
- <meta charset="utf-8" />
7
- <title>codesign viewer</title>
8
- <meta name="viewport" content="width=device-width,initial-scale=1" />
9
- <!-- Mount prefix injected by the viewer router (router.ts). "" on the stdio
10
- listener, "/viewer" on the HTTP transport. Read by viewer.js. -->
11
- <script>
12
- window.__VIEWER_BASE__ = '{{VIEWER_BASE}}';
13
- </script>
14
- <style>
15
- /* -------- reset & canvas -------- */
16
- html,
17
- body {
18
- margin: 0;
19
- padding: 0;
20
- height: 100%;
21
- width: 100%;
22
- background: #1d1d1f;
23
- color: #eee;
24
- font-family: -apple-system, system-ui, sans-serif;
25
- }
26
- #cesdk {
27
- position: fixed;
28
- inset: 0;
29
- }
30
-
31
- /* -------- pill base (spec §7) -------- */
32
- .pill {
33
- display: inline-flex;
34
- align-items: center;
35
- gap: 5px;
36
- padding: 6px 11px;
37
- border-radius: 999px;
38
- font:
39
- 500 12px -apple-system,
40
- system-ui,
41
- sans-serif;
42
- color: #e8e8e8;
43
- background: rgba(20, 20, 22, 0.72);
44
- backdrop-filter: blur(12px);
45
- -webkit-backdrop-filter: blur(12px);
46
- border: 1px solid rgba(255, 255, 255, 0.08);
47
- box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
48
- text-decoration: none;
49
- cursor: pointer;
50
- white-space: nowrap;
51
- }
52
- .pill.icon {
53
- padding: 6px 9px;
54
- }
55
- .pill:hover {
56
- background: rgba(30, 30, 33, 0.82);
57
- }
58
-
59
- /* -------- cluster anchors -------- */
60
- #bar-left,
61
- #bar-right {
62
- position: fixed;
63
- top: 14px;
64
- z-index: 10;
65
- display: flex;
66
- gap: 8px;
67
- }
68
- #bar-left {
69
- left: 14px;
70
- }
71
- #bar-right {
72
- right: 14px;
73
- }
74
-
75
- /* -------- title pill --------
76
- Non-interactive (no click handler) — override .pill's cursor + hover so
77
- the title visually matches its actual behavior. For a local MCP the URL
78
- isn't shareable, so there's nothing useful to copy. Revisit when a
79
- remote MCP ships — copy-share-URL is the natural fit there. */
80
- #title {
81
- font-weight: 600;
82
- max-width: 240px;
83
- overflow: hidden;
84
- text-overflow: ellipsis;
85
- white-space: nowrap;
86
- cursor: default;
87
- }
88
- #title:hover {
89
- background: rgba(20, 20, 22, 0.72);
90
- }
91
-
92
- /* -------- LIVE pill (read-only) -------- */
93
- .pill.live {
94
- color: #c8e6c9;
95
- cursor: default;
96
- }
97
- .pill.live:hover {
98
- background: rgba(20, 20, 22, 0.72);
99
- }
100
- .pill.live::before {
101
- content: '';
102
- width: 6px;
103
- height: 6px;
104
- border-radius: 999px;
105
- background: #34c759;
106
- box-shadow: 0 0 5px rgba(52, 199, 89, 0.55);
107
- }
108
-
109
- /* -------- hash chip -------- */
110
- .pill.hash {
111
- font-family: ui-monospace, monospace;
112
- font-size: 11px;
113
- color: #b8b8b8;
114
- }
115
-
116
- /* -------- Go Live pill + count badge -------- */
117
- .pill.golive {
118
- color: #9ec8ff;
119
- }
120
- .pill.golive .count {
121
- display: inline-flex;
122
- align-items: center;
123
- justify-content: center;
124
- min-width: 16px;
125
- height: 16px;
126
- padding: 0 4px;
127
- border-radius: 999px;
128
- background: #2b6cb0;
129
- color: #fff;
130
- font-size: 10px;
131
- font-weight: 600;
132
- margin-left: 4px;
133
- }
134
-
135
- /* -------- History pill -------- */
136
- .pill.history::before {
137
- content: '⎇';
138
- opacity: 0.8;
139
- }
140
- .pill.history.active,
141
- .pill.history.active:hover {
142
- background: rgba(43, 108, 176, 0.85);
143
- border-color: rgba(43, 108, 176, 0.95);
144
- color: #fff;
145
- }
146
-
147
- /* -------- floating history panel (spec §6) -------- */
148
- #revision-panel {
149
- position: fixed;
150
- top: 62px;
151
- right: 14px;
152
- bottom: 14px;
153
- width: 280px;
154
- background: rgba(20, 20, 22, 0.88);
155
- backdrop-filter: blur(16px);
156
- -webkit-backdrop-filter: blur(16px);
157
- border: 1px solid rgba(255, 255, 255, 0.08);
158
- border-radius: 12px;
159
- box-shadow: 0 14px 36px rgba(0, 0, 0, 0.42);
160
- box-sizing: border-box;
161
- overflow-y: auto;
162
- display: none;
163
- opacity: 0;
164
- transform: translateX(8px);
165
- z-index: 10;
166
- }
167
- body.panel-open #revision-panel {
168
- display: block;
169
- }
170
- @media (prefers-reduced-motion: no-preference) {
171
- #revision-panel {
172
- transition:
173
- opacity 160ms ease-out,
174
- transform 160ms ease-out;
175
- }
176
- body.panel-open #revision-panel {
177
- opacity: 1;
178
- transform: translateX(0);
179
- }
180
- }
181
- @media (prefers-reduced-motion: reduce) {
182
- body.panel-open #revision-panel {
183
- opacity: 1;
184
- transform: none;
185
- }
186
- }
187
-
188
- /* -------- panel internals (polish pass) -------- */
189
- .rp-header {
190
- padding: 12px 14px 10px;
191
- font-size: 10px;
192
- letter-spacing: 0.08em;
193
- text-transform: uppercase;
194
- color: #888;
195
- border-bottom: 1px solid rgba(255, 255, 255, 0.06);
196
- }
197
- .rp-tree {
198
- padding: 8px 6px;
199
- }
200
- .rp-empty {
201
- padding: 16px 14px;
202
- font-size: 12px;
203
- color: #888;
204
- }
205
-
206
- .rp-node {
207
- display: block;
208
- text-decoration: none;
209
- color: inherit;
210
- padding: 6px 8px;
211
- border-radius: 6px;
212
- }
213
- .rp-node:hover {
214
- background: rgba(255, 255, 255, 0.04);
215
- }
216
- .rp-node.rp-current {
217
- background: rgba(30, 47, 68, 0.6);
218
- box-shadow: inset 2px 0 0 #4a9eff;
219
- }
220
- .rp-node-row {
221
- display: flex;
222
- gap: 8px;
223
- align-items: baseline;
224
- }
225
- .rp-elbow {
226
- color: #555;
227
- flex: none;
228
- }
229
- .rp-dot {
230
- flex: none;
231
- width: 12px;
232
- text-align: center;
233
- font-size: 10px;
234
- color: #888;
235
- }
236
- .rp-node.rp-head .rp-dot {
237
- color: #4a9eff;
238
- }
239
- .rp-node.rp-branch .rp-dot {
240
- color: #d6973f;
241
- }
242
- .rp-body {
243
- flex: 1;
244
- min-width: 0;
245
- }
246
- .rp-id {
247
- display: flex;
248
- align-items: center;
249
- gap: 6px;
250
- font-family: ui-monospace, monospace;
251
- font-size: 10px;
252
- color: #888;
253
- }
254
- .rp-tag {
255
- border: 1px solid rgba(255, 255, 255, 0.1);
256
- border-radius: 4px;
257
- padding: 0 4px;
258
- font-size: 9px;
259
- }
260
- .rp-tag.rp-tag-head {
261
- background: rgba(43, 108, 176, 0.85);
262
- border-color: rgba(43, 108, 176, 0.95);
263
- color: #fff;
264
- }
265
- .rp-tag.rp-tag-fork {
266
- color: #d6973f;
267
- border-color: rgba(214, 151, 63, 0.35);
268
- }
269
- .rp-note {
270
- font-size: 12px;
271
- color: #eee;
272
- white-space: nowrap;
273
- overflow: hidden;
274
- text-overflow: ellipsis;
275
- }
276
- .rp-time {
277
- font-size: 10px;
278
- color: #777;
279
- }
280
- .rp-node.rp-current .rp-time::after {
281
- content: ' · on canvas';
282
- color: #4a9eff;
283
- }
284
- .rp-copy {
285
- font: inherit;
286
- font-size: 9px;
287
- color: #ccc;
288
- background: rgba(20, 20, 22, 0.72);
289
- border: 1px solid rgba(255, 255, 255, 0.08);
290
- border-radius: 999px;
291
- padding: 2px 7px;
292
- cursor: pointer;
293
- opacity: 0;
294
- margin-left: auto;
295
- transition:
296
- opacity 120ms ease-out,
297
- background 120ms ease-out;
298
- }
299
- .rp-node:hover .rp-copy {
300
- opacity: 1;
301
- }
302
- .rp-copy:hover {
303
- background: rgba(30, 30, 33, 0.82);
304
- }
305
- </style>
306
- </head>
307
- <body>
308
- <div id="cesdk"></div>
309
- <div id="bar-left">
310
- <a id="back" class="pill icon" href="{{VIEWER_BASE}}/">←</a>
311
- <span id="title" class="pill">loading…</span>
312
- </div>
313
- <div id="bar-right"></div>
314
- <aside id="revision-panel"></aside>
315
- <script type="module" src="{{VIEWER_BASE}}/static/viewer.js"></script>
316
- </body>
317
- </html>
318
- `,
319
- "revision-panel.js": `// revision-panel.js — the in-viewer revision history panel. Fetches a design's
320
- // full revision tree from /api/design/<root>/revisions and renders it; clicking
321
- // a node asks the viewer to navigate there. The viewer drives setCurrent()
322
- // (highlight) and refresh() (re-fetch on SSE).
323
- //
324
- // All DOM is built with createElement + textContent: revision notes are
325
- // agent-authored text and must never reach innerHTML.
326
-
327
- // Mount prefix set by index.html ("" on stdio, "/viewer" on HTTP); node hrefs
328
- // and the revisions fetch are built relative to it. See router.ts.
329
- const BASE = window.__VIEWER_BASE__ ?? '';
330
-
331
- export function createRevisionPanel({ container, root, onNavigate, onCount }) {
332
- let currentRev = null;
333
-
334
- function relTime(ms) {
335
- const s = Math.max(0, Math.round((Date.now() - ms) / 1000));
336
- if (s < 60) return \`\${s}s ago\`;
337
- const m = Math.round(s / 60);
338
- if (m < 60) return \`\${m}m ago\`;
339
- const h = Math.round(m / 60);
340
- if (h < 24) return \`\${h}h ago\`;
341
- return \`\${Math.round(h / 24)}d ago\`;
342
- }
343
-
344
- // Depth-first walk. At each node the spine child (or, off-spine, the latest
345
- // child) continues at the same depth; other children are branches at
346
- // depth + 1, emitted before the continuation so the spine reads top-to-bottom.
347
- function order(nodes) {
348
- const byId = new Map(nodes.map((n) => [n.id, n]));
349
- const children = new Map();
350
- for (const n of nodes) {
351
- if (n.parent != null) {
352
- const sibs = children.get(n.parent) ?? [];
353
- sibs.push(n);
354
- children.set(n.parent, sibs);
355
- }
356
- }
357
- for (const sibs of children.values())
358
- sibs.sort((a, b) => a.createdAt - b.createdAt);
359
- const rootNode = nodes.find((n) => n.parent === null);
360
- const head = nodes.reduce((a, b) => (b.createdAt > a.createdAt ? b : a));
361
- const spine = new Set();
362
- for (let n = head; n; n = n.parent != null ? byId.get(n.parent) : null)
363
- spine.add(n.id);
364
- const out = [];
365
- function walk(node, depth) {
366
- out.push({
367
- node,
368
- depth,
369
- isHead: node.id === head.id,
370
- isRoot: node.parent === null
371
- });
372
- const kids = children.get(node.id) ?? [];
373
- if (kids.length === 0) return;
374
- const primary =
375
- kids.find((k) => spine.has(k.id)) ?? kids[kids.length - 1];
376
- for (const k of kids) if (k !== primary) walk(k, depth + 1);
377
- walk(primary, depth);
378
- }
379
- if (rootNode) walk(rootNode, 0);
380
- return out;
381
- }
382
-
383
- function tag(text, cls) {
384
- const el = document.createElement('span');
385
- el.className = cls ? \`rp-tag \${cls}\` : 'rp-tag';
386
- el.textContent = text;
387
- return el;
388
- }
389
-
390
- function makeNode({ node, depth, isHead, isRoot }) {
391
- const a = document.createElement('a');
392
- a.className = \`rp-node\${depth > 0 ? ' rp-branch' : ''}\${isHead ? ' rp-head' : ''}\`;
393
- if (isRoot) a.dataset.root = '';
394
- a.dataset.rev = node.id;
395
- a.href = \`\${BASE}/d/\${root}/\${node.id}\`;
396
- a.style.marginLeft = \`\${depth * 16}px\`;
397
- if (node.id === currentRev) a.classList.add('rp-current');
398
-
399
- const row = document.createElement('div');
400
- row.className = 'rp-node-row';
401
- if (depth > 0) {
402
- const elbow = document.createElement('span');
403
- elbow.className = 'rp-elbow';
404
- elbow.textContent = '└';
405
- row.appendChild(elbow);
406
- }
407
- const dot = document.createElement('span');
408
- dot.className = 'rp-dot';
409
- dot.textContent = '●';
410
- row.appendChild(dot);
411
-
412
- const body = document.createElement('div');
413
- body.className = 'rp-body';
414
-
415
- const idLine = document.createElement('div');
416
- idLine.className = 'rp-id';
417
- const idText = document.createElement('span');
418
- idText.textContent = node.id.slice(0, 7);
419
- idLine.appendChild(idText);
420
- if (isRoot) idLine.appendChild(tag('root', ''));
421
- if (isHead) idLine.appendChild(tag('head', 'rp-tag-head'));
422
- if (depth > 0) idLine.appendChild(tag('⑂ fork', 'rp-tag-fork'));
423
-
424
- const copy = document.createElement('button');
425
- copy.type = 'button';
426
- copy.className = 'rp-copy';
427
- copy.textContent = '⧉ copy id';
428
- copy.addEventListener('click', async (e) => {
429
- e.preventDefault();
430
- e.stopPropagation();
431
- try {
432
- await navigator.clipboard?.writeText(node.id);
433
- copy.textContent = 'copied';
434
- setTimeout(() => {
435
- copy.textContent = '⧉ copy id';
436
- }, 1200);
437
- } catch {
438
- /* clipboard unavailable — fail quietly (spec §6) */
439
- }
440
- });
441
- idLine.appendChild(copy);
442
- body.appendChild(idLine);
443
-
444
- const note = document.createElement('div');
445
- note.className = 'rp-note';
446
- note.textContent = node.note || '(no note)';
447
- note.title = node.note || '';
448
- body.appendChild(note);
449
-
450
- const time = document.createElement('div');
451
- time.className = 'rp-time';
452
- time.textContent = relTime(node.createdAt);
453
- body.appendChild(time);
454
-
455
- row.appendChild(body);
456
- a.appendChild(row);
457
-
458
- a.addEventListener('click', (e) => {
459
- // Modified clicks (⌘/Ctrl/Shift/middle) fall through to the browser,
460
- // which opens the pinned /d/<root>/<rev> href in a new tab.
461
- if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
462
- return;
463
- e.preventDefault();
464
- onNavigate(node.id);
465
- });
466
- return a;
467
- }
468
-
469
- function paint(nodes) {
470
- container.replaceChildren();
471
- const header = document.createElement('div');
472
- header.className = 'rp-header';
473
- header.textContent = 'Revisions';
474
- container.appendChild(header);
475
- if (nodes.length === 0) {
476
- const empty = document.createElement('div');
477
- empty.className = 'rp-empty';
478
- empty.textContent = 'no revisions yet';
479
- container.appendChild(empty);
480
- return;
481
- }
482
- const tree = document.createElement('div');
483
- tree.className = 'rp-tree';
484
- for (const item of order(nodes)) tree.appendChild(makeNode(item));
485
- container.appendChild(tree);
486
- }
487
-
488
- function showError(message) {
489
- container.replaceChildren();
490
- const el = document.createElement('div');
491
- el.className = 'rp-empty';
492
- el.textContent = message;
493
- container.appendChild(el);
494
- }
495
-
496
- async function refresh() {
497
- try {
498
- const res = await fetch(\`\${BASE}/api/design/\${root}/revisions\`);
499
- if (!res.ok) throw new Error(String(res.status));
500
- const nodes = await res.json();
501
- paint(nodes);
502
- onCount(nodes.length);
503
- } catch {
504
- showError("couldn't load history");
505
- onCount(0);
506
- }
507
- }
508
-
509
- function setCurrent(rev) {
510
- currentRev = rev;
511
- for (const el of container.querySelectorAll('.rp-node')) {
512
- el.classList.toggle('rp-current', el.dataset.rev === rev);
513
- }
514
- }
515
-
516
- refresh();
517
- return { setCurrent, refresh };
518
- }
519
- `,
520
- "studio.html": `<!doctype html>
521
- <html lang="en">
522
- <head>
523
- <meta charset="utf-8" />
524
- <title>IMG.LY CODESIGN MCP Workspace</title>
525
- <meta name="viewport" content="width=device-width,initial-scale=1" />
526
- <!-- Mount prefix injected by the viewer router (router.ts). "" on the stdio
527
- listener, "/viewer" on the HTTP transport. Read by studio.js. -->
528
- <script>
529
- window.__VIEWER_BASE__ = '{{VIEWER_BASE}}';
530
- </script>
531
- <style>
532
- /* -------- reset --------
533
- Same palette + typography as the viewer SPA so the overview reads
534
- as part of the same product. See src/viewer/spa/index.html. */
535
- html,
536
- body {
537
- margin: 0;
538
- padding: 0;
539
- min-height: 100%;
540
- background: #1d1d1f;
541
- color: #eee;
542
- font-family: -apple-system, system-ui, sans-serif;
543
- }
544
-
545
- /* -------- sticky frosted strip --------
546
- Same frosted-glass material as the viewer pills + history panel:
547
- translucent dark + backdrop-blur + 1px hairline border. Sticky so
548
- the title stays visible while scrolling. */
549
- #bar {
550
- position: sticky;
551
- top: 0;
552
- z-index: 10;
553
- padding: 14px 24px;
554
- background: rgba(20, 20, 22, 0.72);
555
- backdrop-filter: blur(12px);
556
- -webkit-backdrop-filter: blur(12px);
557
- border-bottom: 1px solid rgba(255, 255, 255, 0.08);
558
- }
559
- #bar .title {
560
- font:
561
- 600 14px -apple-system,
562
- system-ui,
563
- sans-serif;
564
- letter-spacing: 0.02em;
565
- color: #e8e8e8;
566
- }
567
-
568
- /* -------- design grid -------- */
569
- .grid {
570
- display: grid;
571
- grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
572
- gap: 16px;
573
- padding: 24px;
574
- }
575
-
576
- .empty {
577
- padding: 24px 4px;
578
- font-size: 13px;
579
- color: #888;
580
- }
581
- .empty code {
582
- font-family: ui-monospace, monospace;
583
- background: rgba(255, 255, 255, 0.05);
584
- border: 1px solid rgba(255, 255, 255, 0.08);
585
- border-radius: 4px;
586
- padding: 1px 6px;
587
- font-size: 12px;
588
- color: #cfcfcf;
589
- }
590
-
591
- /* -------- card --------
592
- Frosted-glass card, same materials as the history panel rows.
593
- Hover lifts the card and brightens the border + shadow to match the
594
- pill-hover pattern in the viewer chrome. */
595
- .card {
596
- display: block;
597
- text-decoration: none;
598
- color: inherit;
599
- background: rgba(20, 20, 22, 0.72);
600
- backdrop-filter: blur(12px);
601
- -webkit-backdrop-filter: blur(12px);
602
- border: 1px solid rgba(255, 255, 255, 0.08);
603
- border-radius: 12px;
604
- box-shadow: 0 4px 14px rgba(0, 0, 0, 0.25);
605
- overflow: hidden;
606
- transition:
607
- transform 120ms ease-out,
608
- border-color 120ms ease-out,
609
- box-shadow 120ms ease-out;
610
- }
611
- .card:hover {
612
- transform: translateY(-2px);
613
- border-color: rgba(255, 255, 255, 0.15);
614
- box-shadow: 0 8px 22px rgba(0, 0, 0, 0.35);
615
- }
616
- .card img {
617
- display: block;
618
- width: 100%;
619
- aspect-ratio: 4/3;
620
- object-fit: cover;
621
- background: #000;
622
- }
623
- .card .meta {
624
- padding: 12px 14px;
625
- }
626
- .card .title {
627
- font:
628
- 600 13px -apple-system,
629
- system-ui,
630
- sans-serif;
631
- color: #eee;
632
- overflow: hidden;
633
- text-overflow: ellipsis;
634
- white-space: nowrap;
635
- }
636
- .card .updated {
637
- margin-top: 4px;
638
- font-size: 11px;
639
- color: #888;
640
- }
641
- </style>
642
- </head>
643
- <body>
644
- <header id="bar">
645
- <span class="title">IMG.LY CODESIGN MCP Workspace</span>
646
- </header>
647
- <div id="grid" class="grid"></div>
648
- <script type="module" src="{{VIEWER_BASE}}/static/studio.js"></script>
649
- </body>
650
- </html>
651
- `,
652
- "studio.js": `// Workspace overview landing page. Fetches /api/designs and renders one card
653
- // per design (keyed by root revision). Re-renders on tab focus and a 10s poll
654
- // so card links and thumbnails never go stale after the agent edits.
655
- //
656
- // DOM is built with createElement + textContent (never innerHTML for
657
- // design-supplied strings) so an author-controlled title can't escape into
658
- // HTML. Same pattern as viewer.js.
659
-
660
- // Mount prefix set by studio.html ("" on stdio, "/viewer" on HTTP); card
661
- // links, thumbnails, and the designs fetch are built relative to it. See
662
- // router.ts.
663
- const BASE = window.__VIEWER_BASE__ ?? '';
664
-
665
- const grid = document.getElementById('grid');
666
-
667
- function buildCard(d) {
668
- const card = document.createElement('a');
669
- card.className = 'card';
670
- card.href = \`\${BASE}/d/\${d.rootRevision}\`;
671
-
672
- const img = document.createElement('img');
673
- img.src = \`\${BASE}/snapshot/\${d.latestLeaf}/thumbnail\`;
674
- img.alt = '';
675
- img.addEventListener('error', () => {
676
- img.style.display = 'none';
677
- });
678
- card.appendChild(img);
679
-
680
- const meta = document.createElement('div');
681
- meta.className = 'meta';
682
-
683
- const title = document.createElement('div');
684
- title.className = 'title';
685
- title.textContent = d.title || 'Untitled';
686
- meta.appendChild(title);
687
-
688
- const updated = document.createElement('div');
689
- updated.className = 'updated';
690
- updated.textContent = \`updated \${new Date(d.updatedAt).toLocaleString()}\`;
691
- meta.appendChild(updated);
692
-
693
- card.appendChild(meta);
694
- return card;
695
- }
696
-
697
- function renderEmptyText(text) {
698
- const div = document.createElement('div');
699
- div.className = 'empty';
700
- div.textContent = text;
701
- grid.replaceChildren(div);
702
- }
703
-
704
- function renderFirstRunEmpty() {
705
- const div = document.createElement('div');
706
- div.className = 'empty';
707
- div.appendChild(document.createTextNode('No designs yet — your first '));
708
- const code = document.createElement('code');
709
- code.textContent = 'edit({ parent: null, ... })';
710
- div.appendChild(code);
711
- div.appendChild(document.createTextNode(' will land here.'));
712
- grid.replaceChildren(div);
713
- }
714
-
715
- async function render() {
716
- const res = await fetch(\`\${BASE}/api/designs\`);
717
- if (!res.ok) {
718
- renderEmptyText(\`failed to load designs: \${res.status}\`);
719
- return;
720
- }
721
- const designs = await res.json();
722
- if (designs.length === 0) {
723
- renderFirstRunEmpty();
724
- return;
725
- }
726
- grid.replaceChildren(...designs.map(buildCard));
727
- }
728
-
729
- // \`render()\` rejects on a network error; every caller logs it rather than
730
- // leaving an unhandled promise rejection.
731
- await render().catch(console.error);
732
- document.addEventListener('visibilitychange', () => {
733
- if (!document.hidden) render().catch(console.error);
734
- });
735
- setInterval(() => render().catch(console.error), 10000);
736
- `,
737
- "viewer.js": `// Read-only CE.SDK viewer with a revision-history side panel.
3
+ "editor.js": `// The CoDesign editor — the single browser surface for a design (CODE-74).
738
4
  // /d/<root> LIVE — opens at the design head, follows new revisions
739
5
  // /d/<root>/<rev> PINNED — opens frozen at <rev>
740
6
  //
741
- // Chrome is floating pill clusters in the corners; the history panel is a
742
- // detached card on the right.
743
- // See: docs/superpowers/specs/2026-05-24-viewer-ui-refresh-design.md
7
+ // The canvas is FULLY EDITABLE from the start; the read-only viewer and the
8
+ // detached "Edit a copy" tab are gone. Edits are LOCAL TO THE TAB: nothing
9
+ // writes back to the workspace or notifies the agent — the export actions in
10
+ // the navigation bar (archive / image / PDF) are the only way work leaves
11
+ // this tab. While the tab is untouched, /d/<root> live-follows the head over
12
+ // SSE; the FIRST local edit pauses following, pins the URL, and shows an
13
+ // infinite "not saved back" warning (see the dirty tracking below).
14
+ // See docs/specs/2026-07-10-merged-editor-design.md.
744
15
  //
745
- // Navigation is client-side: clicking a panel node (or the "Go Live →"
746
- // affordance) swaps the canvas and rewrites the URL via the History API — one
747
- // CE.SDK instance for the life of the tab. The SSE connection is held open
748
- // in BOTH modes so the panel's tree always stays fresh; it moves the canvas
749
- // only while \`following\`.
750
-
751
- // Mount prefix set by index.html ("" on stdio, "/viewer" on HTTP). Every
752
- // API/asset/history URL below is built relative to it so the SPA works under
753
- // any mount path. See router.ts.
16
+ // CE.SDK ships NO default UI since v1.69: \`create()\` renders only the bare
17
+ // canvas and every UI area starts EMPTY. The canonical design-editor UI is the
18
+ // \`DesignEditorConfig\` plugin from \`@cesdk/core-configs-web/design-editor\`,
19
+ // but that package is NOT published to npm or the CDN, so it can't be imported
20
+ // in this bundler-less browser module that loads CE.SDK straight from the CDN.
21
+ // \`configureDesignEditorUI()\` below therefore mirrors what \`DesignEditorConfig\`
22
+ // does the exact features, actions and component orders from IMG.LY's Design
23
+ // Editor starter kit via the public Order API, trimmed for this surface: the
24
+ // asset-library dock IS present, with Text / Shapes / Stickers fed by CE.SDK's
25
+ // default asset sources (no demo/stock media, no uploads); video/audio
26
+ // features stay off (so a static design never shows a timeline).
27
+ // See docs/specs/2026-07-07-codesign-detached-editor-design.md.
28
+
29
+ // Mount prefix set by index.html ("" on stdio, "/viewer" on HTTP). See router.ts.
754
30
  const BASE = window.__VIEWER_BASE__ ?? '';
755
31
 
756
- // Path of the current page with the mount prefix stripped, so PATH_RE (which
757
- // is written relative to the SPA root) matches under "/viewer" too.
32
+ const { order, relTime, fetchRevisions } = await import(
33
+ \`\${BASE}/static/revision-data.js\`
34
+ );
35
+
758
36
  const relPath = () =>
759
37
  location.pathname.startsWith(BASE)
760
38
  ? location.pathname.slice(BASE.length)
761
39
  : location.pathname;
762
40
 
763
- const { createRevisionPanel } = await import(
764
- \`\${BASE}/static/revision-panel.js\`
765
- );
766
-
767
41
  const PATH_RE = /^\\/d\\/([0-9a-f]{12})(?:\\/([0-9a-f]{12}))?$/;
768
- const titleEl = document.getElementById('title');
769
- const barRightEl = document.getElementById('bar-right');
770
- const panelEl = document.getElementById('revision-panel');
42
+ const statusEl = document.getElementById('status');
43
+
44
+ const HISTORY_PANEL = 'codesign.revisionHistory';
771
45
 
772
46
  const match = relPath().match(PATH_RE);
773
47
 
774
48
  if (!match) {
775
- titleEl.textContent = 'invalid URL';
49
+ statusEl.textContent = 'invalid URL';
776
50
  } else {
777
51
  const root = match[1];
778
52
 
779
- // Server-supplied license + resource base, fetched before bootstrapping
780
- // CE.SDK. CE.SDK validates the license regardless of role; \`resourceBase\`
781
- // is where workspace:// asset URIs resolve.
53
+ // Server-supplied license + resource base + engine URL.
782
54
  const cfgRes = await fetch(\`\${BASE}/api/config\`);
783
55
  if (!cfgRes.ok) throw new Error(\`/api/config failed: \${cfgRes.status}\`);
784
- const { license, resourceBase, cesdkEngineUrl } = await cfgRes.json();
56
+ const { license, resourceBase, cesdkEngineUrl, defaultAssetsBaseUrl } =
57
+ await cfgRes.json();
785
58
 
786
59
  const cesdkMod = await import(cesdkEngineUrl);
787
60
  const CreativeEditorSDK = cesdkMod.default ?? cesdkMod.CreativeEditorSDK;
788
- const instance = await CreativeEditorSDK.create(
61
+
62
+ // Engine WASM/data (\`baseURL\`) and UI icons/CSS (\`ui.baseURL\`) both load from
63
+ // the CDN we imported the SDK from (\`…/index.js\` → \`…/assets[/ui/]\`), so they
64
+ // track the engine version and the release-vs-nightly path shape.
65
+ const assetBase = cesdkEngineUrl.replace(/\\/index\\.js$/, '/assets');
66
+ const cesdk = await CreativeEditorSDK.create(
789
67
  document.getElementById('cesdk'),
790
68
  {
791
69
  license,
792
- role: 'Viewer',
793
- theme: 'dark',
794
- // The refresh moved all chrome out of CE.SDK and into our own floating
795
- // pills + history panel — back / title / LIVE / Go Live / hash / History.
796
- // CE.SDK's own nav + dock would render in the same physical space and,
797
- // because #cesdk has no stacking context, their internal z-indexes
798
- // outrank our z:10 pills, hiding them. \`ui.hide: true\` disables the
799
- // CE.SDK chrome entirely; users keep mousewheel zoom + drag-to-pan via
800
- // the engine, which is unaffected.
801
- ui: { hide: true }
70
+ baseURL: assetBase,
71
+ ui: { baseURL: assetBase + '/ui/' },
72
+ theme: 'dark'
802
73
  }
803
74
  );
75
+ const engine = cesdk.engine;
76
+
77
+ configureDesignEditorUI(cesdk);
78
+
79
+ // Register CE.SDK's DEFAULT asset sources — shapes, stickers, typefaces,
80
+ // color palettes, LUT filters/effects, page & crop presets — never the
81
+ // demo ones (no stock images/videos/audio). The manifest base URL comes
82
+ // from /api/config so the browser and the headless engine resolve the
83
+ // same manifests (IMGLY_DEFAULT_ASSETS_BASE_URL-aware). \`ly.img.text\` is
84
+ // excluded exactly like the node side (its manifest is missing from the
85
+ // CDN manifest root and one 404 rejects the whole call). Page presets
86
+ // ride along here — the former page-presets plugin registered the same
87
+ // source id and the two cannot coexist (colliding registrations reject
88
+ // the whole call), so the plugin is gone; the \`ly.img.pagePresets\` LIBRARY
89
+ // ENTRY the Resize Page panel reads is wired in configureDesignEditorUI.
90
+ // Best-effort and isolated in its own try/catch: a down/misconfigured
91
+ // IMGLY_DEFAULT_ASSETS_BASE_URL leaves the editor usable with an empty
92
+ // shapes/stickers/typefaces/LUT-filters/presets library, and must NOT take
93
+ // down the engine-CDN sources registered below (different host, see that
94
+ // block's comment).
95
+ try {
96
+ await cesdk.addDefaultAssetSources({
97
+ baseURL: defaultAssetsBaseUrl,
98
+ excludeAssetSourceIds: ['ly.img.text']
99
+ });
100
+ } catch (err) {
101
+ console.error(
102
+ 'codesign: asset-source registration failed — asset library will be empty',
103
+ err
104
+ );
105
+ }
106
+
107
+ // The engine-assets manifests below cover what the default manifest root
108
+ // does NOT carry (each 404s under <defaultAssetsBaseUrl>/<id>/content.json
109
+ // but is published under <engine>/assets/<id>/content.json, where the CDN
110
+ // plugins bundle used to load them from):
111
+ // - the stock \`ly.img.text\` library entry queries the four text sources —
112
+ // with none registered the Text dock panel shows "No Elements";
113
+ // - the appearance Filter panel hard-queries the combined \`ly.img.filter\`
114
+ // source (not filter.lut/filter.duotone) and shows an infinite spinner
115
+ // while that id is unregistered;
116
+ // - the stock Shapes entry titles its group headers with
117
+ // \`libraries.ly.img.vector.shape.<group>.label\`, and the UI bundle only
118
+ // ships translations for THIS source's groups (filled/outline/gradient/
119
+ // image/abstract-*) — the default root's vectorpath source has verbose
120
+ // category group ids that render as raw i18n keys.
121
+ // These six are registered in parallel via Promise.allSettled, each
122
+ // isolated from the others' failures — one 404 must not take out the rest
123
+ // of the library. This block runs UNCONDITIONALLY, regardless of whether
124
+ // addDefaultAssetSources above succeeded: the two target different hosts —
125
+ // a down/misconfigured IMGLY_DEFAULT_ASSETS_BASE_URL must not blank the
126
+ // Text/Filter panels whose manifests come from the (independent) engine
127
+ // CDN. No try/catch needed here: Promise.allSettled never rejects.
128
+ const engineAssetSourceIds = [
129
+ 'ly.img.text',
130
+ 'ly.img.text.styles',
131
+ 'ly.img.text.components',
132
+ 'ly.img.text.curves',
133
+ 'ly.img.filter',
134
+ 'ly.img.vector.shape'
135
+ ];
136
+ const results = await Promise.allSettled(
137
+ engineAssetSourceIds.map((id) =>
138
+ engine.asset.addLocalAssetSourceFromJSONURI(
139
+ \`\${assetBase}/\${id}/content.json\`
140
+ )
141
+ )
142
+ );
143
+ results.forEach((result, index) => {
144
+ if (result.status === 'rejected') {
145
+ console.error(
146
+ 'codesign: failed to register asset source',
147
+ engineAssetSourceIds[index],
148
+ result.reason
149
+ );
150
+ }
151
+ });
804
152
 
805
153
  // Resolve workspace:// asset URIs to the /r/ HTTP route.
806
154
  const absResourceBase = location.origin + resourceBase;
807
- instance.engine.editor.setURIResolver((uri, defaultResolver) =>
155
+ engine.editor.setURIResolver((uri, defaultResolver) =>
808
156
  uri.startsWith('workspace://')
809
157
  ? absResourceBase + uri.slice('workspace://'.length)
810
158
  : defaultResolver(uri)
@@ -814,89 +162,78 @@ if (!match) {
814
162
  let head = null; // the design's latest leaf
815
163
  let currentRev = null; // the revision currently on canvas
816
164
  let following = false; // true → the canvas tracks head as SSE arrives
165
+ let dirty = false; // true → a local edit happened since the last load
166
+ let loading = false; // true while show() swaps the scene programmatically
167
+ let warnNotification = null; // id of the "not saved back" notification
817
168
  let newCount = 0; // revisions arrived since following stopped
818
- let panelOpen = false; // whether the history panel is visible
819
-
820
- async function loadRevision(rev) {
821
- const res = await fetch(\`\${BASE}/snapshot/\${rev}\`);
822
- if (!res.ok) throw new Error(\`snapshot \${rev} \${res.status}\`);
823
- const snap = await res.json();
824
- await instance.engine.scene.loadFromString(snap.scene);
825
- titleEl.textContent = snap.title ?? 'Untitled';
826
- }
827
-
828
- // Builds the right-side cluster from current state. Idempotent — called on
829
- // every mode flip, panel toggle, and newCount change. Uses createElement +
830
- // textContent everywhere (never innerHTML) so any author-supplied strings
831
- // can't escape into HTML.
832
- function renderRightCluster() {
833
- const nodes = [];
834
-
835
- if (following) {
836
- // LIVE — read-only green-dot pill.
837
- const live = document.createElement('span');
838
- live.className = 'pill live';
839
- live.textContent = 'LIVE';
840
- nodes.push(live);
841
- } else {
842
- // PINNED — hash chip (click-to-copy) + Go Live (+ count badge).
843
- const hash = document.createElement('button');
844
- hash.type = 'button';
845
- hash.className = 'pill hash';
846
- hash.title = 'copy revision id';
847
- hash.textContent = currentRev ? currentRev.slice(0, 7) : '';
848
- hash.addEventListener('click', async () => {
849
- if (!currentRev) return;
850
- try {
851
- await navigator.clipboard?.writeText(currentRev);
852
- } catch {
853
- /* clipboard unavailable — fail quietly (matches .rp-copy) */
854
- }
855
- });
856
- nodes.push(hash);
857
-
858
- const golive = document.createElement('button');
859
- golive.type = 'button';
860
- golive.className = 'pill golive';
861
- golive.appendChild(document.createTextNode('Go Live →'));
862
- if (newCount > 0) {
863
- const count = document.createElement('span');
864
- count.className = 'count';
865
- count.textContent = String(newCount);
866
- golive.appendChild(count);
169
+ let orderedRevisions = []; // order(fetchRevisions(...)) — drives the panel
170
+
171
+ // --- CE.SDK re-render bridge ------------------------------------------
172
+ // Builder components re-render when a state() value they read changes. Our
173
+ // editor state lives in the plain variables above, which CE.SDK's reactor
174
+ // can't see, so each reactive component reads a monotonic \`uiVersion\` from
175
+ // state() purely to subscribe, and invalidateUI() bumps every mounted
176
+ // component to force a coherent re-read. The title, too, is bridged — CE.SDK's
177
+ // builder components don't re-render on engine name changes alone (see
178
+ // codesign.title below). Calls are wrapped because a component that has
179
+ // since unmounted (e.g. a closed panel) throws on setValue.
180
+ let uiVersion = 0;
181
+ const bump = new Map(); // componentKey -> setValue, refreshed on each render
182
+ function invalidateUI() {
183
+ uiVersion += 1;
184
+ for (const setVersion of bump.values()) {
185
+ try {
186
+ setVersion(uiVersion);
187
+ } catch {
188
+ /* component unmounted — ignore */
867
189
  }
868
- golive.addEventListener('click', (e) => {
869
- if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
870
- return;
871
- e.preventDefault();
872
- if (head) show(head, { following: true, historyMode: 'push' });
873
- });
874
- nodes.push(golive);
875
190
  }
191
+ }
876
192
 
877
- const history = document.createElement('button');
878
- history.type = 'button';
879
- history.className = \`pill history\${panelOpen ? ' active' : ''}\`;
880
- history.textContent = 'History';
881
- history.addEventListener('click', () => setPanelOpen(!panelOpen));
882
- nodes.push(history);
883
-
884
- barRightEl.replaceChildren(...nodes);
193
+ // Subscribe a component to invalidateUI(). Reading \`version.value\` (not just
194
+ // grabbing setValue) is what registers the reactive dependency — CE.SDK only
195
+ // re-renders a component when a state value it *read* during render changes,
196
+ // so invalidateUI()'s setValue bumps are no-ops unless the value was read.
197
+ function subscribeToUI(state, key) {
198
+ const version = state('v', 0);
199
+ bump.set(key, version.setValue);
200
+ return version.value;
885
201
  }
886
202
 
887
- function setPanelOpen(open) {
888
- panelOpen = open;
889
- document.body.classList.toggle('panel-open', open);
890
- renderRightCluster();
203
+ async function loadRevision(rev) {
204
+ const res = await fetch(\`\${BASE}/snapshot/\${rev}\`);
205
+ if (!res.ok) throw new Error(\`snapshot \${rev} \${res.status}\`);
206
+ const snap = await res.json();
207
+ await engine.scene.loadFromString(snap.scene);
208
+ // Feed the nav-bar title component via engine reactivity.
209
+ const scene = engine.scene.get();
210
+ if (scene != null) engine.block.setName(scene, snap.title ?? 'Untitled');
211
+ // Fit the page in the viewport and keep it fitted on resize.
212
+ const page = engine.block.findByType('page')[0];
213
+ if (page != null)
214
+ engine.scene.enableZoomAutoFit(page, 'Both', 40, 40, 40, 40);
891
215
  }
892
216
 
893
- // The single navigation primitive. Loads \`rev\` onto the canvas, sets the
894
- // LIVE/PINNED state, and writes the URL. \`historyMode\`: "push" adds a
895
- // history entry, "replace" rewrites in place, "none" leaves history alone
896
- // (used by popstate, where the browser already changed the URL).
217
+ // The single navigation primitive. Loads \`rev\`, sets LIVE/PINNED, writes the
218
+ // URL. historyMode: "push" adds a history entry, "replace" rewrites in place,
219
+ // "none" leaves history alone (popstate already changed the URL).
220
+ // Every arrival here is a fresh scene: it clears \`dirty\` and dismisses the
221
+ // warning. Callers navigating away from local edits must confirmDiscard()
222
+ // FIRST — show() itself never asks.
897
223
  async function show(rev, { following: nextFollowing, historyMode }) {
898
224
  try {
899
- await loadRevision(rev);
225
+ // Clear any prior error banner so a later successful load self-heals it.
226
+ statusEl.textContent = '';
227
+ loading = true;
228
+ try {
229
+ await loadRevision(rev);
230
+ } finally {
231
+ loading = false;
232
+ }
233
+ if (dirty) {
234
+ dirty = false;
235
+ dismissEditWarning();
236
+ }
900
237
  currentRev = rev;
901
238
  following = nextFollowing;
902
239
  if (following) newCount = 0;
@@ -904,31 +241,243 @@ if (!match) {
904
241
  if (historyMode === 'push') window.history.pushState({ rev }, '', url);
905
242
  else if (historyMode === 'replace')
906
243
  window.history.replaceState({ rev }, '', url);
907
- panel.setCurrent(rev);
908
244
  } catch {
909
- titleEl.textContent = 'unknown revision';
245
+ statusEl.textContent = 'unknown revision';
246
+ }
247
+ invalidateUI();
248
+ }
249
+
250
+ async function refreshRevisions() {
251
+ try {
252
+ orderedRevisions = order(await fetchRevisions(BASE, root));
253
+ } catch {
254
+ orderedRevisions = [];
910
255
  }
911
- renderRightCluster();
256
+ invalidateUI();
912
257
  }
913
258
 
914
- // --- revision panel ---------------------------------------------------
915
- const panel = createRevisionPanel({
916
- container: panelEl,
917
- root,
918
- // Clicking a node pins it unless it is the head, which is LIVE.
919
- onNavigate: (rev) =>
920
- show(rev, { following: rev === head, historyMode: 'push' }),
921
- onCount: () => {
922
- /* The History pill no longer carries the total count; the unseen-since-
923
- pin count lives on Go Live and is driven by \`newCount\` directly. */
259
+ // --- dirty tracking -----------------------------------------------------
260
+ // The first REAL local edit detaches the tab from live-follow: edits are
261
+ // local-only (no write path back to the workspace), so the canvas must stop
262
+ // being swapped out underneath them. 'Updated' events during a programmatic
263
+ // load (loading === true) and the post-load baseline (canUndo() === false)
264
+ // are not edits. Undoing back to the baseline does NOT clear \`dirty\` — once
265
+ // touched, the tab stays detached until explicit navigation via show().
266
+ function dismissEditWarning() {
267
+ if (warnNotification == null) return;
268
+ try {
269
+ cesdk.ui.dismissNotification?.(warnNotification);
270
+ } catch {
271
+ /* notification already gone — ignore */
924
272
  }
273
+ warnNotification = null;
274
+ }
275
+
276
+ engine.editor.onHistoryUpdatedWithKind((kind) => {
277
+ if (kind !== 'Updated' || loading || dirty) return;
278
+ if (!engine.editor.canUndo()) return;
279
+ dirty = true;
280
+ following = false;
281
+ // The URL mirrors following-state everywhere else (LIVE = /d/<root>,
282
+ // PINNED = /d/<root>/<rev>); a detached-by-edit tab is pinned.
283
+ if (currentRev != null)
284
+ window.history.replaceState(
285
+ { rev: currentRev },
286
+ '',
287
+ \`\${BASE}/d/\${root}/\${currentRev}\`
288
+ );
289
+ warnNotification = cesdk.ui.showNotification({
290
+ type: 'warning',
291
+ duration: 'infinite',
292
+ message:
293
+ "Your edits aren't saved back or seen by the assistant — export to keep them."
294
+ });
295
+ invalidateUI();
925
296
  });
926
297
 
927
- // --- keyboard ---------------------------------------------------------
928
- window.addEventListener('keydown', (e) => {
929
- if (e.key === 'Escape' && panelOpen) setPanelOpen(false);
298
+ // Local edits are throwaway: every navigation that would replace the scene
299
+ // asks before discarding them.
300
+ function confirmDiscard() {
301
+ return (
302
+ !dirty ||
303
+ window.confirm(
304
+ 'Discard your local edits? They are not saved back to the workspace.'
305
+ )
306
+ );
307
+ }
308
+
309
+ // Closing the tab with unexported local edits prompts (browsers show their
310
+ // own generic message; preventDefault is what arms it).
311
+ window.addEventListener('beforeunload', (ev) => {
312
+ if (!dirty) return;
313
+ ev.preventDefault();
314
+ ev.returnValue = '';
930
315
  });
931
316
 
317
+ // --- nav bar components ------------------------------------------------
318
+ installCodesignChrome();
319
+
320
+ function installCodesignChrome() {
321
+ // Panel title (registered here so the toggle's target exists and the i18n
322
+ // title is set once; the toggle itself is hidden for now).
323
+ cesdk.i18n.setTranslations({
324
+ en: { [\`panel.\${HISTORY_PANEL}\`]: 'Revisions' }
325
+ });
326
+
327
+ // Live-follow status: ● LIVE while following; Go Live (+ unseen count) when
328
+ // pinned or detached by a local edit. Reads module state → bridged via the
329
+ // version bump.
330
+ cesdk.ui.registerComponent('codesign.liveStatus', ({ builder, state }) => {
331
+ subscribeToUI(state, 'liveStatus');
332
+ if (following) {
333
+ builder.Button('codesign.liveStatus.live', {
334
+ label: '● LIVE',
335
+ variant: 'plain',
336
+ isDisabled: true
337
+ });
338
+ } else {
339
+ builder.Button('codesign.liveStatus.goLive', {
340
+ label: newCount > 0 ? \`Go Live (\${newCount})\` : 'Go Live',
341
+ variant: 'plain',
342
+ onClick: () => {
343
+ if (head && confirmDiscard())
344
+ show(head, { following: true, historyMode: 'push' });
345
+ }
346
+ });
347
+ }
348
+ });
349
+
350
+ // Document title. The built-in \`ly.img.title.navigationBar\` does NOT read
351
+ // engine.block.getName() reactively — it only renders a static string
352
+ // passed via \`ui.elements.navigation.title\` at create() time or a
353
+ // per-instance \`payload.title\` in the order array (neither of which we
354
+ // set), so it never renders anything here. Read the scene name ourselves
355
+ // instead, bridged through the same state()/invalidateUI() mechanism as
356
+ // codesign.liveStatus so it refreshes on every show(). Refreshes on
357
+ // invalidateUI() bumps; a rename via document settings while the tab is
358
+ // already dirty stays stale until the next bump — accepted cosmetic
359
+ // staleness.
360
+ cesdk.ui.registerComponent('codesign.title', ({ builder, state }) => {
361
+ subscribeToUI(state, 'title');
362
+ const scene = engine.scene.get();
363
+ const title = scene != null ? engine.block.getName(scene) : '';
364
+ if (title) builder.Heading('codesign.title.heading', { content: title });
365
+ });
366
+
367
+ // History toggle. isActive rides CE.SDK's own reactive panel-open state.
368
+ cesdk.ui.registerComponent('codesign.historyToggle', ({ builder }) => {
369
+ const open = cesdk.ui.isPanelOpen(HISTORY_PANEL);
370
+ builder.Button('codesign.historyToggle.button', {
371
+ label: 'History',
372
+ isActive: open,
373
+ onClick: () =>
374
+ open
375
+ ? cesdk.ui.closePanel(HISTORY_PANEL)
376
+ : cesdk.ui.openPanel(HISTORY_PANEL, {
377
+ position: 'right',
378
+ floating: true
379
+ })
380
+ });
381
+ });
382
+
383
+ // Revision history — native builder panel. Reuses revision-data.js's order()
384
+ // output; reads module state → bridged via the version bump. Rows are
385
+ // buttons: indent = depth, label = short id · note · time (+ head/root/fork).
386
+ cesdk.ui.registerPanel(HISTORY_PANEL, ({ builder, state }) => {
387
+ subscribeToUI(state, 'historyPanel');
388
+ builder.Section('codesign.history.section', {
389
+ title: following ? 'Revisions · LIVE' : 'Revisions · PINNED',
390
+ children: () => {
391
+ if (orderedRevisions.length === 0) {
392
+ builder.Text('codesign.history.empty', {
393
+ content: 'No revisions yet'
394
+ });
395
+ return;
396
+ }
397
+ for (const { node, depth, isHead, isRoot } of orderedRevisions) {
398
+ const indent = depth > 0 ? ' '.repeat(depth) + '↳ ' : '';
399
+ const marker = isHead
400
+ ? ' · head'
401
+ : isRoot
402
+ ? ' · root'
403
+ : depth > 0
404
+ ? ' · fork'
405
+ : '';
406
+ const note = node.note ? \` · \${node.note}\` : '';
407
+ builder.Button(\`codesign.history.\${node.id}\`, {
408
+ label: \`\${indent}\${node.id.slice(0, 7)}\${note}\${marker} · \${relTime(node.createdAt)}\`,
409
+ variant: 'plain',
410
+ isActive: node.id === currentRev,
411
+ onClick: () => {
412
+ if (!confirmDiscard()) return;
413
+ show(node.id, {
414
+ following: node.id === head,
415
+ historyMode: 'push'
416
+ });
417
+ }
418
+ });
419
+ }
420
+ }
421
+ });
422
+ });
423
+
424
+ // Final navigation bar: the design-editor chrome (document settings,
425
+ // undo/redo, title, zoom, preview) plus the live-follow status. This
426
+ // REPLACES the order configureDesignEditorUI set, so the export dropdown
427
+ // must be (re-)inserted after it.
428
+ cesdk.ui.setComponentOrder({ in: 'ly.img.navigation.bar' }, [
429
+ 'ly.img.documentSettings.navigationBar',
430
+ 'ly.img.undoRedo.navigationBar',
431
+ 'ly.img.spacer',
432
+ 'codesign.title',
433
+ 'ly.img.spacer',
434
+ 'codesign.liveStatus',
435
+ 'ly.img.zoom.navigationBar',
436
+ 'ly.img.preview.navigationBar'
437
+ // 'codesign.historyToggle' — hidden for now; the panel + refreshRevisions
438
+ // machinery stays wired, so re-add this line to bring History back.
439
+ ]);
440
+
441
+ // Export dropdown. The scene references workspace:// assets, so exporting
442
+ // the bare scene (or "Save") would produce broken links — the self-contained
443
+ // archive (.zip, assets embedded) is the default/prominent action, with
444
+ // image and PDF in the overflow. Each child carries an explicit handler
445
+ // that ends in a browser download — the only way work leaves this tab.
446
+ cesdk.ui.insertOrderComponent(
447
+ { in: 'ly.img.navigation.bar', position: 'end' },
448
+ {
449
+ id: 'ly.img.actions.navigationBar',
450
+ children: [
451
+ {
452
+ id: 'ly.img.exportArchive.navigationBar',
453
+ onClick: async () => {
454
+ const archive = await engine.scene.saveToArchive();
455
+ await cesdk.utils.downloadFile(archive, 'application/zip');
456
+ }
457
+ },
458
+ {
459
+ id: 'ly.img.exportImage.navigationBar',
460
+ onClick: async () => {
461
+ const { blobs, options } = await cesdk.utils.export({
462
+ mimeType: 'image/png'
463
+ });
464
+ await cesdk.utils.downloadFile(blobs[0], options.mimeType);
465
+ }
466
+ },
467
+ {
468
+ id: 'ly.img.exportPDF.navigationBar',
469
+ onClick: async () => {
470
+ const { blobs, options } = await cesdk.utils.export({
471
+ mimeType: 'application/pdf'
472
+ });
473
+ await cesdk.utils.downloadFile(blobs[0], options.mimeType);
474
+ }
475
+ }
476
+ ]
477
+ }
478
+ );
479
+ }
480
+
932
481
  // --- bootstrap --------------------------------------------------------
933
482
  let latestLeaf;
934
483
  try {
@@ -936,20 +485,20 @@ if (!match) {
936
485
  if (!res.ok) throw new Error(String(res.status));
937
486
  latestLeaf = (await res.json()).latestLeaf;
938
487
  } catch {
939
- titleEl.textContent = 'unknown design';
488
+ statusEl.textContent = 'unknown design';
940
489
  }
941
490
 
942
491
  if (latestLeaf !== undefined) {
943
492
  head = latestLeaf;
944
493
  const pinned = match[2]; // undefined → LIVE
945
- // Mode is the URL shape: a pinned URL stays pinned even if <rev> is head.
946
494
  await show(pinned ?? head, {
947
495
  following: pinned === undefined,
948
496
  historyMode: 'replace'
949
497
  });
498
+ await refreshRevisions();
950
499
 
951
- // SSE — held open for the life of the tab, in both modes. Every event
952
- // refreshes the panel's tree; it moves the canvas only while following.
500
+ // SSE — held open for the tab's life, in every state. It moves the canvas
501
+ // only while following (a dirty tab is never following).
953
502
  const es = new EventSource(\`\${BASE}/sse/\${root}\`);
954
503
  es.onmessage = (ev) => {
955
504
  let payload;
@@ -961,21 +510,29 @@ if (!match) {
961
510
  }
962
511
  if (!payload || typeof payload.revision !== 'string') return;
963
512
  head = payload.revision;
964
- panel.refresh();
513
+ refreshRevisions();
965
514
  if (following) {
966
515
  show(head, { following: true, historyMode: 'replace' });
967
516
  } else {
968
517
  newCount += 1;
969
- renderRightCluster();
518
+ invalidateUI();
970
519
  }
971
520
  };
972
521
  }
973
522
 
974
523
  // Browser back/forward: re-resolve the URL shape and sync, without writing
975
- // history (the entry already changed).
524
+ // history (the entry already changed). With unexported local edits, ask
525
+ // first — on cancel, restore the URL of the revision still on canvas.
976
526
  window.addEventListener('popstate', () => {
977
527
  const m = relPath().match(PATH_RE);
978
528
  if (!m || m[1] !== root) return;
529
+ if (!confirmDiscard()) {
530
+ const url = following
531
+ ? \`\${BASE}/d/\${root}\`
532
+ : \`\${BASE}/d/\${root}/\${currentRev}\`;
533
+ window.history.pushState({ rev: currentRev }, '', url);
534
+ return;
535
+ }
979
536
  const pinned = m[2];
980
537
  show(pinned ?? head, {
981
538
  following: pinned === undefined,
@@ -983,6 +540,330 @@ if (!match) {
983
540
  });
984
541
  });
985
542
  }
543
+
544
+ // Install the design-editor UI. This mirrors IMG.LY's \`DesignEditorConfig\`
545
+ // plugin (features → actions → component orders), which we can't import here
546
+ // (see the file header), reproduced verbatim from the Design Editor starter kit
547
+ // and trimmed for this surface:
548
+ // - the dock / asset library IS enabled (features \`ly.img.dock\`,
549
+ // \`ly.img.library.panel\`) with a Text / Shapes / Stickers dock order set
550
+ // below, fed by the default asset sources registered in the boot code;
551
+ // - no video/audio features enabled (so a static design has no timeline);
552
+ // - the Trim and Vector inspector edit modes are omitted because their
553
+ // features (video trimming, vector-path editing) aren't enabled here, so
554
+ // those modes are unreachable;
555
+ // - the Document inspector's "Resize Page" panel is fed by the
556
+ // \`ly.img.pagePresets\` entry wired below to the page-presets source that
557
+ // rides along with the default asset sources (see the boot code);
558
+ // - the navigation-bar order + export dropdown live in
559
+ // installCodesignChrome() (they include codesign components registered
560
+ // after the resetEditor() below).
561
+ function configureDesignEditorUI(cesdk) {
562
+ // Clear the empty default configuration, then apply ours. \`resetEditor()\` can
563
+ // drop the theme passed to \`create()\`, so re-apply it.
564
+ cesdk.resetEditor();
565
+ cesdk.ui.setTheme('dark');
566
+
567
+ // --- Features (starter-kit design set, plus the dock/library) -------------
568
+ cesdk.feature.enable([
569
+ 'ly.img.navigation',
570
+ 'ly.img.dock',
571
+ 'ly.img.library.panel',
572
+ 'ly.img.text',
573
+ 'ly.img.crop',
574
+ 'ly.img.transform',
575
+ 'ly.img.filter',
576
+ 'ly.img.adjustment',
577
+ 'ly.img.effect',
578
+ 'ly.img.blur',
579
+ 'ly.img.shadow',
580
+ 'ly.img.cutout',
581
+ 'ly.img.canvas',
582
+ 'ly.img.inspector',
583
+ 'ly.img.inspector.bar',
584
+ 'ly.img.inspector.toggle',
585
+ 'ly.img.delete',
586
+ 'ly.img.duplicate',
587
+ 'ly.img.group',
588
+ 'ly.img.replace',
589
+ 'ly.img.page',
590
+ 'ly.img.scene.layout',
591
+ 'ly.img.fill.color',
592
+ 'ly.img.fill.image',
593
+ 'ly.img.stroke',
594
+ 'ly.img.opacity',
595
+ 'ly.img.blendMode',
596
+ 'ly.img.shape.options',
597
+ 'ly.img.combine',
598
+ 'ly.img.position',
599
+ 'ly.img.trim',
600
+ 'ly.img.notifications',
601
+ 'ly.img.keyboard.shortcuts'
602
+ ]);
603
+
604
+ // --- Panels: inspector docked on the left, pushing the canvas -------------
605
+ cesdk.ui.setPanelPosition('//ly.img.panel/inspector', 'left');
606
+ cesdk.ui.setPanelFloating('//ly.img.panel/inspector', false);
607
+
608
+ // --- Dock: asset library with Text / Shapes / Stickers --------------------
609
+ // The stock \`ly.img.vector.shape\` and \`ly.img.sticker\` entries ship with
610
+ // EMPTY sourceIds — wire them explicitly (no reliance on hidden
611
+ // auto-population). Shapes points at the engine-CDN \`ly.img.vector.shape\`
612
+ // source registered in the boot code — NOT the default root's vectorpath
613
+ // source, whose group ids have no shipped translations and render as raw
614
+ // i18n keys in the panel's section headers. Stickers uses the source
615
+ // addDefaultAssetSources registers; the \`ly.img.text\` entry keeps its stock
616
+ // sources. No image/video/audio/upload entries: the library carries only
617
+ // the neutral defaults, no demo content.
618
+ cesdk.ui.updateAssetLibraryEntry('ly.img.vector.shape', {
619
+ sourceIds: ['ly.img.vector.shape']
620
+ });
621
+ cesdk.ui.updateAssetLibraryEntry('ly.img.sticker', {
622
+ sourceIds: ['ly.img.sticker']
623
+ });
624
+ // The Document inspector's "Resize Page" panel lists the formats of the
625
+ // sources named by the \`ly.img.pagePresets\` entry (note: entry id, NOT the
626
+ // \`ly.img.page.presets\` source id) — it also ships with EMPTY sourceIds,
627
+ // which left the panel body blank. The source itself is registered by
628
+ // addDefaultAssetSources in the boot code.
629
+ cesdk.ui.updateAssetLibraryEntry('ly.img.pagePresets', {
630
+ sourceIds: ['ly.img.page.presets']
631
+ });
632
+ cesdk.ui.setComponentOrder({ in: 'ly.img.dock' }, [
633
+ {
634
+ id: 'ly.img.assetLibrary.dock',
635
+ key: 'text',
636
+ label: 'libraries.ly.img.text.label',
637
+ icon: '@imgly/Text',
638
+ entries: ['ly.img.text']
639
+ },
640
+ {
641
+ id: 'ly.img.assetLibrary.dock',
642
+ key: 'shapes',
643
+ label: 'libraries.ly.img.vector.shape.label',
644
+ icon: '@imgly/Shapes',
645
+ entries: ['ly.img.vector.shape']
646
+ },
647
+ {
648
+ id: 'ly.img.assetLibrary.dock',
649
+ key: 'stickers',
650
+ label: 'libraries.ly.img.sticker.label',
651
+ icon: '@imgly/Sticker',
652
+ entries: ['ly.img.sticker']
653
+ }
654
+ ]);
655
+
656
+ // --- Inspector bar — contextual controls for the selected block -----------
657
+ cesdk.ui.setComponentOrder(
658
+ { in: 'ly.img.inspector.bar', when: { editMode: 'Transform' } },
659
+ [
660
+ 'ly.img.spacer',
661
+ 'ly.img.pageResize.inspectorBar',
662
+ 'ly.img.separator',
663
+ 'ly.img.video.caption.inspectorBar',
664
+ 'ly.img.shape.options.inspectorBar',
665
+ 'ly.img.cutout.type.inspectorBar',
666
+ 'ly.img.cutout.offset.inspectorBar',
667
+ 'ly.img.cutout.smoothing.inspectorBar',
668
+ 'ly.img.group.create.inspectorBar',
669
+ 'ly.img.group.ungroup.inspectorBar',
670
+ 'ly.img.audio.replace.inspectorBar',
671
+ 'ly.img.separator',
672
+ 'ly.img.text.typeFace.inspectorBar',
673
+ 'ly.img.text.bold.inspectorBar',
674
+ 'ly.img.text.italic.inspectorBar',
675
+ 'ly.img.text.fontSize.inspectorBar',
676
+ 'ly.img.text.alignHorizontal.inspectorBar',
677
+ 'ly.img.text.advanced.inspectorBar',
678
+ 'ly.img.combine.inspectorBar',
679
+ 'ly.img.separator',
680
+ 'ly.img.fill.inspectorBar',
681
+ 'ly.img.trim.inspectorBar',
682
+ 'ly.img.volume.inspectorBar',
683
+ 'ly.img.crop.inspectorBar',
684
+ 'ly.img.separator',
685
+ 'ly.img.stroke.inspectorBar',
686
+ 'ly.img.separator',
687
+ 'ly.img.text.background.inspectorBar',
688
+ 'ly.img.separator',
689
+ 'ly.img.animations.inspectorBar',
690
+ 'ly.img.separator',
691
+ {
692
+ id: 'ly.img.appearance.inspectorBar',
693
+ children: [
694
+ 'ly.img.adjustment.inspectorBar',
695
+ 'ly.img.filter.inspectorBar',
696
+ 'ly.img.effect.inspectorBar',
697
+ 'ly.img.blur.inspectorBar'
698
+ ]
699
+ },
700
+ 'ly.img.separator',
701
+ 'ly.img.shadow.inspectorBar',
702
+ 'ly.img.separator',
703
+ 'ly.img.opacityOptions.inspectorBar',
704
+ 'ly.img.separator',
705
+ 'ly.img.position.inspectorBar',
706
+ 'ly.img.spacer',
707
+ 'ly.img.separator',
708
+ 'ly.img.inspectorToggle.inspectorBar'
709
+ ]
710
+ );
711
+ cesdk.ui.setComponentOrder(
712
+ { in: 'ly.img.inspector.bar', when: { editMode: 'Crop' } },
713
+ ['ly.img.cropControls.inspectorBar']
714
+ );
715
+
716
+ // --- Canvas bar (bottom) and the selection context menu -------------------
717
+ cesdk.ui.setComponentOrder({ in: 'ly.img.canvas.bar', at: 'bottom' }, [
718
+ 'ly.img.settings.canvasBar',
719
+ 'ly.img.spacer',
720
+ 'ly.img.page.add.canvasBar',
721
+ 'ly.img.spacer'
722
+ ]);
723
+ cesdk.ui.setComponentOrder(
724
+ { in: 'ly.img.canvas.menu', when: { editMode: 'Transform' } },
725
+ [
726
+ 'ly.img.group.enter.canvasMenu',
727
+ 'ly.img.group.select.canvasMenu',
728
+ 'ly.img.page.moveUp.canvasMenu',
729
+ 'ly.img.page.moveDown.canvasMenu',
730
+ 'ly.img.separator',
731
+ 'ly.img.text.edit.canvasMenu',
732
+ 'ly.img.text.styles.canvasMenu',
733
+ 'ly.img.replace.canvasMenu',
734
+ 'ly.img.separator',
735
+ 'ly.img.bringForward.canvasMenu',
736
+ 'ly.img.sendBackward.canvasMenu',
737
+ 'ly.img.separator',
738
+ 'ly.img.duplicate.canvasMenu',
739
+ 'ly.img.delete.canvasMenu',
740
+ 'ly.img.separator',
741
+ 'ly.img.options.canvasMenu'
742
+ ]
743
+ );
744
+ cesdk.ui.setComponentOrder(
745
+ { in: 'ly.img.canvas.menu', when: { editMode: 'Text' } },
746
+ [
747
+ 'ly.img.text.color.canvasMenu',
748
+ 'ly.img.separator',
749
+ 'ly.img.text.bold.canvasMenu',
750
+ 'ly.img.text.italic.canvasMenu',
751
+ 'ly.img.text.underline.canvasMenu',
752
+ 'ly.img.text.strikethrough.canvasMenu',
753
+ 'ly.img.separator',
754
+ 'ly.img.text.list.unordered.canvasMenu',
755
+ 'ly.img.text.list.ordered.canvasMenu',
756
+ 'ly.img.separator',
757
+ 'ly.img.text.variables.canvasMenu'
758
+ ]
759
+ );
760
+ }
761
+ `,
762
+ "index.html": `<!doctype html>
763
+ <html lang="en">
764
+ <head>
765
+ <meta charset="utf-8" />
766
+ <title>CoDesign</title>
767
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
768
+ <!-- Mount prefix injected by the viewer router (router.ts). "" on the stdio
769
+ listener, "/viewer" on the HTTP transport. Read by editor.js. -->
770
+ <script>
771
+ window.__VIEWER_BASE__ = '{{VIEWER_BASE}}';
772
+ </script>
773
+ <style>
774
+ html,
775
+ body {
776
+ margin: 0;
777
+ height: 100%;
778
+ background: #1d1d1f;
779
+ color: #eee;
780
+ font-family: -apple-system, system-ui, sans-serif;
781
+ }
782
+ #cesdk {
783
+ position: fixed;
784
+ inset: 0;
785
+ }
786
+ #status {
787
+ position: fixed;
788
+ top: 14px;
789
+ left: 14px;
790
+ z-index: 10;
791
+ font:
792
+ 500 12px -apple-system,
793
+ system-ui,
794
+ sans-serif;
795
+ }
796
+ </style>
797
+ </head>
798
+ <body>
799
+ <div id="cesdk"></div>
800
+ <div id="status"></div>
801
+ <script type="module" src="{{VIEWER_BASE}}/static/editor.js"></script>
802
+ </body>
803
+ </html>
804
+ `,
805
+ "revision-data.js": `// revision-data.js — framework-free data layer for the editor's revision
806
+ // history. No DOM and no top-level \`window\` access, so order()/relTime() are
807
+ // unit-testable under node. Consumed by editor.js's CE.SDK history panel.
808
+
809
+ // Relative time label ("5m ago"). Uses Date.now() at call time (browser + node).
810
+ export function relTime(ms) {
811
+ const s = Math.max(0, Math.round((Date.now() - ms) / 1000));
812
+ if (s < 60) return \`\${s}s ago\`;
813
+ const m = Math.round(s / 60);
814
+ if (m < 60) return \`\${m}m ago\`;
815
+ const h = Math.round(m / 60);
816
+ if (h < 24) return \`\${h}h ago\`;
817
+ return \`\${Math.round(h / 24)}d ago\`;
818
+ }
819
+
820
+ // Depth-first walk. At each node the spine child (or, off-spine, the latest
821
+ // child) continues at the same depth; other children are branches at depth + 1,
822
+ // emitted before the continuation so the spine reads top-to-bottom. Returns
823
+ // [{ node, depth, isHead, isRoot }]. Pure — no DOM, no globals.
824
+ export function order(nodes) {
825
+ if (nodes.length === 0) return [];
826
+ const byId = new Map(nodes.map((n) => [n.id, n]));
827
+ const children = new Map();
828
+ for (const n of nodes) {
829
+ if (n.parent != null) {
830
+ const sibs = children.get(n.parent) ?? [];
831
+ sibs.push(n);
832
+ children.set(n.parent, sibs);
833
+ }
834
+ }
835
+ for (const sibs of children.values())
836
+ sibs.sort((a, b) => a.createdAt - b.createdAt);
837
+ const rootNode = nodes.find((n) => n.parent === null);
838
+ const head = nodes.reduce((a, b) => (b.createdAt > a.createdAt ? b : a));
839
+ const spine = new Set();
840
+ for (let n = head; n; n = n.parent != null ? byId.get(n.parent) : null)
841
+ spine.add(n.id);
842
+ const out = [];
843
+ function walk(node, depth) {
844
+ out.push({
845
+ node,
846
+ depth,
847
+ isHead: node.id === head.id,
848
+ isRoot: node.parent === null
849
+ });
850
+ const kids = children.get(node.id) ?? [];
851
+ if (kids.length === 0) return;
852
+ const primary = kids.find((k) => spine.has(k.id)) ?? kids[kids.length - 1];
853
+ for (const k of kids) if (k !== primary) walk(k, depth + 1);
854
+ walk(primary, depth);
855
+ }
856
+ if (rootNode) walk(rootNode, 0);
857
+ return out;
858
+ }
859
+
860
+ // Fetch a design's full revision tree. \`base\` is the mount prefix ("" on stdio,
861
+ // "/viewer" on HTTP). Throws on a non-2xx response so the caller can degrade.
862
+ export async function fetchRevisions(base, root) {
863
+ const res = await fetch(\`\${base}/api/design/\${root}/revisions\`);
864
+ if (!res.ok) throw new Error(\`revisions \${root} \${res.status}\`);
865
+ return res.json();
866
+ }
986
867
  `,
987
868
  };
988
869
  //# sourceMappingURL=spa-assets.generated.js.map