@jxsuite/studio 10.0.0 → 10.2.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 (244) hide show
  1. package/dist/chunks/{2020-j56903f1.js → 2020-bx702wqa.js} +3 -3
  2. package/dist/chunks/adapter-pp011m5x.js +4 -0
  3. package/dist/chunks/add-new-flow-ecqmzz76.js +4 -0
  4. package/dist/chunks/cf-account-picker-c07znkqt.js +4 -0
  5. package/dist/chunks/cf-settings-0v2bmqy0.js +4 -0
  6. package/dist/chunks/components-yn0dn2pq.js +4 -0
  7. package/dist/chunks/content-source-g0j41yy6.js +4 -0
  8. package/dist/chunks/{convert-to-repeater-fhdwyg14.js → convert-to-repeater-sbjk7tgn.js} +3 -3
  9. package/dist/chunks/editor-xz02jeh1.js +4 -0
  10. package/dist/chunks/entry-commands-kmt02crp.js +4 -0
  11. package/dist/chunks/extension-sections-0rprf12d.js +4 -0
  12. package/dist/chunks/file-ops-mexj5qed.js +4 -0
  13. package/dist/chunks/files-b2tcppdd.js +4 -0
  14. package/dist/chunks/format-host-btsevv0c.js +4 -0
  15. package/dist/chunks/fs-events-r0apejva.js +4 -0
  16. package/dist/chunks/grid-open-n2a8xkht.js +4 -0
  17. package/dist/chunks/{grid-view-ka55z38f.js → grid-view-tghcwrrt.js} +12 -12
  18. package/dist/chunks/iframe-host-gx0ypyh3.js +4 -0
  19. package/dist/chunks/index-bs4k9tr6.js +4 -0
  20. package/dist/chunks/{index-qb98xhse.js → index-vr006kbt.js} +3 -3
  21. package/dist/chunks/{javascript-ces13wny.js → javascript-z1g3s9fd.js} +2 -2
  22. package/dist/chunks/{jsonMode-6md16d4v.js → jsonMode-7szwg8c4.js} +5 -5
  23. package/dist/chunks/jx-validate-ymf10teh.js +4 -0
  24. package/dist/chunks/layers-p9nfya38.js +4 -0
  25. package/dist/chunks/layers-panel-tt1hzhhf.js +4 -0
  26. package/dist/chunks/lit-html-x18n2sfz.js +4 -0
  27. package/dist/chunks/media-library-0n45gv8r.js +4 -0
  28. package/dist/chunks/media-picker-s09at95z.js +4 -0
  29. package/dist/chunks/{monaco-binding-p8j80s4w.js → monaco-binding-v42w9c1v.js} +3 -3
  30. package/dist/chunks/monaco-buffer-een2ea0v.js +4 -0
  31. package/dist/chunks/{monaco-setup-wasktzfg.js → monaco-setup-7bffxd6v.js} +3 -3
  32. package/dist/chunks/notify-qwb8radt.js +4 -0
  33. package/dist/chunks/parse-v77xtwv7.js +4 -0
  34. package/dist/chunks/platform-8jnez0wr.js +4 -0
  35. package/dist/chunks/publish-panel-aysnj611.js +4 -0
  36. package/dist/chunks/{publish-quick-vr695j26.js → publish-quick-kpbqs9mz.js} +3 -3
  37. package/dist/chunks/publish-workspace-nd0a8q99.js +4 -0
  38. package/dist/chunks/quick-search-vg0jkhk2.js +4 -0
  39. package/dist/chunks/references-a7v6a0e2.js +4 -0
  40. package/dist/chunks/right-panel-cvk1vxx5.js +4 -0
  41. package/dist/chunks/source-control-fsqzc6he.js +4 -0
  42. package/dist/chunks/studio-05jfwbmf.js +5 -0
  43. package/dist/chunks/{studio-hy4ye0dx.js → studio-0q5jwddh.js} +7 -7
  44. package/dist/chunks/{studio-jttyqrmh.js → studio-0w2dr0q8.js} +4 -4
  45. package/dist/chunks/{studio-xkxetm65.js → studio-1tzpds97.js} +5 -5
  46. package/dist/chunks/{studio-m48v13s3.js → studio-216b27sv.js} +3 -3
  47. package/dist/chunks/studio-2ejhqns4.js +5 -0
  48. package/dist/chunks/studio-2r90cts3.js +16 -0
  49. package/dist/chunks/{studio-h310mbqm.js → studio-399a25za.js} +4 -4
  50. package/dist/chunks/studio-399xknkb.js +6 -0
  51. package/dist/chunks/{studio-wxhy1yh7.js → studio-43y04qck.js} +4 -4
  52. package/dist/chunks/{studio-06j4en5h.js → studio-4g8yb6jn.js} +2 -2
  53. package/dist/chunks/{studio-jw0q5j6k.js → studio-4jhw38b2.js} +4 -4
  54. package/dist/chunks/{studio-3v5cagy9.js → studio-4ncmwqw5.js} +4 -4
  55. package/dist/chunks/studio-5atwjgkj.js +7 -0
  56. package/dist/chunks/{studio-gr36vnte.js → studio-5frqk7c0.js} +4 -4
  57. package/dist/chunks/{studio-9dz76yqc.js → studio-646nn2ys.js} +5 -5
  58. package/dist/chunks/{studio-tj2s0tks.js → studio-6cwj6xrx.js} +4 -4
  59. package/dist/chunks/studio-6hzdjfv0.js +5 -0
  60. package/dist/chunks/{studio-hcsba201.js → studio-71m92v9s.js} +7 -7
  61. package/dist/chunks/{studio-3c8pp7pb.js → studio-75kcdbsk.js} +4 -4
  62. package/dist/chunks/{studio-gwrhcrss.js → studio-78pywnp2.js} +5 -5
  63. package/dist/chunks/{studio-9cxmp61c.js → studio-86r8hp7r.js} +5 -5
  64. package/dist/chunks/studio-886t6ztg.js +5 -0
  65. package/dist/chunks/studio-8wnvtr4z.js +5 -0
  66. package/dist/chunks/studio-8z66jk4z.js +5 -0
  67. package/dist/chunks/{studio-ajw4k8hp.js → studio-9rbe2xpw.js} +3 -3
  68. package/dist/chunks/{studio-y26hx9re.js → studio-awxwjawq.js} +4 -4
  69. package/dist/chunks/studio-b2qpt0xy.js +14 -0
  70. package/dist/chunks/{studio-dx73md4n.js → studio-bvs9kpst.js} +7 -7
  71. package/dist/chunks/studio-c71806xz.js +7 -0
  72. package/dist/chunks/{studio-7dzyz9n8.js → studio-cbwks69x.js} +144 -144
  73. package/dist/chunks/studio-cg1bfyhf.js +9 -0
  74. package/dist/chunks/studio-cg710sk9.js +5 -0
  75. package/dist/chunks/studio-cw0wb9qk.js +7 -0
  76. package/dist/chunks/studio-cyheys7k.js +5 -0
  77. package/dist/chunks/studio-ea5m9kg5.js +5 -0
  78. package/dist/chunks/{studio-ry81j4q1.js → studio-ea74s8c1.js} +4 -4
  79. package/dist/chunks/{studio-xdpz9rpe.js → studio-efdxjwyp.js} +34 -34
  80. package/dist/chunks/{studio-agyvzfm4.js → studio-egmw2837.js} +4 -4
  81. package/dist/chunks/{studio-5tzgq6fx.js → studio-er5f383a.js} +4 -4
  82. package/dist/chunks/studio-f746039f.js +5 -0
  83. package/dist/chunks/studio-fmdymd9v.js +5 -0
  84. package/dist/chunks/studio-gcm12z00.js +6 -0
  85. package/dist/chunks/{studio-vswcpzk7.js → studio-gj98m8gy.js} +5 -5
  86. package/dist/chunks/{studio-yg3djpnm.js → studio-hasbgjah.js} +4 -4
  87. package/dist/chunks/studio-hhp4x8xf.js +6 -0
  88. package/dist/chunks/{studio-3w35qvay.js → studio-hz5n7x27.js} +4 -4
  89. package/dist/chunks/{studio-7xag4na8.js → studio-j3d2fjqz.js} +4 -4
  90. package/dist/chunks/studio-jeraxdgv.js +7 -0
  91. package/dist/chunks/studio-jvgek9xt.js +5 -0
  92. package/dist/chunks/studio-jws1a87n.js +5 -0
  93. package/dist/chunks/studio-k2sz13zg.js +5 -0
  94. package/dist/chunks/studio-k3m4jkdk.js +5 -0
  95. package/dist/chunks/{studio-r17asgs0.js → studio-kg1abnz0.js} +4 -4
  96. package/dist/chunks/{studio-s0fg2wav.js → studio-kg5ya5r2.js} +4 -4
  97. package/dist/chunks/studio-khwbgrtg.js +5 -0
  98. package/dist/chunks/studio-mx6wzh25.js +5 -0
  99. package/dist/chunks/studio-mxs3ar9t.js +5 -0
  100. package/dist/chunks/studio-nm4szey4.js +5 -0
  101. package/dist/chunks/studio-nqxqf8p2.js +4 -0
  102. package/dist/chunks/{studio-h5tpz5ke.js → studio-pjtntksy.js} +4 -4
  103. package/dist/chunks/studio-qh8berdz.js +5 -0
  104. package/dist/chunks/studio-qn7z4krm.js +5 -0
  105. package/dist/chunks/studio-qyed0ah6.js +5 -0
  106. package/dist/chunks/{studio-x97ay0v4.js → studio-r4bcdwhq.js} +4 -4
  107. package/dist/chunks/{studio-wxdcd42g.js → studio-r7pf0rkv.js} +6 -6
  108. package/dist/chunks/studio-rtfnhrdg.js +5 -0
  109. package/dist/chunks/{studio-fy7jzj9j.js → studio-rzqaz21g.js} +4 -4
  110. package/dist/chunks/studio-s5tzw84t.js +5 -0
  111. package/dist/chunks/{studio-1e8dw2w2.js → studio-sevbc3xv.js} +4 -4
  112. package/dist/chunks/{studio-ewarnzm4.js → studio-sxdb9h3q.js} +6 -6
  113. package/dist/chunks/studio-t51y8v1d.js +5 -0
  114. package/dist/chunks/{studio-k3bcy02e.js → studio-t98vq4r6.js} +4 -4
  115. package/dist/chunks/studio-th7z3fg7.js +5 -0
  116. package/dist/chunks/studio-tsxpqvh0.js +6 -0
  117. package/dist/chunks/{studio-396srjbk.js → studio-v1yascv2.js} +4 -4
  118. package/dist/chunks/{studio-ekgtrndg.js → studio-wc6za3zs.js} +4 -4
  119. package/dist/chunks/{studio-ndskgqef.js → studio-wxq6v5sv.js} +4 -4
  120. package/dist/chunks/{studio-4kzbrr5t.js → studio-y60sts62.js} +3 -3
  121. package/dist/chunks/studio-ybnwymxw.js +5 -0
  122. package/dist/chunks/studio-zndrqawb.js +5 -0
  123. package/dist/chunks/studio-zwaxn5s2.js +5 -0
  124. package/dist/chunks/{tsMode-h6tkz0ea.js → tsMode-f54j66ym.js} +5 -5
  125. package/dist/chunks/workspace-home-x1787wpv.js +4 -0
  126. package/dist/chunks/workspace-zpe1np43.js +4 -0
  127. package/dist/chunks/{ws-client-e951my95.js → ws-client-e8kj684q.js} +3 -3
  128. package/dist/manifest.json +76 -75
  129. package/dist/studio.js +83 -81
  130. package/package.json +4 -4
  131. package/src/browse/library-model.ts +56 -23
  132. package/src/commands/context.ts +1 -0
  133. package/src/editor/shortcuts.ts +3 -0
  134. package/src/panels/data-grid.ts +0 -4
  135. package/src/panels/settings-menu.ts +3 -3
  136. package/src/panels/settings-pane.ts +42 -56
  137. package/src/platform-errors.ts +17 -1
  138. package/src/platforms/cloud.ts +14 -0
  139. package/src/publish/publish-changes.ts +136 -4
  140. package/src/services/ai-import-tools.ts +6 -3
  141. package/src/services/ai-models.ts +65 -11
  142. package/src/services/ai-settings.ts +45 -5
  143. package/src/services/document-assistant.ts +5 -2
  144. package/src/services/settings/definitions.ts +19 -0
  145. package/src/settings/contributed-section.ts +16 -7
  146. package/src/settings/css-vars-editor.ts +4 -4
  147. package/src/settings/general-settings.ts +1 -4
  148. package/src/settings/preferences-accounts.ts +2 -2
  149. package/src/settings/preferences-dialog.ts +4 -2
  150. package/src/settings/project-sections.ts +1 -1
  151. package/src/settings/section-registry.ts +5 -4
  152. package/src/settings/settings-document.ts +33 -24
  153. package/src/settings/settings-workspace.ts +205 -0
  154. package/src/shell.ts +31 -6
  155. package/src/surfaces/ai-credentials-form.json +59 -5
  156. package/src/surfaces/ai-credentials-form.ts +15 -2
  157. package/src/surfaces/commandbar.ts +7 -3
  158. package/src/surfaces/settings-contributed.json +4 -4
  159. package/src/surfaces/settings-contributed.ts +2 -2
  160. package/src/surfaces/settings-pane.json +262 -180
  161. package/src/surfaces/settings-pane.ts +63 -14
  162. package/src/types.ts +14 -0
  163. package/src/ui/ai-credentials-form.ts +128 -11
  164. package/src/ui/regions.ts +5 -5
  165. package/src/workspace/tab-workspace.ts +3 -2
  166. package/src/workspace/workspace-home.ts +18 -4
  167. package/src/workspace/workspace-ids.ts +40 -7
  168. package/dist/chunks/adapter-wc5zvg9a.js +0 -4
  169. package/dist/chunks/add-new-flow-snx1s4b0.js +0 -4
  170. package/dist/chunks/cf-account-picker-xyqw0jex.js +0 -4
  171. package/dist/chunks/cf-settings-hp1pjm0n.js +0 -4
  172. package/dist/chunks/components-nkwphrx3.js +0 -4
  173. package/dist/chunks/content-source-6a253wmk.js +0 -4
  174. package/dist/chunks/editor-wqrc68ca.js +0 -4
  175. package/dist/chunks/entry-commands-nef0wrmp.js +0 -4
  176. package/dist/chunks/extension-sections-8ez2rrk8.js +0 -4
  177. package/dist/chunks/file-ops-y3w71k1n.js +0 -4
  178. package/dist/chunks/files-de90apn9.js +0 -4
  179. package/dist/chunks/format-host-zfjz4rzm.js +0 -4
  180. package/dist/chunks/fs-events-77c0mr0x.js +0 -4
  181. package/dist/chunks/grid-open-4n4fjfpf.js +0 -4
  182. package/dist/chunks/iframe-host-na2mywz4.js +0 -4
  183. package/dist/chunks/index-ewmgdb7c.js +0 -4
  184. package/dist/chunks/jx-validate-b309c020.js +0 -4
  185. package/dist/chunks/layers-8rn8y6qv.js +0 -4
  186. package/dist/chunks/layers-panel-eyah7h9z.js +0 -4
  187. package/dist/chunks/lit-html-wyqw04y2.js +0 -4
  188. package/dist/chunks/media-library-gs3np47y.js +0 -4
  189. package/dist/chunks/media-picker-tt7dk4tb.js +0 -4
  190. package/dist/chunks/monaco-buffer-wsf6cmnq.js +0 -4
  191. package/dist/chunks/notify-qwst108r.js +0 -4
  192. package/dist/chunks/parse-n8dfxnqf.js +0 -4
  193. package/dist/chunks/platform-c16mqvmp.js +0 -4
  194. package/dist/chunks/publish-panel-d34bhvwj.js +0 -4
  195. package/dist/chunks/publish-workspace-2yr60mt4.js +0 -4
  196. package/dist/chunks/quick-search-e6xsdv0d.js +0 -4
  197. package/dist/chunks/references-gsj6trdv.js +0 -4
  198. package/dist/chunks/right-panel-33jv1knc.js +0 -4
  199. package/dist/chunks/source-control-bj9bejjv.js +0 -4
  200. package/dist/chunks/studio-03mqh8ax.js +0 -5
  201. package/dist/chunks/studio-0ydxv2mr.js +0 -7
  202. package/dist/chunks/studio-2hsqbq7b.js +0 -7
  203. package/dist/chunks/studio-2j86y34y.js +0 -6
  204. package/dist/chunks/studio-2ks6ka4g.js +0 -5
  205. package/dist/chunks/studio-34jrz12k.js +0 -5
  206. package/dist/chunks/studio-35vrtpdq.js +0 -5
  207. package/dist/chunks/studio-38g0jdz7.js +0 -5
  208. package/dist/chunks/studio-3zge48z4.js +0 -7
  209. package/dist/chunks/studio-45gyxpcq.js +0 -5
  210. package/dist/chunks/studio-5wzdy1jc.js +0 -16
  211. package/dist/chunks/studio-732mw0g8.js +0 -5
  212. package/dist/chunks/studio-771k2n12.js +0 -5
  213. package/dist/chunks/studio-88bmt9rh.js +0 -5
  214. package/dist/chunks/studio-c1pxszat.js +0 -5
  215. package/dist/chunks/studio-cpkamjdn.js +0 -14
  216. package/dist/chunks/studio-d038kqbs.js +0 -5
  217. package/dist/chunks/studio-d83m0wyq.js +0 -5
  218. package/dist/chunks/studio-dbxv7w4s.js +0 -5
  219. package/dist/chunks/studio-de1s2x9v.js +0 -5
  220. package/dist/chunks/studio-epenqmvk.js +0 -7
  221. package/dist/chunks/studio-et2q4s8n.js +0 -5
  222. package/dist/chunks/studio-h7weqf2q.js +0 -5
  223. package/dist/chunks/studio-hwejq03c.js +0 -5
  224. package/dist/chunks/studio-k3a8rp3d.js +0 -5
  225. package/dist/chunks/studio-p1n2xjda.js +0 -5
  226. package/dist/chunks/studio-pgm83a2f.js +0 -6
  227. package/dist/chunks/studio-pqqq6v85.js +0 -5
  228. package/dist/chunks/studio-psh8mwnw.js +0 -4
  229. package/dist/chunks/studio-rzg8hnkf.js +0 -5
  230. package/dist/chunks/studio-tyw7tk0x.js +0 -5
  231. package/dist/chunks/studio-v9ywz2se.js +0 -6
  232. package/dist/chunks/studio-vzgmyt0v.js +0 -5
  233. package/dist/chunks/studio-x08a3m0a.js +0 -5
  234. package/dist/chunks/studio-x3mjr5cc.js +0 -5
  235. package/dist/chunks/studio-xh683qzy.js +0 -5
  236. package/dist/chunks/studio-yx8hzqja.js +0 -5
  237. package/dist/chunks/studio-zg16eeq1.js +0 -5
  238. package/dist/chunks/studio-zgx7svjd.js +0 -9
  239. package/dist/chunks/studio-zkq3r91p.js +0 -5
  240. package/dist/chunks/workspace-home-zkkrxq3r.js +0 -4
  241. package/dist/chunks/workspace-tdqqjqaa.js +0 -4
  242. package/src/settings/settings-dialog.ts +0 -192
  243. package/src/surfaces/settings-dialog.json +0 -51
  244. package/src/surfaces/settings-dialog.ts +0 -169
@@ -1,11 +1,11 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * The Project Settings dialog's two columns as a mounted document: the mount seam, and nothing
4
- * else.
3
+ * The Project Settings workspace's home as a mounted document: its heading and Back control over
4
+ * the two columns. The mount seam, and nothing else.
5
5
  *
6
6
  * `panels/settings-pane.ts` is the flow: which sections are registered, which group lists each,
7
7
  * which one is current, what a nav click does, and which renderer draws the body. This is the
8
- * surface it draws into, and `surfaces/settings-dialog.ts` is the dialog it sits in.
8
+ * surface it draws into, and `settings/settings-workspace.ts` mounts it into a pane's stage.
9
9
  *
10
10
  * **One island, and the sections are why.** A settings section is handed a container and renders
11
11
  * into it (some of them still with lit, some by mounting a document of their own) so the body
@@ -51,8 +51,14 @@ export interface SettingsPaneMovedRow {
51
51
  linkLabel: string;
52
52
  }
53
53
 
54
- /** What the dialog's columns say right now. */
54
+ /** What the home says right now. */
55
55
  export interface SettingsPaneValues {
56
+ /** `pane.<id>/settings`: the pane this home stands in names it, as Publish's does. */
57
+ region: string;
58
+ /** The heading, which is also the region's name: the workspace's title. */
59
+ heading: string;
60
+ /** The Back control's text, naming where it goes ("Back to Design"). */
61
+ backLabel: string;
56
62
  /** The current section's key. */
57
63
  active: string;
58
64
  /** The current section's row id, so the body is named by the row that chose it. */
@@ -80,6 +86,8 @@ export interface SettingsPaneActions {
80
86
  selectSection: (key: string) => void;
81
87
  /** A Moved footnote's button was activated, naming the workspace it goes to. */
82
88
  openMoved: (workspace: string) => void;
89
+ /** The Back control was activated: leave Settings for the workspace it was opened from. */
90
+ back: () => void;
83
91
  }
84
92
 
85
93
  export interface SettingsPaneHandle {
@@ -95,15 +103,24 @@ export interface SettingsPaneHandle {
95
103
  body: () => HTMLElement | null;
96
104
  /** Settles once the document is mounted, or the surface was disposed first. */
97
105
  ready: Promise<void>;
106
+ /**
107
+ * Whether the document still stands in the container it was mounted into: true while the mount is
108
+ * in flight, false once disposed or once something else replaced the container's children.
109
+ */
110
+ attached: () => boolean;
98
111
  /**
99
112
  * Put the keyboard on the current section's row.
100
113
  *
101
- * `showModal()` focuses the dialog's first focusable descendant, which is always the first
102
- * group's tab stop, so a dialog opened on Advanced › Raw JSON would put the ring on Overview
103
- * while the selection was three rows down a different list. Read off `aria-selected`, which the
104
- * strip writes, because that attribute is the one record of which row is current.
114
+ * Opening Settings on Advanced › Raw JSON has to put the ring on Raw JSON, not on the first
115
+ * group's tab stop, or the selection is three rows down a different list from the keyboard. The
116
+ * row is found by the section key the document was handed (`active`), not by `aria-selected`: the
117
+ * strip writes that a turn after the row is created, and a home opened by a command asks for the
118
+ * keyboard as soon as it has drawn. A row is focusable once its strip has given it a tab stop, so
119
+ * a row whose strip has not synced yet is focused when the strip says it is ready.
120
+ *
121
+ * @returns {Promise<void>} Settles once the keyboard has moved, or at once when there is no row
105
122
  */
106
- focusActive: () => void;
123
+ focusActive: () => Promise<void>;
107
124
  /** Take the document down. Idempotent. */
108
125
  dispose: () => void;
109
126
  }
@@ -125,12 +142,12 @@ function failureStateOf(failure: string): string {
125
142
  }
126
143
 
127
144
  /**
128
- * Mount the columns into `container`, which is the settings dialog's island.
145
+ * Mount the home into `container`, a pane's stage.
129
146
  *
130
147
  * The container is cleared first, lit's render marker included, so a host handed back after some
131
148
  * other renderer used it cannot make the next lit render throw on a part whose nodes have gone.
132
149
  *
133
- * @param {HTMLElement} container The dialog's pane slot
150
+ * @param {HTMLElement} container The pane's stage
134
151
  * @param {SettingsPaneValues} values What to say on the first paint
135
152
  * @param {SettingsPaneActions} actions What a row and a Moved button do. Read once.
136
153
  * @returns {SettingsPaneHandle}
@@ -171,6 +188,13 @@ export function mountSettingsPaneSurface(
171
188
  });
172
189
 
173
190
  return {
191
+ attached: () => {
192
+ if (disposed) {
193
+ return false;
194
+ }
195
+ const root = mounted?.root;
196
+ return root === undefined ? true : root instanceof HTMLElement && container.contains(root);
197
+ },
174
198
  body: () => body,
175
199
  dispose() {
176
200
  disposed = true;
@@ -178,10 +202,32 @@ export function mountSettingsPaneSurface(
178
202
  mounted = null;
179
203
  body = null;
180
204
  },
181
- focusActive() {
205
+ async focusActive() {
182
206
  const root = mounted?.root;
183
- if (root instanceof HTMLElement) {
184
- root.querySelector<HTMLElement>('[part="nav-item"][aria-selected="true"]')?.focus();
207
+ const row =
208
+ root instanceof HTMLElement
209
+ ? [...root.querySelectorAll<HTMLElement>('[part="nav-item"]')].find(
210
+ (candidate) => candidate.dataset["section"] === scope.active,
211
+ )
212
+ : undefined;
213
+ if (!row) {
214
+ return;
215
+ }
216
+ const strip = row.closest("jx-tabs");
217
+ // `aria-selected` is what the strip writes when it syncs, and the sync is what gives a tab stop.
218
+ if (strip && row.getAttribute("aria-selected") !== "true") {
219
+ await new Promise<void>((resolve) => {
220
+ strip.addEventListener(
221
+ "jx-ready",
222
+ () => {
223
+ resolve();
224
+ },
225
+ { once: true },
226
+ );
227
+ });
228
+ }
229
+ if (!disposed) {
230
+ row.focus();
185
231
  }
186
232
  },
187
233
  ready,
@@ -189,6 +235,9 @@ export function mountSettingsPaneSurface(
189
235
  /* Field by field, so an unchanged one is inert, and `active` LAST, because it is what each
190
236
  strip's selection is compared against: writing it before the rows it names have arrived
191
237
  would make `jx-tabs` sync a selection onto a column that does not hold that tab yet. */
238
+ scope.region = next.region;
239
+ scope.heading = next.heading;
240
+ scope.backLabel = next.backLabel;
192
241
  scope.groups = next.groups;
193
242
  scope.moved = next.moved;
194
243
  scope.activeTabId = next.activeTabId;
package/src/types.ts CHANGED
@@ -34,6 +34,7 @@ import type {
34
34
  ExtensionsInfo,
35
35
  FsEvent,
36
36
  GitBranchesResult,
37
+ GitCommitBranchResult,
37
38
  GitLogEntry,
38
39
  GitStatusResult,
39
40
  ImportProgressEvent,
@@ -88,6 +89,7 @@ export type {
88
89
  ExtensionsInfo,
89
90
  FsEvent,
90
91
  GitBranchesResult,
92
+ GitCommitBranchResult,
91
93
  GitFileStatus,
92
94
  GitLogEntry,
93
95
  GitStatusResult,
@@ -580,6 +582,18 @@ export interface StudioPlatform {
580
582
  gitFetch: () => Promise<void>;
581
583
  gitCheckout: (branch: string) => Promise<void>;
582
584
  gitCreateBranch: (name: string) => Promise<void>;
585
+ /**
586
+ * Commit the change set as a NEW branch, parented on the commit this session's edits were made
587
+ * against, and take the committed files out of this session. Rejects with code `branch_exists`
588
+ * when the name is taken (the ref is created, never moved).
589
+ *
590
+ * The way out on a host where a branch is a session (`gitHosted`): a commit to the session's own
591
+ * branch that the remote conflicts with can still be published, as a branch of its own the pull
592
+ * request then reconciles, and a change set on a branch whose every commit is live can be sent
593
+ * for review without landing there first. An ordinary working tree has no need of it: there a
594
+ * branch is created and checked out with the changes still in it (studio.md §22.8).
595
+ */
596
+ gitCommitBranch?: (opts: { branch: string; message: string }) => Promise<GitCommitBranchResult>;
583
597
  gitDiff: (path?: string) => Promise<string>;
584
598
  gitShow: (opts: { path: string; ref?: string }) => Promise<string>;
585
599
  gitDiscard: (files: string[]) => Promise<void>;
@@ -1,6 +1,7 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Ai-credentials-form.ts — reusable AI provider credentials form (key / model / endpoint).
3
+ * Ai-credentials-form.ts — reusable AI provider credentials form (provider / key / model /
4
+ * endpoint).
4
5
  *
5
6
  * Embedded by every host that needs a provider configured — **Preferences › Assistant** (⌘,) and
6
7
  * the New Project modal's Import/Agent gates. All draft/model-list state lives inside the closure,
@@ -23,9 +24,16 @@
23
24
  * @docs studio/ai
24
25
  */
25
26
 
26
- import { fetchAvailableModels, proxyModelsErrorMessage } from "../services/ai-models";
27
+ import type { AiProviderFamily } from "@jxsuite/ai/streaming-client";
28
+ import { isString } from "@jxsuite/schema/guards";
29
+ import {
30
+ backendProviders,
31
+ fetchAvailableModels,
32
+ proxyModelsErrorMessage,
33
+ } from "../services/ai-models";
27
34
  import {
28
35
  aiProviderDiffers,
36
+ getAiProvider,
29
37
  getBaseUrl,
30
38
  getOpenAiKey,
31
39
  saveAiProvider,
@@ -38,10 +46,49 @@ import { createAiCredentialsSurface } from "../surfaces/ai-credentials-form";
38
46
  import type { AiCredentialsSurface, AiCredentialsView } from "../surfaces/ai-credentials-form";
39
47
  import type { SurfaceOption } from "./surface";
40
48
 
41
- /** The blurb a host that says nothing gets. */
49
+ /**
50
+ * The blurb a host that says nothing gets, where the backend reaches the OpenAI-compatible family
51
+ * alone.
52
+ */
42
53
  const DEFAULT_INTRO =
43
54
  "Any OpenAI-compatible key works. Stored locally in this browser; sent only to the Studio proxy (never to a third party except your chosen endpoint).";
44
55
 
56
+ /** The same blurb where the backend also reaches Anthropic, so the picker is drawn. */
57
+ const PROVIDER_INTRO =
58
+ "An Anthropic key for Claude, or any OpenAI-compatible key. Stored locally in this browser; sent only to the Studio proxy (never to a third party except your chosen provider).";
59
+
60
+ /** The families the picker offers, in the order it lists them. */
61
+ const PROVIDER_OPTIONS: SurfaceOption[] = [
62
+ { label: "OpenAI-compatible", value: "openai-compat" },
63
+ { label: "Anthropic (Claude)", value: "anthropic" },
64
+ ];
65
+
66
+ /** What each field says while it is empty, per family. */
67
+ const PLACEHOLDERS: Readonly<
68
+ Record<AiProviderFamily, { key: string; model: string; endpoint: string }>
69
+ > = {
70
+ anthropic: {
71
+ endpoint: "Endpoint (optional, defaults to https://api.anthropic.com)",
72
+ key: "sk-ant-…",
73
+ model: "Model ID (e.g. claude-opus-5-5)",
74
+ },
75
+ "openai-compat": {
76
+ endpoint: "Endpoint (optional, e.g. http://localhost:11434/v1)",
77
+ key: "sk-… or any compatible key",
78
+ model: "Model ID (e.g. gpt-4o, claude-sonnet-4-20250514, etc.)",
79
+ },
80
+ };
81
+
82
+ /**
83
+ * A picker value as a family: the one other family, else the default.
84
+ *
85
+ * @param {string} value - What the picker reported
86
+ * @returns {AiProviderFamily}
87
+ */
88
+ function familyOf(value: string): AiProviderFamily {
89
+ return value === "anthropic" ? "anthropic" : "openai-compat";
90
+ }
91
+
45
92
  export interface AiCredentialsFormOptions {
46
93
  /** Host re-render scheduler — called whenever the form's internal state changes. */
47
94
  requestRender: () => void;
@@ -56,9 +103,11 @@ export interface AiCredentialsFormOptions {
56
103
  * Optional context line replacing the default blurb.
57
104
  *
58
105
  * A string, not a template: the line is drawn by a document, and a document renders text rather
59
- * than somebody else's markup. Nothing that passed one ever passed more than a sentence.
106
+ * than somebody else's markup. Nothing that passed one ever passed more than a sentence. A
107
+ * function is asked whether the provider picker is drawn, so a host's own sentence can name
108
+ * Claude exactly when Claude is a choice.
60
109
  */
61
- intro?: string;
110
+ intro?: string | ((offersProviders: boolean) => string);
62
111
  }
63
112
 
64
113
  export interface AiCredentialsForm {
@@ -79,6 +128,7 @@ export interface AiCredentialsForm {
79
128
  * @returns {AiCredentialsForm}
80
129
  */
81
130
  export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCredentialsForm {
131
+ let providerDraft: AiProviderFamily = "openai-compat";
82
132
  let keyDraft = "";
83
133
  let baseUrlDraft = "";
84
134
  let modelDraft = "";
@@ -95,7 +145,12 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
95
145
  * Cancel replaces it. Starts blank because the drafts do, which is what lets a host that never
96
146
  * calls `startEdit` still open on what is stored.
97
147
  */
98
- let basis: AiProvider = { apiKey: "", baseUrl: "", model: "" };
148
+ let basis: Required<AiProvider> = {
149
+ apiKey: "",
150
+ baseUrl: "",
151
+ model: "",
152
+ provider: "openai-compat",
153
+ };
99
154
 
100
155
  /** Made on the first render, and kept for the life of the controller. */
101
156
  let surface: AiCredentialsSurface | null = null;
@@ -108,13 +163,19 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
108
163
  * writes a choice nobody made, and `jx.ai.model: "gpt-4o"` is exactly what a broken install was
109
164
  * left holding.
110
165
  */
111
- function stored(): AiProvider {
112
- return { apiKey: getOpenAiKey(), baseUrl: getBaseUrl(), model: storedModel() };
166
+ function stored(): Required<AiProvider> {
167
+ return {
168
+ apiKey: getOpenAiKey(),
169
+ baseUrl: getBaseUrl(),
170
+ model: storedModel(),
171
+ provider: getAiProvider(),
172
+ };
113
173
  }
114
174
 
115
175
  /** Load the drafts from what is stored, discarding any edit, and take that as the new basis. */
116
176
  function loadDrafts() {
117
177
  basis = stored();
178
+ providerDraft = basis.provider;
118
179
  keyDraft = basis.apiKey;
119
180
  baseUrlDraft = basis.baseUrl;
120
181
  modelDraft = basis.model;
@@ -141,6 +202,7 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
141
202
  */
142
203
  function followStore() {
143
204
  const now = stored();
205
+ providerDraft = providerDraft === basis.provider ? now.provider : providerDraft;
144
206
  keyDraft = storedEquals(SETTINGS.aiOpenAiKey, keyDraft, basis.apiKey) ? now.apiKey : keyDraft;
145
207
  baseUrlDraft = storedEquals(SETTINGS.aiBaseUrl, baseUrlDraft, basis.baseUrl)
146
208
  ? now.baseUrl
@@ -173,7 +235,12 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
173
235
  * no longer `dirty`: Save and Cancel go away, so a second press has nothing to land on.
174
236
  */
175
237
  function save() {
176
- saveAiProvider({ apiKey: keyDraft, baseUrl: baseUrlDraft, model: modelDraft });
238
+ saveAiProvider({
239
+ apiKey: keyDraft,
240
+ baseUrl: baseUrlDraft,
241
+ model: modelDraft,
242
+ provider: providerDraft,
243
+ });
177
244
  loadDrafts();
178
245
  modelsError = "";
179
246
  /* The fetched list stays: it was listed under exactly these credentials, and dropping it
@@ -213,6 +280,7 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
213
280
  credentials: {
214
281
  apiKey: keyDraft || getOpenAiKey(),
215
282
  baseUrl: baseUrlDraft || getBaseUrl(),
283
+ provider: providerDraft,
216
284
  },
217
285
  force: true,
218
286
  });
@@ -243,24 +311,58 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
243
311
  return availableModels.map((model) => ({ label: model.name, value: model.id }));
244
312
  }
245
313
 
314
+ /**
315
+ * The line under the title: the host's, asked about the picker when it is a function, else the
316
+ * default for whether the picker is drawn.
317
+ *
318
+ * @param {boolean} showProvider - Whether the provider picker is drawn
319
+ * @returns {string}
320
+ */
321
+ function introFor(showProvider: boolean): string {
322
+ const { intro } = opts;
323
+ if (intro === undefined) {
324
+ return showProvider ? PROVIDER_INTRO : DEFAULT_INTRO;
325
+ }
326
+ return isString(intro) ? intro : intro(showProvider);
327
+ }
328
+
246
329
  /** What the form says right now — the whole of what the surface is told. */
247
330
  function view(): AiCredentialsView {
331
+ /* Drawn where it is a choice, and never offered to a backend that cannot honour it: one that
332
+ lists no other family forwards every key to an OpenAI-compatible endpoint. A family already
333
+ drafted or stored keeps the picker, so a reader can always switch back. */
334
+ const showProvider =
335
+ backendProviders().includes("anthropic") ||
336
+ providerDraft !== "openai-compat" ||
337
+ basis.provider !== "openai-compat";
338
+ const placeholders = PLACEHOLDERS[providerDraft];
248
339
  return {
249
340
  baseUrlDraft,
250
341
  /* Compared as the store would hold each value, so what Save would erase — whitespace, an
251
342
  endpoint's trailing slash — is not an edit. See `aiProviderDiffers`. */
252
- dirty: aiProviderDiffers({ apiKey: keyDraft, baseUrl: baseUrlDraft, model: modelDraft }),
343
+ dirty: aiProviderDiffers({
344
+ apiKey: keyDraft,
345
+ baseUrl: baseUrlDraft,
346
+ model: modelDraft,
347
+ provider: providerDraft,
348
+ }),
349
+ endpointPlaceholder: placeholders.endpoint,
253
350
  fetchLabel: modelsLoading
254
351
  ? "Fetching…"
255
352
  : availableModels.length > 0
256
353
  ? "Refresh models"
257
354
  : "Fetch models",
258
- intro: opts.intro ?? DEFAULT_INTRO,
355
+ intro: introFor(showProvider),
259
356
  keyDraft,
357
+ keyPlaceholder: placeholders.key,
260
358
  modelDraft,
359
+ modelPlaceholder: placeholders.model,
261
360
  models: rows(),
262
361
  modelsError,
263
362
  modelsLoading,
363
+ providerDraft,
364
+ providerOptions: PROVIDER_OPTIONS,
365
+ showProvider,
264
366
  };
265
367
  }
266
368
 
@@ -272,6 +374,20 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
272
374
  * would leave the field holding something the flow does not have. Nothing rewrites a draft today;
273
375
  * the echo is what makes a rewrite possible.
274
376
  */
377
+ /**
378
+ * The family the key belongs to. A change drops the listed catalogue, because it was another
379
+ * family's, and the reason the last listing failed with it: Fetch lists the new family's.
380
+ */
381
+ function setProvider(value: string) {
382
+ const next = familyOf(value);
383
+ if (next !== providerDraft) {
384
+ providerDraft = next;
385
+ availableModels = [];
386
+ modelsError = "";
387
+ }
388
+ surface?.update(view());
389
+ }
390
+
275
391
  function setKey(value: string) {
276
392
  keyDraft = value;
277
393
  surface?.update(view());
@@ -305,6 +421,7 @@ export function createAiCredentialsForm(opts: AiCredentialsFormOptions): AiCrede
305
421
  setBaseUrl,
306
422
  setKey,
307
423
  setModel,
424
+ setProvider,
308
425
  });
309
426
  }
310
427
  return surface.host;
package/src/ui/regions.ts CHANGED
@@ -20,7 +20,7 @@
20
20
  * inspector/tab:style the Style tab's body
21
21
  * inspector/field:href a field row (the Inspector's documents already emit `data-prop`)
22
22
  * pane.primary/tabs the primary pane's own tab strip
23
- * overlay.dialog:settings a named overlay slot
23
+ * overlay.dialog:about a named overlay slot
24
24
  * ```
25
25
  *
26
26
  * **Ids are DERIVED, not authored**, which is the property that makes them survive a rename. The
@@ -60,7 +60,7 @@ export type RegionSurface = (typeof REGION_SURFACES)[number];
60
60
  /** A parsed region id. `instance` and `part` are absent for a bare surface. */
61
61
  export interface ParsedRegionId {
62
62
  surface: RegionSurface;
63
- /** `primary` in `pane.primary`, `dialog` in `overlay.dialog:settings`. */
63
+ /** `primary` in `pane.primary`, `dialog` in `overlay.dialog:about`. */
64
64
  instance?: string;
65
65
  /** Everything after the first `/` — `publish/commit` in `pane.primary/publish/commit`. */
66
66
  part?: string;
@@ -412,9 +412,9 @@ export const OVERLAY_INSTANCE: Readonly<Record<"popover" | "modal" | "dialog" |
412
412
  /**
413
413
  * Region id for an overlay slot.
414
414
  *
415
- * With an `id` this is `overlay.dialog:settings` — derived from the key `getLayerSlot` already
416
- * builds, so naming a slot names its region. Without one it is the bare instance, which resolves to
417
- * the topmost open overlay of that kind.
415
+ * With an `id` this is `overlay.dialog:about` — derived from the key `getLayerSlot` already builds,
416
+ * so naming a slot names its region. Without one it is the bare instance, which resolves to the
417
+ * topmost open overlay of that kind.
418
418
  */
419
419
  export function overlayRegion(
420
420
  layer: "popover" | "modal" | "dialog" | "toast",
@@ -11,9 +11,10 @@
11
11
  * written in Content even when it is opened from somewhere else.
12
12
  * - **Media**: the media home and every media file's viewer.
13
13
  * - **Styles**: `project.json`, in any of its three views: Elements (`stylebook`), Variables
14
- * (`tokens`) and Code (`source`). Project Settings is a dialog over every workspace (§17.1), not
15
- * a tab, so it belongs to none.
14
+ * (`tokens`) and Code (`source`).
16
15
  * - **Publish**: the publish home and every comparison (`git-diff`).
16
+ * - **Settings**: its home alone, which hosts the settings editor (§17.1, §22.9). It edits
17
+ * `project.json` through the chokepoint without being a tab over it.
17
18
  * - **Design**: everything else: pages, templates, components and every other file.
18
19
  *
19
20
  * The rules read the tab's CURRENT editor, so switching an entry from its form to the canvas keeps
@@ -3,16 +3,17 @@
3
3
  * Workspace homes — the virtual tab a workspace opens on when none of its own tabs is open.
4
4
  *
5
5
  * A home is a tab like a grid is a tab: it sits in a pane's strip, it can be closed, and it has a
6
- * canvas mode of its own (`content-home`, `media-home`, `publish-home`) that
6
+ * canvas mode of its own (`content-home`, `media-home`, `publish-home`, `settings-home`) that
7
7
  * `canvas/canvas-render.ts` routes here. It has no document — the tab carries a stub, exactly as
8
8
  * the grid tabs do — and its id is in the `workspace://` space `workspace/workspace-ids.ts`
9
9
  * declares, so it can never collide with a file.
10
10
  *
11
11
  * What a home DRAWS is its workspace's own surface: the Content home is the collection it shows
12
12
  * (`browse/library-pane.ts`, studio.md §22.5), the Media home is the media library
13
- * (`media/media-library.ts`, §22.6) and the Publish home is the Publish workspace
14
- * (`publish/publish-workspace.ts`, §22.8). The mechanism (the tab, the mode, the routing and the
15
- * per-pane mount) is the same for all three, and this module only routes.
13
+ * (`media/media-library.ts`, §22.6), the Publish home is the Publish workspace
14
+ * (`publish/publish-workspace.ts`, §22.8) and the Settings home is the settings editor
15
+ * (`settings/settings-workspace.ts`, §22.9). The mechanism (the tab, the mode, the routing and the
16
+ * per-pane mount) is the same for all four, and this module only routes.
16
17
  *
17
18
  * @docs studio/interface
18
19
  */
@@ -30,6 +31,11 @@ import {
30
31
  publishWorkspaceMounted,
31
32
  renderPublishWorkspace,
32
33
  } from "../publish/publish-workspace";
34
+ import {
35
+ detachSettingsWorkspace,
36
+ renderSettingsWorkspace,
37
+ settingsWorkspaceMounted,
38
+ } from "../settings/settings-workspace";
33
39
  import type { HomeWorkspaceId } from "./workspace-ids";
34
40
  import type { Tab } from "../tabs/tab";
35
41
  import type { CanvasSurface } from "../canvas/canvas-surface";
@@ -98,6 +104,9 @@ export function workspaceHomeMounted(paneId: string, tab: Tab): boolean {
98
104
  if (id === "content") {
99
105
  return libraryPaneMounted(paneId, tab);
100
106
  }
107
+ if (id === "settings") {
108
+ return settingsWorkspaceMounted(paneId, tab);
109
+ }
101
110
  return publishWorkspaceMounted(paneId, tab);
102
111
  }
103
112
 
@@ -106,6 +115,7 @@ export function detachWorkspaceHome(paneId: string): void {
106
115
  detachMediaLibrary(paneId);
107
116
  detachLibraryPane(paneId);
108
117
  detachPublishWorkspace(paneId);
118
+ detachSettingsWorkspace(paneId);
109
119
  }
110
120
 
111
121
  /**
@@ -129,5 +139,9 @@ export function renderWorkspaceHome(surface: CanvasSurface, tab: Tab): void {
129
139
  renderLibraryMode(surface, tab);
130
140
  return;
131
141
  }
142
+ if (id === "settings") {
143
+ renderSettingsWorkspace(surface, tab);
144
+ return;
145
+ }
132
146
  renderPublishWorkspace(surface, tab);
133
147
  }
@@ -1,9 +1,13 @@
1
1
  /**
2
- * The five workspaces, declared where a bare Bun process can read them.
2
+ * The six workspaces, declared where a bare Bun process can read them.
3
3
  *
4
4
  * A workspace is the answer to "what am I doing right now": designing pages, writing content,
5
- * managing media, editing the project's styles, or publishing. Studio's top bar switches between
6
- * them, and every open tab belongs to exactly one (`workspace/tab-workspace.ts` derives which).
5
+ * managing media, editing the project's styles, publishing, or configuring the project. Studio's
6
+ * top bar switches between the first five, and every open tab belongs to exactly one
7
+ * (`workspace/tab-workspace.ts` derives which). Project Settings is the sixth: a workspace like the
8
+ * others (a frame, a home tab, a place `view.setWorkspace` can name) that the top bar does not draw
9
+ * a tab for, because it is visited for a minute from a "Project Settings" link (the gear menu, ⌘⇧,
10
+ * and every deep link `settings.open` serves) rather than worked in (studio.md §17.1, §22.9).
7
11
  *
8
12
  * The ids live in a module with no DOM and no state for the reason `NAVIGATOR_PANEL_IDS` does:
9
13
  * `view.setWorkspace`'s `args` enum is built from {@link WORKSPACE_IDS}, and
@@ -19,8 +23,15 @@
19
23
  * @docs studio/interface
20
24
  */
21
25
 
22
- /** The workspaces, in top-bar order. ⌘1 to ⌘5 follow this order. */
23
- export const WORKSPACE_IDS = ["design", "content", "media", "styles", "publish"] as const;
26
+ /** The workspaces, in top-bar order, Settings last. ⌘1 to ⌘5 follow the switcher's five. */
27
+ export const WORKSPACE_IDS = [
28
+ "design",
29
+ "content",
30
+ "media",
31
+ "styles",
32
+ "publish",
33
+ "settings",
34
+ ] as const;
24
35
 
25
36
  export type WorkspaceId = (typeof WORKSPACE_IDS)[number];
26
37
 
@@ -34,6 +45,11 @@ export interface WorkspaceInfo {
34
45
  title: string;
35
46
  /** A key into the kit's glyph manifest (`@jxsuite/ui/icons`), checked by this module's test. */
36
47
  icon: string;
48
+ /**
49
+ * Whether the top bar draws a tab for it, and so whether it has a ⌘-digit chord and a "Go to"
50
+ * palette row. False for Settings alone, which its own links and `settings.open` reach.
51
+ */
52
+ switcher: boolean;
37
53
  }
38
54
 
39
55
  /** Every workspace, in {@link WORKSPACE_IDS} order. */
@@ -41,30 +57,46 @@ export const WORKSPACES: readonly WorkspaceInfo[] = [
41
57
  {
42
58
  icon: "frame-corners",
43
59
  id: "design",
60
+ switcher: true,
44
61
  title: "Design",
45
62
  },
46
63
  {
47
64
  icon: "file-text",
48
65
  id: "content",
66
+ switcher: true,
49
67
  title: "Content",
50
68
  },
51
69
  {
52
70
  icon: "image",
53
71
  id: "media",
72
+ switcher: true,
54
73
  title: "Media",
55
74
  },
56
75
  {
57
76
  icon: "paint-brush",
58
77
  id: "styles",
78
+ switcher: true,
59
79
  title: "Styles",
60
80
  },
61
81
  {
62
82
  icon: "paper-plane-tilt",
63
83
  id: "publish",
84
+ switcher: true,
64
85
  title: "Publish",
65
86
  },
87
+ {
88
+ icon: "gear-six",
89
+ id: "settings",
90
+ switcher: false,
91
+ title: "Project settings",
92
+ },
66
93
  ];
67
94
 
95
+ /** The workspaces the top bar draws a tab for, in order: every one but Settings. */
96
+ export const SWITCHER_WORKSPACES: readonly WorkspaceInfo[] = WORKSPACES.filter(
97
+ (entry) => entry.switcher,
98
+ );
99
+
68
100
  /** Whether a value is a declared workspace id. A stored value is untrusted input. */
69
101
  export function isWorkspaceId(value: unknown): value is WorkspaceId {
70
102
  return typeof value === "string" && WORKSPACE_IDS.some((id) => id === value);
@@ -90,14 +122,15 @@ const HOME_SCHEME = "workspace://";
90
122
  * that tab draws under.
91
123
  *
92
124
  * Design has no home (an empty stage is its home), and Styles' is `project.json` in its stylebook
93
- * editor, opened by the module that owns that document. These three are the generic mechanism's:
125
+ * editor, opened by the module that owns that document. These four are the generic mechanism's:
94
126
  * Content's home is the collection it shows (`browse/library-pane.ts`), Media's the media library,
95
- * and Publish's its own surface.
127
+ * Publish's its own surface, and Settings' the settings editor (`settings/settings-workspace.ts`).
96
128
  */
97
129
  export const WORKSPACE_HOME_MODES = {
98
130
  content: "content-home",
99
131
  media: "media-home",
100
132
  publish: "publish-home",
133
+ settings: "settings-home",
101
134
  } as const satisfies Partial<Record<WorkspaceId, string>>;
102
135
 
103
136
  /** A workspace whose home is a generic home tab. */
@@ -1,4 +0,0 @@
1
- import{qs,Gs,Wr}from"./studio-ewarnzm4.js";export{Wr as draggable,qs as dropTargetForElements,Gs as monitorForElements};
2
-
3
- //# debugId=6706B48D5462E95E64756E2164756E21
4
- //# sourceMappingURL=adapter-wc5zvg9a.js.map