@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
@@ -1,50 +1,146 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Formula workspace — the full-screen structured editing surface for a single `$expression` (spec
4
- * §19.9, milestone M4). Takes over the canvas area exactly like the Monaco function editor
5
- * (renderFunctionEditor): `TabUi.editingFormula` identifies the document position being edited (a
6
- * state entry's `$expression` via defName, or an element event binding's via path + eventKey).
3
+ * ⑪ · Logic — the formula workspace and the function editor, hosted by the Bottom dock.
7
4
  *
8
- * Layout: the chip pipeline summarizes the whole tree with live value badges on top; the recursive
9
- * expression form edits the chip-selected sub-node in the main pane; a data rail mirrors the canvas
10
- * dataScope snapshot on the right; the footer reports the root result (or the evaluation error).
5
+ * **What changed, and why it is the whole point.** Both of these surfaces used to TAKE OVER the
6
+ * canvas: `TabUi.editingFormula` (this file) and `TabUi.editingFunction` (`panels/editors.ts`) each
7
+ * cleared `canvasWrap`, dropped every canvas panel and drew themselves over the stage. So the one
8
+ * artefact whose values you are authoring — the page — was the one thing you could not see while
9
+ * authoring them, and the workspace's own data rail existed to paper over it: a frozen snapshot of
10
+ * a scope, shown because the live thing had just been unmounted. Plan §12 P8.5: "no editor hides
11
+ * the page it computes." They are a **tab of the Bottom dock** now (§3.2 ⑪), which sits under the
12
+ * pane grid, so the page renders beside the formula and keeps rendering while you edit it.
13
+ *
14
+ * **A dock tab is not a reparented takeover.** The surface has to work at dock height, so the
15
+ * header is one row, the chip pipeline and the result are single lines, the data rail is a narrow
16
+ * column that scrolls rather than a 280px slab, and every measurement lives in `styles/panels.css`
17
+ * instead of the inline `style=` attributes that kept seventeen `fw-*` classes on
18
+ * `scripts/check-styles.ts`'s orphan list.
19
+ *
20
+ * **One target, two surfaces, one tab.** {@link logicTarget} is the single reader of the two
21
+ * `TabUi` fields, and the function editor wins when both are set — the precedence `tabs/tab.ts` has
22
+ * always documented, now stated in exactly one place. The tab's `when` is that same predicate, so
23
+ * Logic exists only while something is open in it, and `panels/bottom-dock.ts` reveals the dock on
24
+ * that tab when a target appears.
25
+ *
26
+ * **The close is real.** Both surfaces carry one, and closing is the only thing that clears the
27
+ * target — leaving the dock, switching tabs and collapsing the dock all keep your place, because
28
+ * the editor no longer owns a screen it would otherwise be stranded on.
11
29
  *
12
30
  * Every edit immutably replaces the selected sub-node within the root and writes the WHOLE root
13
- * node back through transactDoc — undo/redo and canvas/panel refresh come free.
31
+ * node back through `transactDoc` — undo/redo and the canvas patch come free, and because the
32
+ * canvas is still mounted the patch lands in a page you can watch.
14
33
  */
15
34
 
16
- import { html, render as litRender } from "lit-html";
35
+ import { html } from "lit-html";
17
36
  import { getEventBinding, isExpressionDef, isJsonObject } from "@jxsuite/schema/guards";
18
37
 
19
- import { canvasPanels, canvasWrap, getNodeAtPath, updateUi } from "../store";
38
+ import { shallowRef } from "../reactivity";
39
+ import { getNodeAtPath, updateUi } from "../store";
20
40
  import { activeTab } from "../workspace/workspace";
21
41
  import { mutateAddDef, mutateUpdateDef, mutateUpdateProperty, transactDoc } from "../tabs/transact";
22
- import { view } from "../view";
42
+ import { setBottomTab } from "../shell";
23
43
  import { chipSummary, renderFormulaChips } from "../ui/formula-chips";
24
44
  import { renderExpressionEditor } from "../ui/expression-editor";
25
45
  import { applyCatalogPick, formulaCatalog } from "../ui/formula-catalog";
26
46
  import { openFormulaPalette } from "../ui/formula-palette";
27
47
  import { livePreviewExpression } from "../services/live-preview";
48
+ import { closeFunctionEditor, functionEditorTemplate, syncFunctionEditor } from "./editors";
49
+ import { registerPanel } from "./panel-registry";
50
+ import { renderEmptyState } from "./empty-state";
28
51
  import { dataTypeLabel, renderDataTreeTemplate, unwrapSignal } from "./data-explorer";
29
52
 
53
+ import {
54
+ argsSchema,
55
+ pathArg,
56
+ pathProperty,
57
+ stringArg,
58
+ stringProperty,
59
+ } from "../commands/command-args";
60
+
30
61
  import type { JxNodeValue } from "../tabs/transact";
31
- import type { FormulaEditDef, JsonValue } from "../types";
62
+ import type { AnyCommand, CommandRegistry } from "../commands/registry";
63
+ import type { PanelBody } from "./panel-registry";
64
+ import type { ExpressionPreview } from "../services/preview-eval";
65
+ import type { FormulaEditDef, FunctionEditDef, JsonValue } from "../types";
32
66
  import type { Tab } from "../tabs/tab";
33
67
  import type { JxStateDefinition } from "@jxsuite/schema/types";
34
68
  import type { TemplateResult } from "lit-html";
35
69
 
36
70
  type NodePath = (string | number)[];
37
71
 
38
- // ─── Module state ────────────────────────────────────────────────────────────
72
+ /** What the Logic tab is showing: the Monaco function body, or the structured `$expression`. */
73
+ export type LogicTarget =
74
+ | { surface: "function"; editing: FunctionEditDef }
75
+ | { surface: "formula"; editing: FormulaEditDef };
39
76
 
40
- /** Chip-selected sub-node path within the root expression (root = []). */
41
- let _selectedPath: NodePath = [];
77
+ /**
78
+ * The Logic tab's target, or `null` when nothing is open in it.
79
+ *
80
+ * The ONE reader of `TabUi.editingFunction` / `TabUi.editingFormula`, and therefore the one place
81
+ * their precedence is decided. Two surfaces used to answer that question separately — the canvas
82
+ * render pipeline by branch order, the pane context bar by an `||` — which is two definition sites
83
+ * for a rule neither of them stated.
84
+ *
85
+ * @param {Tab | null} [tab] The tab to read; defaults to the focused one.
86
+ * @returns {LogicTarget | null}
87
+ */
88
+ export function logicTarget(tab: Tab | null = activeTab.value): LogicTarget | null {
89
+ const ui = tab?.session.ui;
90
+ if (!ui) {
91
+ return null;
92
+ }
93
+ if (ui.editingFunction) {
94
+ return { editing: ui.editingFunction as FunctionEditDef, surface: "function" };
95
+ }
96
+ if (ui.editingFormula) {
97
+ return { editing: ui.editingFormula as FormulaEditDef, surface: "formula" };
98
+ }
99
+ return null;
100
+ }
42
101
 
43
- /** Serialized editing target — the selection resets when the workspace retargets. */
44
- let _targetKey: string | null = null;
102
+ /**
103
+ * Put the Logic tab on screen. Idempotent.
104
+ *
105
+ * `setBottomTab` is the shell's own "reveal" — it selects the tab AND opens the dock, for the same
106
+ * reason `setActivityTab` does: "show me the formula" means the formula is on screen when the call
107
+ * returns, not that a tab is selected inside a closed dock.
108
+ */
109
+ export function revealLogicPanel(): void {
110
+ setBottomTab("logic");
111
+ }
45
112
 
46
- /** Owning tab of the current target — a same-looking target in another tab is a retarget. */
47
- let _targetTab: Tab | null = null;
113
+ /**
114
+ * Open something in the Logic tab and put it on screen. The one WRITER of the two `TabUi` fields
115
+ * {@link logicTarget} reads, as that is its one reader.
116
+ *
117
+ * **One tab holds one target, so opening either surface clears the other.** `logicTarget` gives the
118
+ * function editor precedence when both are set, which is the right tie-break for a state that
119
+ * should never occur — and every opener used to create it. Four buttons and two commands each set
120
+ * their own field and left the other alone, so "Open in formula workspace" on an `$expression`
121
+ * while a Function body was open was a dead click: the dock went on showing the function, and the
122
+ * target key had not changed so nothing revealed either. Writing both fields here means the fifth
123
+ * opener inherits the rule instead of re-deciding it.
124
+ *
125
+ * **The reveal is the OTHER half, and it is a different event from the dock's.**
126
+ * `panels/bottom-dock.ts` reveals when a target APPEARS or CHANGES, at most once per target — that
127
+ * is what lets you close the dock over an open formula and have it stay closed (§16.3). Neither
128
+ * half can do the other's job: an effect on the target cannot see "the user pressed the button
129
+ * again" (same target, no change, no reveal — the dock stayed shut and the button did nothing), and
130
+ * a gesture cannot cover a target that appears without one. So the gesture says so explicitly, and
131
+ * this is what makes a call to it a gesture.
132
+ *
133
+ * @param {LogicTarget} target The surface to show and what it should be pointed at.
134
+ */
135
+ export function openLogicTarget(target: LogicTarget): void {
136
+ updateUi(
137
+ activeTab.value,
138
+ "editingFunction",
139
+ target.surface === "function" ? target.editing : null,
140
+ );
141
+ updateUi(activeTab.value, "editingFormula", target.surface === "formula" ? target.editing : null);
142
+ revealLogicPanel();
143
+ }
48
144
 
49
145
  function isExprNode(value: unknown): value is Record<string, unknown> {
50
146
  return isJsonObject(value) && typeof value.operator === "string";
@@ -119,215 +215,245 @@ function writeRoot(tab: Tab, editing: FormulaEditDef, newRoot: unknown) {
119
215
  mutateUpdateProperty(t, path, eventKey, { $expression: newRoot } as JxNodeValue),
120
216
  );
121
217
  }
122
- renderFormulaWorkspace();
123
218
  }
124
219
 
125
- /** Close the workspace — clears `editingFormula`; the canvas re-renders its regular mode. */
220
+ /** Close the workspace — clears `editingFormula`; the Logic tab leaves the strip with it. */
126
221
  export function closeFormulaWorkspace() {
127
- updateUi("editingFormula", null);
222
+ updateUi(activeTab.value, "editingFormula", null);
128
223
  }
129
224
 
130
- // ─── Rendering ───────────────────────────────────────────────────────────────
225
+ // ─── The Logic tab ───────────────────────────────────────────────────────────
131
226
 
132
- const PANE_BORDER = "1px solid var(--spectrum-gray-200, #323232)";
227
+ /**
228
+ * The chip selection, keyed by the target it belongs to.
229
+ *
230
+ * Keyed rather than reset, because the reset used to happen INSIDE the render — and a render that
231
+ * writes the state it reads is a reactive loop the moment the surface becomes an effect, which is
232
+ * exactly what hosting it in the dock makes it. A selection whose key no longer matches simply does
233
+ * not apply, so retargeting starts at the root with nothing written.
234
+ */
235
+ const _selection = shallowRef<{ tab: Tab | null; key: string; path: NodePath }>({
236
+ key: "",
237
+ path: [],
238
+ tab: null,
239
+ });
133
240
 
134
- const MONO = "font-family:var(--spectrum-code-font-family, monospace)";
241
+ /**
242
+ * The document position a selection belongs to. Paired with the tab OBJECT, never its id: a tab
243
+ * that was closed and reopened at the same path is a different document, and inheriting a chip
244
+ * selection into it is how you edit the wrong sub-node of a formula that merely looks the same.
245
+ */
246
+ function selectionKey(editing: FormulaEditDef): string {
247
+ return JSON.stringify(editing);
248
+ }
135
249
 
136
250
  /**
137
- * Render the formula workspace into the canvas area. Mirrors renderFunctionEditor's takeover: the
138
- * canvas DnD/event handlers and panel registrations are cleared and canvasWrap becomes a stretched
139
- * column owned by this template.
251
+ * Register the Bottom dock's Logic tab.
252
+ *
253
+ * Defined here and registered from `panels/bottom-dock.ts`, the same way Problems is defined beside
254
+ * its notification store: the dock owns the strip, the surface owns the record.
140
255
  */
141
- export function renderFormulaWorkspace() {
142
- const tab = activeTab.value;
143
- const editing = tab?.session.ui.editingFormula as FormulaEditDef | null | undefined;
144
- if (!tab || !editing) {
145
- return;
146
- }
256
+ export function registerLogicPanel(): void {
257
+ registerPanel({
258
+ id: "logic",
259
+ title: "Logic",
260
+ level: "document",
261
+ dock: "bottom",
262
+ icon: "sp-icon-event",
263
+ // No rail button: Logic has no steady state to badge. It exists while a formula or a function
264
+ // Is open and leaves the strip when you close it, which is what `when` says below.
265
+ rail: false,
266
+ when: () => logicTarget() !== null,
267
+ render: (ctx) => logicPanelBody(ctx.rerender),
268
+ afterRender: (_ctx, host) => syncFunctionEditor(host),
269
+ });
270
+ }
147
271
 
148
- // Reset the chip selection when the workspace retargets.
149
- const targetKey = JSON.stringify(editing);
150
- if (targetKey !== _targetKey || tab !== _targetTab) {
151
- _targetKey = targetKey;
152
- _targetTab = tab;
153
- _selectedPath = [];
272
+ /**
273
+ * What the Logic tab draws.
274
+ *
275
+ * @param {() => void} rerender The dock's repaint, handed to the live-preview refresh.
276
+ * @returns {PanelBody}
277
+ */
278
+ export function logicPanelBody(rerender: () => void): PanelBody {
279
+ const tab = activeTab.value;
280
+ const target = logicTarget(tab);
281
+ if (!tab || !target) {
282
+ return renderEmptyState({
283
+ message: "Open a formula or a function to edit it here, beside the page it computes.",
284
+ });
154
285
  }
286
+ return target.surface === "function"
287
+ ? functionPaneTemplate(target.editing)
288
+ : workspaceTemplate(tab, target.editing, rerender);
289
+ }
155
290
 
156
- // Clean up canvas DnD and event handlers (the canvas surface is being replaced).
157
- for (const fn of view.canvasDndCleanups) {
158
- fn();
159
- }
160
- view.canvasDndCleanups = [];
161
- for (const fn of view.canvasEventCleanups) {
162
- fn();
163
- }
164
- view.canvasEventCleanups = [];
165
- canvasPanels.length = 0;
166
-
167
- // Eject foreign DOM (iframe canvas markup) plus any stale Lit part before the first render;
168
- // Subsequent renders diff in place so form focus survives re-renders.
169
- if (!canvasWrap.firstElementChild?.classList.contains("formula-workspace")) {
170
- canvasWrap.textContent = "";
171
- // @ts-expect-error -- _$litPart$ is Lit's private render-part marker, not in the DOM types
172
- delete canvasWrap["_$litPart$"];
173
- }
174
- canvasWrap.style.padding = "0";
175
- canvasWrap.style.flexDirection = "column";
176
- canvasWrap.style.alignItems = "stretch";
291
+ /** The one header both surfaces wear: what is open, what kind it is, its verbs, and the close. */
292
+ function logicHeaderTemplate(
293
+ glyph: string,
294
+ name: string,
295
+ kind: string,
296
+ title: string,
297
+ verbs: TemplateResult | null,
298
+ close: () => void,
299
+ ): TemplateResult {
300
+ return html`
301
+ <div class="fw-header">
302
+ <span class="fw-title" title=${title}>${glyph} ${name}</span>
303
+ <span class="fw-kind">${kind}</span>
304
+ <div class="fw-header-gap"></div>
305
+ ${verbs}
306
+ <sp-action-button
307
+ quiet
308
+ size="s"
309
+ class="fw-close"
310
+ title="Close"
311
+ @click=${() => {
312
+ close();
313
+ }}
314
+ >
315
+ <sp-icon-close slot="icon"></sp-icon-close>
316
+ Close
317
+ </sp-action-button>
318
+ </div>
319
+ `;
320
+ }
177
321
 
178
- litRender(workspaceTemplate(tab, editing), canvasWrap);
322
+ /** The Monaco function body, at dock height. The editor itself is mounted by `afterRender`. */
323
+ function functionPaneTemplate(editing: FunctionEditDef): TemplateResult {
324
+ const name = editing.defName ?? editing.eventKey ?? "?";
325
+ return html`
326
+ <div class="formula-workspace formula-workspace--code">
327
+ ${logicHeaderTemplate(
328
+ "ƒ",
329
+ name,
330
+ editing.type === "def" ? "function body" : "event handler",
331
+ editing.type === "def" ? `state/${name}` : name,
332
+ null,
333
+ () => {
334
+ void closeFunctionEditor();
335
+ },
336
+ )}
337
+ ${functionEditorTemplate()}
338
+ </div>
339
+ `;
179
340
  }
180
341
 
181
- function workspaceTemplate(tab: Tab, editing: FormulaEditDef): TemplateResult {
342
+ function workspaceTemplate(
343
+ tab: Tab,
344
+ editing: FormulaEditDef,
345
+ rerender: () => void,
346
+ ): TemplateResult {
182
347
  const stateEntries = (tab.doc.document?.state ?? {}) as Record<string, JxStateDefinition>;
183
- const title = (editing.type === "def" ? editing.defName : editing.eventKey) ?? "?";
348
+ const name = (editing.type === "def" ? editing.defName : editing.eventKey) ?? "?";
184
349
  const root = formulaRoot(tab, editing);
185
-
186
- const closeButton = html`
187
- <sp-action-button
188
- quiet
189
- size="s"
190
- class="fw-close"
191
- title="Close formula workspace"
192
- @click=${closeFormulaWorkspace}
193
- >
194
- <sp-icon-close slot="icon"></sp-icon-close>
195
- Close
196
- </sp-action-button>
197
- `;
350
+ const kind = editing.type === "def" ? "state expression" : "event expression";
351
+ const title = editing.type === "def" ? `state/${name}` : name;
198
352
 
199
353
  if (!root) {
200
354
  return html`
201
- <div
202
- class="formula-workspace"
203
- style="display:flex;flex-direction:column;flex:1;min-height:0;gap:12px;padding:20px"
204
- >
205
- <div class="fw-header" style="display:flex;align-items:center;gap:8px">
206
- <span class="fw-title" style="${MONO};font-size:14px">fx ${title}</span>
207
- <div style="flex:1"></div>
208
- ${closeButton}
355
+ <div class="formula-workspace">
356
+ ${logicHeaderTemplate("fx", name, kind, title, null, closeFormulaWorkspace)}
357
+ <div class="fw-body">
358
+ ${renderEmptyState({ message: "No expression found at this document position." })}
209
359
  </div>
210
- <div class="empty-state">No expression found at this document position.</div>
211
360
  </div>
212
361
  `;
213
362
  }
214
363
 
215
364
  const { scope } = tab.session.canvas;
216
- // Live-context evaluation in the canvas iframe with snapshot fallback (M6). While the workspace
217
- // Owns canvasWrap the iframe is usually unmounted (immediate snapshot); when a live host exists
218
- // (e.g. another panel's canvas), a landed result re-renders this workspace. An event target's
365
+ // Live-context evaluation in the canvas iframe with snapshot fallback (M6). The canvas is no
366
+ // Longer unmounted while this surface is open — that is the entire point of the move — so the
367
+ // Live path is the ORDINARY case now rather than the one that never happened. An event target's
219
368
  // Element path is the context, so repeater-template formulas bind the first item's $map scope.
220
369
  const preview = livePreviewExpression(
221
370
  tab,
222
371
  `formula:${JSON.stringify(editing)}`,
223
372
  root,
224
373
  editing.type === "event" ? (editing.path ?? null) : null,
225
- () => renderFormulaWorkspace(),
374
+ rerender,
375
+ );
376
+ const stored = _selection.value;
377
+ const key = selectionKey(editing);
378
+ const { node: selected, path: selectedPath } = resolveSelection(
379
+ root,
380
+ stored.tab === tab && stored.key === key ? stored.path : [],
226
381
  );
227
- const { node: selected, path: selectedPath } = resolveSelection(root, _selectedPath);
228
382
  const write = (next: unknown) => writeRoot(tab, editing, next);
229
383
  const writeSelected = (next: unknown) => write(replaceAtPath(root, selectedPath, next));
230
384
  const onChipSelect = (path: NodePath) => {
231
- _selectedPath = path;
232
- renderFormulaWorkspace();
385
+ _selection.value = { key, path, tab };
233
386
  };
234
387
 
235
- // ── Header: target title, catalog browser (palette), Close ──
236
- const header = html`
237
- <div class="fw-header" style="display:flex;align-items:center;gap:8px;padding:14px 20px 6px">
238
- <span
239
- class="fw-title"
240
- style="${MONO};font-size:14px;color:var(--spectrum-gray-800, #d0d0d0)"
241
- title=${editing.type === "def" ? `state/${title}` : title}
242
- >fx ${title}</span
243
- >
244
- <span class="fw-kind" style="font-size:11px;color:var(--spectrum-gray-600, #808080)">
245
- ${editing.type === "def" ? "state expression" : "event expression"}
246
- </span>
247
- <div style="flex:1"></div>
248
- <sp-action-button
249
- quiet
250
- size="s"
251
- class="fw-browse-catalog"
252
- title="Browse catalog"
253
- @click=${(e: Event) =>
254
- openFormulaPalette({
255
- anchor: e.currentTarget as HTMLElement,
256
- entries: formulaCatalog(stateEntries),
257
- onPick: (entry) =>
258
- applyCatalogPick(entry, writeSelected, {
259
- onInsertDef: (name, def) =>
260
- transactDoc(activeTab.value, (t) =>
261
- mutateAddDef(t, name, def as Record<string, JsonValue>),
262
- ),
263
- stateEntries,
264
- }),
265
- })}
266
- >
267
- <sp-icon-brackets slot="icon"></sp-icon-brackets>
268
- Catalog
269
- </sp-action-button>
270
- ${closeButton}
271
- </div>
272
- `;
273
-
274
- // ── Chip strip: the whole tree as chips with live badges; clicking selects the sub-node ──
275
- const chips = html`
276
- <div class="fw-chips" style="padding:2px 20px 10px;border-bottom:${PANE_BORDER}">
277
- ${renderFormulaChips(root, onChipSelect, { preview })}
278
- </div>
388
+ const catalogButton = html`
389
+ <sp-action-button
390
+ quiet
391
+ size="s"
392
+ class="fw-browse-catalog"
393
+ title="Browse catalog"
394
+ @click=${(e: Event) =>
395
+ openFormulaPalette({
396
+ anchor: e.currentTarget as HTMLElement,
397
+ entries: formulaCatalog(stateEntries),
398
+ onPick: (entry) =>
399
+ applyCatalogPick(entry, writeSelected, {
400
+ onInsertDef: (defName, def) =>
401
+ transactDoc(activeTab.value, (t) =>
402
+ mutateAddDef(t, defName, def as Record<string, JsonValue>),
403
+ ),
404
+ stateEntries,
405
+ }),
406
+ })}
407
+ >
408
+ <sp-icon-brackets slot="icon"></sp-icon-brackets>
409
+ Catalog
410
+ </sp-action-button>
279
411
  `;
280
412
 
281
- // ── Main pane: the recursive expression form for the SELECTED sub-node ──
282
- const editor = html`
283
- <div class="fw-editor" style="flex:1;min-width:0;overflow:auto;padding:16px 20px">
284
- <div
285
- class="fw-selected"
286
- style="font-size:11px;color:var(--spectrum-gray-600, #808080);padding-bottom:8px"
287
- >
288
- Selected:
289
- <span style=${MONO}>${selectedPath.length === 0 ? "root" : chipSummary(selected)}</span>
413
+ return html`
414
+ <div class="formula-workspace">
415
+ ${logicHeaderTemplate("fx", name, kind, title, catalogButton, closeFormulaWorkspace)}
416
+ <div class="fw-chips">${renderFormulaChips(root, onChipSelect, { preview })}</div>
417
+ <div class="fw-body">
418
+ <div class="fw-editor">
419
+ <div class="fw-selected">
420
+ Selected:
421
+ <span class="fw-selected-node"
422
+ >${selectedPath.length === 0 ? "root" : chipSummary(selected)}</span
423
+ >
424
+ </div>
425
+ ${renderExpressionEditor(selected, writeSelected, {
426
+ allowEventRef: editing.type === "event",
427
+ depth: 1,
428
+ path: selectedPath,
429
+ preview,
430
+ stateDefs: Object.keys(stateEntries),
431
+ stateEntries,
432
+ })}
433
+ </div>
434
+ ${dataRailTemplate(scope)}
290
435
  </div>
291
- ${renderExpressionEditor(selected, writeSelected, {
292
- allowEventRef: editing.type === "event",
293
- depth: 1,
294
- path: selectedPath,
295
- preview,
296
- stateDefs: Object.keys(stateEntries),
297
- stateEntries,
298
- })}
436
+ ${resultTemplate(preview)}
299
437
  </div>
300
438
  `;
439
+ }
301
440
 
302
- // ── Right rail: live data context (the canvas dataScope snapshot) ──
441
+ /** The live data context — a SECOND opinion now that the page itself is on screen, not the only one. */
442
+ function dataRailTemplate(scope: Record<string, unknown> | null | undefined): TemplateResult {
303
443
  const scopeEntries = scope ? Object.entries(scope) : [];
304
- const rail = html`
305
- <div
306
- class="fw-context"
307
- style="width:280px;flex-shrink:0;overflow:auto;border-left:${PANE_BORDER};padding:16px"
308
- >
309
- <div
310
- class="fw-context-title"
311
- style="font-size:11px;text-transform:uppercase;letter-spacing:0.5px;color:var(--spectrum-gray-600, #808080);padding-bottom:8px"
312
- >
313
- Data
314
- </div>
444
+ return html`
445
+ <div class="fw-context">
446
+ <div class="fw-context-title">Data</div>
315
447
  ${
316
448
  scopeEntries.length === 0
317
449
  ? html`<div class="empty-state">No canvas data snapshot yet</div>`
318
450
  : scopeEntries.map(([name, value]) => {
319
451
  const unwrapped = unwrapSignal(value);
320
452
  return html`
321
- <div class="fw-context-entry" style="margin-bottom:10px">
322
- <div style="display:flex;gap:6px;align-items:baseline">
323
- <span
324
- class="fw-context-name"
325
- style="${MONO};font-size:12px;color:var(--spectrum-gray-800, #d0d0d0)"
326
- >${name}</span
327
- >
328
- <span style="font-size:10px;color:var(--spectrum-gray-600, #808080)"
329
- >${dataTypeLabel(value)}</span
330
- >
453
+ <div class="fw-context-entry">
454
+ <div class="fw-context-head">
455
+ <span class="fw-context-name">${name}</span>
456
+ <span class="fw-context-type">${dataTypeLabel(value)}</span>
331
457
  </div>
332
458
  <div class="data-tree">${renderDataTreeTemplate(unwrapped, 0, 4)}</div>
333
459
  </div>
@@ -336,50 +462,114 @@ function workspaceTemplate(tab: Tab, editing: FormulaEditDef): TemplateResult {
336
462
  }
337
463
  </div>
338
464
  `;
465
+ }
339
466
 
340
- // ── Footer: the root result badge, or the evaluation error ──
341
- const footer = preview?.error
342
- ? html`
343
- <div
344
- class="fw-result fw-result--error"
345
- style="${MONO};font-size:12px;padding:10px 20px;border-top:${PANE_BORDER};color:var(--spectrum-negative-content-color-default, #f76a63)"
346
- >
347
- ${preview.error}
348
- </div>
349
- `
350
- : preview
351
- ? html`
352
- <div
353
- class="fw-result"
354
- style="${MONO};font-size:12px;padding:10px 20px;border-top:${PANE_BORDER};color:var(--spectrum-seafoam-900, #35a690)"
355
- >
356
- = ${preview.values.get("") ?? "undefined"}
357
- ${
358
- preview.mutating
359
- ? html`<span style="color:var(--spectrum-gray-600, #808080)"
360
- >(mutates target)</span
361
- >`
362
- : ""
363
- }
364
- </div>
365
- `
366
- : html`
367
- <div
368
- class="fw-result fw-result--pending"
369
- style="font-size:12px;padding:10px 20px;border-top:${PANE_BORDER};color:var(--spectrum-gray-600, #808080)"
370
- >
371
- Preview unavailable — the canvas has not posted a data snapshot yet
372
- </div>
373
- `;
374
-
467
+ /** The root result, the evaluation error, or the honest "nothing has evaluated this yet". */
468
+ function resultTemplate(preview: ExpressionPreview | null): TemplateResult {
469
+ if (preview?.error) {
470
+ return html`<div class="fw-result fw-result--error">${preview.error}</div>`;
471
+ }
472
+ if (preview) {
473
+ return html`
474
+ <div class="fw-result">
475
+ = ${preview.values.get("") ?? "undefined"}
476
+ ${preview.mutating ? html`<span class="fw-result-note">(mutates target)</span>` : ""}
477
+ </div>
478
+ `;
479
+ }
375
480
  return html`
376
- <div
377
- class="formula-workspace"
378
- style="display:flex;flex-direction:column;flex:1;min-height:0;background:var(--spectrum-gray-75, #1d1d1d)"
379
- >
380
- ${header} ${chips}
381
- <div class="fw-body" style="display:flex;flex:1;min-height:0">${editor} ${rail}</div>
382
- ${footer}
481
+ <div class="fw-result fw-result--pending">
482
+ Preview unavailable — the canvas has not posted a data snapshot yet
383
483
  </div>
384
484
  `;
385
485
  }
486
+
487
+ // ─── Commands ─────────────────────────────────────────────────────────────────
488
+
489
+ /**
490
+ * The formula/function EDITOR verbs — open a state entry's body or an event binding in Logic.
491
+ *
492
+ * They live here rather than beside `panels/editors.ts`'s renderer because this module already owns
493
+ * the other half of the same idea: `editingFormula` (the structured workspace) and
494
+ * `editingFunction` (the code editor) are two surfaces of ONE dock tab, addressing the same two
495
+ * document positions — a state entry by `defName`, or an element event binding by `path` +
496
+ * `eventKey`.
497
+ *
498
+ * Both REFUSE a target the document does not hold. The predecessors wrote `ui.editingFunction`
499
+ * straight from the automation hook with no check at all, so a renamed def opened an editor over
500
+ * nothing and the shot photographed an empty takeover.
501
+ *
502
+ * @returns {AnyCommand[]}
503
+ */
504
+ export function formulaEditorCommands(): AnyCommand[] {
505
+ return [
506
+ {
507
+ args: argsSchema({
508
+ defName: stringProperty("The state entry whose body to open in the code editor."),
509
+ }),
510
+ category: "Document",
511
+ id: "formula.editDef",
512
+ level: "document",
513
+ menus: ["palette"],
514
+ group: "5_data",
515
+ requires: "an open document that defines state",
516
+ when: (ctx) => ctx.document.open,
517
+ run: (_commandCtx, args) => {
518
+ const defName = stringArg("formula.editDef", args, "defName");
519
+ const tab = activeTab.value;
520
+ if (!tab) {
521
+ throw new RangeError(`command "formula.editDef" needs an open document`);
522
+ }
523
+ const defs = tab.doc.document?.state ?? {};
524
+ if (!(defName in defs)) {
525
+ const defined = Object.keys(defs);
526
+ throw new RangeError(
527
+ `command "formula.editDef" argument "defName": "${defName}" is not a state entry ` +
528
+ `this document defines — it defines: ` +
529
+ `${defined.length > 0 ? defined.join(", ") : "nothing"}`,
530
+ );
531
+ }
532
+ openLogicTarget({ editing: { defName, type: "def" }, surface: "function" });
533
+ },
534
+ title: "Edit Function",
535
+ },
536
+ {
537
+ args: argsSchema({
538
+ eventKey: stringProperty('The event binding, e.g. "onclick".'),
539
+ path: pathProperty("The document path of the element that carries the binding."),
540
+ }),
541
+ category: "Document",
542
+ id: "formula.editEvent",
543
+ level: "document",
544
+ menus: ["palette"],
545
+ group: "5_data",
546
+ requires: "an open document",
547
+ when: (ctx) => ctx.document.open,
548
+ run: (_commandCtx, args) => {
549
+ const eventKey = stringArg("formula.editEvent", args, "eventKey");
550
+ const path = pathArg("formula.editEvent", args, "path");
551
+ const tab = activeTab.value;
552
+ if (!tab) {
553
+ throw new RangeError(`command "formula.editEvent" needs an open document`);
554
+ }
555
+ if (!getNodeAtPath(tab.doc.document, path)) {
556
+ throw new RangeError(
557
+ `command "formula.editEvent" argument "path": [${path.join(", ")}] addresses no ` +
558
+ `node in ${tab.documentPath ?? "the open document"}`,
559
+ );
560
+ }
561
+ openLogicTarget({ editing: { eventKey, path, type: "event" }, surface: "function" });
562
+ },
563
+ title: "Edit Event Handler",
564
+ },
565
+ ];
566
+ }
567
+
568
+ /**
569
+ * Register the formula/function editor verbs.
570
+ *
571
+ * @param {CommandRegistry} registry
572
+ */
573
+ export function registerFormulaEditorCommands(registry: CommandRegistry): void {
574
+ registry.registerAll(formulaEditorCommands());
575
+ }