@jxsuite/studio 2.0.1 → 2.1.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 (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +21080 -32585
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +7 -0
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +221 -245
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -53
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +30 -1
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +7 -1
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
  363. package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
  364. package/dist/chunks/files-j8a1t06k.js +0 -50
  365. package/dist/chunks/files-j8a1t06k.js.map +0 -9
  366. package/dist/chunks/git-panel-xtq5z81x.js +0 -28
  367. package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
  368. package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
  369. package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
  370. package/dist/chunks/settings-modal-17ttr96g.js +0 -29
  371. package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-0xgs7xrs.js +0 -429
  381. package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
  382. package/dist/chunks/studio-4d8hzcz5.js +0 -3427
  383. package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
  384. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  385. package/dist/chunks/studio-9smfzqhc.js +0 -19
  386. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  387. package/dist/chunks/studio-jm0z592w.js.map +0 -115
  388. package/dist/chunks/studio-m2t4mdg5.js +0 -1854
  389. package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
  390. package/dist/chunks/studio-n4p11dfy.js +0 -853
  391. package/dist/chunks/studio-n4p11dfy.js.map +0 -12
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-sjh1wt1a.js +0 -3744
  394. package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
  395. package/dist/chunks/studio-v7jkjqng.js.map +0 -46
  396. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  397. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  398. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  399. package/dist/chunks/studio-yfcstdzy.js +0 -399
  400. package/dist/chunks/studio-yfcstdzy.js.map +0 -12
  401. package/dist/chunks/studio-zp7jncs8.js +0 -469
  402. package/dist/chunks/studio-zp7jncs8.js.map +0 -14
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -2,9 +2,18 @@
2
2
  /**
3
3
  * Ai-credentials-form.ts — reusable AI provider credentials form (key / model / endpoint).
4
4
  *
5
- * Extracted from the ai-panel key gate so other hosts (e.g. the New Project modal) can embed the
6
- * same form. All draft/model-list state lives inside the closure, so multiple instances never
7
- * share state. Persists via src/services/ai-settings.ts on Save.
5
+ * Embedded by every host that needs a provider configured — **Preferences › Assistant** (⌘,) and the
6
+ * New Project modal's Import/Agent gates. All draft/model-list state lives inside the closure, so
7
+ * multiple instances never share state. Persists via src/services/ai-settings.ts on Save.
8
+ *
9
+ * The interim `Assistant: Settings…` dialog that used to host it is deleted: a provider key is an
10
+ * application setting, and the surface that owns application settings is also the one that can list
11
+ * it and revoke it (plan §9.3). Nothing about this form changed for the move, which is the argument
12
+ * for it having been a reusable form rather than a section of a dialog.
13
+ *
14
+ * Built from Spectrum controls with its layout in `styles/shell.css`: a roaming credential the user
15
+ * configures once does not get its own bespoke inputs, and it certainly does not get 200-character
16
+ * inline `style=` strings that stop responding the moment the theme changes.
8
17
  *
9
18
  * @license MIT
10
19
  */
@@ -118,35 +127,34 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
118
127
  function render(): TemplateResult {
119
128
  const haveKey = hasOpenAiKey();
120
129
  return html`
121
- <div
122
- class="ai-creds-form"
123
- style="display:flex;flex-direction:column;gap:10px;max-width:320px;text-align:left"
124
- >
125
- <div style="font-weight:600;align-self:center">AI provider key</div>
126
- ${
127
- opts.intro === undefined
128
- ? html`
129
- <div style="font-size:11px;color:var(--fg-dim)">
130
+ <div class="ai-creds-form">
131
+ <div class="ai-creds-title">AI provider key</div>
132
+ <div class="ai-creds-note">
133
+ ${
134
+ opts.intro === undefined
135
+ ? html`
130
136
  Any OpenAI-compatible key works. Stored locally in this browser; sent only to the
131
137
  Studio proxy (never to a third party except your chosen endpoint).
132
- </div>
133
- `
134
- : html`<div style="font-size:11px;color:var(--fg-dim)">${opts.intro}</div>`
135
- }
136
- <input
138
+ `
139
+ : opts.intro
140
+ }
141
+ </div>
142
+ <sp-textfield
143
+ class="ai-creds-field"
137
144
  type="password"
138
- style="width:100%;box-sizing:border-box;padding:6px 8px;border-radius:4px;border:1px solid var(--border);background:var(--bg-input);color:var(--fg);font-size:12px"
145
+ size="s"
139
146
  placeholder="sk-… or any compatible key"
140
147
  .value=${keyDraft}
141
148
  @input=${(e: Event) => {
142
149
  keyDraft = (e.target as HTMLInputElement).value;
143
150
  }}
144
- />
145
- <div style="font-weight:500;font-size:11px;margin-top:4px">Model</div>
151
+ ></sp-textfield>
152
+ <div class="ai-creds-label">Model</div>
146
153
  ${
147
154
  availableModels.length > 0
148
155
  ? html`
149
156
  <sp-combobox
157
+ class="ai-creds-field"
150
158
  size="s"
151
159
  allows-custom-value
152
160
  .value=${modelDraft}
@@ -163,18 +171,18 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
163
171
  </sp-combobox>
164
172
  `
165
173
  : html`
166
- <input
167
- type="text"
168
- style="width:100%;box-sizing:border-box;padding:6px 8px;border-radius:4px;border:1px solid var(--border);background:var(--bg-input);color:var(--fg);font-size:12px"
174
+ <sp-textfield
175
+ class="ai-creds-field"
176
+ size="s"
169
177
  placeholder="Model ID (e.g. gpt-4o, claude-sonnet-4-20250514, etc.)"
170
178
  .value=${modelDraft}
171
179
  @input=${(e: Event) => {
172
180
  modelDraft = (e.target as HTMLInputElement).value;
173
181
  }}
174
- />
182
+ ></sp-textfield>
175
183
  `
176
184
  }
177
- <div style="display:flex;gap:8px;align-items:center">
185
+ <div class="ai-creds-models">
178
186
  <sp-button size="s" variant="secondary" ?disabled=${modelsLoading} @click=${fetchModels}>
179
187
  ${
180
188
  modelsLoading
@@ -184,22 +192,18 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
184
192
  : "Fetch models"
185
193
  }
186
194
  </sp-button>
187
- ${
188
- modelsError
189
- ? html`<span style="font-size:10px;color:var(--danger)">${modelsError}</span>`
190
- : nothing
191
- }
195
+ ${modelsError ? html`<span class="ai-creds-error">${modelsError}</span>` : nothing}
192
196
  </div>
193
- <input
194
- type="text"
195
- style="width:100%;box-sizing:border-box;padding:6px 8px;border-radius:4px;border:1px solid var(--border);background:var(--bg-input);color:var(--fg);font-size:12px"
197
+ <sp-textfield
198
+ class="ai-creds-field"
199
+ size="s"
196
200
  placeholder="Endpoint (optional, e.g. http://localhost:11434/v1)"
197
201
  .value=${baseUrlDraft}
198
202
  @input=${(e: Event) => {
199
203
  baseUrlDraft = (e.target as HTMLInputElement).value;
200
204
  }}
201
- />
202
- <div style="display:flex;gap:8px;align-self:flex-end">
205
+ ></sp-textfield>
206
+ <div class="ai-creds-actions">
203
207
  ${
204
208
  haveKey
205
209
  ? html`<sp-button size="s" variant="secondary" @click=${cancel}>Cancel</sp-button>`
@@ -94,7 +94,13 @@ export function createManagedConnect(opts: ManagedConnectOptions): ManagedConnec
94
94
  return html`
95
95
  <div class="ai-managed-connect">
96
96
  <div>Use Workers AI on your own Cloudflare account — no API key needed.</div>
97
- <sp-button size="s" ?disabled=${busy} @click=${() => void connect()}>
97
+ <sp-button
98
+ size="s"
99
+ ?disabled=${busy}
100
+ @click=${() => {
101
+ void connect();
102
+ }}
103
+ >
98
104
  ${busy ? "Connecting…" : "Connect Cloudflare"}
99
105
  </sp-button>
100
106
  ${connectError ? html`<div class="ai-managed-connect-error">${connectError}</div>` : nothing}
@@ -1,59 +1,120 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Dynamic-slot — the shared "make this dynamic" control for any bindable document position.
3
+ * Dynamic-slot — the shared Value Source control for any bindable document position.
4
4
  *
5
- * Renders the panel-provided static widget plus a label-side mode button that cycles the value up
6
- * the Rule of Least Power ladder (spec §2.2): literal → $ref pointer → ${} template → $expression.
7
- * Capability-driven: each slot offers only the modes its schema position allows, so panels stay
8
- * decoupled from which rungs are blessed where. Each mode's last value is remembered per field for
9
- * the session, so cycling back to an earlier mode restores what the user had there.
5
+ * Renders the panel-provided static widget plus a label-side chip that names, in plain language,
6
+ * how this value is produced — Fixed value / From data… / Mixed text / Formula
7
+ * (`ui/value-source.ts` owns the vocabulary). Clicking the chip opens a picker listing every rung
8
+ * the position permits, so **any rung is one action away**. It used to be a cycle ring: `$ref →
9
+ * literal` meant passing through `${}`, committing a template to the document on the way past, and
10
+ * the only way to know where the next click would land was the tooltip.
11
+ *
12
+ * The rungs are not declared by the caller but **derived from the schema** at the named position
13
+ * (plan §6.3): `caps` is a {@link SlotPosition}, or a schema handed in for the one case a position
14
+ * name cannot cover — a field in an extension's config form, whose schema arrives at runtime. There
15
+ * is no way for a caller to state a rung list of its own.
16
+ *
17
+ * Each mode's last value is remembered per field for the session, so switching back to an earlier
18
+ * mode restores what the user had there. The same memory is exported for the Events tab, which
19
+ * offers the same ladder in its own layout.
10
20
  */
11
21
 
12
22
  import { html, nothing } from "lit-html";
13
23
  import { live } from "lit-html/directives/live.js";
14
- import { isRef, isTemplateString } from "@jxsuite/schema/guards";
24
+ import { isRef } from "@jxsuite/schema/guards";
15
25
  import { cloneValue } from "../tabs/doc-op-apply";
16
26
  import { activeTab } from "../workspace/workspace";
17
27
  import { renderExpressionEditor } from "./expression-editor";
28
+ import { VALUE_SOURCE_HINTS, VALUE_SOURCE_LABELS, slotCaps, slotMode } from "./value-source";
18
29
 
19
30
  import type { TemplateResult } from "lit-html";
20
31
  import type { EditorPreview } from "./expression-editor";
21
32
  import type { JxStateDefinition } from "@jxsuite/schema/types";
22
33
  import type { JsonValue } from "../types";
34
+ import type { SlotCapsSource, SlotMode } from "./value-source";
23
35
 
24
- export type SlotMode = "literal" | "ref" | "template" | "expression";
25
-
26
- const MODE_LABELS: Record<SlotMode, string> = {
27
- expression: "fx",
28
- literal: "abc",
29
- ref: "$ref",
30
- template: "${}",
31
- };
32
-
33
- const MODE_NOUNS: Record<SlotMode, string> = {
34
- expression: "expression",
35
- literal: "static",
36
- ref: "signal binding",
37
- template: "template literal",
38
- };
36
+ export type { SlotCapsSource, SlotMode, SlotPosition } from "./value-source";
37
+ export { slotCaps, slotMode } from "./value-source";
39
38
 
40
39
  /*
41
- * Session memory of each field's last value per mode, so cycling modes round-trips user input.
40
+ * Session memory of each field's last value per mode, so switching modes round-trips user input.
42
41
  * Keyed `${tabId}|${fieldKey}|${mode}`; the mode key is derived from the value at stash time, so a
43
42
  * restore always lands a value whose slotMode() matches the target mode. `undefined` is a
44
43
  * legitimate stash (cleared literal), hence Map.has() discrimination on recall.
45
44
  */
46
45
  const slotModeMemory = new Map<string, JsonValue | undefined>();
47
46
 
48
- /** Test hook: drop all remembered per-mode field values. */
47
+ /*
48
+ * The rung each field is CURRENTLY being edited at, keyed `${tabId}|${fieldKey}`.
49
+ *
50
+ * Without this the rendered rung was re-sniffed from the document value on every render, so typing
51
+ * `${` into a Fixed value field swapped the widget out from under the cursor mid-keystroke — and
52
+ * typing the closing brace away swapped it back. A keystroke can only ever move a value between
53
+ * the literal and mixed-text rungs (both are plain strings); every other transition needs a
54
+ * structural change to the value, which a text edit cannot make. So those two rungs hold once
55
+ * entered, and everything else still follows the document.
56
+ */
57
+ const slotModeRendered = new Map<string, SlotMode>();
58
+
59
+ /** Test hook: drop all remembered per-mode field values and per-field rungs. */
49
60
  export function resetSlotModeMemory(): void {
50
61
  slotModeMemory.clear();
62
+ slotModeRendered.clear();
51
63
  }
52
64
 
53
65
  function memoryKey(fieldKey: string, mode: SlotMode): string {
54
66
  return `${activeTab.value?.id ?? "-"}|${fieldKey}|${mode}`;
55
67
  }
56
68
 
69
+ function fieldScopeKey(fieldKey: string): string {
70
+ return `${activeTab.value?.id ?? "-"}|${fieldKey}`;
71
+ }
72
+
73
+ /** Remember `value` as this field's representation at `mode`, for a later switch back. */
74
+ export function stashSlotValue(
75
+ fieldKey: string,
76
+ mode: SlotMode,
77
+ value: JsonValue | undefined,
78
+ ): void {
79
+ slotModeMemory.set(memoryKey(fieldKey, mode), cloneValue(value));
80
+ }
81
+
82
+ /** Whether this field has a remembered representation at `mode`. */
83
+ export function hasStashedSlotValue(fieldKey: string, mode: SlotMode): boolean {
84
+ return slotModeMemory.has(memoryKey(fieldKey, mode));
85
+ }
86
+
87
+ /** This field's remembered representation at `mode` (a fresh clone). */
88
+ export function recallSlotValue(fieldKey: string, mode: SlotMode): JsonValue | undefined {
89
+ return cloneValue(slotModeMemory.get(memoryKey(fieldKey, mode)));
90
+ }
91
+
92
+ /** Record the rung the user just chose for this field, so the next render honours it. */
93
+ export function setSlotMode(fieldKey: string, mode: SlotMode): void {
94
+ slotModeRendered.set(fieldScopeKey(fieldKey), mode);
95
+ }
96
+
97
+ /** Whether a keystroke alone could have produced this rung transition. */
98
+ function isTextEditRung(mode: SlotMode): boolean {
99
+ return mode === "literal" || mode === "template";
100
+ }
101
+
102
+ /**
103
+ * The rung to render this field at: the one it is already being edited at when a text edit could
104
+ * explain the difference, otherwise whatever the document value says. Records its answer.
105
+ */
106
+ export function effectiveSlotMode(fieldKey: string, value: unknown): SlotMode {
107
+ const key = fieldScopeKey(fieldKey);
108
+ const sniffed = slotMode(value);
109
+ const previous = slotModeRendered.get(key);
110
+ const mode =
111
+ previous !== undefined && isTextEditRung(previous) && isTextEditRung(sniffed)
112
+ ? previous
113
+ : sniffed;
114
+ slotModeRendered.set(key, mode);
115
+ return mode;
116
+ }
117
+
57
118
  function hasRefOptions(opts: DynamicSlotOpts): boolean {
58
119
  return opts.stateDefs.length > 0 || Boolean(opts.extraSignals?.length);
59
120
  }
@@ -72,10 +133,19 @@ export interface DynamicSlotOpts {
72
133
  onChange: (v?: JsonValue) => void;
73
134
  /** Panel-provided literal editor for this slot's type. */
74
135
  staticWidget: unknown;
75
- /** Modes this schema position allows, in ladder order. "literal" and "ref" are the floor. */
76
- caps: SlotMode[];
136
+ /** Where this slot's rungs are derived from — a named document position, or a schema. */
137
+ caps: SlotCapsSource;
77
138
  /** State keys offered by the ref picker and expression editor. */
78
139
  stateDefs: string[];
140
+ /**
141
+ * Let the From data… rung take a pointer that is not in its list.
142
+ *
143
+ * The list is complete wherever it is a component's own signals. It is not complete in an
144
+ * extension's config form, where a value may point at a route param, a signal, or a pointer
145
+ * neither the form nor the panel enumerates — so there the same rung is a combobox whose options
146
+ * are suggestions, which is what `jx-value-selector` is for.
147
+ */
148
+ allowCustomRef?: boolean;
79
149
  /** Full state defs map — lets expression mode resolve named-formula catalog entries. */
80
150
  stateEntries?: Record<string, JxStateDefinition> | null;
81
151
  /** Extra pointer options beyond #/state/* (e.g. $map/item within repeater templates). */
@@ -86,20 +156,16 @@ export interface DynamicSlotOpts {
86
156
  preview?: EditorPreview | null;
87
157
  /** Static value restored when de-escalating to literal mode. */
88
158
  literalDefault?: JsonValue;
89
- }
90
-
91
- /** Detect which rung of the ladder a raw document value occupies. */
92
- export function slotMode(value: unknown): SlotMode {
93
- if (isRef(value)) {
94
- return "ref";
95
- }
96
- if (isTemplateString(value)) {
97
- return "template";
98
- }
99
- if (value && typeof value === "object" && "$expression" in value) {
100
- return "expression";
101
- }
102
- return "literal";
159
+ /**
160
+ * A position-specific seed for a rung, overriding the generic one.
161
+ *
162
+ * The generic expression seed is `{ operator: "??" }`, which is a sensible starting point almost
163
+ * everywhere and an INVALID document in a position whose schema narrows which operators it takes.
164
+ * An element's `tagName` is the first such position — a `TagExpression` is `?:` or `switch` and
165
+ * nothing else — so a generic seed would drop a document that fails its own validator the moment
166
+ * the chip is clicked. Return `undefined` to accept the generic seed.
167
+ */
168
+ seedFor?: (mode: SlotMode) => JsonValue | undefined;
103
169
  }
104
170
 
105
171
  function defaultForSlotMode(mode: SlotMode, opts: DynamicSlotOpts): JsonValue | undefined {
@@ -115,13 +181,47 @@ function defaultForSlotMode(mode: SlotMode, opts: DynamicSlotOpts): JsonValue |
115
181
  case "expression": {
116
182
  return { $expression: { operator: "??", target: null, value: null } };
117
183
  }
184
+ case "function": {
185
+ return { $prototype: "Function", body: "", parameters: [] };
186
+ }
118
187
  default: {
119
188
  return opts.literalDefault;
120
189
  }
121
190
  }
122
191
  }
123
192
 
193
+ /** Every pointer the From data… rung offers, as `{ value, label }` options. */
194
+ function refOptions(opts: DynamicSlotOpts): SignalOption[] {
195
+ return [
196
+ ...opts.stateDefs.map((name) => ({ label: name, value: `#/state/${name}` })),
197
+ ...(opts.extraSignals ?? []),
198
+ ];
199
+ }
200
+
201
+ function renderCustomRefWidget(refVal: string, opts: DynamicSlotOpts) {
202
+ return html`
203
+ <jx-value-selector
204
+ size="s"
205
+ style="flex:1"
206
+ placeholder="#/state/…"
207
+ .value=${refVal}
208
+ .options=${refOptions(opts)}
209
+ @change=${(e: Event & { detail?: { value?: string } }) => {
210
+ const v = (e.detail?.value ?? (e.target as HTMLInputElement).value ?? "").trim();
211
+ if (v) {
212
+ opts.onChange({ $ref: v });
213
+ } else {
214
+ opts.onChange();
215
+ }
216
+ }}
217
+ ></jx-value-selector>
218
+ `;
219
+ }
220
+
124
221
  function renderRefWidget(refVal: string, opts: DynamicSlotOpts) {
222
+ if (opts.allowCustomRef) {
223
+ return renderCustomRefWidget(refVal, opts);
224
+ }
125
225
  return html`
126
226
  <sp-picker
127
227
  size="s"
@@ -168,26 +268,52 @@ function renderTemplateWidget(value: unknown, opts: DynamicSlotOpts) {
168
268
  }
169
269
 
170
270
  export interface DynamicSlotParts {
171
- /** Label-side mode-cycle button. */
271
+ /** Label-side Value Source chip and its picker. */
172
272
  modeButton: TemplateResult;
173
273
  /** Active-mode widget, sized to fill the row's widget column. */
174
274
  widget: TemplateResult;
175
275
  }
176
276
 
177
277
  /**
178
- * The label-side field-mode button. Shows the current mode's glyph — quiet/gray while static and
179
- * accent-tinted once the value is dynamic — and each click cycles to the next capped mode, stashing
180
- * the outgoing mode's value and restoring the incoming mode's remembered one.
278
+ * Switch a field to `next`: stash the outgoing representation, restore the incoming one if this
279
+ * field has been there before, and record the new rung. Shared with the Events tab, which used to
280
+ * throw the outgoing representation away on every mode change.
281
+ */
282
+ export function switchSlotMode(
283
+ fieldKey: string,
284
+ from: SlotMode,
285
+ to: SlotMode,
286
+ current: JsonValue | undefined,
287
+ seed: JsonValue | undefined,
288
+ ): JsonValue | undefined {
289
+ stashSlotValue(fieldKey, from, current);
290
+ setSlotMode(fieldKey, to);
291
+ return hasStashedSlotValue(fieldKey, to) ? recallSlotValue(fieldKey, to) : seed;
292
+ }
293
+
294
+ /** A DOM id for the chip, so `sp-overlay` can name it as its trigger. */
295
+ function chipId(fieldKey: string): string {
296
+ return `value-source-${fieldKey.replaceAll(/[^\dA-Za-z]+/g, "-")}`;
297
+ }
298
+
299
+ /**
300
+ * The label-side Value Source chip: the current rung in plain language — quiet while the value is
301
+ * fixed, accent-tinted once it is produced from something else — opening a picker of every rung
302
+ * this position permits.
181
303
  */
182
- function renderModeButton(mode: SlotMode, caps: SlotMode[], opts: DynamicSlotOpts): TemplateResult {
304
+ function renderModeChip(mode: SlotMode, caps: SlotMode[], opts: DynamicSlotOpts): TemplateResult {
183
305
  const color =
184
- mode !== "literal"
185
- ? "color:var(--spectrum-accent-content-color-default, #5c9dff)"
186
- : "color:var(--spectrum-gray-600, #808080)";
187
- // A ref rung with nothing to point at is a dead end — drop it rather than trap the cycle there.
188
- const cycle = caps.filter((m) => m !== "ref" || hasRefOptions(opts));
189
- if (cycle.length < 2) {
190
- const hint = `Field mode: ${MODE_NOUNS[mode]} (no other modes available)`;
306
+ mode === "literal"
307
+ ? "color:var(--spectrum-gray-600, #808080)"
308
+ : "color:var(--spectrum-accent-content-color-default, #5c9dff)";
309
+ // A ref rung with nothing to point at is a dead end — drop it rather than offer an empty picker.
310
+ const offered = caps.filter((m) => m !== "ref" || hasRefOptions(opts));
311
+ const label = VALUE_SOURCE_LABELS[mode];
312
+ /* Disabled only when the rung this value already sits on is the only one there is. A value on a
313
+ rung the position does NOT permit — a fixed string left in a `$switch`, say — is exactly the
314
+ case that needs the chip most, and counting rungs alone used to grey it out. */
315
+ if (!offered.some((m) => m !== mode)) {
316
+ const hint = `Value source: ${label} (no other source available here)`;
191
317
  return html`
192
318
  <sp-action-button
193
319
  size="xs"
@@ -197,53 +323,70 @@ function renderModeButton(mode: SlotMode, caps: SlotMode[], opts: DynamicSlotOpt
197
323
  style=${color}
198
324
  title=${hint}
199
325
  aria-label=${hint}
200
- >${MODE_LABELS[mode]}</sp-action-button
326
+ >${label}</sp-action-button
201
327
  >
202
328
  `;
203
329
  }
204
- // An uncapped current mode indexes to -1, so its next stop is cycle[0] (literal).
205
- const next = cycle[(cycle.indexOf(mode) + 1) % cycle.length]!;
206
- const hint = `Field mode: ${MODE_NOUNS[mode]} — click for ${MODE_NOUNS[next]}`;
330
+ const id = chipId(opts.fieldKey);
331
+ const hint = `Value source: ${label} — click to change`;
332
+ const choose = (next: SlotMode) => {
333
+ if (next === mode) {
334
+ return;
335
+ }
336
+ /* Leaving ref seeds an empty literal stash with the signal's declared default, so
337
+ unbind-restores-default survives a detour (ref → mixed text → fixed value). */
338
+ if (
339
+ mode === "ref" &&
340
+ opts.literalDefault !== undefined &&
341
+ !hasStashedSlotValue(opts.fieldKey, "literal")
342
+ ) {
343
+ stashSlotValue(opts.fieldKey, "literal", opts.literalDefault);
344
+ }
345
+ opts.onChange(
346
+ switchSlotMode(
347
+ opts.fieldKey,
348
+ mode,
349
+ next,
350
+ cloneValue(opts.value as JsonValue | undefined),
351
+ opts.seedFor?.(next) ?? defaultForSlotMode(next, opts),
352
+ ),
353
+ );
354
+ };
207
355
  return html`
208
356
  <sp-action-button
357
+ id=${id}
209
358
  size="xs"
210
359
  quiet
211
360
  class="dynamic-slot-mode"
212
361
  style=${color}
213
362
  title=${hint}
214
363
  aria-label=${hint}
215
- @click=${() => {
216
- slotModeMemory.set(
217
- memoryKey(opts.fieldKey, mode),
218
- cloneValue(opts.value as JsonValue | undefined),
219
- );
220
- // Leaving ref seeds an empty literal stash with the signal's declared default.
221
- // Unbind-restores-default thus survives multi-hop cycles (ref → template → literal).
222
- if (mode === "ref" && opts.literalDefault !== undefined) {
223
- const litKey = memoryKey(opts.fieldKey, "literal");
224
- if (!slotModeMemory.has(litKey)) {
225
- slotModeMemory.set(litKey, cloneValue(opts.literalDefault));
226
- }
227
- }
228
- const key = memoryKey(opts.fieldKey, next);
229
- opts.onChange(
230
- slotModeMemory.has(key)
231
- ? cloneValue(slotModeMemory.get(key))
232
- : defaultForSlotMode(next, opts),
233
- );
234
- }}
235
- >${MODE_LABELS[mode]}</sp-action-button
364
+ >${label}</sp-action-button
236
365
  >
366
+ <sp-overlay trigger="${id}@click" placement="bottom-start" type="auto">
367
+ <sp-popover>
368
+ <sp-menu size="s" label="Value source">
369
+ ${offered.map(
370
+ (m) => html`
371
+ <sp-menu-item data-mode=${m} ?selected=${m === mode} @click=${() => choose(m)}
372
+ >${VALUE_SOURCE_LABELS[m]}
373
+ <span slot="description">${VALUE_SOURCE_HINTS[m]}</span></sp-menu-item
374
+ >
375
+ `,
376
+ )}
377
+ </sp-menu>
378
+ </sp-popover>
379
+ </sp-overlay>
237
380
  `;
238
381
  }
239
382
 
240
383
  /**
241
384
  * Render a bindable slot as two parts the panel places independently: the active mode's widget for
242
- * the row's value column, and the mode-cycle button for the label side.
385
+ * the row's value column, and the Value Source chip for the label side.
243
386
  */
244
387
  export function renderDynamicSlot(opts: DynamicSlotOpts): DynamicSlotParts {
245
- const caps: SlotMode[] = opts.caps.length > 0 ? opts.caps : ["literal", "ref"];
246
- const mode = slotMode(opts.value);
388
+ const caps = slotCaps(opts.caps);
389
+ const mode = effectiveSlotMode(opts.fieldKey, opts.value);
247
390
 
248
391
  const widget =
249
392
  mode === "ref"
@@ -264,7 +407,7 @@ export function renderDynamicSlot(opts: DynamicSlotOpts): DynamicSlotParts {
264
407
  : opts.staticWidget;
265
408
 
266
409
  return {
267
- modeButton: renderModeButton(mode, caps, opts),
410
+ modeButton: renderModeChip(mode, caps, opts),
268
411
  widget: html`
269
412
  <div class="dynamic-slot" style="display:flex;flex:1;min-width:0">
270
413
  <div style="flex:1;min-width:0">${widget}</div>