@1agh/maude 0.42.0 → 0.44.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 (332) hide show
  1. package/apps/studio/.ai/cache/_stats.json +7 -0
  2. package/apps/studio/acp/bootstrap-brief.ts +12 -2
  3. package/apps/studio/acp/bridge.ts +44 -10
  4. package/apps/studio/acp/env.ts +23 -0
  5. package/apps/studio/acp/login-state.ts +263 -0
  6. package/apps/studio/acp/plugin-bootstrap.ts +31 -25
  7. package/apps/studio/acp/probe.ts +152 -1
  8. package/apps/studio/annotations-layer.tsx +1451 -324
  9. package/apps/studio/annotations-model.ts +58 -0
  10. package/apps/studio/api.ts +568 -24
  11. package/apps/studio/bin/_audio-search.mjs +140 -0
  12. package/apps/studio/bin/_import-asset-pdf-worker.mjs +34 -0
  13. package/apps/studio/bin/_import-asset.mjs +815 -0
  14. package/apps/studio/bin/_import-brand.mjs +635 -0
  15. package/apps/studio/bin/_import-tokens-alias-resolver.mjs +95 -0
  16. package/apps/studio/bin/_import-tokens.mjs +1201 -0
  17. package/apps/studio/bin/_ingest-footage.mjs +386 -0
  18. package/apps/studio/bin/_probe-footage-playwright.mjs +269 -0
  19. package/apps/studio/bin/_transcribe.mjs +419 -0
  20. package/apps/studio/bin/_transcribe.test.mjs +80 -0
  21. package/apps/studio/bin/_video-playwright.mjs +141 -71
  22. package/apps/studio/bin/audio-search.sh +28 -0
  23. package/apps/studio/bin/generate.sh +154 -0
  24. package/apps/studio/bin/import-asset.sh +34 -0
  25. package/apps/studio/bin/import-brand.sh +29 -0
  26. package/apps/studio/bin/import-tokens.sh +33 -0
  27. package/apps/studio/bin/ingest-footage.sh +27 -0
  28. package/apps/studio/bin/photo-adjust.sh +163 -0
  29. package/apps/studio/bin/photo-bg-remove.sh +258 -0
  30. package/apps/studio/bin/probe-footage.sh +28 -0
  31. package/apps/studio/bin/read-annotations.mjs +125 -2
  32. package/apps/studio/bin/transcribe.sh +39 -0
  33. package/apps/studio/canvas-edit.ts +656 -12
  34. package/apps/studio/canvas-lib.tsx +637 -10
  35. package/apps/studio/canvas-pipeline.ts +65 -7
  36. package/apps/studio/canvas-shell.tsx +156 -16
  37. package/apps/studio/client/app.jsx +2385 -699
  38. package/apps/studio/client/export-center.jsx +35 -1
  39. package/apps/studio/client/generate-dialog.jsx +352 -0
  40. package/apps/studio/client/github.js +18 -0
  41. package/apps/studio/client/inspector-controls.jsx +781 -0
  42. package/apps/studio/client/panels/BrandUploadPanel.jsx +233 -0
  43. package/apps/studio/client/panels/ChatPanel.jsx +15 -33
  44. package/apps/studio/client/panels/GitPanel.jsx +8 -0
  45. package/apps/studio/client/panels/IntroVideoDialog.jsx +44 -0
  46. package/apps/studio/client/panels/OnboardingWizard.jsx +12 -0
  47. package/apps/studio/client/panels/ReadinessList.jsx +227 -7
  48. package/apps/studio/client/panels/RepoBranchSwitcher.jsx +133 -21
  49. package/apps/studio/client/panels/SettingsPanel.jsx +828 -0
  50. package/apps/studio/client/panels/SetupChecklist.jsx +223 -0
  51. package/apps/studio/client/panels/StickerPicker.jsx +160 -0
  52. package/apps/studio/client/panels/TimelinePanel.jsx +15 -2
  53. package/apps/studio/client/photo-knobs.jsx +432 -0
  54. package/apps/studio/client/styles/3-shell-maude.css +376 -28
  55. package/apps/studio/client/styles/4-components.css +223 -0
  56. package/apps/studio/client/styles/6-acp-chat.css +86 -0
  57. package/apps/studio/client/tour/quick-setup-tour.js +39 -0
  58. package/apps/studio/commands/annotation-strokes-command.ts +13 -3
  59. package/apps/studio/commands/edit-source-command.ts +31 -4
  60. package/apps/studio/config.schema.json +70 -0
  61. package/apps/studio/context-menu.tsx +42 -8
  62. package/apps/studio/context.ts +16 -0
  63. package/apps/studio/design-setup-readiness.ts +115 -0
  64. package/apps/studio/dist/client.bundle.js +5572 -21
  65. package/apps/studio/dist/comment-mount.js +2 -2
  66. package/apps/studio/dist/runtime/.min-sizes.json +1 -0
  67. package/apps/studio/dist/runtime/@imgly_background-removal.js +5543 -0
  68. package/apps/studio/dist/runtime/pixi-js.js +519 -519
  69. package/apps/studio/dist/styles.css +1 -1
  70. package/apps/studio/dom-selection.ts +25 -0
  71. package/apps/studio/draw/palette.ts +34 -0
  72. package/apps/studio/exporters/jobs.ts +47 -5
  73. package/apps/studio/exporters/video.ts +24 -4
  74. package/apps/studio/footage/schema.test.ts +287 -0
  75. package/apps/studio/footage/schema.ts +662 -0
  76. package/apps/studio/footage-store.ts +235 -0
  77. package/apps/studio/fs-watch.ts +10 -0
  78. package/apps/studio/generation/adapters/elevenlabs.ts +370 -0
  79. package/apps/studio/generation/adapters/gemini.ts +542 -0
  80. package/apps/studio/generation/adapters/groq.test.ts +80 -0
  81. package/apps/studio/generation/adapters/groq.ts +247 -0
  82. package/apps/studio/generation/audio-library.test.ts +95 -0
  83. package/apps/studio/generation/audio-library.ts +156 -0
  84. package/apps/studio/generation/captions.test.ts +78 -0
  85. package/apps/studio/generation/captions.ts +132 -0
  86. package/apps/studio/generation/download.ts +76 -0
  87. package/apps/studio/generation/elevenlabs.test.ts +223 -0
  88. package/apps/studio/generation/gemini.test.ts +350 -0
  89. package/apps/studio/generation/jobs.test.ts +128 -0
  90. package/apps/studio/generation/jobs.ts +243 -0
  91. package/apps/studio/generation/keys.test.ts +75 -0
  92. package/apps/studio/generation/keys.ts +162 -0
  93. package/apps/studio/generation/prefs.test.ts +95 -0
  94. package/apps/studio/generation/prefs.ts +82 -0
  95. package/apps/studio/generation/registry.ts +88 -0
  96. package/apps/studio/generation/types.test.ts +123 -0
  97. package/apps/studio/generation/types.ts +266 -0
  98. package/apps/studio/generation/whisper-models.test.ts +152 -0
  99. package/apps/studio/generation/whisper-models.ts +257 -0
  100. package/apps/studio/generation/whisper-spike-results.md +61 -0
  101. package/apps/studio/git/endpoints.ts +68 -7
  102. package/apps/studio/git/service.ts +199 -21
  103. package/apps/studio/github/service.ts +67 -0
  104. package/apps/studio/handoff.ts +3 -2
  105. package/apps/studio/http.ts +1039 -22
  106. package/apps/studio/input-router.tsx +10 -0
  107. package/apps/studio/inspect.ts +53 -1
  108. package/apps/studio/media/intro.mp4 +0 -0
  109. package/apps/studio/media-commit-chain.ts +119 -0
  110. package/apps/studio/paths.ts +20 -0
  111. package/apps/studio/photo/filters.ts +285 -0
  112. package/apps/studio/photo/pipeline.ts +607 -0
  113. package/apps/studio/photo/schema.ts +367 -0
  114. package/apps/studio/photo-store.ts +154 -0
  115. package/apps/studio/readiness.ts +108 -20
  116. package/apps/studio/runtime-bundle.ts +29 -6
  117. package/apps/studio/scaffold-design.ts +1145 -0
  118. package/apps/studio/server.ts +18 -2
  119. package/apps/studio/stickers/figjam-doodle/manifest.json +104 -0
  120. package/apps/studio/stickers/figjam-doodle/slice1.png +0 -0
  121. package/apps/studio/stickers/figjam-doodle/slice10.png +0 -0
  122. package/apps/studio/stickers/figjam-doodle/slice11.png +0 -0
  123. package/apps/studio/stickers/figjam-doodle/slice12.png +0 -0
  124. package/apps/studio/stickers/figjam-doodle/slice13.png +0 -0
  125. package/apps/studio/stickers/figjam-doodle/slice14.png +0 -0
  126. package/apps/studio/stickers/figjam-doodle/slice15.png +0 -0
  127. package/apps/studio/stickers/figjam-doodle/slice16.png +0 -0
  128. package/apps/studio/stickers/figjam-doodle/slice17.png +0 -0
  129. package/apps/studio/stickers/figjam-doodle/slice18.png +0 -0
  130. package/apps/studio/stickers/figjam-doodle/slice19.png +0 -0
  131. package/apps/studio/stickers/figjam-doodle/slice2.png +0 -0
  132. package/apps/studio/stickers/figjam-doodle/slice20.png +0 -0
  133. package/apps/studio/stickers/figjam-doodle/slice21.png +0 -0
  134. package/apps/studio/stickers/figjam-doodle/slice22.png +0 -0
  135. package/apps/studio/stickers/figjam-doodle/slice23.png +0 -0
  136. package/apps/studio/stickers/figjam-doodle/slice24.png +0 -0
  137. package/apps/studio/stickers/figjam-doodle/slice3.png +0 -0
  138. package/apps/studio/stickers/figjam-doodle/slice4.png +0 -0
  139. package/apps/studio/stickers/figjam-doodle/slice5.png +0 -0
  140. package/apps/studio/stickers/figjam-doodle/slice6.png +0 -0
  141. package/apps/studio/stickers/figjam-doodle/slice7.png +0 -0
  142. package/apps/studio/stickers/figjam-doodle/slice8.png +0 -0
  143. package/apps/studio/stickers/figjam-doodle/slice9.png +0 -0
  144. package/apps/studio/stickers/life-style/best.png +0 -0
  145. package/apps/studio/stickers/life-style/come-on.png +0 -0
  146. package/apps/studio/stickers/life-style/cut.png +0 -0
  147. package/apps/studio/stickers/life-style/detox.png +0 -0
  148. package/apps/studio/stickers/life-style/just.png +0 -0
  149. package/apps/studio/stickers/life-style/manifest.json +68 -0
  150. package/apps/studio/stickers/life-style/mirror.png +0 -0
  151. package/apps/studio/stickers/life-style/nice.png +0 -0
  152. package/apps/studio/stickers/life-style/objects.png +0 -0
  153. package/apps/studio/stickers/life-style/ok.png +0 -0
  154. package/apps/studio/stickers/life-style/queen.png +0 -0
  155. package/apps/studio/stickers/life-style/star.png +0 -0
  156. package/apps/studio/stickers/life-style/sun.png +0 -0
  157. package/apps/studio/stickers/life-style/water.png +0 -0
  158. package/apps/studio/stickers/life-style/yeah.png +0 -0
  159. package/apps/studio/stickers/life-style/you-can.png +0 -0
  160. package/apps/studio/stickers/opposing-thoughts/check-the-deets.png +0 -0
  161. package/apps/studio/stickers/opposing-thoughts/cut-it.png +0 -0
  162. package/apps/studio/stickers/opposing-thoughts/dope.png +0 -0
  163. package/apps/studio/stickers/opposing-thoughts/fresh.png +0 -0
  164. package/apps/studio/stickers/opposing-thoughts/hot.png +0 -0
  165. package/apps/studio/stickers/opposing-thoughts/idea.png +0 -0
  166. package/apps/studio/stickers/opposing-thoughts/keep-exploring.png +0 -0
  167. package/apps/studio/stickers/opposing-thoughts/killed-it.png +0 -0
  168. package/apps/studio/stickers/opposing-thoughts/love-it.png +0 -0
  169. package/apps/studio/stickers/opposing-thoughts/manifest.json +88 -0
  170. package/apps/studio/stickers/opposing-thoughts/not-sure.png +0 -0
  171. package/apps/studio/stickers/opposing-thoughts/ok.png +0 -0
  172. package/apps/studio/stickers/opposing-thoughts/pure-gold.png +0 -0
  173. package/apps/studio/stickers/opposing-thoughts/save-for-later.png +0 -0
  174. package/apps/studio/stickers/opposing-thoughts/steal-this.png +0 -0
  175. package/apps/studio/stickers/opposing-thoughts/take-a-peek.png +0 -0
  176. package/apps/studio/stickers/opposing-thoughts/this-or-that.png +0 -0
  177. package/apps/studio/stickers/opposing-thoughts/thoughts.png +0 -0
  178. package/apps/studio/stickers/opposing-thoughts/vibes.png +0 -0
  179. package/apps/studio/stickers/opposing-thoughts/winner.png +0 -0
  180. package/apps/studio/stickers/opposing-thoughts/wip.png +0 -0
  181. package/apps/studio/stickers/project-status/group-100.png +0 -0
  182. package/apps/studio/stickers/project-status/group-101.png +0 -0
  183. package/apps/studio/stickers/project-status/group-102.png +0 -0
  184. package/apps/studio/stickers/project-status/group-103.png +0 -0
  185. package/apps/studio/stickers/project-status/group-104.png +0 -0
  186. package/apps/studio/stickers/project-status/group-105.png +0 -0
  187. package/apps/studio/stickers/project-status/group-106.png +0 -0
  188. package/apps/studio/stickers/project-status/group-107.png +0 -0
  189. package/apps/studio/stickers/project-status/group-108.png +0 -0
  190. package/apps/studio/stickers/project-status/group-109.png +0 -0
  191. package/apps/studio/stickers/project-status/group-110.png +0 -0
  192. package/apps/studio/stickers/project-status/group-111.png +0 -0
  193. package/apps/studio/stickers/project-status/group-112.png +0 -0
  194. package/apps/studio/stickers/project-status/group-113.png +0 -0
  195. package/apps/studio/stickers/project-status/group-114.png +0 -0
  196. package/apps/studio/stickers/project-status/group-115.png +0 -0
  197. package/apps/studio/stickers/project-status/group-117.png +0 -0
  198. package/apps/studio/stickers/project-status/group-118.png +0 -0
  199. package/apps/studio/stickers/project-status/group-119.png +0 -0
  200. package/apps/studio/stickers/project-status/group-120.png +0 -0
  201. package/apps/studio/stickers/project-status/group-121.png +0 -0
  202. package/apps/studio/stickers/project-status/group-122.png +0 -0
  203. package/apps/studio/stickers/project-status/group-41.png +0 -0
  204. package/apps/studio/stickers/project-status/group-42.png +0 -0
  205. package/apps/studio/stickers/project-status/group-43.png +0 -0
  206. package/apps/studio/stickers/project-status/group-44.png +0 -0
  207. package/apps/studio/stickers/project-status/group-45.png +0 -0
  208. package/apps/studio/stickers/project-status/group-46.png +0 -0
  209. package/apps/studio/stickers/project-status/group-47.png +0 -0
  210. package/apps/studio/stickers/project-status/group-48.png +0 -0
  211. package/apps/studio/stickers/project-status/group-49.png +0 -0
  212. package/apps/studio/stickers/project-status/group-50.png +0 -0
  213. package/apps/studio/stickers/project-status/group-51.png +0 -0
  214. package/apps/studio/stickers/project-status/group-52.png +0 -0
  215. package/apps/studio/stickers/project-status/group-53.png +0 -0
  216. package/apps/studio/stickers/project-status/group-54.png +0 -0
  217. package/apps/studio/stickers/project-status/group-55.png +0 -0
  218. package/apps/studio/stickers/project-status/group-56.png +0 -0
  219. package/apps/studio/stickers/project-status/group-57.png +0 -0
  220. package/apps/studio/stickers/project-status/group-58.png +0 -0
  221. package/apps/studio/stickers/project-status/group-59.png +0 -0
  222. package/apps/studio/stickers/project-status/group-60.png +0 -0
  223. package/apps/studio/stickers/project-status/group-61.png +0 -0
  224. package/apps/studio/stickers/project-status/group-62.png +0 -0
  225. package/apps/studio/stickers/project-status/group-63.png +0 -0
  226. package/apps/studio/stickers/project-status/group-64.png +0 -0
  227. package/apps/studio/stickers/project-status/group-65.png +0 -0
  228. package/apps/studio/stickers/project-status/group-66.png +0 -0
  229. package/apps/studio/stickers/project-status/group-67.png +0 -0
  230. package/apps/studio/stickers/project-status/group-68.png +0 -0
  231. package/apps/studio/stickers/project-status/group-69.png +0 -0
  232. package/apps/studio/stickers/project-status/group-70.png +0 -0
  233. package/apps/studio/stickers/project-status/group-71.png +0 -0
  234. package/apps/studio/stickers/project-status/group-72.png +0 -0
  235. package/apps/studio/stickers/project-status/group-73.png +0 -0
  236. package/apps/studio/stickers/project-status/group-74.png +0 -0
  237. package/apps/studio/stickers/project-status/group-75.png +0 -0
  238. package/apps/studio/stickers/project-status/group-76.png +0 -0
  239. package/apps/studio/stickers/project-status/group-77.png +0 -0
  240. package/apps/studio/stickers/project-status/group-78.png +0 -0
  241. package/apps/studio/stickers/project-status/group-79.png +0 -0
  242. package/apps/studio/stickers/project-status/group-80.png +0 -0
  243. package/apps/studio/stickers/project-status/group-81.png +0 -0
  244. package/apps/studio/stickers/project-status/group-82.png +0 -0
  245. package/apps/studio/stickers/project-status/group-83.png +0 -0
  246. package/apps/studio/stickers/project-status/group-84.png +0 -0
  247. package/apps/studio/stickers/project-status/group-85.png +0 -0
  248. package/apps/studio/stickers/project-status/group-86.png +0 -0
  249. package/apps/studio/stickers/project-status/group-87.png +0 -0
  250. package/apps/studio/stickers/project-status/group-88.png +0 -0
  251. package/apps/studio/stickers/project-status/group-89.png +0 -0
  252. package/apps/studio/stickers/project-status/group-90.png +0 -0
  253. package/apps/studio/stickers/project-status/group-91.png +0 -0
  254. package/apps/studio/stickers/project-status/group-92.png +0 -0
  255. package/apps/studio/stickers/project-status/group-93.png +0 -0
  256. package/apps/studio/stickers/project-status/group-94.png +0 -0
  257. package/apps/studio/stickers/project-status/group-96.png +0 -0
  258. package/apps/studio/stickers/project-status/group-97.png +0 -0
  259. package/apps/studio/stickers/project-status/group-98.png +0 -0
  260. package/apps/studio/stickers/project-status/group-99.png +0 -0
  261. package/apps/studio/stickers/project-status/manifest.json +328 -0
  262. package/apps/studio/test/_helpers.ts +15 -2
  263. package/apps/studio/test/acp-bootstrap-brief.test.ts +11 -0
  264. package/apps/studio/test/acp-bridge.test.ts +34 -1
  265. package/apps/studio/test/acp-commands.test.ts +2 -1
  266. package/apps/studio/test/acp-env.test.ts +30 -0
  267. package/apps/studio/test/acp-plugin-bootstrap.test.ts +23 -57
  268. package/apps/studio/test/acp-session-plugins.test.ts +72 -1
  269. package/apps/studio/test/annotation-strokes-command.test.ts +17 -0
  270. package/apps/studio/test/annotations-layer.test.ts +174 -0
  271. package/apps/studio/test/asset-api.test.ts +3 -3
  272. package/apps/studio/test/canvas-edit.test.ts +17 -6
  273. package/apps/studio/test/canvas-origin-gate.test.ts +50 -0
  274. package/apps/studio/test/csp-canvas-shell.test.ts +11 -0
  275. package/apps/studio/test/csrf-write-guard.test.ts +23 -1
  276. package/apps/studio/test/design-setup-readiness.test.ts +155 -0
  277. package/apps/studio/test/dynamic-text-edit.test.ts +162 -0
  278. package/apps/studio/test/edit-source-command.test.ts +23 -0
  279. package/apps/studio/test/element-structural-edit.test.ts +154 -0
  280. package/apps/studio/test/fixtures/fake-claude-auth.mjs +13 -0
  281. package/apps/studio/test/footage-store.test.ts +100 -0
  282. package/apps/studio/test/generate-route.test.ts +106 -0
  283. package/apps/studio/test/git-branches.test.ts +97 -0
  284. package/apps/studio/test/github-api.test.ts +56 -1
  285. package/apps/studio/test/import-asset-browser.test.ts +64 -0
  286. package/apps/studio/test/import-asset-route.test.ts +71 -0
  287. package/apps/studio/test/import-asset.test.ts +399 -0
  288. package/apps/studio/test/import-brand-browser.test.ts +88 -0
  289. package/apps/studio/test/import-brand-route.test.ts +120 -0
  290. package/apps/studio/test/import-brand.test.ts +206 -0
  291. package/apps/studio/test/import-tokens.test.ts +722 -0
  292. package/apps/studio/test/input-router.test.ts +33 -0
  293. package/apps/studio/test/inspect-script-syntax.test.ts +53 -0
  294. package/apps/studio/test/media-commit-chain.test.ts +210 -0
  295. package/apps/studio/test/photo-bg-remove-validation.test.ts +71 -0
  296. package/apps/studio/test/photo-canvas-bundle.test.ts +61 -0
  297. package/apps/studio/test/photo-edit-api.test.ts +201 -0
  298. package/apps/studio/test/photo-filters.test.ts +149 -0
  299. package/apps/studio/test/photo-pipeline.test.ts +106 -0
  300. package/apps/studio/test/photo-taxonomy.test.ts +96 -0
  301. package/apps/studio/test/read-annotations.test.ts +164 -0
  302. package/apps/studio/test/readiness.test.ts +13 -11
  303. package/apps/studio/test/scaffold-design.test.ts +122 -0
  304. package/apps/studio/test/shell-importmap.test.ts +49 -0
  305. package/apps/studio/test/text-editability-stamp.test.ts +131 -0
  306. package/apps/studio/test/timeline-parse.test.ts +57 -0
  307. package/apps/studio/test/tool-palette-insert-anchor.test.ts +5 -3
  308. package/apps/studio/test/tour-overlay.test.tsx +16 -0
  309. package/apps/studio/test/undo-stack.test.ts +33 -0
  310. package/apps/studio/test/video-asset.test.ts +5 -5
  311. package/apps/studio/test/video-render-bridge.test.ts +23 -1
  312. package/apps/studio/text-caret.ts +239 -0
  313. package/apps/studio/tool-palette.tsx +49 -21
  314. package/apps/studio/ui-prefs.ts +130 -0
  315. package/apps/studio/undo-stack.ts +94 -2
  316. package/apps/studio/use-annotation-resize.tsx +4 -4
  317. package/apps/studio/use-canvas-media-drop.tsx +40 -1
  318. package/apps/studio/use-chrome-visibility.tsx +8 -2
  319. package/apps/studio/use-selection-set.tsx +21 -0
  320. package/apps/studio/video-comp.tsx +48 -7
  321. package/apps/studio/whats-new.json +149 -0
  322. package/apps/studio/ws.ts +5 -0
  323. package/cli/bin/maude.mjs +6 -6
  324. package/cli/commands/design.mjs +67 -3
  325. package/cli/lib/gitignore-block.mjs +2 -0
  326. package/cli/lib/pkg-root.mjs +107 -0
  327. package/cli/lib/pkg-root.test.mjs +79 -0
  328. package/cli/lib/reconstruct-toolset.test.mjs +194 -0
  329. package/cli/lib/update-check.mjs +8 -0
  330. package/package.json +13 -11
  331. package/plugins/design/dependencies.json +18 -0
  332. package/plugins/design/templates/_shell.html +1 -0
@@ -158,21 +158,65 @@ function jsxElementName(opening: AnyNode): string {
158
158
  return '?';
159
159
  }
160
160
 
161
- function hasDataCdIdAttr(opening: AnyNode): boolean {
161
+ function hasJsxAttr(opening: AnyNode, name: string): boolean {
162
162
  const attrs = opening?.attributes;
163
163
  if (!Array.isArray(attrs)) return false;
164
164
  for (const a of attrs) {
165
- if (
166
- a?.type === 'JSXAttribute' &&
167
- a.name?.type === 'JSXIdentifier' &&
168
- a.name.name === 'data-cd-id'
169
- ) {
165
+ if (a?.type === 'JSXAttribute' && a.name?.type === 'JSXIdentifier' && a.name.name === name) {
170
166
  return true;
171
167
  }
172
168
  }
173
169
  return false;
174
170
  }
175
171
 
172
+ /**
173
+ * Phase 6 (unified-text-editing) — mirror of `applyTextEdit`'s editability
174
+ * test (canvas-edit.ts): the element's children are exactly ONE meaningful
175
+ * node, and it is either a static JSXText or a `{'string literal'}`
176
+ * expression container (DDR-150 P1). Elements that pass get a build-time
177
+ * `data-cd-editable="text"` marker so the shell only offers inline editing
178
+ * where a commit will actually save — the rendered DOM cannot distinguish
179
+ * `<p>Total: {1 + 1} items</p>` (multiple text nodes, mixed source → the
180
+ * engine refuses the edit) from honest leaf text, but the AST can. KEEP THE
181
+ * TWO PREDICATES IN LOCKSTEP — if applyTextEdit's acceptance changes, this
182
+ * must change with it, or the marker starts lying.
183
+ */
184
+ function inlineEditableKind(node: AnyNode): 'text' | 'var' | null {
185
+ const children: AnyNode[] = Array.isArray(node?.children) ? node.children : [];
186
+ const meaningful = children.filter(
187
+ (c) => !(c?.type === 'JSXText' && typeof c.value === 'string' && c.value.trim() === '')
188
+ );
189
+ if (meaningful.length !== 1) return null;
190
+ const only = meaningful[0];
191
+ if (only?.type === 'JSXText') return 'text';
192
+ if (only?.type === 'JSXExpressionContainer') {
193
+ const expr = only.expression;
194
+ // A `{'string literal'}` rewrites in place (DDR-150 P1) — same as JSXText.
195
+ if (
196
+ expr &&
197
+ (expr.type === 'Literal' || expr.type === 'StringLiteral') &&
198
+ typeof expr.value === 'string'
199
+ ) {
200
+ return 'text';
201
+ }
202
+ // A `{variable}` or `{item.prop}` — the string lives in a const or a
203
+ // `.map()`ed array; the edit engine traces it back at commit time
204
+ // (canvas-edit.ts resolveDynamicTextSpan). Computed forms (calls, template
205
+ // strings, `a + b`, ternaries) are NOT offered — no single source string.
206
+ if (expr?.type === 'Identifier') return 'var';
207
+ if (
208
+ expr?.type === 'MemberExpression' &&
209
+ !expr.computed &&
210
+ expr.object?.type === 'Identifier' &&
211
+ expr.property?.type === 'Identifier'
212
+ ) {
213
+ return 'var';
214
+ }
215
+ return null;
216
+ }
217
+ return null;
218
+ }
219
+
176
220
  function computeId(componentName: string, idx: number): string {
177
221
  // 8 hex chars from Bun.hash — 32 bits of entropy. Per-component collision
178
222
  // probability for the canvas-scale we ship (≤ ~300 JSX elements) is < 1e-5.
@@ -215,7 +259,7 @@ function walkInjectIds(
215
259
  const idx = frame.jsxIndex;
216
260
  frame.jsxIndex += 1;
217
261
 
218
- if (!hasDataCdIdAttr(opening)) {
262
+ if (!hasJsxAttr(opening, 'data-cd-id')) {
219
263
  const id = computeId(frame.componentName, idx);
220
264
  // Insert " data-cd-id=\"<id>\"" right after the tag name. magic-string's
221
265
  // appendLeft puts the new text before any subsequent attribute. JSX
@@ -235,6 +279,20 @@ function walkInjectIds(
235
279
  }
236
280
  }
237
281
 
282
+ // Phase 6 (unified-text-editing) — build-time editability marker, so
283
+ // the shell's inline-edit entry and the edit engine can never disagree
284
+ // (inlineEditableKind mirrors applyTextEdit's acceptance). `text` = a
285
+ // literal rewritten in place; `var` = a `{variable}` traced back to its
286
+ // source string at commit (canvas-edit.ts). Independent of the id
287
+ // injection above: hand-authored data-cd-id elements still get it.
288
+ const editKind = inlineEditableKind(node);
289
+ if (editKind && !hasJsxAttr(opening, 'data-cd-editable')) {
290
+ const insertAt: number | undefined = opening?.name?.end;
291
+ if (typeof insertAt === 'number') {
292
+ s.appendLeft(insertAt, ` data-cd-editable="${editKind}"`);
293
+ }
294
+ }
295
+
238
296
  // Recurse into opening attributes + children, with the jsxPath extended.
239
297
  frame.jsxPath.push(elName);
240
298
  if (opening) visit(opening.attributes);
@@ -41,6 +41,7 @@ import { AnnotationsLayer } from './annotations-layer.tsx';
41
41
  import { ArtboardMarqueeOverlay } from './artboard-marquee.tsx';
42
42
  import {
43
43
  type ArtboardRect,
44
+ PhotoPreviewBridge,
44
45
  SnapGuideOverlay,
45
46
  useArtboardsContext,
46
47
  useDragStateContext,
@@ -88,6 +89,7 @@ import {
88
89
  import { ElementMarqueeOverlay } from './marquee-overlay.tsx';
89
90
  import { MeasureOverlay } from './measure-overlay.tsx';
90
91
  import { ParticipantsChrome } from './participants-chrome.tsx';
92
+ import { mountCaret, placeCaretAt } from './text-caret.ts';
91
93
  import { ToolPalette } from './tool-palette.tsx';
92
94
  import { UndoHud } from './undo-hud.tsx';
93
95
  import {
@@ -327,6 +329,22 @@ const HALO_CSS = `
327
329
  outline-offset: 1px;
328
330
  border-radius: 1px;
329
331
  cursor: text;
332
+ /* Unify the caret with the annotation editors (CARET_FIX_STYLE in
333
+ annotations-layer.tsx) — same accent, visible against any background.
334
+ No compositing trigger here (no translateZ/will-change) so WebKit keeps
335
+ blinking the native caret. */
336
+ caret-color: var(--maude-hud-accent, #4a63e7);
337
+ }
338
+ /* Dogfood fix — hover affordance for the same leaf-text elements the dblclick
339
+ handler above enters edit mode on. The :not(:empty):not(:has(> *)) selector
340
+ proxies "all children are text nodes" (the exact isLeafText check happens
341
+ in JS on dblclick — this only needs to be a good-enough visual hint). Loses
342
+ to an active draw/annotation tool's !important global cursor override
343
+ (use-tool-mode.tsx), so it only shows in Move mode, same as the edit itself.
344
+ NOTE: keep this comment backtick-free — it lives inside a JS template
345
+ literal and a stray backtick closes it early (bun parse fail). */
346
+ [data-cd-id]:not(:empty):not(:has(> *)):hover {
347
+ cursor: text;
330
348
  }
331
349
  /* Phase 12.1 (DDR-138) — in-canvas drag-to-reorder (Figma-style). Dragging a
332
350
  SELECTED element floats it with the cursor (via transform, applied inline —
@@ -549,6 +567,18 @@ function getLastLayersArtboardId(): string | null {
549
567
  }
550
568
  }
551
569
 
570
+ // DDR-148 — an artboard hosting a <VideoComp> stamps a `[data-comp-id]`
571
+ // element inside it (see video-comp.tsx). Gates the artboard-chrome context
572
+ // menu's "Open Timeline" entry to artboards that actually have a sequence.
573
+ function artboardHasVideo(artboardId: string | null | undefined): boolean {
574
+ if (typeof document === 'undefined' || !artboardId) return false;
575
+ try {
576
+ return !!document.querySelector(`[data-dc-screen="${CSS.escape(artboardId)}"] [data-comp-id]`);
577
+ } catch {
578
+ return false;
579
+ }
580
+ }
581
+
552
582
  function postLayersTree(artboardId: string | null | undefined): void {
553
583
  if (typeof document === 'undefined' || !artboardId) return;
554
584
  const root = document.querySelector(`[data-dc-screen="${CSS.escape(artboardId)}"]`);
@@ -1440,6 +1470,34 @@ function buildRegistry(deps: {
1440
1470
  }
1441
1471
  },
1442
1472
  },
1473
+ {
1474
+ // feature-photo-editor (Task 16) — only on a content-addressed `<img>`.
1475
+ // Selects it (so the Inspector derives the Photo target from the
1476
+ // selection's `photoAsset`) + opens the Inspector on the Photo tab.
1477
+ id: 'edit-photo',
1478
+ label: 'Edit Photo…',
1479
+ hidden: (target) => {
1480
+ const el = target.el as HTMLImageElement | null;
1481
+ if (el?.tagName?.toLowerCase() !== 'img') return true;
1482
+ return !/assets\/[0-9a-f]{8}\.[a-z0-9]+/i.test(el.getAttribute?.('src') || '');
1483
+ },
1484
+ onSelect: (target) => {
1485
+ if (target.el) {
1486
+ selSet.replace(
1487
+ hoverTargetToSelection({
1488
+ el: target.el,
1489
+ cdId: target.cdId ?? null,
1490
+ artboardId: target.artboardId ?? null,
1491
+ } as HoverTarget)
1492
+ );
1493
+ }
1494
+ try {
1495
+ window.parent.postMessage({ dgn: 'open-inspector', tab: 'photo' }, '*');
1496
+ } catch {
1497
+ /* detached / cross-origin */
1498
+ }
1499
+ },
1500
+ },
1443
1501
  {
1444
1502
  id: 'select-layer',
1445
1503
  label: 'Select layer',
@@ -1631,6 +1689,29 @@ function buildRegistry(deps: {
1631
1689
  fitItem,
1632
1690
  resetItem,
1633
1691
  ],
1692
+ [
1693
+ // DDR-148 — only meaningful (and only enabled) on a video-comp
1694
+ // artboard; scopes + opens the shell's Timeline dock.
1695
+ {
1696
+ id: 'open-timeline',
1697
+ label: 'Open Timeline',
1698
+ shortcut: '⌘⇧T',
1699
+ disabled: (target) => !artboardHasVideo(target.artboardId),
1700
+ disabledHint: 'This artboard has no video sequence',
1701
+ onSelect: (target) => {
1702
+ if (!target.artboardId) return;
1703
+ focusArtboard(target.artboardId);
1704
+ try {
1705
+ window.parent.postMessage(
1706
+ { dgn: 'open-timeline-request', artboardId: target.artboardId },
1707
+ '*'
1708
+ );
1709
+ } catch {
1710
+ /* detached / cross-origin */
1711
+ }
1712
+ },
1713
+ },
1714
+ ],
1634
1715
  [
1635
1716
  // G7 — align commands. Six modes; gated on ≥ 2 selected artboards
1636
1717
  // (alignment is well-defined with 2). Primary surface is the
@@ -2134,6 +2215,9 @@ function CanvasRouter({
2134
2215
  if (!host) return;
2135
2216
  let editing: HTMLElement | null = null;
2136
2217
  let original = '';
2218
+ // Unified custom blinking caret (text-caret.ts) — mounted for the whole
2219
+ // edit session, disposed in teardown. Hides the native caret while up.
2220
+ let caretDispose: (() => void) | null = null;
2137
2221
  // DDR-150 P1 — the committed-but-unconfirmed edit, so a shell rejection
2138
2222
  // (dynamic/mixed content the engine refuses) can revert the optimistic
2139
2223
  // contenteditable text instead of letting it silently vanish on the next
@@ -2150,6 +2234,8 @@ function CanvasRouter({
2150
2234
  }
2151
2235
  };
2152
2236
  function teardown(elx: HTMLElement): void {
2237
+ caretDispose?.();
2238
+ caretDispose = null;
2153
2239
  elx.removeAttribute('contenteditable');
2154
2240
  elx.classList.remove('dc-text-editing');
2155
2241
  elx.removeEventListener('blur', onBlur, true);
@@ -2169,8 +2255,22 @@ function CanvasRouter({
2169
2255
  const cdId = elx.getAttribute('data-cd-id');
2170
2256
  if (!cdId) return;
2171
2257
  const file = deriveFile();
2258
+ // For `{variable}` text the engine needs to know WHICH rendered instance
2259
+ // this is (a `.map()` renders one source element N×) — its index among
2260
+ // same-cd-id nodes — plus the pre-edit text to target the right array
2261
+ // item. Harmless for literal text (the engine ignores both there).
2262
+ let occurrence = 0;
2172
2263
  try {
2173
- window.parent.postMessage({ dgn: 'edit-text', id: cdId, file, text }, '*');
2264
+ const sameId = Array.from(document.querySelectorAll(`[data-cd-id="${CSS.escape(cdId)}"]`));
2265
+ occurrence = Math.max(0, sameId.indexOf(elx));
2266
+ } catch {
2267
+ /* querySelectorAll/CSS.escape unavailable — occurrence stays 0 */
2268
+ }
2269
+ try {
2270
+ window.parent.postMessage(
2271
+ { dgn: 'edit-text', id: cdId, file, text, occurrence, before: original.trim() },
2272
+ '*'
2273
+ );
2174
2274
  } catch {
2175
2275
  /* detached / cross-origin */
2176
2276
  }
@@ -2187,9 +2287,36 @@ function CanvasRouter({
2187
2287
  key: '',
2188
2288
  before: original.trim(),
2189
2289
  after: text,
2290
+ // Carry the occurrence so undo/redo can re-target a `{variable}` edit
2291
+ // (the .map()/component-prop resolution needs it — canvas-edit.ts).
2292
+ occurrence,
2190
2293
  })
2191
2294
  );
2192
2295
  }
2296
+ // Enter contenteditable edit mode on a leaf-text `stamped` element, caret
2297
+ // placed at (clientX, clientY) rather than a full select-all (that used to
2298
+ // force a delete-and-retype for any edit that isn't a full replace).
2299
+ // `caretRangeFromPoint` (WebKit/Chromium) and its standardized successor
2300
+ // `caretPositionFromPoint` (Firefox + newer engines) both resolve the
2301
+ // exact character under the pointer; select-all-contents is the fallback
2302
+ // only when neither exists. Extracted so both the native dblclick path
2303
+ // AND the annotation Text-tool's click-through (maude:enter-text-edit,
2304
+ // dispatched from annotations-layer.tsx when its own click lands on an
2305
+ // existing editable element) share one entry point.
2306
+ function enterEditModeAt(stamped: HTMLElement, clientX: number, clientY: number): void {
2307
+ if (editing) return;
2308
+ editing = stamped;
2309
+ original = stamped.textContent ?? '';
2310
+ stamped.setAttribute('contenteditable', 'plaintext-only');
2311
+ stamped.classList.add('dc-text-editing');
2312
+ stamped.addEventListener('blur', onBlur, true);
2313
+ stamped.addEventListener('keydown', onKey, true);
2314
+ stamped.focus();
2315
+ // Unified placement chain (text-caret.ts) — the annotation editors use
2316
+ // the exact same helper, so caret-at-click can never diverge per surface.
2317
+ placeCaretAt(stamped, window, { x: clientX, y: clientY });
2318
+ caretDispose = mountCaret(stamped, window);
2319
+ }
2193
2320
  const onDbl = (e: MouseEvent): void => {
2194
2321
  if (editing) return;
2195
2322
  const t = e.target as HTMLElement | null;
@@ -2229,22 +2356,32 @@ function CanvasRouter({
2229
2356
  }
2230
2357
  e.preventDefault();
2231
2358
  e.stopPropagation();
2232
- editing = stamped;
2233
- original = stamped.textContent ?? '';
2234
- stamped.setAttribute('contenteditable', 'plaintext-only');
2235
- stamped.classList.add('dc-text-editing');
2236
- stamped.addEventListener('blur', onBlur, true);
2237
- stamped.addEventListener('keydown', onKey, true);
2238
- stamped.focus();
2239
- try {
2240
- const range = document.createRange();
2241
- range.selectNodeContents(stamped);
2242
- const sel = window.getSelection();
2243
- sel?.removeAllRanges();
2244
- sel?.addRange(range);
2245
- } catch {
2246
- /* selection API unavailable */
2359
+ // Phase 6 (unified-text-editing) only offer inline edit where the
2360
+ // engine will actually save: the build-time data-cd-editable marker
2361
+ // (canvas-pipeline.ts) mirrors applyTextEdit's acceptance. The DOM-only
2362
+ // isLeafText heuristic above can't tell `<p>Total: {1 + 1} items</p>`
2363
+ // (mixed source, engine refuses, DDR-150 dead end) from honest static
2364
+ // text — the AST-stamped marker can.
2365
+ if (!stamped.hasAttribute('data-cd-editable')) {
2366
+ showCanvasToast(
2367
+ 'This text is filled in from code (a variable) — edit it via chat or /design:edit.'
2368
+ );
2369
+ return;
2247
2370
  }
2371
+ enterEditModeAt(stamped, e.clientX, e.clientY);
2372
+ };
2373
+ // Dogfood fix — the annotation Text tool's click-through: clicking an
2374
+ // existing editable element with the Text (T) tool active should edit
2375
+ // THAT text in place instead of dropping a new standalone annotation on
2376
+ // top of it. annotations-layer.tsx hit-tests past its own input-capture
2377
+ // overlay and dispatches this when the click resolves to a leaf-text
2378
+ // `[data-cd-id]` element; canvas-shell owns the DOM/contentEditable side
2379
+ // of editing, so it's the one that must act on it.
2380
+ const onEnterTextEdit = (e: Event): void => {
2381
+ const detail = (e as CustomEvent<{ el?: HTMLElement; clientX?: number; clientY?: number }>)
2382
+ .detail;
2383
+ if (!detail?.el || !host.contains(detail.el)) return;
2384
+ enterEditModeAt(detail.el, detail.clientX ?? 0, detail.clientY ?? 0);
2248
2385
  };
2249
2386
  // The shell rejected the last inline text edit — revert the optimistic
2250
2387
  // contenteditable text so the canvas reflects the true (unsaved) source
@@ -2266,9 +2403,11 @@ function CanvasRouter({
2266
2403
  // Capture phase so we beat the fit-to-view dblclick handler.
2267
2404
  host.addEventListener('dblclick', onDbl, true);
2268
2405
  window.addEventListener('message', onEditReverted);
2406
+ document.addEventListener('maude:enter-text-edit', onEnterTextEdit);
2269
2407
  return () => {
2270
2408
  host.removeEventListener('dblclick', onDbl, true);
2271
2409
  window.removeEventListener('message', onEditReverted);
2410
+ document.removeEventListener('maude:enter-text-edit', onEnterTextEdit);
2272
2411
  if (editing) teardown(editing);
2273
2412
  };
2274
2413
  }, [hostRef, selSet]);
@@ -2378,6 +2517,7 @@ function CanvasRouter({
2378
2517
  alignArtboards={alignArtboards}
2379
2518
  />
2380
2519
  <SnapGuideOverlay />
2520
+ <PhotoPreviewBridge />
2381
2521
  <UndoHud />
2382
2522
  <CursorsOverlay />
2383
2523
  <AiBanner />