@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
@@ -859,6 +859,12 @@
859
859
  }
860
860
  .help-modal-body::-webkit-scrollbar { width: 6px; }
861
861
  .help-modal-body::-webkit-scrollbar-thumb { background: var(--u-border); }
862
+
863
+ /* Watch-the-intro player (DDR-166 Phase 1 / T2) — wider modal, no padding
864
+ around the video so it fills edge-to-edge. */
865
+ .help-modal--wide { width: min(960px, 100%); }
866
+ .intro-video-body { padding: 0; overflow: hidden; }
867
+ .intro-video-el { display: block; width: 100%; aspect-ratio: 16 / 9; background: #000; }
862
868
  .help-modal-body details {
863
869
  margin: 0 0 var(--u-s-2);
864
870
  border-bottom: 1px solid var(--u-border-subtle);
@@ -1652,3 +1658,220 @@
1652
1658
  background: var(--u-accent);
1653
1659
  color: var(--u-accent-fg);
1654
1660
  }
1661
+
1662
+ /* ── Settings — AI generation (feature-ai-media-generation, DDR-16x) ─────── */
1663
+ .st-settings-intro {
1664
+ font-size: var(--type-xs, 12px);
1665
+ color: var(--fg-3);
1666
+ line-height: 1.5;
1667
+ margin: 6px 0 12px;
1668
+ }
1669
+ .st-settings-intro code {
1670
+ font-family: var(--font-mono);
1671
+ font-size: 0.92em;
1672
+ color: var(--fg-2);
1673
+ }
1674
+ .st-provider-card {
1675
+ border: 1px solid var(--border-default);
1676
+ border-radius: var(--radius-sm);
1677
+ background: var(--bg-2);
1678
+ padding: 12px;
1679
+ margin-bottom: 10px;
1680
+ }
1681
+ .st-provider-hd {
1682
+ display: flex;
1683
+ align-items: center;
1684
+ gap: 8px;
1685
+ }
1686
+ .st-provider-name {
1687
+ font-family: var(--font-mono);
1688
+ font-size: var(--type-sm, 13px);
1689
+ color: var(--fg-1);
1690
+ font-weight: 600;
1691
+ }
1692
+ .st-provider-card .st-pill {
1693
+ display: inline-flex;
1694
+ align-items: center;
1695
+ gap: 3px;
1696
+ font-size: 10px;
1697
+ text-transform: uppercase;
1698
+ letter-spacing: 0.03em;
1699
+ color: var(--fg-3);
1700
+ border: 1px solid var(--border-default);
1701
+ border-radius: var(--radius-pill, 999px);
1702
+ padding: 1px 7px;
1703
+ }
1704
+ .st-provider-card .st-pill.is-local {
1705
+ color: var(--accent);
1706
+ border-color: var(--accent-muted, var(--accent));
1707
+ }
1708
+ .st-provider-configured {
1709
+ display: inline-flex;
1710
+ align-items: center;
1711
+ gap: 3px;
1712
+ margin-left: auto;
1713
+ font-size: 11px;
1714
+ color: var(--accent);
1715
+ }
1716
+ .st-provider-modalities {
1717
+ font-family: var(--font-mono);
1718
+ font-size: 11px;
1719
+ color: var(--fg-3);
1720
+ margin-top: 4px;
1721
+ }
1722
+ .st-provider-notes {
1723
+ font-size: 11px;
1724
+ color: var(--fg-3);
1725
+ line-height: 1.5;
1726
+ margin-top: 6px;
1727
+ }
1728
+ .st-provider-keylink {
1729
+ display: inline-flex;
1730
+ align-items: center;
1731
+ gap: 4px;
1732
+ font-size: 11px;
1733
+ color: var(--accent);
1734
+ text-decoration: none;
1735
+ margin-top: 6px;
1736
+ }
1737
+ .st-provider-keylink:hover {
1738
+ text-decoration: underline;
1739
+ }
1740
+ .st-provider-keyrow {
1741
+ display: flex;
1742
+ align-items: center;
1743
+ gap: 6px;
1744
+ margin-top: 10px;
1745
+ }
1746
+ .st-provider-keyinput {
1747
+ flex: 1;
1748
+ min-width: 0;
1749
+ }
1750
+ .st-provider-status {
1751
+ font-size: 11px;
1752
+ margin-top: 6px;
1753
+ }
1754
+
1755
+ /* ── Transcription-engine selector (Task 2.6) ───────────────────────────── */
1756
+ .st-engine-radios {
1757
+ display: flex;
1758
+ flex-direction: column;
1759
+ gap: 6px;
1760
+ margin-top: 10px;
1761
+ }
1762
+ .st-engine-radio {
1763
+ display: flex;
1764
+ align-items: flex-start;
1765
+ gap: 9px;
1766
+ padding: 9px 11px;
1767
+ border: 1px solid var(--border-default);
1768
+ border-radius: var(--radius-sm);
1769
+ background: var(--bg-1);
1770
+ cursor: pointer;
1771
+ }
1772
+ .st-engine-radio.is-selected {
1773
+ border-color: var(--accent);
1774
+ background: var(--bg-2);
1775
+ }
1776
+ .st-engine-radio input {
1777
+ margin-top: 2px;
1778
+ accent-color: var(--accent);
1779
+ }
1780
+ .st-engine-radio-body {
1781
+ display: flex;
1782
+ flex-direction: column;
1783
+ gap: 2px;
1784
+ }
1785
+ .st-engine-radio-label {
1786
+ font-size: var(--type-sm, 13px);
1787
+ color: var(--fg-1);
1788
+ font-weight: 600;
1789
+ }
1790
+ .st-engine-radio-note {
1791
+ font-size: 11px;
1792
+ color: var(--fg-3);
1793
+ }
1794
+
1795
+ /* ── Local whisper model download (Task 2.7) ────────────────────────────── */
1796
+ .st-model-list {
1797
+ display: flex;
1798
+ flex-direction: column;
1799
+ gap: 8px;
1800
+ margin-top: 10px;
1801
+ }
1802
+ .st-model-row {
1803
+ display: flex;
1804
+ align-items: flex-start;
1805
+ justify-content: space-between;
1806
+ gap: 10px;
1807
+ padding: 9px 11px;
1808
+ border: 1px solid var(--border-default);
1809
+ border-radius: var(--radius-sm);
1810
+ background: var(--bg-1);
1811
+ }
1812
+ .st-model-info {
1813
+ display: flex;
1814
+ flex-direction: column;
1815
+ gap: 3px;
1816
+ min-width: 0;
1817
+ }
1818
+ .st-model-label {
1819
+ display: flex;
1820
+ align-items: center;
1821
+ gap: 7px;
1822
+ flex-wrap: wrap;
1823
+ font-size: var(--type-sm, 13px);
1824
+ color: var(--fg-1);
1825
+ font-weight: 600;
1826
+ }
1827
+ .st-model-size {
1828
+ font-size: 11px;
1829
+ color: var(--fg-3);
1830
+ font-weight: 400;
1831
+ }
1832
+ .st-model-progress {
1833
+ font-size: 11px;
1834
+ color: var(--accent);
1835
+ font-variant-numeric: tabular-nums;
1836
+ }
1837
+ .st-model-actions {
1838
+ display: flex;
1839
+ align-items: center;
1840
+ gap: 8px;
1841
+ flex-shrink: 0;
1842
+ }
1843
+
1844
+ /* ── Generate dialog (feature-ai-media-generation, DDR-16x) ─────────────── */
1845
+ .st-gen-prompt {
1846
+ width: 100%;
1847
+ resize: vertical;
1848
+ min-height: 60px;
1849
+ font-family: var(--font-sans, system-ui, sans-serif);
1850
+ margin-bottom: 10px;
1851
+ }
1852
+ .st-gen-go {
1853
+ color: var(--accent);
1854
+ border-color: var(--accent-muted, var(--accent));
1855
+ }
1856
+ .st-gen-result {
1857
+ display: flex;
1858
+ gap: 10px;
1859
+ align-items: flex-start;
1860
+ margin-top: 12px;
1861
+ padding: 10px;
1862
+ border: 1px solid var(--border-default);
1863
+ border-radius: var(--radius-sm);
1864
+ background: var(--bg-2);
1865
+ }
1866
+ .st-gen-thumb {
1867
+ width: 96px;
1868
+ height: 96px;
1869
+ object-fit: cover;
1870
+ border-radius: var(--radius-sm);
1871
+ background: var(--bg-3);
1872
+ }
1873
+ .st-gen-result-actions {
1874
+ display: flex;
1875
+ flex-direction: column;
1876
+ gap: 6px;
1877
+ }
@@ -1181,7 +1181,93 @@
1181
1181
  }
1182
1182
  .rdy-copy:hover { color: var(--fg-0); background: var(--bg-3); }
1183
1183
  .rdy-foot { display: flex; justify-content: flex-end; }
1184
+ /* DDR-166 T0c/T0d — verbatim install command + Sign-in action rows. */
1185
+ .rdy-cmd {
1186
+ margin-top: var(--space-2); display: flex; gap: var(--space-2);
1187
+ align-items: center; flex-wrap: wrap;
1188
+ }
1189
+ .rdy-cmd-tx {
1190
+ font-family: var(--font-mono); font-size: var(--type-xs); color: var(--fg-0);
1191
+ background: var(--bg-3); padding: var(--space-1) var(--space-2);
1192
+ border-radius: var(--radius-xs); overflow-wrap: anywhere; flex: 1 1 auto;
1193
+ }
1194
+ .rdy-fix-tx--err { color: var(--status-error); }
1195
+ .rdy-fallback-label { display: block; margin-top: var(--space-2); color: var(--fg-3); }
1196
+ /* DDR-166 Decision 3 — the resolved-binary disclosure shown before Sign-in. */
1197
+ .rdy-resolved-path { display: block; margin-top: var(--space-2); color: var(--fg-3); word-break: break-all; }
1198
+ .rdy-resolved-path code { font-family: var(--font-mono); color: var(--fg-1); }
1199
+ .rdy-signin {
1200
+ flex-shrink: 0; cursor: pointer;
1201
+ font-size: var(--type-xs); font-weight: 600; color: var(--bg-0);
1202
+ background: var(--accent); border: 1px solid var(--accent);
1203
+ border-radius: var(--radius-xs); padding: 3px 10px;
1204
+ }
1205
+ .rdy-signin:hover { filter: brightness(1.08); }
1184
1206
  /* Help ▸ Check AI editing readiness… modal (reuses .help-modal chrome). */
1185
1207
  .rdy-modal { max-width: 520px; }
1186
1208
  .rdy-modal-note { margin: 0 0 var(--space-3); color: var(--fg-2); font-size: var(--type-sm); line-height: var(--lh-md); }
1209
+ /* DDR-166 Decision 5 — auto-setup opt-out, mirrors .ob-crash-optin. */
1210
+ .rdy-autosetup-toggle { display: flex; align-items: flex-start; gap: var(--space-2); margin-top: var(--space-3); color: var(--fg-2); font-size: var(--type-xs); line-height: var(--lh-md); cursor: pointer; text-align: left; }
1211
+ .rdy-autosetup-toggle input { margin-top: 2px; flex: 0 0 auto; accent-color: var(--accent); cursor: pointer; }
1212
+ }
1213
+
1214
+ /* DDR-166 plan, Phase 2 (T7/T8) — design-setup checklist (SetupChecklist.jsx).
1215
+ Mirrors .rdy-* above (same present/missing two-state shape, no remediation
1216
+ copy — this probe never offers an automated fix, just progress). */
1217
+ @layer components {
1218
+ .setup-cl { display: flex; flex-direction: column; gap: var(--space-3); }
1219
+ .setup-cl-list {
1220
+ list-style: none; margin: 0; padding: 0;
1221
+ display: flex; flex-direction: column; gap: var(--space-2);
1222
+ }
1223
+ .setup-cl-row {
1224
+ display: flex; gap: var(--space-2); align-items: flex-start;
1225
+ padding: var(--space-2) var(--space-3);
1226
+ border: 1px solid var(--border-subtle); border-radius: var(--radius-sm);
1227
+ background: var(--bg-2); text-align: left;
1228
+ }
1229
+ .setup-cl-ic { flex-shrink: 0; margin-top: 1px; }
1230
+ .setup-cl-row--present .setup-cl-ic { color: var(--status-success); }
1231
+ .setup-cl-row--missing .setup-cl-ic { color: var(--fg-3); }
1232
+ .setup-cl-tx { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
1233
+ .setup-cl-label { font-size: var(--type-sm); font-weight: 600; color: var(--fg-0); }
1234
+ .setup-cl-detail { font-size: var(--type-xs); color: var(--fg-2); line-height: var(--lh-xs); }
1235
+ .setup-cl-foot { display: flex; justify-content: flex-end; gap: var(--space-2); flex-wrap: wrap; }
1236
+ .setup-cl-brand { text-decoration: none; }
1237
+ .setup-cl-modal { max-width: 520px; }
1238
+ .setup-cl-note { margin: 0 0 var(--space-3); color: var(--fg-2); font-size: var(--type-sm); line-height: var(--lh-md); }
1239
+ }
1240
+
1241
+ /* T12 (DDR-173) — the in-app Brand-upload panel (BrandUploadPanel.jsx).
1242
+ Reuses .help-modal-* chrome; new rules cover the pick/result content only. */
1243
+ @layer components {
1244
+ .brand-up-modal { max-width: 480px; }
1245
+ .brand-up-note { margin: 0 0 var(--space-4); color: var(--fg-2); font-size: var(--type-sm); line-height: var(--lh-md); }
1246
+ .brand-up-pick { display: flex; justify-content: center; padding: var(--space-5) 0; }
1247
+ .brand-up-error { margin: var(--space-2) 0 0; color: var(--status-error); font-size: var(--type-sm); }
1248
+ .brand-up-empty { margin: 0; color: var(--fg-3); font-size: var(--type-xs); line-height: var(--lh-sm); }
1249
+ .brand-up-preview {
1250
+ display: block; max-width: 100%; max-height: 120px; margin: 0 auto var(--space-4);
1251
+ border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); background: var(--bg-3);
1252
+ padding: var(--space-3);
1253
+ }
1254
+ .brand-up-sechd {
1255
+ margin: var(--space-4) 0 var(--space-2); font-size: var(--type-xs); font-weight: 600;
1256
+ text-transform: uppercase; letter-spacing: var(--tracking-wide); color: var(--fg-2);
1257
+ }
1258
+ .brand-up-swatches { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
1259
+ .brand-up-swatch { display: flex; align-items: center; gap: var(--space-2); }
1260
+ .brand-up-swatch-chip {
1261
+ display: inline-block; width: 20px; height: 20px; border-radius: var(--radius-xs);
1262
+ border: 1px solid var(--border-default);
1263
+ }
1264
+ .brand-up-swatch-value { font-family: var(--font-mono); font-size: var(--type-xs); color: var(--fg-2); }
1265
+ .brand-up-fonts { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--space-2); }
1266
+ .brand-up-font-chip {
1267
+ padding: 2px var(--space-2); border-radius: var(--radius-pill); border: 1px solid var(--border-default);
1268
+ background: var(--bg-2); font-size: var(--type-xs); color: var(--fg-1);
1269
+ }
1270
+ .brand-up-nextstep { margin: var(--space-4) 0 0; font-size: var(--type-sm); color: var(--fg-1); line-height: var(--lh-md); }
1271
+ .brand-up-nextstep code { font-family: var(--font-mono); background: var(--bg-3); padding: 1px 4px; border-radius: var(--radius-xs); }
1272
+ .brand-up-actions { display: flex; gap: var(--space-2); margin-top: var(--space-3); }
1187
1273
  }
@@ -0,0 +1,39 @@
1
+ // quick-setup-tour.js — DDR-166 plan, Phase 2 (T7). The "empty → design system
2
+ // → first canvas → first AI edit" journey, on the existing tour engine
3
+ // (overlay.jsx), never a fork — same convention as usage-tour.js/collab-tour.js.
4
+ //
5
+ // Native, no-terminal posture (DDR-126/128): this deck can't execute
6
+ // `/design:setup-ds` for the user (the canvas iframe is cross-origin — DDR-054 —
7
+ // and the shell has no terminal). It coach-marks the Assistant panel instead:
8
+ // point at it, tell the user what to type, let THEM send it. The terminal
9
+ // `/design:*` path stays the power-user route.
10
+
11
+ export const QUICK_SETUP_TOUR = [
12
+ {
13
+ // Centered, no target — the overview step.
14
+ title: "Let's get you set up",
15
+ body: 'Three short steps: a design system for your brand, a first canvas, then your first AI edit. Everything after this happens right here — no terminal.',
16
+ },
17
+ {
18
+ target: "[data-tour='sidebar']",
19
+ title: 'Your canvases will live here',
20
+ body: "Empty for now. Once you have a design system, this fills up with real screens — click any one to open it.",
21
+ placement: 'right',
22
+ },
23
+ {
24
+ target: "[data-testid='assistant-toggle']",
25
+ title: 'Ask the Assistant to build your design system',
26
+ body: 'Open the Assistant and describe your brand — colors, type, the vibe you want. It sets up a real design system for you. Already have one? Say so and it can bring it in.',
27
+ placement: 'left',
28
+ },
29
+ {
30
+ target: "[data-tour='viewport']",
31
+ title: 'Your first canvas appears here',
32
+ body: "Once the design system is ready, ask for a first screen and watch it land right in this space, ready to edit.",
33
+ },
34
+ {
35
+ // Centered — the close-out step.
36
+ title: "You're ready",
37
+ body: 'Hold ⌘ and click anything on a canvas to point at it, describe the change in the Assistant, and watch it update live. Restart this tour any time from Help ▸ Quick setup.',
38
+ },
39
+ ];
@@ -27,8 +27,18 @@ import { registerCommand } from '../undo-stack.ts';
27
27
  * Push-once callable that submits a full `Stroke[]` to the server (or its
28
28
  * test stub) AND updates the iframe's local strokes state. The
29
29
  * annotations-layer `putStrokes` is the production binding.
30
+ *
31
+ * `before` is the baseline THIS command's `next` was computed from — passed
32
+ * through so the consumer can distinguish "an id `prev` still has that
33
+ * `before` already lacked too" (a genuinely concurrent addition, fold it in)
34
+ * from "an id `before` had that `next` deliberately dropped" (this command's
35
+ * own delete — must stay dropped even if React's rendered state hasn't
36
+ * caught up yet). See `reconcileCommit` in annotations-layer.tsx.
30
37
  */
31
- export type StrokesPutFn = (next: readonly Stroke[]) => void | Promise<void>;
38
+ export type StrokesPutFn = (
39
+ next: readonly Stroke[],
40
+ before: readonly Stroke[]
41
+ ) => void | Promise<void>;
32
42
 
33
43
  export interface AnnotationStrokesPayload {
34
44
  before: readonly Stroke[];
@@ -58,10 +68,10 @@ export function createAnnotationStrokesCommand(init: AnnotationStrokesCommandIni
58
68
  kind,
59
69
  label,
60
70
  async do() {
61
- await putFn(after.map(cloneStroke));
71
+ await putFn(after.map(cloneStroke), before.map(cloneStroke));
62
72
  },
63
73
  async undo() {
64
- await putFn(before.map(cloneStroke));
74
+ await putFn(before.map(cloneStroke), after.map(cloneStroke));
65
75
  },
66
76
  };
67
77
  }
@@ -46,6 +46,14 @@ export interface EditSourcePayload {
46
46
  before: string | null;
47
47
  /** Value after the edit. `null` = the edit removed the prop/attr (the reset path). */
48
48
  after: string | null;
49
+ /**
50
+ * `op:'text'` only — which rendered instance this edit targeted (index among
51
+ * same-cd-id DOM nodes). Needed to re-apply a `{variable}` text edit through
52
+ * undo/redo: the engine traces the string back to the right `.map()` item or
53
+ * component-prop usage, and that resolution needs the occurrence. Undefined
54
+ * for literal text (the engine ignores it there) and for css/attr.
55
+ */
56
+ occurrence?: number;
49
57
  }
50
58
 
51
59
  /**
@@ -60,6 +68,14 @@ export type EditSourceApplyFn = (apply: {
60
68
  id: string;
61
69
  key: string;
62
70
  value: string | null;
71
+ /**
72
+ * `op:'text'` re-application only — the value CURRENTLY on disk (the side we
73
+ * replace FROM: `before` on redo, `after` on undo), so a `{variable}` edit
74
+ * can be traced to the right source string. Plus the occurrence. Ignored by
75
+ * css/attr and by literal-text edits.
76
+ */
77
+ from?: string | null;
78
+ occurrence?: number;
63
79
  }) => void | Promise<void>;
64
80
 
65
81
  export const EDIT_SOURCE_KIND = 'edit-source';
@@ -72,16 +88,27 @@ export interface EditSourceCommandInit {
72
88
 
73
89
  export function createEditSourceCommand(init: EditSourceCommandInit): EditCommand {
74
90
  const { payload, applyFn } = init;
75
- const apply = (value: string | null) =>
76
- applyFn({ op: payload.op, canvas: payload.canvas, id: payload.id, key: payload.key, value });
91
+ // `from` = the value on disk BEFORE this re-application (what a `{variable}`
92
+ // text resolver matches against). do() writes `after` over `before`; undo()
93
+ // writes `before` over `after`.
94
+ const apply = (value: string | null, from: string | null) =>
95
+ applyFn({
96
+ op: payload.op,
97
+ canvas: payload.canvas,
98
+ id: payload.id,
99
+ key: payload.key,
100
+ value,
101
+ from,
102
+ occurrence: payload.occurrence,
103
+ });
77
104
  return {
78
105
  kind: EDIT_SOURCE_KIND,
79
106
  label: init.label ?? defaultLabel(payload),
80
107
  async do() {
81
- await apply(payload.after);
108
+ await apply(payload.after, payload.before);
82
109
  },
83
110
  async undo() {
84
- await apply(payload.before);
111
+ await apply(payload.before, payload.after);
85
112
  },
86
113
  };
87
114
  }
@@ -231,6 +231,76 @@
231
231
  }
232
232
  },
233
233
  "additionalProperties": false
234
+ },
235
+ "generation": {
236
+ "type": "object",
237
+ "description": "BYOK AI-media generation preferences (feature-ai-media-generation, DDR-16x). NON-SECRET ONLY — provider API keys are NEVER in this committed config; they live in the OS keychain (native) or ~/.config/maude/keys.json mode 0600 (browser). This block only carries routing/UI preferences. Hot-reloadable — Settings saves take effect without a server restart.",
238
+ "properties": {
239
+ "defaultImageProvider": {
240
+ "type": "string",
241
+ "description": "Provider id used when an image-generation request leaves the provider unspecified (e.g. 'gemini'). Must be a registered provider that supports the image modality.",
242
+ "default": "gemini"
243
+ },
244
+ "defaultAudioProvider": {
245
+ "type": "string",
246
+ "description": "Provider id used when an audio-generation request (music / sfx / voiceover) leaves the provider unspecified. Must be a registered provider that supports the audio modality (v1: 'elevenlabs').",
247
+ "default": "elevenlabs"
248
+ },
249
+ "defaultModels": {
250
+ "type": "object",
251
+ "description": "Per-provider default model id (e.g. { \"gemini\": \"gemini-2.5-flash-image\" }). Used when a request leaves the model unspecified.",
252
+ "additionalProperties": { "type": "string" },
253
+ "default": {}
254
+ },
255
+ "preferLocalWhenAvailable": {
256
+ "type": "boolean",
257
+ "description": "When a local runtime (Ollama / ComfyUI / Draw Things, post-v1) is detected on loopback, prefer it over a cloud provider for the same modality (free, offline, private). Inert until local engines ship (Phase 5).",
258
+ "default": false
259
+ },
260
+ "transcription": {
261
+ "type": "object",
262
+ "description": "Speech-to-text / subtitle engine preference (Task 2.6, DDR-164). The engine is an EXPLICIT user choice, never an automatic fallback: a chosen-but-unavailable engine errors clearly and Maude never silently switches to another. `maude design transcribe --provider` overrides this per-call.",
263
+ "properties": {
264
+ "provider": {
265
+ "type": "string",
266
+ "enum": ["whisper", "elevenlabs", "groq"],
267
+ "description": "Which engine transcribes when no --provider flag is given. `whisper` = local whisper.cpp (free, offline, no key); `elevenlabs` = ElevenLabs Scribe (cloud, key); `groq` = Groq Whisper (cloud, key). Absent ⇒ whisper.",
268
+ "default": "whisper"
269
+ },
270
+ "model": {
271
+ "type": "string",
272
+ "description": "Optional engine-specific model id override (e.g. a Groq 'whisper-large-v3-turbo', an ElevenLabs 'scribe_v1', or a local ggml model name). Absent ⇒ the engine's own default."
273
+ },
274
+ "whisperModel": {
275
+ "type": "string",
276
+ "enum": ["tiny", "base", "base.en", "small", "large-v3-turbo"],
277
+ "description": "Preferred managed local whisper.cpp model id (Task 2.7). Downloaded via Settings → Subtitles → 'Download model' into a per-machine cache; `maude design transcribe --provider whisper` auto-resolves it (no --model). Absent ⇒ any downloaded multilingual model. 'base' is the recommended default; '.en' variants are English-only."
278
+ }
279
+ },
280
+ "additionalProperties": false
281
+ },
282
+ "providers": {
283
+ "type": "object",
284
+ "description": "Per-provider non-secret toggles keyed by provider id. `enabled: false` hides a provider from the generate UI even if a key is present. `localEndpoint` overrides the default host:port for a local runtime.",
285
+ "additionalProperties": {
286
+ "type": "object",
287
+ "properties": {
288
+ "enabled": {
289
+ "type": "boolean",
290
+ "description": "Whether this provider is offered in the generate UI. Default true.",
291
+ "default": true
292
+ },
293
+ "localEndpoint": {
294
+ "type": "string",
295
+ "description": "Override host:port for a local runtime provider (e.g. '127.0.0.1:11434' for Ollama). Ignored for cloud providers."
296
+ }
297
+ },
298
+ "additionalProperties": false
299
+ },
300
+ "default": {}
301
+ }
302
+ },
303
+ "additionalProperties": false
234
304
  }
235
305
  }
236
306
  }
@@ -54,7 +54,12 @@ export interface MenuItem {
54
54
  /** Right-aligned shortcut hint (e.g. `⌘C`, `⌫`). */
55
55
  shortcut?: string;
56
56
  destructive?: boolean;
57
- disabled?: boolean;
57
+ /**
58
+ * A FUNCTION form is resolved per-click against `target` — same rationale as
59
+ * the function-form `submenu` below (e.g. "Open Timeline" only enabled when
60
+ * THIS right-clicked artboard actually contains a video sequence).
61
+ */
62
+ disabled?: boolean | ((target: ContextTarget) => boolean);
58
63
  /**
59
64
  * Optional nested flyout (e.g. `Theme ▸ DS default / Light / Dark / Follow
60
65
  * chrome`). When present the row opens a submenu on hover / ArrowRight /
@@ -70,6 +75,13 @@ export interface MenuItem {
70
75
  submenu?: MenuItem[] | ((target: ContextTarget) => MenuItem[]);
71
76
  /** Hover/title hint shown when the item is `disabled` (a11y affordance). */
72
77
  disabledHint?: string;
78
+ /**
79
+ * Fully REMOVE the item for a target (vs. `disabled`, which greys it in place).
80
+ * Resolved per-click. Use when an entry is meaningless — not merely
81
+ * unavailable — for the right-clicked element (e.g. "Edit Photo…" only makes
82
+ * sense on an `<img>`, so it shouldn't appear at all on a `<div>`).
83
+ */
84
+ hidden?: boolean | ((target: ContextTarget) => boolean);
73
85
  onSelect: (target: ContextTarget) => void;
74
86
  }
75
87
 
@@ -204,10 +216,19 @@ const MENU_CSS = `
204
216
  font: inherit;
205
217
  color: inherit;
206
218
  }
207
- .dc-context-menu .dc-menu-item:hover,
219
+ .dc-context-menu .dc-menu-item:hover {
220
+ background: color-mix(in oklab, var(--maude-chrome-fg-0, #1c1917) 8%, transparent);
221
+ }
208
222
  .dc-context-menu .dc-menu-item:focus-visible {
223
+ /* fix-photo-editor-followup-debt Task 16 — was outline: none (an
224
+ ~1.05:1-contrast, effectively invisible focus ring). Reuses
225
+ --maude-hud-accent (canvas-lib.tsx's own established focus-visible
226
+ token for this chrome family — artboard labels, zoom toolbar) rather
227
+ than inventing a new one; -2px inset offset matches that same
228
+ convention for a control inside a bordered container. */
209
229
  background: color-mix(in oklab, var(--maude-chrome-fg-0, #1c1917) 8%, transparent);
210
- outline: none;
230
+ outline: 2px solid var(--maude-hud-accent, #d63b1f);
231
+ outline-offset: -2px;
211
232
  }
212
233
  .dc-context-menu .dc-menu-item[disabled] {
213
234
  opacity: 0.45;
@@ -371,9 +392,19 @@ export function ContextMenuView({
371
392
  };
372
393
  }, [target.clientX, target.clientY, onClose, pos.x, pos.y]);
373
394
 
395
+ // Resolve per-target `hidden` and drop now-empty sections (so a section that
396
+ // held only a hidden item doesn't leave a dangling separator).
397
+ const visibleSections = sections
398
+ .map((section) =>
399
+ section.filter((item) => {
400
+ const h = typeof item.hidden === 'function' ? item.hidden(target) : item.hidden;
401
+ return !h;
402
+ })
403
+ )
404
+ .filter((section) => section.length > 0);
374
405
  return (
375
406
  <div ref={ref} className="dc-context-menu" role="menu" style={{ left: pos.x, top: pos.y }}>
376
- {sections.map((section, si) => {
407
+ {visibleSections.map((section, si) => {
377
408
  const sectionKey = section.map((i) => i.id).join('|') || `s${si}`;
378
409
  return (
379
410
  <div key={sectionKey} role="group">
@@ -411,17 +442,20 @@ function MenuItemRow({
411
442
  // Resolve a function-form submenu against THIS click's target (e.g.
412
443
  // "Select layer" computing candidates from `target.clientX/clientY`).
413
444
  const submenuItems = typeof item.submenu === 'function' ? item.submenu(target) : item.submenu;
445
+ // Same per-click resolution for a function-form `disabled` (e.g. "Open
446
+ // Timeline" only enabled on an artboard that actually contains video).
447
+ const disabled = typeof item.disabled === 'function' ? item.disabled(target) : item.disabled;
414
448
 
415
449
  if (!submenuItems || submenuItems.length === 0) {
416
450
  return (
417
451
  <button
418
452
  type="button"
419
453
  role="menuitem"
420
- disabled={item.disabled}
421
- title={item.disabled ? item.disabledHint : undefined}
454
+ disabled={disabled}
455
+ title={disabled ? item.disabledHint : undefined}
422
456
  className={`dc-menu-item${item.destructive ? ' is-destructive' : ''}`}
423
457
  onClick={() => {
424
- if (item.disabled) return;
458
+ if (disabled) return;
425
459
  item.onSelect(target);
426
460
  onClose();
427
461
  }}
@@ -464,7 +498,7 @@ function MenuItemRow({
464
498
  role="menuitem"
465
499
  aria-haspopup="menu"
466
500
  aria-expanded={subOpen}
467
- disabled={item.disabled}
501
+ disabled={disabled}
468
502
  className="dc-menu-item"
469
503
  onClick={() => (subOpen ? setSubOpen(false) : open())}
470
504
  onKeyDown={(e) => {
@@ -55,9 +55,25 @@ export interface DevServerConfig {
55
55
  newCanvasDir: string;
56
56
  newComponentDir: string;
57
57
  linkedHub?: LinkedHub;
58
+ /**
59
+ * feature-ai-media-generation (DDR-16x) — BYOK generation preferences.
60
+ * NON-SECRET ONLY: provider keys live in the OS keychain / ~/.config/maude/
61
+ * keys.json (0600), NEVER here. Hot-reloadable via the full in-place cfg swap
62
+ * in reloadConfig (only designRoot + linkedHub are boot-pinned). See
63
+ * config.schema.json for the field contract.
64
+ */
65
+ generation?: GenerationConfig;
58
66
  _source: ConfigSource;
59
67
  }
60
68
 
69
+ /** Non-secret AI-media generation preferences (DDR-16x). Keys are NOT here. */
70
+ export interface GenerationConfig {
71
+ defaultImageProvider?: string;
72
+ defaultModels?: Record<string, string>;
73
+ preferLocalWhenAvailable?: boolean;
74
+ providers?: Record<string, { enabled?: boolean; localEndpoint?: string }>;
75
+ }
76
+
61
77
  const DEFAULT_CONFIG: Omit<DevServerConfig, '_source'> = {
62
78
  name: 'Design',
63
79
  projectLabel: null,