@jxsuite/studio 5.6.0 → 6.0.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 (176) hide show
  1. package/dist/chunks/{2020-zvax9fmb.js → 2020-cs2sb9t1.js} +3 -3
  2. package/dist/chunks/adapter-y5gdq19c.js +4 -0
  3. package/dist/chunks/{cf-account-picker-zfycs4k2.js → cf-account-picker-bb2yq946.js} +3 -3
  4. package/dist/chunks/cf-settings-20dznc3j.js +4 -0
  5. package/dist/chunks/components-q037dya7.js +4 -0
  6. package/dist/chunks/content-source-t0q184js.js +4 -0
  7. package/dist/chunks/{convert-to-repeater-x19jr6c7.js → convert-to-repeater-vqkt7s3s.js} +3 -3
  8. package/dist/chunks/editor-j5sq149q.js +4 -0
  9. package/dist/chunks/entry-commands-fxzgcc4k.js +4 -0
  10. package/dist/chunks/{extension-sections-y90sa0n4.js → extension-sections-8beq9ac3.js} +3 -3
  11. package/dist/chunks/file-ops-2n77759h.js +4 -0
  12. package/dist/chunks/files-fjy7zpxt.js +4 -0
  13. package/dist/chunks/format-host-wj3mf3n8.js +4 -0
  14. package/dist/chunks/fs-events-7kcgn4e9.js +4 -0
  15. package/dist/chunks/git-panel-7z2mpt2d.js +4 -0
  16. package/dist/chunks/{grid-view-d9mybhn0.js → grid-view-ca10def6.js} +10 -10
  17. package/dist/chunks/iframe-host-a0zjd6xp.js +4 -0
  18. package/dist/chunks/{index-vnwy9d44.js → index-9ydfe04y.js} +3 -3
  19. package/dist/chunks/index-m0x5ztax.js +4 -0
  20. package/dist/chunks/{javascript-f0nrbdjb.js → javascript-d9pqgc2p.js} +2 -2
  21. package/dist/chunks/{jsonMode-nfa90dtf.js → jsonMode-800yvbem.js} +4 -4
  22. package/dist/chunks/jx-validate-zc9zp77s.js +4 -0
  23. package/dist/chunks/layers-p677nrh5.js +4 -0
  24. package/dist/chunks/layers-panel-y4w1dq90.js +4 -0
  25. package/dist/chunks/lit-html-cfn61dn7.js +4 -0
  26. package/dist/chunks/media-picker-xg7yjpz9.js +4 -0
  27. package/dist/chunks/menu-fpekzj6w.js +4 -0
  28. package/dist/chunks/{monaco-binding-antn79jf.js → monaco-binding-62dtmgbf.js} +3 -3
  29. package/dist/chunks/monaco-buffer-nn4dtx27.js +4 -0
  30. package/dist/chunks/{monaco-setup-k8s40vwg.js → monaco-setup-tj13ndtm.js} +3 -3
  31. package/dist/chunks/notify-ahs57a2z.js +4 -0
  32. package/dist/chunks/platform-v9xyfkbx.js +4 -0
  33. package/dist/chunks/publish-panel-xtbfgg2r.js +4 -0
  34. package/dist/chunks/quick-search-d3a6m7j3.js +4 -0
  35. package/dist/chunks/references-99ws0b0m.js +4 -0
  36. package/dist/chunks/right-panel-31vbk5xp.js +4 -0
  37. package/dist/chunks/section-registry-b2yzp9sw.js +4 -0
  38. package/dist/chunks/{studio-y17505ge.js → studio-01kbbqg3.js} +207 -207
  39. package/dist/chunks/{studio-gxna6yta.js → studio-01qza1n3.js} +4 -4
  40. package/dist/chunks/{studio-n2e133d6.js → studio-03wwfvyt.js} +4 -4
  41. package/dist/chunks/{studio-09n3fyy0.js → studio-0bbxhhce.js} +4 -4
  42. package/dist/chunks/studio-1a99w39s.js +5 -0
  43. package/dist/chunks/studio-1q6amtph.js +5 -0
  44. package/dist/chunks/{studio-eegyf5vg.js → studio-1wvfxkyy.js} +4 -4
  45. package/dist/chunks/{studio-z47mm077.js → studio-3ck2a5c4.js} +6 -6
  46. package/dist/chunks/{studio-ymsjnvp9.js → studio-4fde4a0x.js} +4 -4
  47. package/dist/chunks/{studio-je15p5v8.js → studio-4jj9gme2.js} +5 -5
  48. package/dist/chunks/{studio-w8w9frjy.js → studio-4m50vh74.js} +12 -12
  49. package/dist/chunks/{studio-891t1tv9.js → studio-4r0sjtx8.js} +4 -4
  50. package/dist/chunks/{studio-6exg5z4x.js → studio-4xppwncr.js} +3 -3
  51. package/dist/chunks/{studio-011em5cf.js → studio-5d7wfmnp.js} +4 -4
  52. package/dist/chunks/{studio-zkkdbbvd.js → studio-5e9zsamh.js} +6 -6
  53. package/dist/chunks/{studio-pvj970st.js → studio-5gs9th4b.js} +4 -4
  54. package/dist/chunks/{studio-dtmdjmqb.js → studio-5vy2wfm5.js} +4 -4
  55. package/dist/chunks/{studio-654fxcr0.js → studio-7pgy3xaf.js} +4 -4
  56. package/dist/chunks/{studio-z1vdxq0e.js → studio-7qvs7v0m.js} +4 -4
  57. package/dist/chunks/studio-84rtc120.js +5 -0
  58. package/dist/chunks/{studio-ks0ecgjx.js → studio-8xcwewjq.js} +4 -4
  59. package/dist/chunks/{studio-sadckcpp.js → studio-aekhcdzw.js} +5 -5
  60. package/dist/chunks/{studio-mf41717j.js → studio-aje966s9.js} +4 -4
  61. package/dist/chunks/studio-aqmph6k4.js +5 -0
  62. package/dist/chunks/{studio-28pz4s1k.js → studio-bftp4n4b.js} +4 -4
  63. package/dist/chunks/{studio-tzs6njnb.js → studio-c6zdm25z.js} +7 -7
  64. package/dist/chunks/{studio-sgzss898.js → studio-capk0583.js} +7 -7
  65. package/dist/chunks/{studio-805fwp9k.js → studio-cbr9czkn.js} +4 -4
  66. package/dist/chunks/{studio-1pxvgx1t.js → studio-chdmwwzw.js} +4 -4
  67. package/dist/chunks/{studio-dknr8m7b.js → studio-cp1gbff9.js} +7 -7
  68. package/dist/chunks/{studio-ft8pfpp0.js → studio-cqmtbg2p.js} +4 -4
  69. package/dist/chunks/studio-e47215hh.js +5 -0
  70. package/dist/chunks/{studio-9n2j9nqk.js → studio-edbtvcd1.js} +4 -4
  71. package/dist/chunks/{studio-mm8x32gg.js → studio-evwgp1ym.js} +4 -4
  72. package/dist/chunks/{studio-02y9cb4h.js → studio-fbx6twps.js} +5 -5
  73. package/dist/chunks/{studio-0nm2skex.js → studio-fqq890ap.js} +3 -3
  74. package/dist/chunks/studio-fthwn4kf.js +5 -0
  75. package/dist/chunks/{studio-s85vah66.js → studio-g8dsj3d2.js} +5 -5
  76. package/dist/chunks/{studio-0c3fpdqj.js → studio-ge5fqjth.js} +5 -5
  77. package/dist/chunks/{studio-ebt49j3c.js → studio-gjknvzam.js} +5 -5
  78. package/dist/chunks/{studio-zkvyd3ev.js → studio-gp02nqnw.js} +4 -4
  79. package/dist/chunks/{studio-5fc0728f.js → studio-hjbg8d5y.js} +7 -7
  80. package/dist/chunks/{studio-d007j0z3.js → studio-hnvnfbdv.js} +6 -6
  81. package/dist/chunks/{studio-b5rxwkj2.js → studio-mk00zd5d.js} +4 -4
  82. package/dist/chunks/{studio-36s6qjgq.js → studio-n78gjfy8.js} +4 -4
  83. package/dist/chunks/studio-nbxqcgvf.js +5 -0
  84. package/dist/chunks/{studio-y164brht.js → studio-pwqfmt4p.js} +4 -4
  85. package/dist/chunks/studio-qa73881a.js +5 -0
  86. package/dist/chunks/{studio-6zyt56ek.js → studio-r3kaka4d.js} +4 -4
  87. package/dist/chunks/{studio-njmg0e73.js → studio-rmn2dz59.js} +5 -5
  88. package/dist/chunks/{studio-wr0pq67n.js → studio-rr4xkv81.js} +4 -4
  89. package/dist/chunks/{studio-tj28pxa1.js → studio-semd1s8s.js} +4 -4
  90. package/dist/chunks/{studio-7w04zw8h.js → studio-st75kn99.js} +5 -5
  91. package/dist/chunks/{studio-dmymvk2j.js → studio-sxc0zvpt.js} +3 -3
  92. package/dist/chunks/studio-t1eedsyx.js +4 -0
  93. package/dist/chunks/{studio-9tkaykt5.js → studio-tmeq6mjb.js} +4 -4
  94. package/dist/chunks/{studio-153mwkyq.js → studio-tp642n5g.js} +4 -4
  95. package/dist/chunks/{studio-t0v47dpc.js → studio-tzegmy4w.js} +4 -4
  96. package/dist/chunks/studio-vavmwshr.js +5 -0
  97. package/dist/chunks/studio-x5g1xk77.js +5 -0
  98. package/dist/chunks/studio-x92tgnee.js +64 -0
  99. package/dist/chunks/{studio-4yztve2t.js → studio-xg1942as.js} +4 -4
  100. package/dist/chunks/{studio-k9pwvmz8.js → studio-xkfs0dp2.js} +4 -4
  101. package/dist/chunks/{studio-6k1eswp9.js → studio-y7jehjrp.js} +4 -4
  102. package/dist/chunks/{studio-81twpmzj.js → studio-yr04mv2j.js} +4 -4
  103. package/dist/chunks/{studio-gj4j9e7k.js → studio-zd4kayn2.js} +4 -4
  104. package/dist/chunks/{studio-jsr14n1s.js → studio-zev8ssp1.js} +4 -4
  105. package/dist/chunks/{studio-r7yeeqc4.js → studio-ztvcxm3w.js} +4 -4
  106. package/dist/chunks/{tsMode-eq26t9gs.js → tsMode-k9s9fcag.js} +4 -4
  107. package/dist/chunks/workspace-5vmgsagj.js +4 -0
  108. package/dist/chunks/{ws-client-3gxqp06s.js → ws-client-0ja08haa.js} +3 -3
  109. package/dist/iframe-entry.js +3 -3
  110. package/dist/manifest.json +62 -62
  111. package/dist/studio.js +95 -95
  112. package/package.json +12 -12
  113. package/src/canvas/stage-scrollbar.ts +7 -7
  114. package/src/files/file-ops.ts +5 -8
  115. package/src/panels/jump-bar.ts +132 -158
  116. package/src/panels/pane-context.ts +8 -8
  117. package/src/panels/pane-grid.ts +18 -26
  118. package/src/panels/style-panel.ts +2 -2
  119. package/src/services/context-manager.ts +41 -70
  120. package/src/services/document-assistant.ts +12 -3
  121. package/src/services/harness/chat-reducer.ts +115 -0
  122. package/src/services/harness/turn-hooks.ts +50 -0
  123. package/src/services/tool-executor.ts +69 -288
  124. package/src/services/tool-outcomes.ts +12 -10
  125. package/src/studio.ts +2 -5
  126. package/src/surfaces/block-action-bar.json +6 -1
  127. package/src/surfaces/jump-bar.json +9 -12
  128. package/src/surfaces/jump-bar.ts +15 -16
  129. package/src/surfaces/pane-context.json +2 -2
  130. package/src/surfaces/pane-grid.json +8 -25
  131. package/src/surfaces/pane-grid.ts +7 -8
  132. package/src/surfaces/statusbar.json +71 -3
  133. package/src/surfaces/statusbar.ts +66 -139
  134. package/src/surfaces/style-panel.json +3 -3
  135. package/src/surfaces/style-panel.ts +11 -1
  136. package/src/ui/schema-form.ts +6 -2
  137. package/dist/chunks/adapter-6zhjbpmd.js +0 -4
  138. package/dist/chunks/cf-settings-fpa485q3.js +0 -4
  139. package/dist/chunks/components-b0vh4sq8.js +0 -4
  140. package/dist/chunks/content-source-z5t0d0h9.js +0 -4
  141. package/dist/chunks/editor-7e23n4a8.js +0 -4
  142. package/dist/chunks/entry-commands-5cm6aba2.js +0 -4
  143. package/dist/chunks/file-ops-hv7gf757.js +0 -4
  144. package/dist/chunks/files-kb0epdxf.js +0 -4
  145. package/dist/chunks/format-host-eg1wy2bp.js +0 -4
  146. package/dist/chunks/fs-events-ky3vxfmh.js +0 -4
  147. package/dist/chunks/git-panel-yyg3bzp2.js +0 -4
  148. package/dist/chunks/iframe-host-9wdf0hes.js +0 -4
  149. package/dist/chunks/index-2tf0m2ne.js +0 -4
  150. package/dist/chunks/jx-validate-7w11db9v.js +0 -4
  151. package/dist/chunks/layers-7zasrpse.js +0 -4
  152. package/dist/chunks/layers-panel-9nzc4fdq.js +0 -4
  153. package/dist/chunks/lit-html-5qr7nbng.js +0 -4
  154. package/dist/chunks/media-picker-maevn9d4.js +0 -4
  155. package/dist/chunks/menu-wq6q0v3y.js +0 -4
  156. package/dist/chunks/monaco-buffer-9ry2rn5x.js +0 -4
  157. package/dist/chunks/notify-g91gjd6w.js +0 -4
  158. package/dist/chunks/platform-jdwjp2vt.js +0 -4
  159. package/dist/chunks/publish-panel-8yym447p.js +0 -4
  160. package/dist/chunks/quick-search-9bzm9kh3.js +0 -4
  161. package/dist/chunks/references-znssfqvq.js +0 -4
  162. package/dist/chunks/right-panel-czbzktm8.js +0 -4
  163. package/dist/chunks/section-registry-0rtq73qs.js +0 -4
  164. package/dist/chunks/studio-0mm38y62.js +0 -5
  165. package/dist/chunks/studio-27tc8q0h.js +0 -5
  166. package/dist/chunks/studio-7djymzt2.js +0 -5
  167. package/dist/chunks/studio-ant1zbp3.js +0 -5
  168. package/dist/chunks/studio-b29h7g7s.js +0 -5
  169. package/dist/chunks/studio-eb5sty2k.js +0 -5
  170. package/dist/chunks/studio-frv7vjhw.js +0 -5
  171. package/dist/chunks/studio-mb8jhtr4.js +0 -64
  172. package/dist/chunks/studio-nvykrgbj.js +0 -4
  173. package/dist/chunks/studio-nw4q9sdn.js +0 -5
  174. package/dist/chunks/studio-p6avneaq.js +0 -5
  175. package/dist/chunks/studio-vz7b43ng.js +0 -5
  176. package/dist/chunks/workspace-emt8454d.js +0 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/studio",
3
- "version": "5.6.0",
3
+ "version": "6.0.0",
4
4
  "description": "Jx Studio — visual builder for Jx documents",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -61,22 +61,22 @@
61
61
  "dependencies": {
62
62
  "@atlaskit/pragmatic-drag-and-drop": "^4.0.0",
63
63
  "@atlaskit/pragmatic-drag-and-drop-hitbox": "^3.0.0",
64
- "@jxsuite/ai": "^0.39.1",
65
- "@jxsuite/collab": "^0.11.0",
66
- "@jxsuite/create": "^1.3.12",
67
- "@jxsuite/formulas": "^0.0.19",
68
- "@jxsuite/markup": "^0.4.11",
69
- "@jxsuite/protocol": "^2.6.0",
70
- "@jxsuite/runtime": "^4.0.2",
71
- "@jxsuite/schema": "^2.3.0",
72
- "@jxsuite/site": "^2.0.2",
73
- "@jxsuite/ui": "^0.4.0",
64
+ "@jxsuite/ai": "^0.39.3",
65
+ "@jxsuite/collab": "^0.11.1",
66
+ "@jxsuite/create": "^1.3.13",
67
+ "@jxsuite/formulas": "^0.0.20",
68
+ "@jxsuite/markup": "^0.4.12",
69
+ "@jxsuite/protocol": "^2.6.1",
70
+ "@jxsuite/runtime": "^4.0.3",
71
+ "@jxsuite/schema": "^3.0.0",
72
+ "@jxsuite/site": "^2.0.3",
73
+ "@jxsuite/ui": "^0.4.1",
74
74
  "@vue/reactivity": "3.5.43",
75
75
  "ajv": "^8.20.0",
76
76
  "ajv-formats": "^3.0.1",
77
77
  "lit-html": "^3.3.3",
78
78
  "monaco-editor": "^0.57.0",
79
- "tabulator-tables": "^6.6.0",
79
+ "tabulator-tables": "^6.6.1",
80
80
  "yaml": "^2.9.1"
81
81
  },
82
82
  "devDependencies": {
@@ -13,14 +13,14 @@
13
13
  * belongs to `surfaces/canvas-stage.json`; the pod to `surfaces/pane-context.json`. So the
14
14
  * renderer, which is the one module that knows which element scrolls in which mode, hands the
15
15
  * scroller here, and this module writes its width onto the cell as {@link STAGE_SCROLLBAR_VAR}. The
16
- * pod's margin adds it by cascade. It is the seam `--jump-bar-h` and `--pane-context-h` already
17
- * use, run the other way: there each bar owns its answer and the stage reads it; here the stage
18
- * owns its scroller and the chrome reads the answer.
16
+ * pod's margin adds it by cascade. It is the seam `--pane-context-h` already uses, run the other
17
+ * way: there the bar owns its answer and the stage reads it; here the stage owns its scroller and
18
+ * the chrome reads the answer.
19
19
  *
20
- * **On the CELL, never on `:root`**, for the reason `panels/jump-bar.ts` gives at the same seam:
21
- * two panes have two stages, and one document-level number would push the other pane's pod in by a
22
- * scrollbar it does not have. A stage outside any cell writes the root, which only happens in a
23
- * test fixture.
20
+ * **On the CELL, never on `:root`**, for the reason `panels/pane-context.ts` gives at the same
21
+ * seam: two panes have two stages, and one document-level number would push the other pane's pod in
22
+ * by a scrollbar it does not have. A stage outside any cell writes the root, which only happens in
23
+ * a test fixture.
24
24
  *
25
25
  * **Edit only, and 0 everywhere else by construction.** `canvas-render.ts` tracks the scroller from
26
26
  * the Edit branch and releases it at every teardown (a mode transition, a full reset, the pane's
@@ -27,7 +27,6 @@ import { loadMediaUsages } from "./media-usage";
27
27
  import { showConfirmDialog } from "../ui/layers";
28
28
  import { locateDocument } from "../services/code-services";
29
29
  import { errorMessage } from "@jxsuite/schema/parse";
30
- import { noteDocumentSaved } from "../surfaces/statusbar";
31
30
  import { notify } from "../services/notify";
32
31
  import { validateComponentSlots } from "../services/cem-export";
33
32
  import { reportPopoverProblems } from "../services/popover-report";
@@ -157,8 +156,8 @@ export async function openFile() {
157
156
  } else {
158
157
  throw noFormatError(name);
159
158
  }
160
- // Opening a file is stated permanently by the tab strip and the status bar's DOCUMENT
161
- // Field; it does not need a message that erases itself.
159
+ // Opening a file is stated permanently by the tab strip and the status bar's breadcrumb
160
+ // Trail; it does not need a message that erases itself.
162
161
  };
163
162
 
164
163
  if ("showOpenFilePicker" in window) {
@@ -197,10 +196,9 @@ export async function openFile() {
197
196
  /**
198
197
  * Record a successful write, and raise the slot-validation warning component documents can carry.
199
198
  *
200
- * The success half no longer notifies at ALL. "Saved" is ambient state: the status bar's DOCUMENT
201
- * field says "Saved 2m ago" for as long as it is true, which is strictly more information than a
202
- * message that said it once and erased itself — and it is the field a reader looks at to ask the
203
- * question in the first place.
199
+ * The success half no longer notifies at ALL. "Saved" is ambient state: the tab strip's dirty
200
+ * marker and the Command Bar's Save state it for as long as it is true, which is strictly more
201
+ * information than a message that said it once and erased itself.
204
202
  *
205
203
  * The warning half became a PROBLEM. A component whose slots do not line up is a thing to fix, and
206
204
  * it was previously shown for six seconds in the same grey as the word "Saved".
@@ -229,7 +227,6 @@ export function setDocumentSavedListener(
229
227
  }
230
228
 
231
229
  function reportSaved(tab: Tab) {
232
- noteDocumentSaved(tab.documentPath);
233
230
  const doc = tab.doc.document;
234
231
  _onDocumentSaved(tab.documentPath, doc);
235
232
  const warning =
@@ -1,67 +1,79 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * The jump bar — the pane's address, and every place you can go from it.
3
+ * The jump bar — the focused pane's address, as a breadcrumb trail along the foot of the window.
4
4
  *
5
- * **The two half-breadcrumbs this replaces.** Studio grew two trails, neither of which could say
5
+ * **Where it lives, and why it moved.** The trail is the status bar's DOCUMENT field: the file,
6
+ * then every ancestor of the selected element, leaf last, each one a button that selects it. That
7
+ * is the pattern the Gutenberg block editor taught a generation of authors, and it is where they
8
+ * look for it. It used to be a 10px row across the top of every pane, between the tab strip and the
9
+ * context bar, and in that position it read as part of the tab strip rather than as a control: the
10
+ * address was on screen and authors reported it missing. It also cost every pane 24px of stage
11
+ * height. At the foot it costs nothing, because the status bar's row is already there, and the
12
+ * field it replaced (the file path, a view name the context bar already prints, and a save word the
13
+ * tab strip's dirty marker and the Command Bar's Save button already state) was the bar's least
14
+ * useful content.
15
+ *
16
+ * **One bar, for the focused pane.** The status bar is one row for the whole window, so the trail
17
+ * addresses the pane that has focus, which is the pane the Inspector, the Outline and the keyboard
18
+ * already address. Clicking into the other pane re-points it.
19
+ *
20
+ * **The two half-breadcrumbs this replaced.** Studio grew two trails, neither of which could say
6
21
  * where you were:
7
22
  *
8
23
  * 1. `panels/pane-context.ts`'s `navTpl()` (the old `#tab-bar` breadcrumb, `tab-bar.ts:129-185`) — a
9
24
  * `Back` button plus one span per `session.documentStack` frame. It appeared ONLY while you were
10
- * inside a sub-document, printed the frame's file basename (which is the SAME basename for a
11
- * `$map` template, so it read `index.json › index.json`), and knew nothing about the project
12
- * above it or the selection below it. It is deleted, and so is the stack it walked: nothing ever
13
- * pushed a frame, so it could only ever draw its empty branch.
14
- * 2. `surfaces/statusbar.ts`'s selection field — a clickable ancestor trail, `selection.set` per
15
- * crumb. It appeared ONLY while something was selected, and knew nothing about the document it
16
- * was inside.
25
+ * inside a sub-document and knew nothing about the selection below it. It is deleted, and so is
26
+ * the stack it walked: nothing ever pushed a frame, so it could only ever draw its empty
27
+ * branch.
28
+ * 2. `surfaces/statusbar.ts`'s selection field — a clickable ancestor trail that appeared ONLY while
29
+ * something was selected and knew nothing about the document it was inside.
17
30
  *
18
- * Between them they never rendered at the same time as each other and never rendered the whole
19
- * address, so neither one was a place to look. **This bar is the whole chain**, always:
31
+ * This bar is the whole chain, always:
20
32
  *
21
33
  * ```text
22
- * ◈ Site › pages/blog/[slug].json › article › h1 ⌄
23
- * project file selection ancestors
34
+ * Jx Suite ⑂ main ‖ pages/blog/[slug].json › article › header › h1 ⌃ ‖ 3 selected
35
+ * PROJECT field ‖ this bar: file, then the selection's ancestors ‖ SELECTION field
24
36
  * ```
25
37
  *
26
- * **The status bar keeps only what an address cannot say.** `studio.md` §16.2 gives the bar AMBIENT
27
- * STATE, and an ancestor trail in a second place is exactly the duplication the shell redesign
28
- * exists to remove — three breadcrumbs would be worse than the two we started with. So the trail
29
- * moves here whole, and `statusbar/selection` keeps the two facts this bar cannot state: HOW MANY
30
- * things are selected (a count is not a path) and which Stylebook rule the Style panel is editing
31
- * (a selector is not a node).
38
+ * The project is not a step of the trail: the PROJECT field directly to its left already names it,
39
+ * with the same `project.openRecent` command, and two adjacent buttons saying one thing is the
40
+ * duplication studio-ui-guidelines.md §12.2's chrome budget forbids. `statusbar/selection` keeps
41
+ * the two facts a trail cannot state: HOW MANY things are selected (a count is not a path) and
42
+ * which Stylebook rule the Style panel is editing (a selector is not a node).
32
43
  *
33
44
  * **Every interactive item is a command, resolved from the registry.** There is no click handler in
34
45
  * this file that names behaviour: a segment names a command id and its args, and the registry
35
- * supplies the title, the chord, the enablement and the run. Three ids carry the whole bar —
36
- * `project.openRecent`, `palette.openFiles` and `selection.set` — and this file declares none of
37
- * them. It did declare a fourth, `document.setStackLevel`, for the sub-document stack; the stack
38
- * had no way in and both are gone.
46
+ * supplies the title, the chord, the enablement and the run. Two ids carry the whole bar —
47
+ * `palette.openFiles` (or `pane.pin` in a derived pane) and `selection.set` — and this file
48
+ * declares none of them.
39
49
  *
40
50
  * **A segment whose command the registry does not have becomes a readout, it does not disappear.**
41
- * That is the one place this bar deliberately differs from the status bar, where an unavailable
42
- * item vanishes. An address with a hole in it is a lie about containment; a readout is merely a
43
- * step you cannot take.
51
+ * That is the one place this bar deliberately differs from the status bar's own items, where an
52
+ * unavailable item vanishes. An address with a hole in it is a lie about containment; a readout is
53
+ * merely a step you cannot take.
44
54
  *
45
55
  * **This file is the FLOW; the markup is a document.** `surfaces/jump-bar.json` owns the bar's
46
- * structure, its ARIA and every value in its style, and `surfaces/jump-bar.ts` mounts one per pane
47
- * (studio-ui-guidelines.md §6, §9.3). What stays here is the part that is a decision: which pane
48
- * this bar is about, what its address is, which of its steps the registry can run, and what a
49
- * chevron opens — and the chevron opens the KIT MENU, `surfaces/menu.ts`, because a list of
50
- * commands is what that surface already is (studio-ui-guidelines.md §12.5).
56
+ * structure, its ARIA and every value in its style, and `surfaces/jump-bar.ts` mounts it into the
57
+ * host the status bar's document leaves for it (studio-ui-guidelines.md §6, §9.3). What stays here
58
+ * is the part that is a decision: which pane this bar is about, what its address is, which of its
59
+ * steps the registry can run, and what a chevron opens — and the chevron opens the KIT MENU,
60
+ * `surfaces/menu.ts`, because a list of commands is what that surface already is
61
+ * (studio-ui-guidelines.md §12.5).
62
+ *
63
+ * @docs studio/interface
51
64
  */
52
65
 
53
66
  import { displayTagName } from "@jxsuite/schema/guards";
54
67
  import { childList, getNodeAtPath, nodeLabel, pathsEqual, projectState } from "../store";
55
68
  import { effect, effectScope } from "../reactivity";
56
- import { PRIMARY_PANE, workspace } from "../workspace/workspace";
69
+ import { activePane, workspace } from "../workspace/workspace";
57
70
  import { derivationOfPane, tabOfPane } from "../canvas/canvas-surface";
58
- import { paneRegion } from "../ui/regions";
59
71
  import { primarySelection } from "../tabs/selection";
60
72
  import { activeRegistry } from "../commands/active-registry";
61
73
  import { runReported } from "../commands/run-reported";
62
74
  import { openMenu } from "../surfaces/menu";
75
+ import { rectOf } from "../utils/geometry";
63
76
  import { mountJumpBarSurface } from "../surfaces/jump-bar";
64
- import { PANE_SELECTOR } from "../surfaces/pane-grid";
65
77
  import type { CommandArgs, CommandRegistry } from "../commands/registry";
66
78
  import type { FormulaEditDef, FunctionEditDef } from "../types";
67
79
  import type { JxPath } from "../state";
@@ -71,11 +83,17 @@ import type { Tab } from "../tabs/tab";
71
83
  import type { PaneDerivation } from "../workspace/workspace";
72
84
  import type { EffectScope } from "@vue/reactivity";
73
85
 
74
- /** The CSS variable the stage is offset by while the jump bar is on screen. */
75
- export const JUMP_BAR_VAR = "--jump-bar-h";
86
+ /**
87
+ * The region the trail stamps on itself. It is the status bar's DOCUMENT field: the document, and
88
+ * where in it you are.
89
+ */
90
+ export const JUMP_BAR_REGION = "statusbar/document";
76
91
 
77
- /** The bar's height. Declared here because the offset projection and the document must agree. */
78
- const JUMP_BAR_HEIGHT = 24;
92
+ /**
93
+ * How a segment's alternatives hang from its chevron: ABOVE it, leading edges aligned. The bar sits
94
+ * on the window's floor, so the kit menu's default (below the opener) would open off screen.
95
+ */
96
+ const ABOVE_OPENER = "block-start span-inline-end";
79
97
 
80
98
  // ─── The model ───────────────────────────────────────────────────────────────
81
99
 
@@ -84,7 +102,7 @@ const JUMP_BAR_HEIGHT = 24;
84
102
  * step of the address without matching its rendered text, which is derived (the shot contract's R1,
85
103
  * `scripts/screenshots/README.md`).
86
104
  */
87
- export type JumpSegmentKind = "project" | "file" | "editor" | "node";
105
+ export type JumpSegmentKind = "file" | "editor" | "node";
88
106
 
89
107
  /** One alternative in a segment's dropdown. Each is a command, exactly like the segment itself. */
90
108
  export interface JumpChoice {
@@ -111,12 +129,12 @@ export interface JumpSegment {
111
129
  // ─── Labels ──────────────────────────────────────────────────────────────────
112
130
 
113
131
  /**
114
- * The document's path with the project root taken off — the root is the segment before it.
132
+ * The document's path with the project root taken off — the root is the PROJECT field to its left.
115
133
  *
116
134
  * Lives here rather than in `statusbar.ts`, where it started: the jump bar is the surface whose
117
- * whole job is naming the containment chain, and the status bar's DOCUMENT field is a second reader
118
- * of the same fact. Having the reader own it also kept `mock.module("surfaces/statusbar")` — which
119
- * six bootstrap tests do — from deciding whether the jump bar can name a file.
135
+ * whole job is naming the containment chain. Having the reader own it also keeps
136
+ * `mock.module("surfaces/statusbar")` — which six bootstrap tests do — from deciding whether the
137
+ * jump bar can name a file.
120
138
  */
121
139
  export function documentLabel(path: string | null): string {
122
140
  if (!path) {
@@ -235,22 +253,12 @@ export function jumpSegments(
235
253
  if (!tab) {
236
254
  return [];
237
255
  }
238
- const segments: JumpSegment[] = [];
239
- const project = projectState;
240
- if (project) {
241
- segments.push({
242
- choices: [],
243
- command: "project.openRecent",
244
- kind: "project",
245
- label: project.name,
246
- title: project.projectRoot,
247
- });
248
- }
249
-
250
- // ONE document per tab. This used to walk `session.documentStack` and emit a `subdocument`
251
- // Segment per frame, each one a `document.setStackLevel` you could click back to — but nothing
252
- // In `src/` ever pushed a frame, so the loop had exactly one iteration in the shipped app and the
253
- // Address it drew was always this line. A tab holds a document; drilling in opens another tab.
256
+ // ONE document per tab, and no project segment before it: the status bar's PROJECT field sits
257
+ // Directly to the left of this trail and already names the project, with the same
258
+ // `project.openRecent` command. The document segment used to follow a `subdocument` segment per
259
+ // `session.documentStack` frame, each one a `document.setStackLevel` you could click back to —
260
+ // But nothing in `src/` ever pushed a frame. A tab holds a document; drilling in opens another
261
+ // Tab.
254
262
  /* In a DERIVED pane the leading verb is Keep, not Open.
255
263
  The address is still the document — a lens draws the source pane's, a companion its own — but
256
264
  the question an author has about a pane that is following something is "can I stop it
@@ -258,13 +266,15 @@ export function jumpSegments(
258
266
  lens: `projectStep` already renders a step whose command is disabled as a disabled button with
259
267
  `disabledReason` in the tooltip, so the sentence explaining why Code, Diff and breakpoint views
260
268
  cannot be pinned arrives for free. */
261
- segments.push({
262
- choices: [],
263
- command: derived ? "pane.pin" : "palette.openFiles",
264
- kind: "file",
265
- label: documentLabel(tab.documentPath),
266
- title: tab.documentPath ?? "Not saved to disk yet",
267
- });
269
+ const segments: JumpSegment[] = [
270
+ {
271
+ choices: [],
272
+ command: derived ? "pane.pin" : "palette.openFiles",
273
+ kind: "file",
274
+ label: documentLabel(tab.documentPath),
275
+ title: tab.documentPath ?? "Not saved to disk yet",
276
+ },
277
+ ];
268
278
 
269
279
  // A logic editor is the leaf of the address, and it has no selection under it: the Monaco buffer
270
280
  // And the formula workspace are editing a definition, not a node of the document tree.
@@ -377,8 +387,8 @@ function projectStep(
377
387
 
378
388
  // ─── Rendering ───────────────────────────────────────────────────────────────
379
389
 
380
- /** One pane's bar: the document mounted in its cell, and the address it last drew. */
381
- interface PaneBar {
390
+ /** The bar: the document mounted in the status bar's slot, and the address it last drew. */
391
+ interface Bar {
382
392
  host: HTMLElement;
383
393
  surface: JumpBarSurface;
384
394
  /** The segments behind the projection, by key — what `run` and `choose` resolve against. */
@@ -386,14 +396,13 @@ interface PaneBar {
386
396
  }
387
397
 
388
398
  /**
389
- * Where each pane's bar is mounted. One entry per drawn cell.
399
+ * Where the bar is mounted, once the status bar has a slot for it.
390
400
  *
391
- * A Map rather than a `let _host`, because the bar addresses a PANE: it prints where you are, and
392
- * with two panes on screen there are two answers. `panels/pane-grid.ts` attaches a cell's
393
- * `.pane-jump` as the cell is built and detaches it as the cell is disposed, which is the same
394
- * hand-over `panels/frontmatter-panel.ts` takes from the stage.
401
+ * ONE bar, not one per pane: the status bar is one row for the window, so the trail addresses the
402
+ * focused pane. `surfaces/statusbar.ts` attaches its `trail` slot when its document mounts and
403
+ * detaches it when it is disposed.
395
404
  */
396
- const _bars = new Map<string, PaneBar>();
405
+ let _bar: Bar | null = null;
397
406
 
398
407
  let _scope: EffectScope | null = null;
399
408
 
@@ -414,6 +423,9 @@ export function dismissJumpMenu(): void {
414
423
  * nothing here draws a panel: `surfaces/menu.ts` owns the roving caret, typeahead, Escape and light
415
424
  * dismissal, because a list of commands is what that surface already is: a second one is the defect
416
425
  * studio-ui-guidelines.md §12.5 names.
426
+ *
427
+ * The menu hangs ABOVE the chevron. The bar is the window's floor, so there is nothing below it;
428
+ * `place` is the same answer in coordinates, for an engine that cannot anchor a popover.
417
429
  */
418
430
  function openChoices(segment: JumpSegment, anchor: HTMLElement | null): void {
419
431
  const registry = activeRegistry();
@@ -442,6 +454,8 @@ function openChoices(segment: JumpSegment, anchor: HTMLElement | null): void {
442
454
  }
443
455
  },
444
456
  opener: anchor,
457
+ placement: ABOVE_OPENER,
458
+ ...(anchor ? { place: (box: DOMRect) => aboveOpener(anchor, box) } : {}),
445
459
  region: "jump-bar",
446
460
  rows,
447
461
  });
@@ -449,33 +463,17 @@ function openChoices(segment: JumpSegment, anchor: HTMLElement | null): void {
449
463
  }
450
464
 
451
465
  /**
452
- * Keep the stage clear of the bar.
453
- *
454
- * The same one-projection-one-variable shape `pane-context.ts` uses for its own band, and for the
455
- * same reason: the stage's offset is the SUM of the two, so a pane with no tab open (no bar) costs
456
- * the welcome screen no dead band.
457
- *
458
- * **The variable is written on the PANE, not on `:root`** — when there is a pane to write it on.
459
- * Two cells have two bars and two stages, and one document-level number would offset both by
460
- * whichever pane painted last: a side pane showing a document would push the primary's welcome
461
- * screen down by 24px, and closing the last tab in one pane would lift the other's document under
462
- * its own bar. `.pane-stage` reads it from its cell by cascade. A host outside a cell (the tests,
463
- * and the bare offset callers) still writes the root, which is exactly what it meant before.
464
- *
465
- * @param {number} height Bar height in px. `0` when the pane has no address to print.
466
- * @param {HTMLElement | null} [host] The bar's host. Its cell takes the variable when it has one.
466
+ * The menu's corner when it hangs above `anchor`: its bottom on the anchor's top, leading edges
467
+ * aligned.
467
468
  */
468
- export function applyJumpBarOffset(height: number, host?: HTMLElement | null): void {
469
- const target = host?.closest<HTMLElement>(PANE_SELECTOR) ?? document.documentElement;
470
- target.style.setProperty(JUMP_BAR_VAR, `${height}px`);
469
+ export function aboveOpener(anchor: HTMLElement, box: DOMRect): { x: number; y: number } {
470
+ const rect = rectOf(anchor);
471
+ return { x: Math.round(rect.left), y: Math.max(0, Math.round(rect.top - box.height)) };
471
472
  }
472
473
 
473
- /**
474
- * The address one pane's document should be showing, and the segments behind it.
475
- *
476
- * @param {string} paneId
477
- */
478
- function projectPane(paneId: string): { steps: ProjectedStep[]; segments: JumpSegment[] } {
474
+ /** The address of the focused pane, and the segments behind it. */
475
+ function projectFocused(): { steps: ProjectedStep[]; segments: JumpSegment[] } {
476
+ const paneId = activePane().id;
479
477
  const segments = jumpSegments(tabOfPane(paneId), derivationOfPane(paneId));
480
478
  const registry = activeRegistry();
481
479
  return {
@@ -486,65 +484,52 @@ function projectPane(paneId: string): { steps: ProjectedStep[]; segments: JumpSe
486
484
  };
487
485
  }
488
486
 
489
- /**
490
- * Paint one pane's bar. Exported because the bootstrap paints once before mounting the effect.
491
- *
492
- * @param {string} [paneId] Defaults to every attached pane — what "render the jump bar" means when
493
- * the caller is a lifecycle rather than a pane.
494
- */
495
- export function renderJumpBar(paneId?: string): void {
496
- if (_bars.size === 0) {
487
+ /** Paint the bar. Exported because the bootstrap paints once before mounting the effect. */
488
+ export function renderJumpBar(): void {
489
+ if (!_bar) {
497
490
  return;
498
491
  }
499
492
  // The address changed, so an open menu is describing a place that may no longer be on the bar.
500
493
  // Repaints happen only when tracked state moves, so this cannot close a menu you are reading.
501
494
  dismissJumpMenu();
502
- for (const [id, bar] of _bars) {
503
- if (paneId !== undefined && id !== paneId) {
504
- continue;
505
- }
506
- const { segments, steps } = projectPane(id);
507
- bar.segments = new Map(steps.map((step, i) => [step.key, segments[i]!]));
508
- bar.surface.update(steps);
509
- applyJumpBarOffset(steps.length === 0 ? 0 : JUMP_BAR_HEIGHT, bar.host);
510
- }
495
+ const { segments, steps } = projectFocused();
496
+ _bar.segments = new Map(steps.map((step, i) => [step.key, segments[i]!]));
497
+ _bar.surface.update(steps);
511
498
  }
512
499
 
513
500
  /**
514
- * Give a pane's bar somewhere to paint, or take it away.
501
+ * Give the bar somewhere to paint, or take it away.
515
502
  *
516
- * Called by `panels/pane-grid.ts` as a cell is built and as it is disposed. Detaching disposes the
517
- * mount first: a cell being removed still has this bar's document in it, and the runtime that owns
518
- * that DOM is about to be unreachable.
503
+ * Called by `surfaces/statusbar.ts` as its document mounts and as it is disposed. Detaching
504
+ * disposes the mount first: the slot being removed still has this bar's document in it, and the
505
+ * runtime that owns that DOM is about to be unreachable.
519
506
  *
520
- * @param {string} paneId
521
507
  * @param {HTMLElement | null} host
522
508
  */
523
- export function attachJumpBarHost(paneId: string, host: HTMLElement | null): void {
524
- const previous = _bars.get(paneId);
525
- if (previous?.host === host) {
509
+ export function attachJumpBarHost(host: HTMLElement | null): void {
510
+ if (_bar?.host === host) {
526
511
  return;
527
512
  }
528
- if (previous) {
529
- previous.surface.dispose();
530
- applyJumpBarOffset(0, previous.host);
531
- _bars.delete(paneId);
513
+ if (_bar) {
514
+ dismissJumpMenu();
515
+ _bar.surface.dispose();
516
+ _bar = null;
532
517
  }
533
518
  if (!host) {
534
519
  return;
535
520
  }
536
- const bar: PaneBar = {
521
+ _bar = {
537
522
  host,
538
523
  segments: new Map(),
539
- surface: mountJumpBarSurface(host, paneRegion(paneId, "jump"), {
524
+ surface: mountJumpBarSurface(host, JUMP_BAR_REGION, {
540
525
  choose: (key, anchor) => {
541
- const segment = _bars.get(paneId)?.segments.get(key);
526
+ const segment = _bar?.segments.get(key);
542
527
  if (segment) {
543
528
  openChoices(segment, anchor);
544
529
  }
545
530
  },
546
531
  run: (key) => {
547
- const segment = _bars.get(paneId)?.segments.get(key);
532
+ const segment = _bar?.segments.get(key);
548
533
  const registry = activeRegistry();
549
534
  if (segment?.command && registry) {
550
535
  void runReported(registry, segment.command, segment.args, "Jump Bar");
@@ -552,22 +537,18 @@ export function attachJumpBarHost(paneId: string, host: HTMLElement | null): voi
552
537
  },
553
538
  }),
554
539
  };
555
- _bars.set(paneId, bar);
556
- renderJumpBar(paneId);
540
+ renderJumpBar();
557
541
  }
558
542
 
559
543
  /**
560
- * Subscribe the bar to the state it renders. Idempotent.
544
+ * Subscribe the bar to the state it renders. Idempotent: a second call replaces the effect.
561
545
  *
562
- * `host` is the PRIMARY pane's, the same bargain `panels/tab-strip.ts`'s `mount` makes: the
563
- * bootstrap holds the primary's cell and hands it over, and every other pane's host arrives from
564
- * the grid through {@link attachJumpBarHost}.
565
- *
566
- * @param {HTMLElement} host
546
+ * Called by `surfaces/statusbar.ts`'s `mountStatusbar`, which owns the bar's lifecycle because the
547
+ * trail is its DOCUMENT field. The host arrives separately ({@link attachJumpBarHost}), and either
548
+ * may come first: the effect paints whatever host is attached, and attaching paints at once.
567
549
  */
568
- export function mountJumpBar(host: HTMLElement): void {
569
- unmountJumpBar();
570
- attachJumpBarHost(PRIMARY_PANE, host);
550
+ export function mountJumpBar(): void {
551
+ _scope?.stop();
571
552
  _scope = effectScope();
572
553
  _scope.run(() => {
573
554
  effect(() => {
@@ -575,14 +556,11 @@ export function mountJumpBar(host: HTMLElement): void {
575
556
  // Reading it here is what repaints the bar from a skeleton into the real thing.
576
557
  void activeRegistry();
577
558
  void projectState?.name;
578
- /* EVERY pane's tab, not the focused one's. Two bars print two addresses, and the side pane's
579
- has to repaint when its own document moves — an effect that tracked `activeTab` alone left
580
- the unfocused bar frozen on whatever it last said. */
581
- for (const pane of workspace.panes) {
582
- const tab = tabOfPane(pane.id);
583
- if (!tab) {
584
- continue;
585
- }
559
+ // The FOCUSED pane's tab: clicking into the other pane re-points the trail.
560
+ void workspace.activePaneId;
561
+ void workspace.panes.length;
562
+ const tab = tabOfPane(activePane().id);
563
+ if (tab) {
586
564
  void tab.doc.document;
587
565
  void tab.documentPath;
588
566
  void tab.session.selection.map((path) => path.join("/")).join("|");
@@ -594,21 +572,17 @@ export function mountJumpBar(host: HTMLElement): void {
594
572
  });
595
573
  }
596
574
 
575
+ /** Stop the effect and take the bar down. */
597
576
  export function unmountJumpBar(): void {
598
577
  dismissJumpMenu();
599
578
  _scope?.stop();
600
579
  _scope = null;
601
- for (const bar of _bars.values()) {
602
- bar.surface.dispose();
603
- applyJumpBarOffset(0, bar.host);
604
- }
605
- _bars.clear();
606
- applyJumpBarOffset(0);
580
+ attachJumpBarHost(null);
607
581
  }
608
582
 
609
583
  // ─── This bar contributes no commands ────────────────────────────────────────
610
584
  // It declared one, `document.setStackLevel` — "leave a sub-document by naming the level you want to
611
585
  // Be at". Its `enablement` read `session.documentStack.length > 0`, a stack nothing could ever push
612
586
  // To, so the command was permanently disabled and its palette entry permanently unreachable. Every
613
- // Id the bar renders now belongs to the surface that owns the behaviour: `project.openRecent`,
614
- // `palette.openFiles`, `selection.set`.
587
+ // Id the bar renders now belongs to the surface that owns the behaviour: `palette.openFiles`,
588
+ // `pane.pin`, `selection.set`.
@@ -303,10 +303,9 @@ export function unmount() {
303
303
  * welcome screen (no tab, no bar) does not open under a 28px gap nothing explains, and a read-only
304
304
  * banner does not sit on top of the document it is warning you about.
305
305
  *
306
- * **On the PANE, not on `:root`**, for the reason `panels/jump-bar.ts` gives at the same seam: two
307
- * cells have two bands of different heights, and a single document-level number offsets both stages
308
- * by whichever pane painted last. A host outside a cell writes the root, which is what it meant
309
- * when the shell had one bar.
306
+ * **On the PANE, not on `:root`**: two cells have two bands of different heights, and a single
307
+ * document-level number offsets both stages by whichever pane painted last. A host outside a cell
308
+ * writes the root, which is what it meant when the shell had one bar.
310
309
  *
311
310
  * @param {number} height Band height in px. `0` when the pane has no chrome.
312
311
  * @param {HTMLElement | null} [host] The bar's host. Its cell takes the variable when it has one.
@@ -409,7 +408,8 @@ export function dismissPresetMenu(): void {
409
408
  *
410
409
  * The leading slot existed to push the three axes right. It is where §18.4's preset menu goes
411
410
  * because the menu is about THIS PANE and the bar is the pane's own chrome; the alternative homes
412
- * (the tab strip, the jump bar) are about a document and an address respectively.
411
+ * (the tab strip, the status bar's breadcrumb trail) are about a document and an address
412
+ * respectively.
413
413
  *
414
414
  * **There is no `pane.showDerivePresets`.** §13.5, quoted verbatim in `canvas/canvas-render.ts`:
415
415
  * opening a menu to press an item names a CONTROL; the item is the command. Every row here runs
@@ -625,9 +625,9 @@ export function presetRows(paneId: string): PresetRow[] {
625
625
  * the stage and the zoom pod still has something to zoom. Suppressing them while the dock was open
626
626
  * removed the controls for the very document the reader could still see.
627
627
  *
628
- * **There is no breadcrumb either.** The address is the jump bar's job — `panels/jump-bar.ts`, one
629
- * row above — and the Logic tab's own header carries the Close. This bar drew a second Back and a
630
- * second trail beside both of them.
628
+ * **There is no breadcrumb either.** The address is the jump bar's job — `panels/jump-bar.ts`, the
629
+ * status bar's trail — and the Logic tab's own header carries the Close. This bar drew a second
630
+ * Back and a second trail beside both of them.
631
631
  */
632
632
  function viewFor(tab: Tab, paneId: string, ctx: PaneContextCtx): PaneContextView {
633
633
  /* A LENS suppresses the two axes that WRITE. Editor kind and Canvas view both land in