@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
@@ -9,6 +9,13 @@
9
9
  * editor's `border-left` nesting idiom) with their own add-statement pickers. Cards drag-reorder
10
10
  * within their lane via the pragmatic-drag-and-drop tree-item pattern (see panels/dnd.ts). All
11
11
  * edits flow through `onChange(next)` immutably — no statement object is mutated in place.
12
+ *
13
+ * **The layout is CSS, in `styles/inspector.css` (§ "The statement editor").** Every frame, indent
14
+ * and connector here used to be an inline `style=` attribute, so nothing in the chain could set
15
+ * `min-width: 0` and the operand controls refused to shrink: in a 280px Inspector an If/Else card
16
+ * pushed Operator, Target and Value past the right edge of the window. This editor has two hosts of
17
+ * very different widths — the Navigator's State panel and the Inspector's Events tab — plus the
18
+ * Bottom dock, so a fixed pixel layout is wrong by construction and only a stylesheet can say so.
12
19
  */
13
20
 
14
21
  import { html, nothing } from "lit-html";
@@ -42,6 +49,24 @@ export interface StatementEditorOpts {
42
49
  allowEventRef: boolean;
43
50
  /** The entry's declared CEM events — offered as dispatchEvent name completions. */
44
51
  emits?: CemEvent[];
52
+ /**
53
+ * The region id of THIS editor, supplied by the surface that is drawing it.
54
+ *
55
+ * Required, and required of every host, which is the point. The editor used to hard-stamp
56
+ * `navigator/statements` on itself, and it has two hosts that can be open at the same time — the
57
+ * Navigator's State panel (`signals-panel.ts`) and the INSPECTOR's Events tab
58
+ * (`events-panel.ts`). `resolveRegion` takes the last match in document order and `#right-panel`
59
+ * follows `#left-panel`, so the id resolved to the Inspector's editor while saying Navigator, and
60
+ * the shot that crops it cropped the wrong control.
61
+ *
62
+ * A shared control cannot know where it is, so it may not claim to. This is the same verdict
63
+ * `ui/regions.ts`'s `DERIVED_RESOLVERS` records for the media picker's Browse button — _an
64
+ * `inspector/…` id on an element outside the Inspector is not a pane-scoping problem, it is a
65
+ * wrong id_ — reached the other way round: the picker's id is derived from the Inspector because
66
+ * nothing addresses the card's copies, whereas both statement editors are real surfaces that
67
+ * deserve names, so the HOST names them.
68
+ */
69
+ region: string;
45
70
  }
46
71
 
47
72
  /**
@@ -120,7 +145,13 @@ export function laneListAt(list: JxStatement[], path: LanePath): JxStatement[] |
120
145
  return list;
121
146
  }
122
147
  const [idx, key] = path;
123
- const stmt = typeof idx === "number" ? (list[idx] as Record<string, unknown> | undefined) : null;
148
+ /*
149
+ * `unknown`, not a cast to `Record<string, unknown>`. A `JxStatement` is a union of interfaces
150
+ * with no index signature, so asserting one into an open bag is a lie TypeScript rightly refuses
151
+ * — and it was buying nothing, because `isJsonObject` takes `unknown` and narrows to exactly the
152
+ * open bag this wants. The guard on the next line is the real check either way.
153
+ */
154
+ const stmt: unknown = typeof idx === "number" ? list[idx] : null;
124
155
  if (!isJsonObject(stmt) || typeof key !== "string") {
125
156
  return null;
126
157
  }
@@ -292,9 +323,6 @@ function registerStatementsDnD(
292
323
 
293
324
  // ─── Small shared widgets ────────────────────────────────────────────────────
294
325
 
295
- const laneLabelStyle =
296
- "font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.04em;color:var(--spectrum-gray-600, #808080)";
297
-
298
326
  function operandOpts(opts: StatementEditorOpts) {
299
327
  return {
300
328
  allowEventRef: opts.allowEventRef,
@@ -346,9 +374,9 @@ function renderLane(
346
374
  extras: { onRemove?: () => void } = {},
347
375
  ): TemplateResult {
348
376
  return html`
349
- <div class="statement-lane" style="margin:4px 0 4px 6px">
350
- <div class="statement-lane-header" style="display:flex;align-items:center;gap:4px">
351
- <span style=${laneLabelStyle}>${label}</span>
377
+ <div class="statement-lane">
378
+ <div class="statement-lane-header">
379
+ <span class="statement-lane-label">${label}</span>
352
380
  ${
353
381
  extras.onRemove
354
382
  ? html`
@@ -453,7 +481,6 @@ function renderSwitchBody(
453
481
  <sp-textfield
454
482
  size="s"
455
483
  class="statement-case-key"
456
- style="width:96px"
457
484
  placeholder="value"
458
485
  .value=${live(key)}
459
486
  @change=${(e: Event) => {
@@ -543,7 +570,6 @@ function renderDispatchBody(
543
570
  size="s"
544
571
  allows-custom-value
545
572
  class="statement-dispatch-name"
546
- style="flex:1"
547
573
  .value=${live(stmt.dispatchEvent ?? "")}
548
574
  @change=${commitName}
549
575
  >
@@ -554,7 +580,6 @@ function renderDispatchBody(
554
580
  <sp-textfield
555
581
  size="s"
556
582
  class="statement-dispatch-name"
557
- style="flex:1"
558
583
  placeholder="event-name"
559
584
  .value=${live(stmt.dispatchEvent ?? "")}
560
585
  @input=${commitName}
@@ -576,7 +601,7 @@ function renderDispatchBody(
576
601
  label: "Options",
577
602
  prop: "eventInit",
578
603
  widget: html`
579
- <div style="display:flex;gap:12px;align-items:center">
604
+ <div class="statement-dispatch-options">
580
605
  <sp-checkbox
581
606
  size="s"
582
607
  class="statement-dispatch-bubbles"
@@ -641,17 +666,10 @@ function renderStatementCard(
641
666
  data-stmt-kind=${kind}
642
667
  data-stmt-lane=${JSON.stringify(lanePath)}
643
668
  data-stmt-index=${index}
644
- style="border:1px solid var(--spectrum-gray-300, #3c3c3c);border-radius:4px;padding:4px 6px;margin:0 0 6px;background:var(--spectrum-gray-75, #1e1e1e)"
645
669
  >
646
- <div class="statement-card-header" style="display:flex;align-items:center;gap:6px">
647
- <span
648
- class="statement-drag-handle"
649
- title="Drag to reorder"
650
- style="cursor:grab;color:var(--spectrum-gray-500, #6e6e6e);font-size:11px;line-height:1;user-select:none"
651
- >⠿</span
652
- >
653
- <span class="statement-kind-label" style=${laneLabelStyle}>${kindLabel(stmt)}</span>
654
- <span style="flex:1"></span>
670
+ <div class="statement-card-header">
671
+ <span class="statement-drag-handle" title="Drag to reorder">⠿</span>
672
+ <span class="statement-kind-label">${kindLabel(stmt)}</span>
655
673
  <sp-action-button
656
674
  quiet
657
675
  size="xs"
@@ -675,10 +693,7 @@ function renderStatementList(
675
693
  lanePath: LanePath,
676
694
  ): TemplateResult {
677
695
  return html`
678
- <div
679
- class="statement-list"
680
- style="margin-left:4px;border-left:2px solid var(--spectrum-gray-300, #3c3c3c);padding:2px 0 0 8px"
681
- >
696
+ <div class="statement-list">
682
697
  ${list.map((stmt, index) =>
683
698
  renderStatementCard(stmt, index, list, commitList, opts, lanePath),
684
699
  )}
@@ -702,6 +717,7 @@ export function renderStatementEditor(
702
717
  return html`
703
718
  <div
704
719
  class="statement-editor"
720
+ data-jx-region=${opts.region}
705
721
  ${ref((el) => {
706
722
  if (el) {
707
723
  registerStatementsDnD(el as HTMLElement, safe, onChange);
@@ -1,140 +1,376 @@
1
1
  /// <reference lib="dom" />
2
- /** Statusbar — status message display for Jx Studio */
2
+ /**
3
+ * ⑫ The status bar — ambient state, in scope order, and nothing else.
4
+ *
5
+ * What this replaces: a bar built by `innerHTML` string concatenation with a three-character
6
+ * escaper, carrying whatever the last of 78 `statusMessage()` calls had said, for three seconds.
7
+ * Transient messages have left entirely for the toast host (`ui/layers.ts`) and the Problems list
8
+ * (`services/notify.ts`); what remains here is state that is TRUE for as long as it is shown.
9
+ *
10
+ * **Three fixed fields, in the shell's own left-to-right level order** (plan §3.2 ⑫), so the bar
11
+ * restates the containment model on every glance:
12
+ *
13
+ * ```text
14
+ * PROJECT ‖ DOCUMENT ‖ SELECTION
15
+ * name · branch ↑n↓n · problems ‖ path · view · save state ‖ count · style rule
16
+ * ```
17
+ *
18
+ * The SELECTION field held an ancestor breadcrumb until region ⑥ landed. The trail is an ADDRESS,
19
+ * not ambient state, and it now lives once — in the jump bar (`panels/jump-bar.ts`), which merged
20
+ * it with the pane context bar's document-stack chain. What is left here is the count and the
21
+ * Stylebook's rule, neither of which is a path.
22
+ *
23
+ * `statusbar/project`, `statusbar/document` and `statusbar/selection` are three placements the
24
+ * level × placement matrix already declares, each admitting exactly one level — so the bar's
25
+ * mixedness is structural rather than an exemption.
26
+ *
27
+ * **The effective view, not a mode string.** The bar used to print "Content Mode" from
28
+ * `tab.doc.mode` while the Command Bar printed "Design" from `canvasMode` — two surfaces reading
29
+ * two fields to answer one question. Both now read `registry.context()`, which is the same record
30
+ * every `when` predicate reads, so they cannot disagree again.
31
+ *
32
+ * **Every interactive item is a command.** There are no click handlers in this file: an item names
33
+ * a command id, and renders as a button only when the registry has that command and its `when`
34
+ * holds. That is what lets an item that has no command yet — the peers count, whose `Collaborate:`
35
+ * family lands with P4.8 — sit in the template today and start rendering the day it is registered,
36
+ * with no edit here. The three readouts that are NOT buttons are marked: the view name (its control
37
+ * is the pane context bar ⑦; a second mode picker in 24px would be the chrome duplication §2
38
+ * principle 9 exists to prevent), the save wording once a document IS saved, and the stylebook
39
+ * selector.
40
+ */
3
41
 
4
- import { getNodeAtPath, nodeLabel, renderOnly, statusbarEl, updateSession } from "../store";
42
+ import { html, render as litRender, nothing } from "lit-html";
43
+ import { projectState, statusbarEl } from "../store";
44
+ import { documentLabel } from "./jump-bar";
45
+ import { shell } from "../shell";
5
46
  import { effect, effectScope } from "../reactivity";
6
47
  import { activeTab } from "../workspace/workspace";
7
- import type { JxPath } from "../state";
48
+ import { EDITOR_KIND_LABELS } from "../commands/context";
49
+ import { activeRegistry } from "../commands/active-registry";
50
+ import { deployStatusItem } from "../publish/deploy-checklist";
51
+ import { collabState } from "../collab/collab-state";
52
+ import { problemCount, problems } from "../services/notify";
53
+ import { now } from "../services/clock";
54
+ import { relativeTime } from "./ai-chat/sessions-view";
55
+ import type { CommandRegistry } from "../commands/registry";
56
+ import type { EditorKind } from "../commands/context";
8
57
  import type { EffectScope } from "@vue/reactivity";
58
+ import type { TemplateResult } from "lit-html";
9
59
 
10
- // ─── Module state ────────────────────────────────────────────────────────────
11
-
12
- let statusMsg = "";
13
- /** @type {ReturnType<typeof setTimeout> | undefined} */
14
- let statusTimeout: ReturnType<typeof setTimeout> | undefined;
15
- let _rerender: (() => void) | null = null;
16
60
  let _scope: EffectScope | null = null;
17
61
 
18
62
  /**
19
- * Register the callback used to re-render the statusbar. Called once from studio.js during init.
63
+ * Document path → when it was last written, from the {@link now} seam.
20
64
  *
21
- * @param {() => void} fn
65
+ * Kept here rather than on the `Tab` record for one reason: a save time is something the STATUS BAR
66
+ * observes, not something a document is. `files/file-ops.ts` reports it after a successful write —
67
+ * the same place the old `statusMessage("Saved …")` was raised from, so the fact has not moved, it
68
+ * has only stopped being a message that erases itself.
22
69
  */
23
- export function setStatusbarRenderer(fn: () => void) {
24
- _rerender = fn;
70
+ const _savedAt = new Map<string, number>();
71
+
72
+ /** Record a successful write. Called by the save path; keyed by document path. */
73
+ export function noteDocumentSaved(path: string | null | undefined): void {
74
+ if (path) {
75
+ _savedAt.set(path, now());
76
+ }
25
77
  }
26
78
 
27
- /** Subscribe the statusbar to state changes via reactive effect. */
28
- export function mountStatusbar() {
29
- _scope = effectScope();
30
- _scope.run(() => {
31
- effect(() => {
32
- const tab = activeTab.value;
33
- if (!tab) {
34
- return;
35
- }
36
- // Track relevant reactive properties
37
- void tab.doc.document;
38
- void tab.doc.mode;
39
- void tab.session.selection;
40
- void tab.session.ui.stylebookSelection;
41
- renderStatusbar();
42
- });
43
- });
79
+ /** Forget every recorded save. Project close and the tests both want a clean slate. */
80
+ export function forgetSavedTimes(): void {
81
+ _savedAt.clear();
82
+ }
44
83
 
45
- statusbarEl?.addEventListener("click", _onStatusbarClick);
84
+ // ─── Items ───────────────────────────────────────────────────────────────────
85
+
86
+ /** One thing the bar can show. A `command` makes it a button; without one it is a readout. */
87
+ interface StatusItem {
88
+ /** Command id, or `null` for a pure readout. */
89
+ command: string | null;
90
+ label: string;
91
+ /** Extra context for the tooltip. The command's own title and chord are added to it. */
92
+ title?: string;
93
+ args?: Record<string, unknown>;
46
94
  }
47
95
 
48
- export function unmountStatusbar() {
49
- _scope?.stop();
50
- _scope = null;
51
- statusbarEl?.removeEventListener("click", _onStatusbarClick);
96
+ /**
97
+ * Render one item.
98
+ *
99
+ * A named command that the registry does not have, or whose `when` is false, renders NOTHING — the
100
+ * item disappears rather than becoming a dead label. That is the whole mechanism by which the bar
101
+ * stays a rendering of the registry instead of a second place capabilities are decided.
102
+ */
103
+ function itemTpl(registry: CommandRegistry | null, item: StatusItem) {
104
+ if (item.command === null) {
105
+ return html`<span class="sb-item sb-state" title=${item.title ?? item.label}
106
+ >${item.label}</span
107
+ >`;
108
+ }
109
+ const id = item.command;
110
+ if (!registry?.get(id) || !registry.isVisible(id)) {
111
+ return nothing;
112
+ }
113
+ const command = registry.get(id)!;
114
+ const reason = registry.disabledReason(id);
115
+ const chord = registry.keymap.formatBinding(id);
116
+ const suffix = reason
117
+ ? `${command.title} — requires ${reason}`
118
+ : chord
119
+ ? `${command.title} (${chord})`
120
+ : command.title;
121
+ return html`<button
122
+ class="sb-item"
123
+ ?disabled=${reason !== undefined}
124
+ title=${item.title ? `${item.title} · ${suffix}` : suffix}
125
+ @click=${() => {
126
+ void registry.run(id, item.args);
127
+ }}
128
+ >
129
+ ${item.label}
130
+ </button>`;
52
131
  }
53
132
 
54
- // ─── Statusbar ───────────────────────────────────────────────────────────────
133
+ /** What {@link itemTpl} returns: a rendered item, or the absence of one. */
134
+ type ItemResult = TemplateResult | typeof nothing;
55
135
 
56
- /** @param {string} text */
57
- function esc(text: string) {
58
- return String(text).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
136
+ /** A field, with its region stamp. Renders nothing at all when it has no items. */
137
+ function fieldTpl(region: string, items: readonly ItemResult[]) {
138
+ const live = items.filter((item): item is TemplateResult => item !== nothing);
139
+ if (live.length === 0) {
140
+ return nothing;
141
+ }
142
+ return html`<div class="sb-field" data-jx-region=${region}>${live}</div>`;
59
143
  }
60
144
 
61
- /** Render the statusbar content. */
62
- export function renderStatusbar() {
63
- const tab = activeTab.value;
64
- const parts = [];
65
- if (tab?.doc.mode === "content") {
66
- parts.push("Content Mode");
67
- }
68
- if (tab?.session.selection?.length) {
69
- const sel = tab.session.selection as JxPath;
70
- const node = getNodeAtPath(tab.doc.document, sel);
71
- parts.push(`Selected: ${esc(nodeLabel(node))}`);
72
-
73
- // Walk the path one structural step at a time. Most steps are `["children", index]` or
74
- // `["cases", name]` pairs, but a repeater template is reached by a lone `"map"` segment — so the
75
- // Step width varies. Emitting a crumb per node keeps the array pseudo-element ("Repeater") and
76
- // Its template both visible instead of collapsing the array into a bare `[index]`.
77
- const pathSegments = [];
78
- for (let i = 0; i < sel.length;) {
79
- const seg = sel[i];
80
- const step = seg === "map" ? 1 : 2;
81
- const subPath = sel.slice(0, i + step);
82
- const childNode = getNodeAtPath(tab.doc.document, subPath);
83
- const fallbackTag = childNode?.tag;
84
- const label =
85
- childNode?.$prototype === "Array"
86
- ? "Repeater"
87
- : childNode?.tagName ||
88
- (typeof fallbackTag === "string" ? fallbackTag : "") ||
89
- (seg === "cases" ? String(sel[i + 1]) : `[${sel[i + 1]}]`);
90
- const dataPath = JSON.stringify(subPath);
91
- pathSegments.push(
92
- `<span class="sb-path-seg" data-path='${esc(dataPath)}'>${esc(label)}</span>`,
93
- );
94
- i += step;
95
- }
96
- parts.push(`Path: ${pathSegments.join(' <span class="sb-path-sep">&gt;</span> ')}`);
97
- } else if (tab?.session.ui.stylebookSelection) {
98
- const sel = tab.session.ui.stylebookSelection;
99
- parts.push(`Style: ${esc(sel.replaceAll(" ", " > "))}`);
145
+ // ─── ⑫a PROJECT ──────────────────────────────────────────────────────────────
146
+
147
+ /** `↑2↓1`, or "" when the branch is level with its upstream. */
148
+ export function aheadBehindLabel(ahead: number, behind: number): string {
149
+ return `${ahead > 0 ? ` ↑${ahead}` : ""}${behind > 0 ? ` ↓${behind}` : ""}`;
150
+ }
151
+
152
+ function projectFieldTpl(registry: CommandRegistry | null) {
153
+ const project = projectState;
154
+ const { status } = shell.git;
155
+ const count = problemCount();
156
+ const deploy = deployStatusItem();
157
+ const peers = activeTab.value ? collabState(activeTab.value).peers.length : 0;
158
+ return fieldTpl("statusbar/project", [
159
+ project
160
+ ? itemTpl(registry, { command: "project.openRecent", label: project.name })
161
+ : itemTpl(registry, { command: "project.open", label: "No project" }),
162
+ /* WHICH branch, when there is one — and deliberately no "not tracked" twin, though plan §12 P1
163
+ workstream 9's "repo state becomes a persistent status-bar field" reads like a request for
164
+ one. An untracked project already states itself in this field, one item along:
165
+ `deployStatusItem()`'s first link is `repo`, whose label is "Track this project with git" and
166
+ whose command is `git.init`. A second item beside it would carry no fact the first does not
167
+ — "Not tracked" and "Track this project with git" answer the same question with the same verb
168
+ — and adjacent duplicate chrome is what §2 principle 9 forbids. `tests/statusbar.test.ts`
169
+ pins the pairing from both ends, so deleting the checklist's repo step fails there rather
170
+ than quietly taking the state off the bar. */
171
+ status?.isRepo === true && status.branch
172
+ ? itemTpl(registry, {
173
+ command: "panel.focus.git",
174
+ label: `⑂ ${status.branch}${aheadBehindLabel(status.ahead, status.behind)}`,
175
+ title: `${status.files.length} changed file(s)`,
176
+ })
177
+ : nothing,
178
+ // Where the project stands with shipping — ambient state, so it belongs beside the branch and
179
+ // The problem count rather than in a toast. `deployStatusItem` names the NEXT missing step
180
+ // While anything is missing, and the deployment itself once nothing is.
181
+ deploy
182
+ ? itemTpl(registry, {
183
+ command: deploy.command,
184
+ label: deploy.label,
185
+ title: deploy.title,
186
+ })
187
+ : nothing,
188
+ // `view.setBottomTab`, not `panel.focus.problems`: the latter is generated from the rail
189
+ // Roster, and Problems left the rail. That is the same verb Diff, Logic and Activity are
190
+ // Addressed by — one door per bottom tab — and it is what keeps this readout the ONLY standing
191
+ // Mention of problems in the chrome, which is the point of taking the rail button away.
192
+ count > 0
193
+ ? itemTpl(registry, {
194
+ args: { tab: "problems" },
195
+ command: "view.setBottomTab",
196
+ label: `⚠ ${count}`,
197
+ title: `${count} problem(s)`,
198
+ })
199
+ : nothing,
200
+ /* `collab.showStatus` — "what is happening in this document?" — because that is the question a
201
+ peer count raises.
202
+
203
+ This said `collab.share` and carried a comment promising the item would "appear the day the
204
+ `Collaborate:` family lands, with no edit to this file". The family landed, under five ids,
205
+ and `share` was renamed `collab.setEnabled` on the way (the set*-not-toggle* rule). `itemTpl`
206
+ renders `nothing` for an id the registry does not have, so the readout was permanently blank
207
+ and the comment said it was fine. An id is not a stable interface between two files unless
208
+ something checks it, which is what `tests/statusbar.test.ts` now does. */
209
+ peers > 0
210
+ ? itemTpl(registry, {
211
+ command: "collab.showStatus",
212
+ label: `${peers} peer${peers === 1 ? "" : "s"}`,
213
+ title: `${peers} peer${peers === 1 ? "" : "s"} in this document`,
214
+ })
215
+ : nothing,
216
+ ]);
217
+ }
218
+
219
+ // ─── ⑫b DOCUMENT ─────────────────────────────────────────────────────────────
220
+
221
+ /**
222
+ * What the pane is showing, in the words the pane context bar uses.
223
+ *
224
+ * Derived from the command context — `editor.kind` and `canvas.view` — so it is the SAME two facts
225
+ * the Command Bar, the keyboard scope stack and every `when` predicate read.
226
+ */
227
+ export function viewLabel(kind: EditorKind, view: string): string {
228
+ if (kind === "none") {
229
+ return "";
100
230
  }
101
- if (statusMsg) {
102
- parts.push(esc(statusMsg));
231
+ if (kind !== "canvas") {
232
+ return EDITOR_KIND_LABELS[kind];
103
233
  }
104
- statusbarEl.innerHTML = parts.join(" | ") || "Jx Studio";
234
+ return view.charAt(0).toUpperCase() + view.slice(1);
105
235
  }
106
236
 
107
- /** @param {Event} e */
108
- function _onStatusbarClick(e: Event) {
109
- const target = e.target as HTMLElement;
110
- if (!target.classList.contains("sb-path-seg")) {
111
- return;
237
+ /**
238
+ * The save state, in words.
239
+ *
240
+ * A dot glyph said one bit and required a legend nobody has. These four sentences say which of the
241
+ * four states the document is in, and the one that needs an action IS the action: "Unsaved changes"
242
+ * renders as the `file.save` button.
243
+ */
244
+ function saveItem(dirty: boolean, readOnly: boolean, savedAt: number | undefined): StatusItem {
245
+ if (readOnly) {
246
+ return { command: null, label: "Read-only", title: "A collaborator owns this session's file" };
112
247
  }
113
- const pathStr = target.dataset.path;
114
- if (!pathStr) {
115
- return;
248
+ if (dirty) {
249
+ return { command: "file.save", label: "Unsaved changes" };
116
250
  }
117
- try {
118
- const path = JSON.parse(pathStr) as JxPath;
119
- updateSession({ selection: path });
120
- renderOnly("leftPanel", "rightPanel", "canvas");
121
- } catch {
122
- // Ignore
251
+ return savedAt === undefined
252
+ ? { command: null, label: "Saved" }
253
+ : { command: null, label: `Saved ${relativeTime(savedAt)}` };
254
+ }
255
+
256
+ function documentFieldTpl(registry: CommandRegistry | null) {
257
+ const tab = activeTab.value;
258
+ if (!tab) {
259
+ return nothing;
123
260
  }
261
+ const ctx = registry?.context() ?? null;
262
+ const view = ctx ? viewLabel(ctx.editor.kind, ctx.canvas.view) : "";
263
+ const collab = collabState(tab);
264
+ const savedAt = tab.documentPath === null ? undefined : _savedAt.get(tab.documentPath);
265
+ return fieldTpl("statusbar/document", [
266
+ itemTpl(registry, {
267
+ command: "palette.openFiles",
268
+ label: documentLabel(tab.documentPath),
269
+ title: tab.documentPath ?? "Not saved to disk yet",
270
+ }),
271
+ view
272
+ ? itemTpl(registry, {
273
+ command: null,
274
+ label: view,
275
+ title: "The pane's view — change it on the pane context bar",
276
+ })
277
+ : nothing,
278
+ itemTpl(registry, saveItem(tab.doc.dirty, collab.readOnly, savedAt)),
279
+ ]);
124
280
  }
125
281
 
282
+ // ─── ⑫c SELECTION ────────────────────────────────────────────────────────────
283
+
126
284
  /**
127
- * Show a temporary status message.
285
+ * What is selected — the COUNT, and nothing that is an address.
286
+ *
287
+ * This field used to carry a clickable ancestor trail, one `selection.set` per crumb. That trail
288
+ * has moved whole to the jump bar (⑥, `panels/jump-bar.ts`), which is region ⑥'s entire reason to
289
+ * exist: Studio had two half-breadcrumbs — that one, and the pane context bar's document-stack
290
+ * chain — and neither ever rendered the whole address. Leaving a copy here would have made three.
128
291
  *
129
- * @param {string} msg
130
- * @param {number} [duration]
292
+ * What is left is what the jump bar CANNOT say, and both are ambient state in the §16.2 sense: how
293
+ * many things are selected (a count is not a path, and the jump bar names only the primary), and
294
+ * which style rule the Stylebook is editing (a CSS selector is not a node in this document).
295
+ *
296
+ * A single selection therefore leaves this field empty. That is deliberate — the jump bar's leaf
297
+ * segment states it permanently, with its ancestors, and a second copy at 11px says nothing new.
131
298
  */
132
- export function statusMessage(msg: string, duration = 3000) {
133
- statusMsg = msg;
134
- _rerender?.();
135
- clearTimeout(statusTimeout);
136
- statusTimeout = setTimeout(() => {
137
- statusMsg = "";
138
- _rerender?.();
139
- }, duration);
299
+ function selectionFieldTpl(registry: CommandRegistry | null) {
300
+ const paths = activeTab.value?.session.selection ?? [];
301
+ if (paths.length > 0) {
302
+ // A document selection owns the field even when it prints nothing: the Stylebook's selector
303
+ // Below would otherwise appear while an element is picked, which is two answers to one question.
304
+ return paths.length > 1
305
+ ? fieldTpl("statusbar/selection", [
306
+ itemTpl(registry, {
307
+ command: null,
308
+ label: `${paths.length} selected`,
309
+ title: `${paths.length} elements are selected; the jump bar names the primary`,
310
+ }),
311
+ ])
312
+ : nothing;
313
+ }
314
+ // The stylebook's own selection is a selection: it is what the Style panel is editing, and it is
315
+ // The only thing in this field when no document node is picked.
316
+ return shell.stylebook.selection
317
+ ? fieldTpl("statusbar/selection", [
318
+ itemTpl(registry, {
319
+ command: null,
320
+ label: shell.stylebook.selection.replaceAll(" ", " › "),
321
+ title: "The style rule being edited",
322
+ }),
323
+ ])
324
+ : nothing;
325
+ }
326
+
327
+ // ─── The bar ─────────────────────────────────────────────────────────────────
328
+
329
+ /** The whole bar, as one template. There is no second variant for the empty states. */
330
+ export function statusbarTemplate() {
331
+ const registry = activeRegistry();
332
+ return html`${projectFieldTpl(registry)}${documentFieldTpl(registry)}${selectionFieldTpl(
333
+ registry,
334
+ )}`;
335
+ }
336
+
337
+ /** Paint the bar. Exported because the bootstrap paints once before mounting the effect. */
338
+ export function renderStatusbar(): void {
339
+ if (statusbarEl) {
340
+ litRender(statusbarTemplate(), statusbarEl);
341
+ }
342
+ }
343
+
344
+ /** Subscribe the bar to the state it renders. Idempotent. */
345
+ export function mountStatusbar(): void {
346
+ unmountStatusbar();
347
+ _scope = effectScope();
348
+ _scope.run(() => {
349
+ effect(() => {
350
+ // The registry is composed AFTER the bootstrap mounts this, and it is a reactive holder —
351
+ // Reading it here is what repaints the bar from a skeleton into the real thing.
352
+ void activeRegistry();
353
+ void shell.stylebook.selection;
354
+ void shell.git.status;
355
+ void problems.length;
356
+ const tab = activeTab.value;
357
+ if (tab) {
358
+ void tab.doc.document;
359
+ void tab.doc.dirty;
360
+ void tab.doc.mode;
361
+ void tab.documentPath;
362
+ void tab.session.selection.map((path) => path.join("/")).join("|");
363
+ void tab.session.ui.canvasMode;
364
+ void tab.session.ui.preview;
365
+ void collabState(tab).peers.length;
366
+ void collabState(tab).readOnly;
367
+ }
368
+ renderStatusbar();
369
+ });
370
+ });
371
+ }
372
+
373
+ export function unmountStatusbar(): void {
374
+ _scope?.stop();
375
+ _scope = null;
140
376
  }