@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,50 +7,37 @@
7
7
  import { html, nothing } from "lit-html";
8
8
  import { errorMessage } from "@jxsuite/schema/parse";
9
9
  import { flushAllCollab } from "../collab/collab-session";
10
- import type {
11
- GitBranchesResult,
12
- GitDiffState,
13
- GitFileStatus,
14
- GitStatusResult,
15
- StudioPlatform,
16
- } from "../types";
10
+ import type { GitDiffState, GitFileStatus, StudioPlatform } from "../types";
17
11
  import { live } from "lit-html/directives/live.js";
18
12
  import { repeat } from "lit-html/directives/repeat.js";
19
13
  import { getPlatform } from "../platform";
14
+ import { now } from "../services/clock";
20
15
  import { formatForPath } from "../format/format-host";
21
- import { projectState, renderOnly, updateUi } from "../store";
16
+ import { projectState } from "../store";
22
17
  import { activeTab } from "../workspace/workspace";
23
- import { view } from "../view";
18
+ import type { Tab } from "../tabs/tab";
19
+ import { shell } from "../shell";
20
+ import type { GitLogEntry } from "../shell";
24
21
  import { showConfirmDialog, showPromptDialog } from "../ui/layers";
25
- import { statusMessage } from "./statusbar";
26
- import { publishToGithub } from "../github/github-publish";
22
+ import { POLL_GIT } from "../ui/timing";
23
+ import { renderEmptyState } from "./empty-state";
24
+ import { registerPanel } from "./panel-registry";
25
+ import { notify } from "../services/notify";
26
+ import { authenticateGithub } from "../github/github-auth";
27
+ import { createGithubRepository } from "../github/github-publish";
27
28
  import { pullWithPackageSync } from "../packages/pull-package-sync";
28
-
29
- interface GitLogEntry {
30
- hash: string;
31
- message: string;
32
- author: string;
33
- date: string;
34
- }
29
+ import type { AnyCommand, CommandRegistry } from "../commands/registry";
35
30
 
36
31
  type GitFileEntry = GitFileStatus;
37
32
 
38
- interface GitUiState {
39
- gitStatus?: Partial<GitStatusResult> | null;
40
- gitBranches?: Partial<GitBranchesResult> | null;
41
- gitLoading?: boolean;
42
- gitError?: string | null;
43
- gitCommitMessage?: string;
44
- gitLogEntries?: GitLogEntry[] | null;
45
- }
46
-
47
33
  export async function refreshGitStatus() {
48
34
  if (!projectState) {
49
35
  return;
50
36
  }
51
37
  const plat = getPlatform();
52
- updateUi("gitLoading", true);
53
- updateUi("gitError", null);
38
+ const { git } = shell;
39
+ git.loading = true;
40
+ git.error = null;
54
41
  try {
55
42
  // Settled independently, not Promise.all: outside a work tree `git branch` exits non-zero
56
43
  // While `git status` answers cleanly with isRepo:false. Letting the branch lookup reject the
@@ -58,10 +45,10 @@ export async function refreshGitStatus() {
58
45
  // Again, and span — a request per render for as long as the tab stayed open.
59
46
  const [status, branches] = await Promise.allSettled([plat.gitStatus(), plat.gitBranches()]);
60
47
  if (status.status === "fulfilled") {
61
- updateUi("gitStatus", status.value);
48
+ git.status = status.value;
62
49
  }
63
50
  if (branches.status === "fulfilled") {
64
- updateUi("gitBranches", branches.value);
51
+ git.branches = branches.value;
65
52
  }
66
53
  // A branch lookup that fails on a repo-less project is expected, not an error worth showing;
67
54
  // Anything else (including a failed status) surfaces.
@@ -72,14 +59,13 @@ export async function refreshGitStatus() {
72
59
  ? branches.reason
73
60
  : null;
74
61
  if (failure) {
75
- updateUi("gitError", errorMessage(failure));
62
+ git.error = errorMessage(failure);
76
63
  }
77
- _lastUpdated = new Date();
64
+ git.lastUpdated = now();
78
65
  } catch (error) {
79
- updateUi("gitError", errorMessage(error));
66
+ git.error = errorMessage(error);
80
67
  } finally {
81
- updateUi("gitLoading", false);
82
- renderOnly("leftPanel");
68
+ git.loading = false;
83
69
  }
84
70
  }
85
71
 
@@ -91,7 +77,7 @@ export async function refreshGitStatus() {
91
77
  export async function cloneRepository(ctx: { openRecentProject: (root: string) => Promise<void> }) {
92
78
  const platform = getPlatform();
93
79
  if (!platform.gitClone) {
94
- statusMessage("Clone not supported on this platform");
80
+ notify.warn("Cloning is not supported on this platform.", { source: "Source Control" });
95
81
  return;
96
82
  }
97
83
 
@@ -107,14 +93,18 @@ export async function cloneRepository(ctx: { openRecentProject: (root: string) =
107
93
  }
108
94
 
109
95
  try {
110
- statusMessage("Cloning repository...");
96
+ notify.info("Cloning repository…", { key: "git.clone", source: "Source Control" });
111
97
  const result = await platform.gitClone(url);
112
98
  if (result?.root) {
113
- statusMessage("Clone complete");
99
+ notify.success("Clone complete.", { key: "git.clone" });
114
100
  await ctx.openRecentProject(result.root);
115
101
  }
116
102
  } catch (error) {
117
- statusMessage(`Clone failed: ${errorMessage(error)}`);
103
+ notify.error("Could not clone the repository.", {
104
+ detail: errorMessage(error),
105
+ key: "git.clone",
106
+ source: "Source Control",
107
+ });
118
108
  }
119
109
  }
120
110
 
@@ -123,6 +113,100 @@ export function platformSupportsClone() {
123
113
  return Boolean(getPlatform().gitClone);
124
114
  }
125
115
 
116
+ /**
117
+ * One file's comparison against HEAD: the committed text and the working copy.
118
+ *
119
+ * **One definition site, because there are now two callers.** This panel's row click has always
120
+ * made this pair of reads; `workspace/pane-derive.ts`'s Diff lens needs the same answer for the
121
+ * document its source pane is showing, and it must not reach `shell.git.diffState` — that slot
122
+ * holds whatever THIS panel last opened, which is a different file (§18.4, finding 4). A second
123
+ * copy of these five lines is how the panel and the lens would come to disagree about what "the
124
+ * diff of this file" means, which is the rule {@link initRepository} is written under too.
125
+ *
126
+ * `A` (added) has no `HEAD` copy, so its original is the empty string rather than a `gitShow` that
127
+ * would throw. Callers narrow to `M`/`A` before asking.
128
+ *
129
+ * @param {string} path
130
+ * @param {string} fileStatus
131
+ * @returns {Promise<GitDiffState>}
132
+ */
133
+ export async function readGitDiff(path: string, fileStatus: string): Promise<GitDiffState> {
134
+ const plat = getPlatform();
135
+ const [originalContent, currentContent] = await Promise.all([
136
+ fileStatus === "A" ? Promise.resolve("") : plat.gitShow({ path, ref: "HEAD" }),
137
+ plat.readFile(path),
138
+ ]);
139
+ return { currentContent, filePath: path, fileStatus, originalContent };
140
+ }
141
+
142
+ /**
143
+ * The Diff lens's reader: {@link readGitDiff}, with a failure the PANE can state.
144
+ *
145
+ * A rejection here is not an error the shell should raise — the author asked to see a comparison
146
+ * beside their page, and "could not read it" is a sentence the derived pane draws in its own empty
147
+ * state. `applyDerivation` turns the `null` into exactly that.
148
+ *
149
+ * @param {string} path
150
+ * @param {string} fileStatus
151
+ * @returns {Promise<GitDiffState | null>}
152
+ */
153
+ export async function loadDiffForLens(
154
+ path: string,
155
+ fileStatus: string,
156
+ ): Promise<GitDiffState | null> {
157
+ try {
158
+ return await readGitDiff(path, fileStatus);
159
+ } catch (error) {
160
+ // Reported where it can be acted on — the pane says so — rather than as a toast over a
161
+ // Document the author did not ask about.
162
+ console.warn("loadDiffForLens:", errorMessage(error));
163
+ return null;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Start tracking this project with git.
169
+ *
170
+ * A function rather than an inline click handler because it is now also a command ({@link
171
+ * sourceControlCommands}) and the first step of the deploy checklist, and three call sites of the
172
+ * same three lines is how the panel and the palette come to disagree about what a verb does.
173
+ */
174
+ export async function initRepository(): Promise<void> {
175
+ try {
176
+ notify.info("Initializing repository…", { key: "git.init" });
177
+ await getPlatform().gitInit();
178
+ notify.success("Repository initialized.", { key: "git.init" });
179
+ } catch (error) {
180
+ notify.error("Could not initialize the repository.", {
181
+ action: "git.init",
182
+ detail: errorMessage(error),
183
+ key: "git.init",
184
+ source: "Source Control",
185
+ });
186
+ return;
187
+ }
188
+ await refreshGitStatus();
189
+ }
190
+
191
+ /** Push the current branch. Same loading/error contract as every other panel verb. */
192
+ export async function pushCurrentBranch(): Promise<void> {
193
+ await gitAction("gitPush");
194
+ }
195
+
196
+ /**
197
+ * Sign in to GitHub, or report why it could not.
198
+ *
199
+ * The report belongs to `github/github-auth.ts` — this only says what a SUCCESS was, which that
200
+ * module cannot, because it is also called mid-flow by "Create GitHub Repository" where a toast
201
+ * saying "Signed in" in front of a half-finished operation would be noise.
202
+ */
203
+ export async function signInToGithub(): Promise<void> {
204
+ const token = await authenticateGithub();
205
+ if (token) {
206
+ notify.success("Signed in to GitHub.", { key: "github.auth" });
207
+ }
208
+ }
209
+
126
210
  /**
127
211
  * @param {string} action
128
212
  * @param {unknown} [body]
@@ -130,87 +214,87 @@ export function platformSupportsClone() {
130
214
  async function gitAction(action: string, body?: unknown) {
131
215
  const plat = getPlatform() as Record<string, (...args: unknown[]) => Promise<unknown>> &
132
216
  StudioPlatform;
133
- updateUi("gitLoading", true);
134
- updateUi("gitError", null);
217
+ shell.git.loading = true;
218
+ shell.git.error = null;
135
219
  try {
136
220
  await plat[action]!(body);
137
221
  await refreshGitStatus();
138
222
  } catch (error) {
139
- updateUi("gitError", errorMessage(error));
140
- updateUi("gitLoading", false);
141
- renderOnly("leftPanel");
223
+ shell.git.error = errorMessage(error);
224
+ shell.git.loading = false;
142
225
  }
143
226
  }
144
227
 
145
228
  /** Pull via the package-aware orchestrator; same loading/error contract as gitAction. */
146
229
  async function doPull() {
147
- updateUi("gitLoading", true);
148
- updateUi("gitError", null);
230
+ shell.git.loading = true;
231
+ shell.git.error = null;
149
232
  try {
150
233
  await pullWithPackageSync();
151
234
  await refreshGitStatus();
152
235
  } catch (error) {
153
- updateUi("gitError", errorMessage(error));
154
- updateUi("gitLoading", false);
155
- renderOnly("leftPanel");
236
+ shell.git.error = errorMessage(error);
237
+ shell.git.loading = false;
156
238
  }
157
239
  }
158
240
 
241
+ /**
242
+ * The background refresh handle. The interval itself is infrastructure, not state — the sub-tab and
243
+ * the "last updated" stamp it used to sit beside are on `shell.git`, so opening a second project no
244
+ * longer inherits the first one's History selection and timestamp.
245
+ */
159
246
  let _pollTimer = null as ReturnType<typeof setInterval> | null;
160
- let _lastUpdated = null as Date | null;
161
- let _gitSubTab = "changes";
162
247
 
163
248
  async function fetchGitLog() {
164
249
  const plat = getPlatform();
165
250
  try {
166
- const entries = await plat.gitLog(30);
167
- updateUi("gitLogEntries", entries);
168
- renderOnly("leftPanel");
251
+ shell.git.logEntries = await plat.gitLog(30);
169
252
  } catch (error) {
170
- updateUi("gitError", errorMessage(error));
171
- renderOnly("leftPanel");
253
+ shell.git.error = errorMessage(error);
172
254
  }
173
255
  }
174
256
 
175
257
  /**
176
- * @param {{ ui: GitUiState }} S
258
+ * Render the Source Control panel.
259
+ *
260
+ * Takes no state argument: everything it reads is project-level and lives on `shell.git`, which is
261
+ * the whole point of the hoist — the panel renders identically with no document open.
262
+ *
177
263
  * @param {{
178
- * setCanvasMode?: (mode: string) => void;
264
+ * setCanvasMode?: (tab: Tab | null, mode: string) => void;
179
265
  * setGitDiffState?: (state: GitDiffState | null) => void;
180
266
  * cloneRepository?: () => void;
181
267
  * }} ctx
182
268
  */
183
- export function renderGitPanel(
184
- S: { ui: GitUiState },
185
- ctx: {
186
- setCanvasMode?: (mode: string) => void;
187
- setGitDiffState?: (state: GitDiffState | null) => void;
188
- cloneRepository?: () => void;
189
- },
190
- ) {
269
+ export function renderGitPanel(ctx: {
270
+ setCanvasMode?: (tab: Tab | null, mode: string) => void;
271
+ setGitDiffState?: (state: GitDiffState | null) => void;
272
+ cloneRepository?: () => void;
273
+ }) {
191
274
  if (!projectState) {
192
275
  return html`<div class="git-panel git-panel-empty">
193
- <div class="git-empty-state">
194
- <p>Open a project to use source control.</p>
195
- ${
196
- platformSupportsClone()
197
- ? html`<sp-action-button size="m" @click=${() => ctx.cloneRepository?.()}>
198
- <sp-icon-download slot="icon"></sp-icon-download>
199
- Clone Git Repository
200
- </sp-action-button>`
201
- : nothing
202
- }
203
- </div>
276
+ ${renderEmptyState({
277
+ actions: platformSupportsClone()
278
+ ? [
279
+ {
280
+ icon: html`<sp-icon-download slot="icon"></sp-icon-download>`,
281
+ label: "Clone Git Repository",
282
+ run: () => ctx.cloneRepository?.(),
283
+ },
284
+ ]
285
+ : [],
286
+ message:
287
+ "Source control keeps every version of a project, so any change can be undone. " +
288
+ "Open a project to see its history.",
289
+ })}
204
290
  </div>`;
205
291
  }
206
- const status = S.ui.gitStatus;
207
- const branches = S.ui.gitBranches;
208
- const loading = S.ui.gitLoading;
292
+ const { branches, loading, status } = shell.git;
209
293
 
210
294
  // First paint kicks off the fetch. A refresh that already failed must NOT re-arm it here, or the
211
295
  // Render it triggers becomes the next render's reason to fetch again; the Refresh button and the
212
296
  // Poll timer are the ways back.
213
- if (!status && !loading && !S.ui.gitError) {
297
+ if (!status && !loading && !shell.git.error) {
214
298
  void refreshGitStatus();
215
299
  return html`<div class="git-panel">
216
300
  <div class="git-loading">Loading...</div>
@@ -219,42 +303,38 @@ export function renderGitPanel(
219
303
 
220
304
  if (status && !status.isRepo) {
221
305
  return html`<div class="git-panel git-panel-empty">
222
- <div class="git-empty-state">
223
- <p>This project is not yet a git repository.</p>
224
- <sp-action-button
225
- size="m"
226
- @click=${async () => {
227
- statusMessage("Initializing repository…");
228
- await getPlatform().gitInit();
229
- statusMessage("Repository initialized");
230
- await refreshGitStatus();
231
- }}
232
- ?disabled=${loading}
233
- >
234
- <sp-icon-add slot="icon"></sp-icon-add>
235
- Initialize Repository
236
- </sp-action-button>
237
- <sp-action-button
238
- size="m"
239
- @click=${() =>
240
- publishToGithub({
241
- projectName: projectState?.name || "my-project",
242
- })}
243
- ?disabled=${loading}
244
- >
245
- <sp-icon-share slot="icon"></sp-icon-share>
246
- Publish to GitHub
247
- </sp-action-button>
248
- </div>
306
+ ${renderEmptyState({
307
+ actions: [
308
+ {
309
+ disabled: Boolean(loading),
310
+ icon: html`<sp-icon-add slot="icon"></sp-icon-add>`,
311
+ label: "Initialize Repository",
312
+ run: () => {
313
+ void initRepository();
314
+ },
315
+ },
316
+ {
317
+ disabled: Boolean(loading),
318
+ icon: html`<sp-icon-share slot="icon"></sp-icon-share>`,
319
+ label: "Create GitHub repository",
320
+ run: () => {
321
+ void createGithubRepository({ projectName: projectState?.name || "my-project" });
322
+ },
323
+ },
324
+ ],
325
+ message:
326
+ "This project is not tracked by git yet. Start tracking it to keep a history " +
327
+ "of every change and to publish it anywhere.",
328
+ })}
249
329
  </div>`;
250
330
  }
251
331
 
252
332
  if (!_pollTimer) {
253
333
  _pollTimer = setInterval(() => {
254
- if (view.leftTab === "git" && !S.ui.gitLoading) {
334
+ if (shell.leftTab === "git" && !shell.git.loading) {
255
335
  void refreshGitStatus();
256
336
  }
257
- }, 30_000);
337
+ }, POLL_GIT);
258
338
  }
259
339
 
260
340
  const stagedFiles = status?.files?.filter((f: GitFileEntry) => f.staged) || [];
@@ -262,12 +342,11 @@ export function renderGitPanel(
262
342
  const totalChanges = status?.files?.length || 0;
263
343
 
264
344
  const doCommit = async () => {
265
- const tab = activeTab.value;
266
- const msg = tab?.session.ui.gitCommitMessage?.trim();
345
+ const msg = shell.git.commitMessage.trim();
267
346
  if (!msg) {
268
347
  return;
269
348
  }
270
- updateUi("gitCommitMessage", "");
349
+ shell.git.commitMessage = "";
271
350
  // Fold co-editing sessions into the backend's tree first so the commit never misses
272
351
  // Trailing keystrokes (the mirror is debounced).
273
352
  await flushAllCollab();
@@ -275,14 +354,13 @@ export function renderGitPanel(
275
354
  };
276
355
 
277
356
  const doCommitAndSync = async () => {
278
- const tab = activeTab.value;
279
- const msg = tab?.session.ui.gitCommitMessage?.trim();
357
+ const msg = shell.git.commitMessage.trim();
280
358
  if (!msg) {
281
359
  return;
282
360
  }
283
- updateUi("gitCommitMessage", "");
284
- updateUi("gitLoading", true);
285
- updateUi("gitError", null);
361
+ shell.git.commitMessage = "";
362
+ shell.git.loading = true;
363
+ shell.git.error = null;
286
364
  await flushAllCollab();
287
365
  const plat = getPlatform();
288
366
  try {
@@ -290,9 +368,8 @@ export function renderGitPanel(
290
368
  await plat.gitPush();
291
369
  await refreshGitStatus();
292
370
  } catch (error) {
293
- updateUi("gitError", errorMessage(error));
294
- updateUi("gitLoading", false);
295
- renderOnly("leftPanel");
371
+ shell.git.error = errorMessage(error);
372
+ shell.git.loading = false;
296
373
  }
297
374
  };
298
375
 
@@ -301,8 +378,8 @@ export function renderGitPanel(
301
378
  const syncLabel = isUpToDate
302
379
  ? "Up to date"
303
380
  : `${status?.ahead ? `${status.ahead} ahead` : ""}${status?.ahead && status?.behind ? ", " : ""}${status?.behind ? `${status.behind} behind` : ""}`;
304
- const lastUpdatedStr = _lastUpdated
305
- ? _lastUpdated.toLocaleTimeString([], {
381
+ const lastUpdatedStr = shell.git.lastUpdated
382
+ ? new Date(shell.git.lastUpdated).toLocaleTimeString([], {
306
383
  hour: "2-digit",
307
384
  minute: "2-digit",
308
385
  })
@@ -341,7 +418,9 @@ export function renderGitPanel(
341
418
  </sp-action-button>
342
419
  <sp-action-button
343
420
  title="Pull${status?.behind ? ` (${status.behind} behind)` : ""}"
344
- @click=${() => void doPull()}
421
+ @click=${() => {
422
+ void doPull();
423
+ }}
345
424
  ?disabled=${loading}
346
425
  >
347
426
  <sp-icon-arrow-down slot="icon" size="xs"></sp-icon-arrow-down>
@@ -374,13 +453,13 @@ export function renderGitPanel(
374
453
  <sp-action-button
375
454
  size="s"
376
455
  @click=${() =>
377
- publishToGithub({
456
+ createGithubRepository({
378
457
  projectName: projectState?.name || "my-project",
379
458
  })}
380
459
  ?disabled=${loading}
381
460
  >
382
461
  <sp-icon-share slot="icon"></sp-icon-share>
383
- Publish to GitHub
462
+ Create GitHub repository
384
463
  </sp-action-button>
385
464
  </div>
386
465
  `;
@@ -445,23 +524,22 @@ export function renderGitPanel(
445
524
 
446
525
  // ─── 3. Tabs: Local Changes / History ────────────────────────────────────
447
526
  const switchTab = (tab: string) => {
448
- _gitSubTab = tab;
449
- if (tab === "history" && !S.ui.gitLogEntries) {
527
+ shell.git.subTab = tab;
528
+ if (tab === "history" && !shell.git.logEntries) {
450
529
  void fetchGitLog();
451
530
  }
452
- renderOnly("leftPanel");
453
531
  };
454
532
 
455
533
  const tabsT = html`
456
534
  <div class="git-tabs">
457
535
  <button
458
- class="git-tab ${_gitSubTab === "changes" ? "active" : ""}"
536
+ class="git-tab ${shell.git.subTab === "changes" ? "active" : ""}"
459
537
  @click=${() => switchTab("changes")}
460
538
  >
461
539
  Local Changes${totalChanges > 0 ? ` (${totalChanges})` : ""}
462
540
  </button>
463
541
  <button
464
- class="git-tab ${_gitSubTab === "history" ? "active" : ""}"
542
+ class="git-tab ${shell.git.subTab === "history" ? "active" : ""}"
465
543
  @click=${() => switchTab("history")}
466
544
  >
467
545
  History
@@ -470,16 +548,21 @@ export function renderGitPanel(
470
548
  `;
471
549
 
472
550
  // ─── 4. Commit form ──────────────────────────────────────────────────────
551
+ // `navigator/panel:git/commit` is a HAND-STAMPED leaf: the panel host derives
552
+ // `navigator/panel:git`, but nothing derives the parts inside a panel body. Leaves are the one
553
+ // Category of region id that is authored, and they are counted for exactly that reason.
473
554
  const commitT = html`
474
- <div class="git-commit-area">
555
+ <div class="git-commit-area" data-jx-region="navigator/panel:git/commit">
475
556
  <label class="git-commit-label">Please write a commit message</label>
476
557
  <sp-textfield
477
558
  size="s"
478
559
  multiline
479
560
  class="git-commit-input"
480
561
  placeholder="Describe your changes"
481
- .value=${live(S.ui.gitCommitMessage || "")}
482
- @input=${(e: Event) => updateUi("gitCommitMessage", (e.target as HTMLInputElement).value)}
562
+ .value=${live(shell.git.commitMessage)}
563
+ @input=${(e: Event) => {
564
+ shell.git.commitMessage = (e.target as HTMLInputElement).value;
565
+ }}
483
566
  @keydown=${(e: KeyboardEvent) => {
484
567
  if ((e.ctrlKey || e.metaKey) && e.key === "Enter") {
485
568
  e.preventDefault();
@@ -545,35 +628,22 @@ export function renderGitPanel(
545
628
  }
546
629
 
547
630
  try {
548
- const plat = getPlatform();
549
- updateUi("gitLoading", true);
631
+ shell.git.loading = true;
550
632
 
551
- const [originalContent, currentContent] = await Promise.all([
552
- file.status === "A"
553
- ? Promise.resolve("")
554
- : plat.gitShow({ path: file.path, ref: "HEAD" }),
555
- plat.readFile(file.path),
556
- ]);
633
+ const diffState = await readGitDiff(file.path, file.status);
557
634
 
558
- const diffState = {
559
- currentContent,
560
- filePath: file.path,
561
- fileStatus: file.status,
562
- originalContent,
563
- };
564
-
565
- updateUi("gitDiffState", diffState);
635
+ shell.git.diffState = diffState;
566
636
 
567
637
  if (ctx?.setCanvasMode) {
568
638
  if (ctx.setGitDiffState) {
569
639
  ctx.setGitDiffState(diffState);
570
640
  }
571
- ctx.setCanvasMode("git-diff");
641
+ ctx.setCanvasMode(activeTab.value, "git-diff");
572
642
  }
573
643
  } catch (error) {
574
- updateUi("gitError", `Failed to load diff: ${errorMessage(error)}`);
644
+ shell.git.error = `Failed to load diff: ${errorMessage(error)}`;
575
645
  } finally {
576
- updateUi("gitLoading", false);
646
+ shell.git.loading = false;
577
647
  }
578
648
  };
579
649
 
@@ -733,18 +803,24 @@ export function renderGitPanel(
733
803
  `,
734
804
  )}
735
805
  `
736
- : html`<div class="git-empty">No changes</div>`
806
+ : renderEmptyState({
807
+ compact: true,
808
+ message: "Nothing to commit. Files you edit and save show up here.",
809
+ })
737
810
  }
738
811
  </div>
739
812
  `;
740
813
 
741
814
  // ─── 6. History tab content ──────────────────────────────────────────────
742
- const logEntries = S.ui.gitLogEntries || [];
815
+ const logEntries = shell.git.logEntries || [];
743
816
  const historyT = html`
744
817
  <div class="git-history">
745
818
  ${
746
819
  logEntries.length === 0
747
- ? html`<div class="git-empty">No history</div>`
820
+ ? renderEmptyState({
821
+ compact: true,
822
+ message: "No commits yet. Each commit you make is a version you can come back to.",
823
+ })
748
824
  : repeat(
749
825
  logEntries,
750
826
  (e: GitLogEntry) => e.hash,
@@ -753,7 +829,7 @@ export function renderGitPanel(
753
829
  <span class="git-history-hash">${entry.hash.slice(0, 7)}</span>
754
830
  <span class="git-history-message">${entry.message}</span>
755
831
  <span class="git-history-meta"
756
- >${entry.author} · ${_relativeDate(entry.date)}</span
832
+ >${entry.author} · ${relativeDate(entry.date)}</span
757
833
  >
758
834
  </div>
759
835
  `,
@@ -765,18 +841,25 @@ export function renderGitPanel(
765
841
  return html`
766
842
  <div class="git-panel">
767
843
  ${syncBarT} ${branchSelectorT} ${tabsT}
768
- ${_gitSubTab === "changes" ? html`${commitT}${changesT}` : historyT}
844
+ ${shell.git.subTab === "changes" ? html`${commitT}${changesT}` : historyT}
769
845
  ${loading ? html`<div class="git-loading">Loading...</div>` : nothing}
770
- ${S.ui.gitError ? html`<div class="git-error">${S.ui.gitError}</div>` : nothing}
846
+ ${shell.git.error ? html`<div class="git-error">${shell.git.error}</div>` : nothing}
771
847
  </div>
772
848
  `;
773
849
  }
774
850
 
775
- /** @param {string} iso */
776
- function _relativeDate(iso: string) {
851
+ /**
852
+ * A commit age, relative to {@link now}.
853
+ *
854
+ * Exported so it is testable at all: reading the wall clock inline meant "yesterday" and the
855
+ * locale-date fallback could only be asserted against an offset from the real present, and two
856
+ * captures minutes apart legitimately disagreed.
857
+ *
858
+ * @param {string} iso
859
+ */
860
+ export function relativeDate(iso: string) {
777
861
  const d = new Date(iso);
778
- const now = Date.now();
779
- const diff = now - d.getTime();
862
+ const diff = now() - d.getTime();
780
863
  const mins = Math.floor(diff / 60_000);
781
864
  if (mins < 1) {
782
865
  return "just now";
@@ -795,9 +878,137 @@ function _relativeDate(iso: string) {
795
878
  return d.toLocaleDateString();
796
879
  }
797
880
 
881
+ /**
882
+ * The `Source Control:` family — the four verbs that were only ever buttons.
883
+ *
884
+ * Every one of them was reachable from exactly one place: two from an empty state that disappears
885
+ * the moment the repository exists, one from a 20px icon with a `title` attribute, and the fourth —
886
+ * signing in to GitHub — from nowhere at all, because it only ever happened as a side effect of
887
+ * something else. That is why a failed sign-in had no Retry to name: there was no record to point
888
+ * at. Naming them here gives the palette, `__jxAutomation`, the assistant, the deploy checklist and
889
+ * every `notify` Retry the same four ids.
890
+ *
891
+ * `git.signInToGithub` is `application`-level and the other three are `project`-level, and the
892
+ * split is the credential's: a GitHub token is one per machine and is revoked in Preferences ›
893
+ * Accounts, while a branch, a remote and a push belong to one repository.
894
+ */
895
+ export function sourceControlCommands(): AnyCommand[] {
896
+ return [
897
+ {
898
+ category: "Source Control",
899
+ id: "git.init",
900
+ level: "project",
901
+ menus: ["commandbar/overflow", "palette"],
902
+ group: "7_scm",
903
+ requires: "an open project that git is not already tracking",
904
+ when: (ctx) => ctx.project.open,
905
+ // `when` already asked about the project; an `enablement` that re-asks is the same rule
906
+ // Written twice, and the two places drift.
907
+ enablement: (ctx) => !ctx.project.isRepo,
908
+ aiTool: {
909
+ description:
910
+ "Run git init in the project root so the project has a history and can be pushed.",
911
+ name: "init_repository",
912
+ },
913
+ run: async () => {
914
+ await initRepository();
915
+ },
916
+ title: "Initialize Repository",
917
+ },
918
+ {
919
+ category: "Source Control",
920
+ id: "git.createGithubRepository",
921
+ level: "project",
922
+ menus: ["commandbar/overflow", "palette"],
923
+ group: "7_scm",
924
+ /* THE STRICTEST OF THE THREE, because it is the one that leaves something behind on a
925
+ server. It declared no `enablement` at all while `git.push` — which does strictly less —
926
+ required a tracked repository, so on an untracked project Push was correctly disabled and
927
+ this was fully live from the palette, the overflow, the deploy checklist and the
928
+ `create_github_repository` AI tool. Its flow creates the repository on GitHub BEFORE it
929
+ touches the local one, so `gitAddRemote` then throws and it reports "The repository was
930
+ created, but the remote could not be added." The refused verb leaves nothing behind; the
931
+ unrefused one leaves an empty repository on the user's account. */
932
+ requires: "a project tracked by git",
933
+ when: (ctx) => ctx.project.open,
934
+ enablement: (ctx) => ctx.project.isRepo,
935
+ aiTool: {
936
+ description:
937
+ "Create a new GitHub repository for this project, add it as the origin remote, and push. " +
938
+ "This creates a repository; it does not deploy a site — that is publish.setUp.",
939
+ name: "create_github_repository",
940
+ },
941
+ run: async () => {
942
+ await createGithubRepository({ projectName: projectState?.name || "my-project" });
943
+ },
944
+ title: "Create GitHub Repository",
945
+ },
946
+ {
947
+ category: "Source Control",
948
+ id: "git.push",
949
+ level: "project",
950
+ menus: ["commandbar/overflow", "palette"],
951
+ group: "7_scm",
952
+ requires: "a project tracked by git",
953
+ when: (ctx) => ctx.project.open,
954
+ enablement: (ctx) => ctx.project.isRepo,
955
+ aiTool: {
956
+ description: "Push the current branch to its remote.",
957
+ name: "git_push",
958
+ },
959
+ run: async () => {
960
+ await pushCurrentBranch();
961
+ },
962
+ title: "Push",
963
+ },
964
+ {
965
+ category: "Source Control",
966
+ id: "git.signInToGithub",
967
+ level: "application",
968
+ menus: ["palette"],
969
+ group: "7_scm",
970
+ run: async () => {
971
+ await signInToGithub();
972
+ },
973
+ title: "Sign In to GitHub",
974
+ },
975
+ ];
976
+ }
977
+
978
+ /** Register the `Source Control:` family. */
979
+ export function registerSourceControlCommands(registry: CommandRegistry): void {
980
+ registry.registerAll(sourceControlCommands());
981
+ }
982
+
983
+ /** Stop the background refresh. Called on unmount and whenever a different project is opened. */
798
984
  export function cleanupGitPanel() {
799
985
  if (_pollTimer) {
800
986
  clearInterval(_pollTimer);
801
987
  _pollTimer = null;
802
988
  }
803
989
  }
990
+
991
+ /**
992
+ * Contribute the Source Control panel.
993
+ *
994
+ * `level: "project"` — a branch, a working tree and a commit belong to the repository, not to
995
+ * whichever document happens to be focused. That is why the badge below reads `ctx.git.dirtyCount`
996
+ * (sourced from the hoisted `shell.git` record) and why this panel's render ignores `ctx.doc`
997
+ * entirely: the count used to come from `activeTab.session.ui.gitStatus`, so it vanished when the
998
+ * last tab closed and two tabs could disagree about the branch.
999
+ */
1000
+ export function registerGitPanel(): void {
1001
+ registerPanel({
1002
+ id: "git",
1003
+ title: "Source Control",
1004
+ level: "project",
1005
+ dock: "navigator",
1006
+ // A KEY into `activity-bar.ts`'s `tabIcon()` map, not a tag: this one resolves to
1007
+ // `gitBranchIcon`, a hand-drawn inline SVG, because the workflow set has no Git family.
1008
+ icon: "sp-icon-git-branch",
1009
+ badge: (ctx) => ctx.git.dirtyCount || null,
1010
+ // Through `deps`, not the local binding: `studio.ts` owns the wiring (the clone action and the
1011
+ // Diff-state setter come from the bootstrap), and the Navigator has injected it all along.
1012
+ render: (ctx) => ctx.deps.renderGitPanel(ctx.deps),
1013
+ });
1014
+ }