@jxsuite/studio 2.0.0 → 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-0hzentwq.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-0hzentwq.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-2c188n41.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-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-97qcgvdn.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-hhj92gcr.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-ygzav7z5.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-n9zmpxjg.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 +23711 -35185
  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 +34 -9
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +270 -262
  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 -120
  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 +75 -2
  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 +113 -0
  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-qct0gt8f.js +0 -531
  363. package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
  364. package/dist/chunks/files-02zn5y24.js +0 -50
  365. package/dist/chunks/files-02zn5y24.js.map +0 -9
  366. package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
  367. package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
  368. package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
  369. package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
  370. package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
  371. package/dist/chunks/settings-modal-gj0hrah4.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-4k0m4fmp.js.map +0 -13
  381. package/dist/chunks/studio-568nx4t5.js +0 -469
  382. package/dist/chunks/studio-568nx4t5.js.map +0 -14
  383. package/dist/chunks/studio-9smfzqhc.js +0 -19
  384. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  385. package/dist/chunks/studio-asvf0j9e.js +0 -429
  386. package/dist/chunks/studio-asvf0j9e.js.map +0 -13
  387. package/dist/chunks/studio-d77qm3pk.js +0 -3427
  388. package/dist/chunks/studio-d77qm3pk.js.map +0 -26
  389. package/dist/chunks/studio-j6gqyjf4.js +0 -1854
  390. package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
  391. package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-rq0vxb19.js +0 -3744
  394. package/dist/chunks/studio-rq0vxb19.js.map +0 -22
  395. package/dist/chunks/studio-wd6ewdvd.js +0 -853
  396. package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
  397. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  398. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  399. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  400. package/dist/chunks/studio-ygzav7z5.js.map +0 -46
  401. package/dist/chunks/studio-zmy7241e.js +0 -399
  402. package/dist/chunks/studio-zmy7241e.js.map +0 -12
  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-2c188n41.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-hhj92gcr.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
@@ -7,48 +7,103 @@
7
7
  * loading/error surfaces stay live without renderCanvas involvement. The Tabulator view is created
8
8
  * once per tab entry, after the controller has loaded columns, and destroyed on detach — all grid
9
9
  * data survives in the controller/buffer, so a rebuild on re-entry is cheap.
10
+ *
11
+ * **The panel owns the view state** (plan §12 P7.2). Saved views persist through `grid-layout.ts` —
12
+ * one store, the grid id as its key, so "per collection" needs no code — and this module is what
13
+ * drives them onto the four surfaces they touch: the engine (column order, width and visibility,
14
+ * applied by a REBUILD, which is cheap because the data never leaves the controller), the
15
+ * controller (sort and grouping, which are row order and belong to the data), and the toolbar's own
16
+ * filter box. Applying a view is therefore one function with one order of operations, not five
17
+ * controls that each half-remember what the others did.
10
18
  */
11
19
  import { html, render as litRender, nothing } from "lit-html";
12
20
  import { ref } from "lit-html/directives/ref.js";
13
- import { effect, effectScope } from "../reactivity";
14
- import { renderPopover } from "../ui/layers";
15
- import { statusMessage } from "../panels/statusbar";
21
+ import { effect, effectScope, reactive } from "../reactivity";
22
+ import { renderPopover, showConfirmDialog, showPromptDialog } from "../ui/layers";
23
+ import { notify } from "../services/notify";
16
24
  import { rectOf } from "../utils/geometry";
25
+ import { activeTab, workspace } from "../workspace/workspace";
17
26
  import { createGridController, getGridController } from "./grid-controller";
18
27
  import { createCsvFileSource } from "./sources/csv-file-source";
19
28
  import { createGridView } from "./grid-view";
20
29
  import { parseGridTabId } from "./grid-source";
30
+ import {
31
+ activeViewModified,
32
+ activeViewName,
33
+ applySavedView,
34
+ deleteSavedView,
35
+ listSavedViews,
36
+ loadGridLayout,
37
+ resetGridLayout,
38
+ saveGridLayout,
39
+ saveViewAs,
40
+ } from "./grid-layout";
41
+ import { argsSchema, stringArg, stringProperty } from "../commands/command-args";
21
42
  import type { EffectScope } from "@vue/reactivity";
22
43
  import type { GridController } from "./grid-controller";
44
+ import type { GridLayout, GridSortSpec } from "./grid-layout";
23
45
  import type { GridView } from "./grid-view";
24
46
  import type { Tab } from "../tabs/tab";
47
+ import type { CanvasSurface } from "../canvas/canvas-surface";
48
+ import type { AnyCommand, CommandRegistry } from "../commands/registry";
25
49
 
26
50
  interface ActiveGridPanel {
51
+ /** The pane whose stage this grid is drawn on. */
52
+ paneId: string;
27
53
  tabId: string;
28
54
  scope: EffectScope;
29
55
  view: GridView | null;
30
56
  wrap: HTMLElement;
57
+ /**
58
+ * Rebuild the engine over the same controller.
59
+ *
60
+ * Column order, width and visibility are read by `createGridView` from the saved layout, so this
61
+ * is how a view change reaches them. Nothing is re-fetched: rows, edits, undo history and the
62
+ * dirty flag all live in the controller, and the engine is a rendering of them.
63
+ */
64
+ remount: () => void;
65
+ /** Apply a whole layout — sort, grouping, columns, filter — in one order of operations. */
66
+ applyLayout: (layout: GridLayout | null) => void;
67
+ /** Re-render the toolbar. `localStorage` is not reactive, so a view edit has to say so. */
68
+ bump: () => void;
31
69
  }
32
70
 
33
- let active: ActiveGridPanel | null = null;
71
+ /**
72
+ * The grid mounted in each pane, keyed by pane id.
73
+ *
74
+ * A module-level `let active` described a shell with one stage. With two, pane B mounting a grid
75
+ * destroyed pane A's Tabulator view and effect scope out from under it — and `resetCanvasView`
76
+ * calls `detachGridPanel` on every pane that empties, so a second grid was never required.
77
+ */
78
+ const _active = new Map<string, ActiveGridPanel>();
34
79
 
35
- /** Destroy the live grid view/effects (canvas-render teardown + tab switches). */
36
- export function detachGridPanel() {
37
- if (!active) {
80
+ /** The grid mounted in a pane, or null. */
81
+ function activeIn(paneId: string): ActiveGridPanel | null {
82
+ return _active.get(paneId) ?? null;
83
+ }
84
+
85
+ /** Destroy one pane's grid view/effects (canvas-render teardown + tab switches). */
86
+ export function detachGridPanel(paneId: string) {
87
+ const panel = _active.get(paneId);
88
+ if (!panel) {
38
89
  return;
39
90
  }
40
- active.view?.destroy();
41
- active.scope.stop();
42
- active = null;
91
+ panel.view?.destroy();
92
+ panel.scope.stop();
93
+ _active.delete(paneId);
43
94
  }
44
95
 
45
- /** Whether the grid panel is live for this tab (canvas-render fast-path guard). */
46
- export function gridPanelMounted(tab: Tab): boolean {
47
- return active !== null && active.tabId === tab.id && active.wrap.isConnected;
96
+ /** Whether the grid panel is live in this pane for this tab (canvas-render fast-path guard). */
97
+ export function gridPanelMounted(paneId: string, tab: Tab): boolean {
98
+ const panel = activeIn(paneId);
99
+ return panel !== null && panel.tabId === tab.id && panel.wrap.isConnected;
48
100
  }
49
101
 
50
- function toolbarTpl(controller: GridController, getView: () => GridView | null) {
102
+ function toolbarTpl(controller: GridController, panel: ActiveGridPanel) {
103
+ const getView = () => panel.view;
51
104
  const { state } = controller;
105
+ const gridId = controller.source.id;
106
+ const layout = loadGridLayout(gridId);
52
107
  const dirty = controller.buffer.dirtyCount();
53
108
  const sourceRef = parseGridTabId(controller.source.id);
54
109
  const lossyNote =
@@ -67,7 +122,9 @@ function toolbarTpl(controller: GridController, getView: () => GridView | null)
67
122
  size="s"
68
123
  variant="accent"
69
124
  ?disabled=${dirty === 0 || state.saving}
70
- @click=${() => void controller.save()}
125
+ @click=${() => {
126
+ void controller.save();
127
+ }}
71
128
  >
72
129
  Save${dirty > 0 ? ` (${dirty})` : ""}
73
130
  </sp-button>
@@ -76,7 +133,9 @@ function toolbarTpl(controller: GridController, getView: () => GridView | null)
76
133
  quiet
77
134
  title="Reload from source"
78
135
  ?disabled=${state.loading || state.saving}
79
- @click=${() => void controller.refresh()}
136
+ @click=${() => {
137
+ void controller.refresh();
138
+ }}
80
139
  >
81
140
  Refresh
82
141
  </sp-action-button>
@@ -121,15 +180,32 @@ function toolbarTpl(controller: GridController, getView: () => GridView | null)
121
180
  >
122
181
  Replace
123
182
  </sp-action-button>
183
+ <sp-action-button
184
+ size="s"
185
+ quiet
186
+ class="jx-grid-view-button"
187
+ title="Saved views, columns, sort and grouping"
188
+ @click=${(e: MouseEvent) => openViewPopover(controller, panel, e.currentTarget as HTMLElement)}
189
+ >
190
+ ${viewButtonLabel(gridId)}
191
+ </sp-action-button>
124
192
  <sp-search
125
193
  size="s"
126
194
  quiet
127
195
  placeholder="Filter rows"
128
- @input=${(e: Event) => getView()?.setSearch((e.target as HTMLInputElement).value)}
196
+ .value=${layout?.filter ?? ""}
197
+ @input=${(e: Event) => {
198
+ const term = (e.target as HTMLInputElement).value;
199
+ // The filter is part of the view, so it persists with the rest of it rather than
200
+ // Evaporating on a tab switch and taking a saved view's meaning with it.
201
+ saveGridLayout(gridId, { filter: term });
202
+ getView()?.setSearch(term);
203
+ }}
129
204
  @submit=${(e: Event) => e.preventDefault()}
130
205
  ></sp-search>
131
206
  <span class="jx-grid-spacer"></span>
132
- ${lossyNote} ${controller.source.capabilities.remotePaging ? pagerTpl(controller) : nothing}
207
+ ${groupNoteTpl(controller)} ${lossyNote}
208
+ ${controller.source.capabilities.remotePaging ? pagerTpl(controller) : nothing}
133
209
  <span class="jx-grid-count">
134
210
  ${
135
211
  state.loading
@@ -143,6 +219,262 @@ function toolbarTpl(controller: GridController, getView: () => GridView | null)
143
219
  `;
144
220
  }
145
221
 
222
+ // ─── Saved views ──────────────────────────────────────────────────────────────
223
+
224
+ /**
225
+ * The View button's label: the applied view's name, with a dot when the layout has since drifted.
226
+ *
227
+ * "View" alone when nothing is applied — the grid still remembers its columns, and claiming a name
228
+ * for a layout the author never named would make Save-as look like a no-op.
229
+ */
230
+ function viewButtonLabel(gridId: string): string {
231
+ const name = activeViewName(gridId);
232
+ if (!name) {
233
+ return "View";
234
+ }
235
+ return activeViewModified(gridId) ? `${name} •` : name;
236
+ }
237
+
238
+ /** "Grouped by Status · 3 groups" — the grouping is invisible in the rows, so the toolbar says it. */
239
+ function groupNoteTpl(controller: GridController) {
240
+ const field = controller.state.grouping;
241
+ if (!field) {
242
+ return nothing;
243
+ }
244
+ const title = controller.state.columns.find((column) => column.field === field)?.title ?? field;
245
+ const count = controller.groups().length;
246
+ return html`<span class="jx-grid-note" title="Rows are ordered so each group is contiguous.">
247
+ Grouped by ${title} · ${count} group${count === 1 ? "" : "s"}
248
+ </span>`;
249
+ }
250
+
251
+ /**
252
+ * The View popover: saved views, then the four facets one of them is made of.
253
+ *
254
+ * One control rather than four, because the chrome budget is a cap on named things in the toolbar
255
+ * (plan §2, principle 9) and these four are only ever adjusted together. Each edit writes the
256
+ * working layout and applies immediately — there is no Apply button, so there is no state in which
257
+ * the popover shows something the grid is not already doing.
258
+ */
259
+ function openViewPopover(controller: GridController, panel: ActiveGridPanel, anchor: HTMLElement) {
260
+ const rect = rectOf(anchor);
261
+ const gridId = controller.source.id;
262
+ // Opened empty and filled by `rerender()` below, because the body's handlers close over `handle`
263
+ // And every one of them re-renders the popover in place rather than closing it.
264
+ const handle = renderPopover(nothing as never, {
265
+ dismissOnOutsideClick: true,
266
+ region: "grid/views",
267
+ });
268
+ const rerender = () => handle.update(body());
269
+ const close = () => handle.dismiss();
270
+
271
+ /** Persist one facet, put it on screen, and refresh both the popover and the toolbar. */
272
+ const change = (patch: GridLayout, apply: () => void) => {
273
+ saveGridLayout(gridId, patch);
274
+ apply();
275
+ panel.bump();
276
+ rerender();
277
+ };
278
+
279
+ function body() {
280
+ const layout = loadGridLayout(gridId) ?? {};
281
+ const views = listSavedViews(gridId);
282
+ const activeName = activeViewName(gridId);
283
+ const hidden = new Set(layout.hidden);
284
+ const sort = layout.sort ?? null;
285
+ const { columns } = controller.state;
286
+
287
+ const setSort = (spec: GridSortSpec | null) =>
288
+ change({ sort: spec }, () => {
289
+ void controller.setSort(spec);
290
+ });
291
+
292
+ return html`<sp-popover
293
+ open
294
+ class="jx-grid-view-popover"
295
+ style="position:fixed;z-index:10000;left:${Math.max(4, rect.left)}px;top:${rect.bottom + 4}px"
296
+ >
297
+ <div class="jx-grid-cell-popover-body">
298
+ <div class="jx-grid-cell-popover-title">Saved views</div>
299
+ ${
300
+ views.length === 0
301
+ ? html`<div class="jx-grid-view-empty">
302
+ No saved views yet. Arrange the grid, then save it under a name.
303
+ </div>`
304
+ : views.map(
305
+ (view) => html`<div class="jx-grid-view-row">
306
+ <button
307
+ class="jx-grid-view-name ${view.name === activeName ? "is-active" : ""}"
308
+ @click=${() => {
309
+ panel.applyLayout(applySavedView(gridId, view.name));
310
+ close();
311
+ }}
312
+ >
313
+ ${view.name}
314
+ </button>
315
+ <sp-action-button
316
+ size="s"
317
+ quiet
318
+ title="Delete this view"
319
+ @click=${async () => {
320
+ const confirmed = await showConfirmDialog(
321
+ "Delete View",
322
+ `Delete the saved view "${view.name}"? The grid keeps its current layout.`,
323
+ { confirmLabel: "Delete", destructive: true },
324
+ );
325
+ if (confirmed && deleteSavedView(gridId, view.name)) {
326
+ panel.bump();
327
+ rerender();
328
+ }
329
+ }}
330
+ >✕</sp-action-button
331
+ >
332
+ </div>`,
333
+ )
334
+ }
335
+ <div class="jx-grid-cell-popover-actions">
336
+ <sp-button
337
+ size="s"
338
+ variant="accent"
339
+ @click=${() => {
340
+ void promptSaveView(controller, panel, rerender);
341
+ }}
342
+ >
343
+ ${activeName ? "Save as…" : "Save view…"}
344
+ </sp-button>
345
+ <sp-button
346
+ size="s"
347
+ variant="secondary"
348
+ title="Forget this grid's saved columns, sort, grouping and filter. Named views are kept."
349
+ @click=${() => {
350
+ resetGridLayout(gridId);
351
+ panel.applyLayout(null);
352
+ close();
353
+ }}
354
+ >
355
+ Reset
356
+ </sp-button>
357
+ </div>
358
+
359
+ <div class="jx-grid-cell-popover-title">Columns</div>
360
+ ${columns.map(
361
+ (column) => html`<label class="jx-grid-view-check">
362
+ <input
363
+ type="checkbox"
364
+ .checked=${!hidden.has(column.field)}
365
+ data-field=${column.field}
366
+ @change=${(e: Event) => {
367
+ const next = new Set(hidden);
368
+ if ((e.target as HTMLInputElement).checked) {
369
+ next.delete(column.field);
370
+ } else {
371
+ next.add(column.field);
372
+ }
373
+ change({ hidden: [...next] }, () => panel.remount());
374
+ }}
375
+ />
376
+ ${column.title}
377
+ </label>`,
378
+ )}
379
+
380
+ <div class="jx-grid-cell-popover-title">Sort</div>
381
+ <select
382
+ class="jx-grid-input jx-grid-sort-field"
383
+ @change=${(e: Event) => {
384
+ const field = (e.target as HTMLSelectElement).value;
385
+ setSort(field === "" ? null : { dir: sort?.dir ?? "asc", field });
386
+ }}
387
+ >
388
+ <option value="" ?selected=${!sort}>Source order</option>
389
+ ${columns.map(
390
+ (column) =>
391
+ html`<option value=${column.field} ?selected=${sort?.field === column.field}>
392
+ ${column.title}
393
+ </option>`,
394
+ )}
395
+ </select>
396
+ <select
397
+ class="jx-grid-input jx-grid-sort-dir"
398
+ ?disabled=${!sort}
399
+ @change=${(e: Event) => {
400
+ const dir = (e.target as HTMLSelectElement).value === "desc" ? "desc" : "asc";
401
+ if (sort) {
402
+ setSort({ dir, field: sort.field });
403
+ }
404
+ }}
405
+ >
406
+ <option value="asc" ?selected=${sort?.dir !== "desc"}>Ascending</option>
407
+ <option value="desc" ?selected=${sort?.dir === "desc"}>Descending</option>
408
+ </select>
409
+
410
+ <div class="jx-grid-cell-popover-title">Group by</div>
411
+ <select
412
+ class="jx-grid-input jx-grid-group-field"
413
+ @change=${(e: Event) => {
414
+ const field = (e.target as HTMLSelectElement).value || null;
415
+ change({ groupBy: field }, () => controller.setGrouping(field));
416
+ }}
417
+ >
418
+ <option value="" ?selected=${!controller.state.grouping}>Ungrouped</option>
419
+ ${columns.map(
420
+ (column) =>
421
+ html`<option
422
+ value=${column.field}
423
+ ?selected=${controller.state.grouping === column.field}
424
+ >
425
+ ${column.title}
426
+ </option>`,
427
+ )}
428
+ </select>
429
+ </div>
430
+ </sp-popover>`;
431
+ }
432
+
433
+ rerender();
434
+ // The button's label is derived from storage, and storage is ALSO written by the engine — a
435
+ // Column drag or resize goes straight to `saveGridLayout` from `grid-view.ts`. Refreshing the
436
+ // Toolbar as the control opens is what stops the drift dot lagging a resize by one interaction.
437
+ panel.bump();
438
+ return handle;
439
+ }
440
+
441
+ /**
442
+ * Name the current layout.
443
+ *
444
+ * An existing name overwrites that view — the author who types it means "update this one", and a
445
+ * confirmation for replacing something they can re-save in two clicks is chrome for its own sake. A
446
+ * blank name is refused in the field, so the dialog never returns one.
447
+ */
448
+ async function promptSaveView(
449
+ controller: GridController,
450
+ panel: ActiveGridPanel,
451
+ rerender: () => void,
452
+ ): Promise<string | null> {
453
+ const gridId = controller.source.id;
454
+ const name = await showPromptDialog("Save Grid View", {
455
+ confirmLabel: "Save View",
456
+ message: `Columns, sort, grouping and filter, saved for ${controller.source.label}.`,
457
+ placeholder: "Recent drafts",
458
+ validate: (candidate) => (candidate.trim() === "" ? "Name the view." : ""),
459
+ value: activeViewName(gridId) ?? "",
460
+ });
461
+ if (name === null) {
462
+ return null;
463
+ }
464
+ const saved = saveViewAs(gridId, name);
465
+ if (!saved) {
466
+ // Storage is off (privacy mode): the layout still works, it just cannot be remembered.
467
+ notify.warn("Views cannot be saved — this browser has local storage disabled.", {
468
+ key: "grid.saveView",
469
+ source: "Data",
470
+ });
471
+ return null;
472
+ }
473
+ panel.bump();
474
+ rerender();
475
+ return saved.name;
476
+ }
477
+
146
478
  /** Find & replace popover — buffers all replacements as one undo group. */
147
479
  function openReplacePopover(controller: GridController, anchor: HTMLElement) {
148
480
  const rect = rectOf(anchor);
@@ -172,11 +504,14 @@ function openReplacePopover(controller: GridController, anchor: HTMLElement) {
172
504
  variant="accent"
173
505
  @click=${() => {
174
506
  const changed = controller.replaceAll(find, replace);
175
- statusMessage(
176
- changed === 0
177
- ? "No matches"
178
- : `Replaced in ${changed} cell${changed === 1 ? "" : "s"} — save to apply`,
179
- );
507
+ if (changed === 0) {
508
+ notify.info("No matches.", { key: "grid.replaceAll" });
509
+ } else {
510
+ notify.success(
511
+ `Replaced in ${changed} cell${changed === 1 ? "" : "s"} — save to apply.`,
512
+ { action: "file.save", key: "grid.replaceAll" },
513
+ );
514
+ }
180
515
  if (changed > 0) {
181
516
  handle.dismiss();
182
517
  }
@@ -207,12 +542,13 @@ function pagerTpl(controller: GridController) {
207
542
  quiet
208
543
  title="Previous page"
209
544
  ?disabled=${offset === 0 || state.loading}
210
- @click=${() =>
545
+ @click=${() => {
211
546
  void controller.setQuery({
212
547
  ...state.query,
213
548
  limit,
214
549
  offset: Math.max(0, offset - limit),
215
- })}
550
+ });
551
+ }}
216
552
  >
217
553
  ‹ Prev
218
554
  </sp-action-button>
@@ -222,7 +558,9 @@ function pagerTpl(controller: GridController) {
222
558
  quiet
223
559
  title="Next page"
224
560
  ?disabled=${offset + limit >= state.total || state.loading}
225
- @click=${() => void controller.setQuery({ ...state.query, limit, offset: offset + limit })}
561
+ @click=${() => {
562
+ void controller.setQuery({ ...state.query, limit, offset: offset + limit });
563
+ }}
226
564
  >
227
565
  Next ›
228
566
  </sp-action-button>
@@ -231,12 +569,12 @@ function pagerTpl(controller: GridController) {
231
569
 
232
570
  function shellTpl(
233
571
  controller: GridController,
234
- getView: () => GridView | null,
572
+ panel: ActiveGridPanel,
235
573
  onHost: (el: HTMLElement | undefined) => void,
236
574
  ) {
237
575
  return html`
238
576
  <div class="jx-grid">
239
- ${toolbarTpl(controller, getView)}
577
+ ${toolbarTpl(controller, panel)}
240
578
  <div class="jx-grid-host" ${ref((el) => onHost(el as HTMLElement | undefined))}></div>
241
579
  </div>
242
580
  `;
@@ -249,11 +587,12 @@ function shellTpl(
249
587
  * @param {HTMLElement} canvasWrap
250
588
  * @param {Tab} tab
251
589
  */
252
- export function renderGridMode(canvasWrap: HTMLElement, tab: Tab) {
253
- if (gridPanelMounted(tab)) {
590
+ export function renderGridMode(surface: CanvasSurface, tab: Tab) {
591
+ const { paneId, wrap: canvasWrap } = surface;
592
+ if (gridPanelMounted(paneId, tab)) {
254
593
  return;
255
594
  }
256
- detachGridPanel();
595
+ detachGridPanel(paneId);
257
596
 
258
597
  let controller = getGridController(tab);
259
598
  // CSV file tabs can reach grid mode through any open path (deep link, quick search, recents) —
@@ -268,17 +607,47 @@ export function renderGridMode(canvasWrap: HTMLElement, tab: Tab) {
268
607
  }
269
608
 
270
609
  const scope = effectScope();
271
- const panel: ActiveGridPanel = { scope, tabId: tab.id, view: null, wrap: canvasWrap };
272
- active = panel;
273
-
610
+ // `localStorage` is not reactive and neither is the engine, so the toolbar needs something that
611
+ // Is: a saved-view edit bumps this and the panel's one effect re-renders like any other change.
612
+ const local = reactive({ views: 0 });
613
+ const live = controller;
274
614
  let hostEl: HTMLElement | null = null;
615
+
616
+ const panel: ActiveGridPanel = {
617
+ applyLayout(layout) {
618
+ void live.setSort(layout?.sort ?? null);
619
+ live.setGrouping(layout?.groupBy ?? null);
620
+ panel.remount();
621
+ panel.view?.setSearch(layout?.filter ?? "");
622
+ panel.bump();
623
+ },
624
+ bump() {
625
+ local.views += 1;
626
+ },
627
+ remount() {
628
+ panel.view?.destroy();
629
+ panel.view = hostEl ? createGridView(hostEl, live) : null;
630
+ },
631
+ paneId,
632
+ scope,
633
+ tabId: tab.id,
634
+ view: null,
635
+ wrap: canvasWrap,
636
+ };
637
+ _active.set(paneId, panel);
638
+
275
639
  const onHost = (el: HTMLElement | undefined) => {
276
640
  hostEl = el ?? null;
277
641
  };
278
642
 
643
+ // The stored sort, grouping and filter are applied ONCE, when the engine first exists. Re-running
644
+ // Them on every render would fight the author: every ad-hoc header sort would be undone by the
645
+ // Next repaint, which is the behaviour a saved view is supposed to replace, not impose.
646
+ let restored = false;
647
+
279
648
  scope.run(() => {
280
649
  effect(() => {
281
- if (active !== panel) {
650
+ if (activeIn(paneId) !== panel) {
282
651
  return;
283
652
  }
284
653
  // Track everything the toolbar shows.
@@ -287,12 +656,11 @@ export function renderGridMode(canvasWrap: HTMLElement, tab: Tab) {
287
656
  void controller.state.error;
288
657
  void controller.state.total;
289
658
  void controller.state.query.offset;
659
+ void controller.state.grouping;
290
660
  void controller.buffer.dirtyCount();
661
+ void local.views;
291
662
 
292
- litRender(
293
- shellTpl(controller, () => panel.view, onHost),
294
- canvasWrap,
295
- );
663
+ litRender(shellTpl(controller, panel, onHost), canvasWrap);
296
664
 
297
665
  // Create the engine once the columns exist and the host div is in the DOM.
298
666
  if (
@@ -302,7 +670,154 @@ export function renderGridMode(canvasWrap: HTMLElement, tab: Tab) {
302
670
  hostEl
303
671
  ) {
304
672
  panel.view = createGridView(hostEl, controller);
673
+ if (!restored) {
674
+ restored = true;
675
+ const layout = loadGridLayout(controller.source.id);
676
+ void controller.setSort(layout?.sort ?? null);
677
+ controller.setGrouping(layout?.groupBy ?? null);
678
+ panel.view.setSearch(layout?.filter ?? "");
679
+ }
305
680
  }
306
681
  });
307
682
  });
308
683
  }
684
+
685
+ // ─── Commands ─────────────────────────────────────────────────────────────────
686
+
687
+ /**
688
+ * The live grid surface, or null.
689
+ *
690
+ * Both halves are required: a controller says the active tab HAS a grid, and a mounted panel says
691
+ * the grid is what is on screen. A view command that ran against a grid tab currently showing its
692
+ * Monaco source mode would remount an engine into a host that is not there.
693
+ */
694
+ function activeGridSurface(): { controller: GridController; panel: ActiveGridPanel } | null {
695
+ const controller = getGridController(activeTab.value);
696
+ const active = activeIn(workspace.activePaneId);
697
+ if (!controller || !active || active.tabId !== controller.tab.id) {
698
+ return null;
699
+ }
700
+ return { controller, panel: active };
701
+ }
702
+
703
+ /** The saved-view verbs. Every one of them is also a control in the View popover. */
704
+ function requireSurface(commandId: string) {
705
+ const surface = activeGridSurface();
706
+ if (!surface) {
707
+ throw new Error(`command "${commandId}" needs a grid on screen`);
708
+ }
709
+ return surface;
710
+ }
711
+
712
+ /**
713
+ * Saved views, as commands.
714
+ *
715
+ * A view is named, so it is addressable, so it belongs in the palette, on the `__jxAutomation`
716
+ * surface and in front of the assistant — not only behind a popover the author has to find. The
717
+ * refusals name what the grid actually holds, in the idiom of `collection.editInGrid`: an unknown
718
+ * view lists the views that exist rather than doing nothing.
719
+ *
720
+ * @returns {AnyCommand[]}
721
+ */
722
+ export function gridViewCommands(): AnyCommand[] {
723
+ const viewNameArg = (commandId: string, args: unknown): { gridId: string; name: string } => {
724
+ const { controller } = requireSurface(commandId);
725
+ const gridId = controller.source.id;
726
+ const name = stringArg(commandId, args as Record<string, unknown>, "name");
727
+ const known = listSavedViews(gridId).map((view) => view.name);
728
+ if (!known.includes(name)) {
729
+ throw new RangeError(
730
+ `command "${commandId}" argument "name": "${name}" is not a saved view of ` +
731
+ `${controller.source.label} — it has: ${known.length > 0 ? known.join(", ") : "none"}`,
732
+ );
733
+ }
734
+ return { gridId, name };
735
+ };
736
+
737
+ return [
738
+ {
739
+ category: "View",
740
+ id: "grid.saveView",
741
+ level: "document",
742
+ menus: ["palette"],
743
+ group: "5_data",
744
+ requires: "a grid on screen",
745
+ when: (ctx) => ctx.project.open,
746
+ enablement: () => activeGridSurface() !== null,
747
+ aiTool: {
748
+ description:
749
+ "Save the open grid's columns, sort, grouping and filter as a named view. Prompts for the name.",
750
+ name: "save_grid_view",
751
+ },
752
+ run: async () => {
753
+ const { controller, panel } = requireSurface("grid.saveView");
754
+ await promptSaveView(controller, panel, () => {});
755
+ },
756
+ title: "Save Grid View…",
757
+ },
758
+ {
759
+ args: argsSchema({ name: stringProperty("The saved view's name.") }),
760
+ category: "View",
761
+ id: "grid.applyView",
762
+ level: "document",
763
+ menus: ["palette"],
764
+ group: "5_data",
765
+ requires: "a grid on screen",
766
+ when: (ctx) => ctx.project.open,
767
+ enablement: () => activeGridSurface() !== null,
768
+ aiTool: {
769
+ description: "Apply a saved view to the open grid by name.",
770
+ name: "apply_grid_view",
771
+ },
772
+ run: (_ctx, args) => {
773
+ const { gridId, name } = viewNameArg("grid.applyView", args);
774
+ const { panel } = requireSurface("grid.applyView");
775
+ panel.applyLayout(applySavedView(gridId, name));
776
+ },
777
+ title: "Apply Grid View",
778
+ },
779
+ {
780
+ args: argsSchema({ name: stringProperty("The saved view's name.") }),
781
+ category: "View",
782
+ id: "grid.deleteView",
783
+ level: "document",
784
+ menus: ["palette"],
785
+ group: "5_data",
786
+ requires: "a grid on screen",
787
+ destructive: true,
788
+ when: (ctx) => ctx.project.open,
789
+ enablement: () => activeGridSurface() !== null,
790
+ run: (_ctx, args) => {
791
+ const { gridId, name } = viewNameArg("grid.deleteView", args);
792
+ deleteSavedView(gridId, name);
793
+ requireSurface("grid.deleteView").panel.bump();
794
+ },
795
+ title: "Delete Grid View",
796
+ },
797
+ {
798
+ category: "View",
799
+ id: "grid.resetView",
800
+ level: "document",
801
+ menus: ["palette"],
802
+ group: "5_data",
803
+ requires: "a grid on screen",
804
+ when: (ctx) => ctx.project.open,
805
+ enablement: () => activeGridSurface() !== null,
806
+ run: () => {
807
+ const { controller, panel } = requireSurface("grid.resetView");
808
+ resetGridLayout(controller.source.id);
809
+ panel.applyLayout(null);
810
+ },
811
+ title: "Reset Grid View",
812
+ },
813
+ ];
814
+ }
815
+
816
+ /**
817
+ * Register the saved-view commands.
818
+ *
819
+ * @param {CommandRegistry} registry
820
+ */
821
+ export function registerGridViewCommands(registry: CommandRegistry): void {
822
+ registry.registerAll(gridViewCommands());
823
+ }