@deepseek-insight/dsi 0.5.19 → 0.5.20

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 (88) hide show
  1. package/.agents/skills/dsi-ov-setup/SKILL.md +114 -0
  2. package/bin/dsi.mjs +62 -2
  3. package/build/client/_app/immutable/assets/{2.Da1CnqbG.css → 2.Dk-hwaP_.css} +1 -1
  4. package/build/client/_app/immutable/chunks/CodI3RGj.js +1 -0
  5. package/build/client/_app/immutable/chunks/{CLNDb8QN.js → CxmtCU9s.js} +1 -1
  6. package/build/client/_app/immutable/chunks/DKDzTags.js +2 -0
  7. package/build/client/_app/immutable/chunks/{Dv2f6AVG.js → azDKH4zi.js} +1 -1
  8. package/build/client/_app/immutable/chunks/qIBjcgkE.js +1 -0
  9. package/build/client/_app/immutable/entry/{app.C8CwtYTr.js → app.CqYGeqRv.js} +2 -2
  10. package/build/client/_app/immutable/entry/start.CIXiL_7f.js +1 -0
  11. package/build/client/_app/immutable/nodes/{0.VWq3h0rH.js → 0.u99aBgpL.js} +1 -1
  12. package/build/client/_app/immutable/nodes/{1.Dnl1P2aB.js → 1.qcRcZCY6.js} +1 -1
  13. package/build/client/_app/immutable/nodes/2.GgqfB5rv.js +171 -0
  14. package/build/client/_app/version.json +1 -1
  15. package/build/handler.js +4 -4
  16. package/build/index.js +4 -4
  17. package/build/server/chunks/chunks/{app-config.svelte.js-rN4N5yAI.js → app-config.svelte.js-CFb4kaFl.js} +7 -3
  18. package/build/server/chunks/chunks/{db.js-DbbYS9o8.js → db.js-9wP1ySFv.js} +2 -2
  19. package/build/server/chunks/chunks/{db2.js-BQWa7bEG.js → db2.js-B_Un4Zvq.js} +7 -5
  20. package/build/server/chunks/chunks/{dsh-connection.js-CpcCtA42.js → dsh-connection.js-CwL9I98w.js} +4 -3
  21. package/build/server/chunks/chunks/{git-probe.js-CvkToKO4.js → git-probe.js-C7z6xDzg.js} +3 -3
  22. package/build/server/chunks/chunks/{insight-config.js-Dw_Qi0fO.js → insight-config.js-COS9LOWZ.js} +13 -4
  23. package/build/server/chunks/chunks/{internal.js-BDS_h7zE.js → internal.js-Bf2uRIVS.js} +2 -2
  24. package/build/server/chunks/chunks/{permission-state.js-yvwUSnug.js → permission-state.js-BoJAoWqi.js} +18 -15
  25. package/build/server/chunks/chunks/settings-home.js-CqnqJVOu.js +9 -0
  26. package/build/server/chunks/chunks/settings-home2.js-f75-B6RI.js +102 -0
  27. package/build/server/chunks/chunks/{state.js-eZmA7eF7.js → state.js-OWX1K3Yj.js} +2 -2
  28. package/build/server/chunks/chunks/{watcher.js-C97Xyorx.js → watcher.js-Dc9hzRLI.js} +4 -4
  29. package/build/server/chunks/entries/endpoints/api/a2a/_id_/{_server.ts.js-Cqh1iqgu.js → _server.ts.js-0rfV7w20.js} +4 -3
  30. package/build/server/chunks/entries/endpoints/api/a2a/{_server.ts.js-DNKD4rZu.js → _server.ts.js-DX0DUIja.js} +6 -5
  31. package/build/server/chunks/entries/endpoints/api/a2a/register/{_server.ts.js-CJ-FwkrZ.js → _server.ts.js-zB-V9Naa.js} +6 -5
  32. package/build/server/chunks/entries/endpoints/api/config/{_server.ts.js-CMM4kYVx.js → _server.ts.js-Bo4VgIna.js} +5 -3
  33. package/build/server/chunks/entries/endpoints/api/dsh/directory/{_server.ts.js-epVplzwB.js → _server.ts.js-_Wc4NU0Q.js} +4 -3
  34. package/build/server/chunks/entries/endpoints/api/dsh/pick-directory/{_server.ts.js-bXHb97gT.js → _server.ts.js-CvoINUYy.js} +4 -3
  35. package/build/server/chunks/entries/endpoints/api/dsh/presets/{_server.ts.js-BNOHhtzy.js → _server.ts.js-B4UwZ8nd.js} +4 -3
  36. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/attachment/{_server.ts.js-BBi6jeVj.js → _server.ts.js-Hml2J0Bq.js} +4 -3
  37. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/cancel/{_server.ts.js-N0nKPuqL.js → _server.ts.js-CsZ2u8cs.js} +4 -3
  38. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/catalog/{_server.ts.js-VGFCPAbs.js → _server.ts.js-BhF1Nzig.js} +4 -3
  39. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/command/{_server.ts.js-na_sUb0Z.js → _server.ts.js-BGXjV_Ak.js} +4 -3
  40. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/events/{_server.ts.js-CeNf028P.js → _server.ts.js-BF4vBIja.js} +5 -4
  41. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/fork/{_server.ts.js-DfqO-GO4.js → _server.ts.js-B2OwrEQD.js} +4 -3
  42. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/goal/{_server.ts.js-Dj9GYNl7.js → _server.ts.js-DboY_7cp.js} +4 -3
  43. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/history/{_server.ts.js-DqShsRl9.js → _server.ts.js-BpioYdnc.js} +4 -3
  44. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/models/{_server.ts.js-BQSBUf70.js → _server.ts.js-CLrgUoq0.js} +4 -3
  45. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/path/{_server.ts.js-CeCKDyKB.js → _server.ts.js-BlZr_mA6.js} +3 -2
  46. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/permission/{_server.ts.js-CadTCNUJ.js → _server.ts.js-BiU_IgYD.js} +4 -3
  47. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/prompt/{_server.ts.js-BXzgi8xX.js → _server.ts.js-FV-4ougZ.js} +4 -3
  48. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/rename/{_server.ts.js-BfD5FXiL.js → _server.ts.js-ChxB0XU5.js} +4 -3
  49. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/respond/{_server.ts.js-CiDPC9TY.js → _server.ts.js-DkSydg_X.js} +4 -3
  50. package/build/server/chunks/entries/endpoints/api/dsh/session/_sessionId_/select-model/{_server.ts.js-DGcZgN96.js → _server.ts.js-Wbuf-vse.js} +4 -3
  51. package/build/server/chunks/entries/endpoints/api/dsh/sessions/{_server.ts.js-DnU1CCOw.js → _server.ts.js-B-BfCKKL.js} +4 -3
  52. package/build/server/chunks/entries/endpoints/api/dsh/workspace-file/{_server.ts.js-BZ0UV1n2.js → _server.ts.js-fKfv5g19.js} +4 -3
  53. package/build/server/chunks/entries/endpoints/api/dsh/workspace-file-bytes/{_server.ts.js-lxLpuPFL.js → _server.ts.js-DZpRzvas.js} +4 -3
  54. package/build/server/chunks/entries/endpoints/api/dsh/workspace-tree/{_server.ts.js-Cw3vl704.js → _server.ts.js-Hn5-fnsU.js} +4 -3
  55. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/{_server.ts.js-KKnR2UO4.js → _server.ts.js-CtPDRjV7.js} +4 -3
  56. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/delete/{_server.ts.js-I1eSZn60.js → _server.ts.js-DOp3nIcK.js} +4 -3
  57. package/build/server/chunks/entries/endpoints/api/dsh/workspaces/_workspaceId_/rename/{_server.ts.js-B0zqAKnV.js → _server.ts.js-DiwcZfK9.js} +4 -3
  58. package/build/server/chunks/entries/endpoints/api/prompts/_id_/{_server.ts.js-C-QN0enK.js → _server.ts.js-SoNSMvWj.js} +4 -3
  59. package/build/server/chunks/entries/endpoints/api/prompts/{_server.ts.js-CwTGUPIp.js → _server.ts.js-DIZiiHk-.js} +12 -4
  60. package/build/server/chunks/entries/endpoints/api/prompts/use/{_server.ts.js-Bvotxm37.js → _server.ts.js-CbsUk0fk.js} +4 -3
  61. package/build/server/chunks/entries/endpoints/api/settings/{_server.ts.js-CRRLXk0y.js → _server.ts.js-DCeIpzKT.js} +3 -2
  62. package/build/server/chunks/entries/endpoints/api/settings-home/file/_server.ts.js-C1gSFX1P.js +68 -0
  63. package/build/server/chunks/entries/endpoints/api/settings-home/file-write/_server.ts.js-Dx0glK_q.js +68 -0
  64. package/build/server/chunks/entries/endpoints/api/settings-home/tree/_server.ts.js-CAmn2SKB.js +61 -0
  65. package/build/server/chunks/entries/endpoints/api/workspace/file-write/{_server.ts.js-DY_NDlYl.js → _server.ts.js-Blw1UFX2.js} +6 -5
  66. package/build/server/chunks/entries/endpoints/api/workspace/git-events/{_server.ts.js-DHEaymrG.js → _server.ts.js-yflU8VH2.js} +6 -5
  67. package/build/server/chunks/entries/endpoints/api/workspace/git-file-head/{_server.ts.js-AQgNkfCV.js → _server.ts.js-q9jXuJ1p.js} +10 -18
  68. package/build/server/chunks/entries/endpoints/api/workspace/git-map/{_server.ts.js-C9vxPbB3.js → _server.ts.js-YjwnbPfG.js} +12 -20
  69. package/build/server/chunks/entries/endpoints/api/workspace/git-status/{_server.ts.js-BlMRR11v.js → _server.ts.js-DRQay15h.js} +12 -20
  70. package/build/server/chunks/entries/endpoints/api/workspace/write-gate/_server.ts.js-BiCzLyNH.js +54 -0
  71. package/build/server/chunks/entries/pages/{_error.svelte.js-DIRRk8AK.js → _error.svelte.js-DkmmDHjP.js} +3 -3
  72. package/build/server/chunks/entries/pages/{_page.server.ts.js-ze2dUhXW.js → _page.server.ts.js-BHW1mzYQ.js} +3 -3
  73. package/build/server/chunks/entries/pages/{_page.svelte.js-CYT_NWww.js → _page.svelte.js-BSt3-Adz.js} +993 -532
  74. package/build/server/chunks/{handler-jvQdYpdY.js → handler-CEAxnB4f.js} +3 -3
  75. package/build/server/chunks/{index.js-BGLJR092.js → index.js-B-3rUYGC.js} +2 -2
  76. package/build/server/chunks/{manifest.js-CIb6Wm5j.js → manifest.js--ZDTDlzU.js} +71 -43
  77. package/build/server/chunks/nodes/{0.js-CQ3k21rg.js → 0.js-BQXxAZac.js} +2 -2
  78. package/build/server/chunks/nodes/{1.js-NI6SZ6un.js → 1.js-DMcugevB.js} +3 -3
  79. package/build/server/chunks/nodes/{2.js-BG_HkTrm.js → 2.js-BrT7uCAN.js} +9 -8
  80. package/package.json +2 -2
  81. package/template/.dsi/config.json +2 -2
  82. package/template/.dsi/prompts.sqlite +0 -0
  83. package/template/.dsi/settings.yaml +2 -1
  84. package/build/client/_app/immutable/chunks/BeeAmuEs.js +0 -2
  85. package/build/client/_app/immutable/chunks/BgF4MPCH.js +0 -1
  86. package/build/client/_app/immutable/chunks/h_AAZIXW.js +0 -1
  87. package/build/client/_app/immutable/entry/start.CCr7WSxA.js +0 -1
  88. package/build/client/_app/immutable/nodes/2.0fqLf_Nw.js +0 -170
@@ -1,13 +1,13 @@
1
- import { c as onDestroy } from '../../chunks/internal.js-BDS_h7zE.js';
2
- import { V as head, y as derived, X as bind_props, W as escape_html, Y as attr, Z as attr_style, _ as attr_class, x as setContext, $ as spread_props, a0 as attributes, a1 as clsx, a2 as ensure_array_like, a3 as element, J as getContext, a4 as stringify, a5 as html, a6 as props_id, F as run } from '../../chunks/server.js-11-pxAWV.js';
1
+ import { c as onDestroy } from '../../chunks/internal.js-Bf2uRIVS.js';
2
+ import { V as head, y as derived, X as bind_props, W as escape_html, Y as attr, Z as attr_style, _ as attr_class, x as setContext, $ as spread_props, a0 as attributes, a1 as clsx, a2 as ensure_array_like, a3 as element, J as getContext, a4 as stringify, a5 as html, F as run, a6 as props_id } from '../../chunks/server.js-11-pxAWV.js';
3
3
  import { U as UI_LOCALES } from '../../chunks/config.js-iG_1bK3r.js';
4
4
  import { m as mergeEntries, c as collapseSystemPromptEntries, t as truncate } from '../../chunks/dsh-events.js-BtDxaV6Q.js';
5
5
  import { p as parseA2aSignature, m as mintA2aId, b as buildDeliveredText } from '../../chunks/a2a-protocol.js-C77mVg2Q.js';
6
- import { b as applyKnobEvent, E as EMPTY_KNOBS, r as resolvePermission, l as localPermission, c as presetLabel, d as presetDescription } from '../../chunks/permission-state.js-yvwUSnug.js';
6
+ import { b as applyKnobEvent, E as EMPTY_KNOBS, r as resolvePermission, l as localPermission, c as presetLabel, d as presetDescription } from '../../chunks/permission-state.js-BoJAoWqi.js';
7
7
  import { D as DEFAULT_IMAGE_LIMITS } from '../../chunks/image-limits.js-CUdJ0f7r.js';
8
- import { a as appConfig, t as textPreviewOf, b as triggerMode, g as getTrigger, p as previewSegments, i as isValidTagWord, s as searchKey, c as shouldRecordPrompt } from '../../chunks/app-config.svelte.js-rN4N5yAI.js';
8
+ import { a as appConfig, t as textPreviewOf, b as triggerMode, g as getTrigger, p as previewSegments, i as isValidTagWord, s as searchKey, c as shouldRecordPrompt } from '../../chunks/app-config.svelte.js-CFb4kaFl.js';
9
9
  import { c as getLocale } from '../../chunks/runtime.js-BPLc5RLR.js';
10
- import { p as page, a as afterNavigate, t, r as replaceState, c as currentLocale } from '../../chunks/state.js-eZmA7eF7.js';
10
+ import { p as page, a as afterNavigate, t, r as replaceState, c as currentLocale } from '../../chunks/state.js-OWX1K3Yj.js';
11
11
  import 'yaml';
12
12
  import 'html-to-image';
13
13
  import hljs from 'highlight.js/lib/core';
@@ -1535,6 +1535,34 @@ var fileeyeexternalchange3 = ((inputs = {}, options = {}) => {
1535
1535
  return en_fileeyeexternalchange3();
1536
1536
  });
1537
1537
  //#endregion
1538
+ //#region src/lib/paraglide/messages/fileeyenewfile3.js
1539
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1540
+ /** @typedef {{}} Fileeyenewfile3Inputs */
1541
+ var en_fileeyenewfile3 = () => {
1542
+ return `New file — nothing committed to diff against yet`;
1543
+ };
1544
+ var zh_fileeyenewfile3 = () => {
1545
+ return `新文件——尚无已提交版本可供对比`;
1546
+ };
1547
+ var id_fileeyenewfile3 = () => {
1548
+ return `Berkas baru — belum ada versi terkommit untuk dibandingkan`;
1549
+ };
1550
+ /**
1551
+ * | output |
1552
+ * | --- |
1553
+ * | "New file — nothing committed to diff against yet" |
1554
+ *
1555
+ * @param {Fileeyenewfile3Inputs} inputs
1556
+ * @param {{ locale?: "en" | "zh" | "id" }} options
1557
+ * @returns {LocalizedString}
1558
+ */
1559
+ var fileeyenewfile3 = ((inputs = {}, options = {}) => {
1560
+ const locale = options.locale ?? getLocale();
1561
+ if (locale === "zh") return zh_fileeyenewfile3();
1562
+ if (locale === "id") return id_fileeyenewfile3();
1563
+ return en_fileeyenewfile3();
1564
+ });
1565
+ //#endregion
1538
1566
  //#region src/lib/paraglide/messages/fileeyetogglelabel3.js
1539
1567
  /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
1540
1568
  /** @typedef {{}} Fileeyetogglelabel3Inputs */
@@ -6747,7 +6775,7 @@ function AppSidebar($$renderer, $$props) {
6747
6775
  * page (OCI ownership split); the gutter just reports mousedown, and the
6748
6776
  * single width clamp lives in sidebar-prefs (clampSidebarWidth).
6749
6777
  */
6750
- let { profile = null, collapsed = false, width = 280, embedded = false, currentSessionId, paneledSessionIds = [], captureContainer = null, onToggleCollapse} = $$props;
6778
+ let { profile = null, collapsed = false, width = 400, embedded = false, currentSessionId, paneledSessionIds = [], captureContainer = null, onToggleCollapse} = $$props;
6751
6779
  let filter = loadSessionFilter(profile);
6752
6780
  if (collapsed) {
6753
6781
  $$renderer.push("<!--[0-->");
@@ -8092,7 +8120,10 @@ function PromptManagerSearch($$renderer, $$props) {
8092
8120
  * (the host owns the 300ms debounce + refresh).
8093
8121
  */
8094
8122
  let { value = "", oninput } = $$props;
8095
- $$renderer.push(`<input data-svelte="PromptManagerSearch" type="text" class="mgr-search svelte-w3lpt8"${attr("placeholder", t(searchprompts1))}${attr("value", value)}/>`);
8123
+ $$renderer.push(`<textarea data-svelte="PromptManagerSearch" class="mgr-search svelte-w3lpt8" rows="2"${attr("placeholder", t(searchprompts1))}>`);
8124
+ const $$body = escape_html(value);
8125
+ if ($$body) $$renderer.push(`${$$body}`);
8126
+ $$renderer.push(`</textarea>`);
8096
8127
  bind_props($$props, { value });
8097
8128
  });
8098
8129
  }
@@ -8472,15 +8503,8 @@ function blockBounds(panels, idx, facts) {
8472
8503
  let i = idx;
8473
8504
  while (i > 0 && panels[i - 1].kind === "workspace-file") i--;
8474
8505
  const head = panels[i - 1];
8475
- let explorerIdx = -1;
8476
- if (at.kind === "workspace-explorer") explorerIdx = idx;
8477
- else if (head !== void 0 && head.kind === "workspace-explorer" && fileBelongsTo(at, head)) explorerIdx = i - 1;
8478
- if (explorerIdx >= 0) {
8479
- const explorer = panels[explorerIdx];
8480
- const family = explorer.kind === "workspace-explorer" ? unitOf(panels, explorer.sessionId, facts) : null;
8481
- if (family !== null && explorerIdx > family[0] && explorerIdx <= family[1]) return family;
8482
- return explorerUnit(panels, explorerIdx);
8483
- }
8506
+ if (at.kind === "workspace-explorer") return explorerUnit(panels, idx);
8507
+ if (head !== void 0 && head.kind === "workspace-explorer" && fileBelongsTo(at, head)) return explorerUnit(panels, i - 1);
8484
8508
  return [idx, idx];
8485
8509
  }
8486
8510
  if (at.kind !== "injected-doc") return [idx, idx];
@@ -8532,29 +8556,10 @@ function unitOf(panels, sessionId, facts) {
8532
8556
  end = i;
8533
8557
  continue;
8534
8558
  }
8535
- if (p.kind === "workspace-explorer" && familySessions.has(p.sessionId)) {
8536
- const wsUnit = explorerUnit(panels, i);
8537
- end = wsUnit[1];
8538
- i = wsUnit[1];
8539
- continue;
8540
- }
8541
8559
  break;
8542
8560
  }
8543
8561
  return [start, end];
8544
8562
  }
8545
- /**
8546
- * Insert index for AFTER-SOURCE placement (the fork child, 2026-09-01):
8547
- * directly below the panel showing `sourceSessionId` — the source keeps
8548
- * its slot, the newcomer takes the next one. The family clamp mirrors
8549
- * the Focused Slot's, downward: the slot rides PAST the source's whole
8550
- * family unit, so a fork never wedges between a spawner and its pinned
8551
- * children (I2 — families stay contiguous). Null when the source holds
8552
- * no panel; the caller falls back to its own default placement.
8553
- */
8554
- function afterSourceSlot(panels, sourceSessionId, facts) {
8555
- const unit = unitOf(panels, sourceSessionId, facts);
8556
- return unit === null ? null : unit[1] + 1;
8557
- }
8558
8563
  //#endregion
8559
8564
  //#region src/lib/services/conversation/injected-shelf.ts
8560
8565
  /** The synthetic shelf name for the conversation's system prompt (ADR D7)
@@ -8792,7 +8797,7 @@ function panelHeaderLabel(panel) {
8792
8797
  if (panel.kind === "prompt-manager") return MANAGER_ROW_TITLE;
8793
8798
  if (panel.kind === "settings-editor") return SETTINGS_ROW_TITLE[panel.target];
8794
8799
  if (panel.kind === "injected-doc") return injectedDocTitle(panel.displayPath);
8795
- if (panel.kind === "workspace-explorer") return workspaceExplorerTitle(panel.root);
8800
+ if (panel.kind === "workspace-explorer") return panel.title ?? workspaceExplorerTitle(panel.root);
8796
8801
  if (panel.kind === "workspace-file") return workspaceFileTitle(panel.path);
8797
8802
  }
8798
8803
  /** Open-panel session ids — floor membership for display facts AND the
@@ -8830,31 +8835,21 @@ function panelRowsFor(input) {
8830
8835
  canMoveUp: index > 0,
8831
8836
  canMoveDown: index < panels.length - 1
8832
8837
  };
8833
- if (p.kind === "workspace-explorer" || p.kind === "workspace-file") {
8834
- const convIdx = panels.findIndex((q) => q.kind === "conversation" && q.sessionId === p.sessionId);
8835
- const convDepth = convIdx >= 0 ? lineageFacts.depthById.get(p.sessionId) ?? 0 : null;
8836
- const explorerDepth = convDepth === null ? 0 : convDepth + 1;
8837
- const isFile = p.kind === "workspace-file";
8838
- let depth = explorerDepth;
8839
- if (isFile) depth = panels.some((q) => q.kind === "workspace-explorer" && q.sessionId === p.sessionId && (p.explorerPanelId === null || q.id === p.explorerPanelId)) ? explorerDepth + 1 : explorerDepth;
8840
- const parent = isFile ? panels.some((q) => q.kind === "workspace-explorer" && q.id === p.explorerPanelId) ? p.explorerPanelId ?? null : convIdx >= 0 ? p.sessionId : null : convIdx >= 0 ? p.sessionId : null;
8841
- return {
8842
- panel: p,
8843
- title: isFile ? workspaceFileTitle(p.path) : workspaceExplorerTitle(p.root),
8844
- workspace: null,
8845
- dead: false,
8846
- running: false,
8847
- depth,
8848
- runningDescendants: 0,
8849
- kind: "panel",
8850
- parentSessionId: parent,
8851
- fork: false,
8852
- wsChild: true,
8853
- spawnerTitle: null,
8854
- canMoveUp: false,
8855
- canMoveDown: false
8856
- };
8857
- }
8838
+ if (p.kind === "workspace-explorer" || p.kind === "workspace-file") return {
8839
+ panel: p,
8840
+ title: p.kind === "workspace-file" ? workspaceFileTitle(p.path) : p.title ?? workspaceExplorerTitle(p.root),
8841
+ workspace: null,
8842
+ dead: false,
8843
+ running: false,
8844
+ depth: 0,
8845
+ runningDescendants: 0,
8846
+ kind: "panel",
8847
+ parentSessionId: null,
8848
+ fork: false,
8849
+ spawnerTitle: null,
8850
+ canMoveUp: movePanelLineage(panels, p.id, "up", moveFacts) !== null,
8851
+ canMoveDown: movePanelLineage(panels, p.id, "down", moveFacts) !== null
8852
+ };
8858
8853
  if (p.kind === "injected-doc") return {
8859
8854
  panel: p,
8860
8855
  title: injectedDocTitle(p.displayPath),
@@ -9029,33 +9024,14 @@ function parseCommand(text) {
9029
9024
  }
9030
9025
  case "/promptmanager":
9031
9026
  case "/dsisettings":
9032
- case "/dshsettings":
9033
- if (/^(--add)$/.exec(args) === null) return {
9034
- type: token.slice(1),
9035
- args
9036
- };
9037
- return {
9038
- type: token.slice(1),
9039
- args: "",
9040
- addPanel: true
9041
- };
9042
- case "/loadinjected": {
9043
- if (args === "" || args === "?") return {
9044
- type: "loadinjected",
9045
- args
9046
- };
9047
- const m = /^(\S+)(?:\s+(--add))?$/.exec(args);
9048
- if (m === null || m[1].toLowerCase() === "--add") return {
9049
- type: "loadinjected",
9050
- args
9051
- };
9052
- return {
9053
- type: "loadinjected",
9054
- args: "",
9055
- filename: m[1],
9056
- ...m[2] !== void 0 ? { addPanel: true } : {}
9057
- };
9058
- }
9027
+ case "/dshsettings": return {
9028
+ type: token.slice(1),
9029
+ args
9030
+ };
9031
+ case "/loadinjected": return {
9032
+ type: "loadinjected",
9033
+ args
9034
+ };
9059
9035
  default: return null;
9060
9036
  }
9061
9037
  }
@@ -9098,6 +9074,7 @@ function commandHelpTopic(text) {
9098
9074
  if (trimmed.startsWith("@")) return /^@\S+\s+\?$/.test(trimmed) ? "mention" : null;
9099
9075
  const command = parseCommand(trimmed);
9100
9076
  if (command === null || command.type === "mention") return null;
9077
+ if (command.type === "loadinjected") return null;
9101
9078
  return command.args === "?" ? command.type : null;
9102
9079
  }
9103
9080
  /** The help card copy, per topic. Preset names mirror the shipped
@@ -9165,66 +9142,30 @@ var COMMAND_HELP = {
9165
9142
  ]
9166
9143
  },
9167
9144
  promptmanager: {
9168
- usage: "/promptmanager [--add]",
9169
- summary: "Opens the prompts manager as a floor panel beside your conversations. Default: it replaces this panel. With --add: it opens in a NEW panel to the right — and takes the focus (unlike /new --add, which leaves focus here).",
9170
- params: [{
9171
- name: "--add",
9172
- description: "Open the manager in a NEW panel placed to the RIGHT of the current panel and SELECT it (the documented divergence from /new --add: the fresh manager is the thing to read, so it takes the caret)."
9173
- }],
9145
+ usage: "/promptmanager",
9146
+ summary: "Aims the prompts manager panel: if one is already open it takes the focus; otherwise it opens in a NEW panel to the right of this conversation — and takes the focus. Nothing you have open is ever replaced.",
9147
+ params: [],
9174
9148
  examples: [{
9175
9149
  line: "/promptmanager",
9176
- description: "The prompts manager takes over this panel."
9177
- }, {
9178
- line: "/promptmanager --add",
9179
- description: "The prompts manager opens in a panel to the right — and is focused."
9150
+ description: "The prompts manager panel comes forward — opened beside this conversation if it was closed."
9180
9151
  }]
9181
9152
  },
9182
9153
  dsisettings: {
9183
- usage: "/dsisettings [--add]",
9184
- summary: "Opens a code editor for DSI’s own settings (~/.dsi/settings.yaml) as a floor panel. Default: it replaces this panel. With --add: it opens in a NEW panel to the right — and takes the focus. Saves are YAML-checked; edits apply without a restart.",
9185
- params: [{
9186
- name: "--add",
9187
- description: "Open the editor in a NEW panel placed to the RIGHT of the current panel and SELECT it (the same divergence as /promptmanager --add: the fresh editor is the thing to read)."
9188
- }],
9154
+ usage: "/dsisettings",
9155
+ summary: "Aims the DSI settings home explorer (~/.dsi, titled “DSI - Settings”): the open explorer takes the focus; otherwise a NEW explorer opens to the right of this conversation, selected. settings.yaml opens as a tab and stays editable; saves are YAML-checked on read.",
9156
+ params: [],
9189
9157
  examples: [{
9190
9158
  line: "/dsisettings",
9191
- description: "The DSI settings editor takes over this panel."
9192
- }, {
9193
- line: "/dsisettings --add",
9194
- description: "The DSI settings editor opens in a panel to the right — and is focused."
9159
+ description: "The DSI settings explorer comes forward — opened beside this conversation if it was closed."
9195
9160
  }]
9196
9161
  },
9197
9162
  dshsettings: {
9198
- usage: "/dshsettings [--add]",
9199
- summary: "Opens a code editor for the harness’s settings (~/.dsh/settings.yaml) as a floor panel. Default: it replaces this panel. With --add: it opens in a NEW panel to the right — and takes the focus. Saves are YAML-checked; the running harness picks edits up through its own file watcher.",
9200
- params: [{
9201
- name: "--add",
9202
- description: "Open the editor in a NEW panel placed to the RIGHT of the current panel and SELECT it (the same divergence as /promptmanager --add)."
9203
- }],
9163
+ usage: "/dshsettings",
9164
+ summary: "Aims the harness settings home explorer (~/.dsh, titled “DSH - Settings”): the open explorer takes the focus; otherwise a NEW explorer opens to the right of this conversation, selected. settings.yaml opens as a tab and stays editable; the running harness picks edits up through its own file watcher.",
9165
+ params: [],
9204
9166
  examples: [{
9205
9167
  line: "/dshsettings",
9206
- description: "The DSH settings editor takes over this panel."
9207
- }, {
9208
- line: "/dshsettings --add",
9209
- description: "The DSH settings editor opens in a panel to the right — and is focused."
9210
- }]
9211
- },
9212
- loadinjected: {
9213
- usage: "/loadinjected <filename.md> [--add]",
9214
- summary: "Re-opens a document this conversation already received — the system prompt or an injected instructions file — as a READ-ONLY panel showing what the model actually saw (the logged payload, never the live file). Default: it replaces this panel. With --add: it opens in a NEW panel BELOW this conversation — the document belongs to it — and takes the focus.",
9215
- params: [{
9216
- name: "<filename.md>",
9217
- description: "Which document to open — a shelf name like AGENTS.md or system-prompt.md. An exact name matches first; a bare basename works when it is unambiguous."
9218
- }, {
9219
- name: "--add",
9220
- description: "Open the document in a NEW panel placed BELOW this conversation’s panel and SELECT it (a document child of this conversation — maroon in the sidebar)."
9221
- }],
9222
- examples: [{
9223
- line: "/loadinjected AGENTS.md",
9224
- description: "Re-open the injected AGENTS.md in place of this panel."
9225
- }, {
9226
- line: "/loadinjected AGENTS.md --add",
9227
- description: "Re-open AGENTS.md in a panel below this conversation — and focus it."
9168
+ description: "The DSH settings explorer comes forward — opened beside this conversation if it was closed."
9228
9169
  }]
9229
9170
  },
9230
9171
  permission: {
@@ -9299,12 +9240,6 @@ var MENU_GESTURES = [
9299
9240
  seed: "/dshsettings ",
9300
9241
  description: COMMAND_HELP.dshsettings.summary
9301
9242
  },
9302
- {
9303
- name: "loadinjected",
9304
- display: "/loadinjected",
9305
- seed: "/loadinjected ",
9306
- description: COMMAND_HELP.loadinjected.summary
9307
- },
9308
9243
  {
9309
9244
  name: "@mention",
9310
9245
  display: "@mention",
@@ -14493,8 +14428,8 @@ async function executeCommand(command, ctx, onNote) {
14493
14428
  if (command.type === "permission") return runPermissionLine(`/permission${command.args === "" ? "" : " " + command.args}`, ctx, onNote);
14494
14429
  if (command.type === "workspace") return runWorkspaceLine(command.wsPath, command.wsName, ctx, onNote);
14495
14430
  if (command.type === "mention") return runMention(command.sessionId ?? "", command.args, command.a2aId, ctx, onNote);
14496
- if (command.type === "promptmanager") return runPromptManager(command.args, command.addPanel === true, ctx, onNote);
14497
- if (command.type === "dsisettings" || command.type === "dshsettings") return runSettingsEditor(command.type === "dsisettings" ? "dsi" : "dsh", "/" + command.type, command.args, command.addPanel === true, ctx, onNote);
14431
+ if (command.type === "promptmanager") return runPromptManager(command.args, ctx, onNote);
14432
+ if (command.type === "dsisettings" || command.type === "dshsettings") return runSettingsEditor(command.type === "dsisettings" ? "dsi" : "dsh", "/" + command.type, command.args, ctx, onNote);
14498
14433
  if (command.type === "loadinjected") return runLoadInjected(command.filename, command.addPanel === true, ctx, onNote);
14499
14434
  if (command.args !== "") {
14500
14435
  const usage = "usage: /new [@agent] [workspace] [--add]";
@@ -14609,19 +14544,16 @@ async function executeCommand(command, ctx, onNote) {
14609
14544
  }
14610
14545
  }
14611
14546
  /**
14612
- * /promptmanager (2026-09-06, ADR The Manager in the Panel D7): open the
14613
- * prompts manager as a floor panel from the composer.
14614
- * bare → REPLACE the panel the command ran in (the manager
14615
- * successor-swap — slot and width kept, fresh panel id)
14616
- * --add → NEW manager panel to the RIGHT of this session's panel,
14617
- * SELECTED (the D7 divergence: unlike /new --add's
14618
- * keepSelection, the newcomer takes the focus — it is the
14619
- * thing the operator is about to read)
14620
- * No wire call — the manager content talks to /api/prompts itself.
14547
+ * /promptmanager (re-pointed 2026-09-17, ADR The Focus Command D1/D2): AIM
14548
+ * the prompts manager panel from the composer — the floor's add path
14549
+ * FOCUSES the already-open manager (one live manager, D3) or opens a NEW
14550
+ * one right of this session's panel, selected. The bare successor-swap and
14551
+ * the --add flag are retired; the floor never replaces a panel for this
14552
+ * command. No wire call — the manager content talks to /api/prompts itself.
14621
14553
  */
14622
- async function runPromptManager(args, addPanel, ctx, onNote) {
14554
+ async function runPromptManager(args, ctx, onNote) {
14623
14555
  if (args !== "") {
14624
- const usage = "usage: /promptmanager [--add]";
14556
+ const usage = "usage: /promptmanager";
14625
14557
  onNote?.(false, usage);
14626
14558
  return {
14627
14559
  ok: false,
@@ -14636,20 +14568,9 @@ async function runPromptManager(args, addPanel, ctx, onNote) {
14636
14568
  note
14637
14569
  };
14638
14570
  }
14639
- if (addPanel) {
14640
- if (!addPanelFromSidebar({
14641
- afterSessionId: ctx.sessionId
14642
- })) {
14643
- const note = "/promptmanager: the floor is not mounted";
14644
- onNote?.(false, note);
14645
- return {
14646
- ok: false,
14647
- note
14648
- };
14649
- }
14650
- return { ok: true };
14651
- }
14652
- if (!replacePanelFromRegistry(ctx.panelId)) {
14571
+ if (!addPanelFromSidebar({
14572
+ afterSessionId: ctx.sessionId
14573
+ })) {
14653
14574
  const note = "/promptmanager: the floor is not mounted";
14654
14575
  onNote?.(false, note);
14655
14576
  return {
@@ -14660,16 +14581,18 @@ async function runPromptManager(args, addPanel, ctx, onNote) {
14660
14581
  return { ok: true };
14661
14582
  }
14662
14583
  /**
14663
- * /dsisettings and /dshsettings (2026-09-07, The Settings Panel ADR
14664
- * D3/D6): open the settings-editor panel for one target document —
14665
- * /promptmanager's grammar verbatim (bare = successor-swap, --add = new
14666
- * panel right + selected), one runner for both commands; the command
14667
- * name carries the target. No wire call — the editor content talks to
14668
- * /api/settings itself.
14584
+ * /dsisettings and /dshsettings (re-pointed by The Settings Tree ADR,
14585
+ * 2026-09-18, D2; aimed 2026-09-17, The Focus Command ADR D1): open a
14586
+ * SESSION-LESS workspace-explorer over the settings HOME folder (~/.dsi |
14587
+ * ~/.dsh), titled — one runner for both commands; the command name carries
14588
+ * the home. The floor's add path dedupes by root, so a repeat FOCUSES the
14589
+ * open home panel; the successor-swap and the --add flag are retired. No
14590
+ * wire call — the explorer content talks to the DSI-local
14591
+ * /api/settings-home routes itself.
14669
14592
  */
14670
- async function runSettingsEditor(target, commandName, args, addPanel, ctx, onNote) {
14593
+ async function runSettingsEditor(home, commandName, args, ctx, onNote) {
14671
14594
  if (args !== "") {
14672
- const usage = "usage: " + commandName + " [--add]";
14595
+ const usage = "usage: " + commandName;
14673
14596
  onNote?.(false, usage);
14674
14597
  return {
14675
14598
  ok: false,
@@ -14684,20 +14607,9 @@ async function runSettingsEditor(target, commandName, args, addPanel, ctx, onNot
14684
14607
  note
14685
14608
  };
14686
14609
  }
14687
- if (addPanel) {
14688
- if (!addPanelFromSidebar({
14689
- afterSessionId: ctx.sessionId
14690
- })) {
14691
- const note = commandName + ": the floor is not mounted";
14692
- onNote?.(false, note);
14693
- return {
14694
- ok: false,
14695
- note
14696
- };
14697
- }
14698
- return { ok: true };
14699
- }
14700
- if (!replacePanelFromRegistry(ctx.panelId)) {
14610
+ if (!addPanelFromSidebar({
14611
+ afterSessionId: ctx.sessionId
14612
+ })) {
14701
14613
  const note = commandName + ": the floor is not mounted";
14702
14614
  onNote?.(false, note);
14703
14615
  return {
@@ -14708,25 +14620,28 @@ async function runSettingsEditor(target, commandName, args, addPanel, ctx, onNot
14708
14620
  return { ok: true };
14709
14621
  }
14710
14622
  /**
14711
- * /loadinjected <filename.md> [--add] (2026-09-07, The Loadinjected ADR
14712
- * D2–D6): re-open one LOGGED injected document as a floor panel that
14713
- * belongs to this conversation.
14714
- * member resolution rides the injected-shelf selector over ctx.entries —
14715
- * exact displayPath first, then a unique basename; the executor never
14716
- * re-implements it (D4).
14717
- * --add → a NEW panel BELOW this conversation's panel, selected (D3);
14718
- * bare → replaces the panel it ran in (D4). Dedupe→focus is the FLOOR
14719
- * handler's D5 policy — the executor never pre-checks it, so command and
14720
- * anchor-click cannot diverge (D6: one write path to the floor).
14623
+ * /loadinjected — INTERNAL command (2026-09-17, The Retired Typed Command
14624
+ * ADR D1/D2; ex-/loadinjected <filename.md> [--add], The Loadinjected ADR
14625
+ * D2-D6): the shelf button composes it via the parser's
14626
+ * loadinjectedCommand() constructor — the typed surface is retired.
14627
+ * filename present (the constructor's shape) → resolve as the
14628
+ * Loadinjected ADR built it: member resolution rides the injected-shelf
14629
+ * selector over ctx.entries — exact displayPath first, then a unique
14630
+ * basename; the executor never re-implements it (D4). --add → a NEW
14631
+ * panel BELOW this conversation's panel, selected (D3); bare → replaces
14632
+ * the panel it ran in (D4). Dedupe→focus is the FLOOR handler's D5
14633
+ * policy — the executor never pre-checks it (D6: one write path).
14634
+ * filename absent (every TYPED shape) → the D2 retirement note pointing
14635
+ * at the InjectedShelfButton — honest, never a silent unknown-command.
14721
14636
  * No wire call — the payload is the record the panel reads at mount.
14722
14637
  */
14723
14638
  async function runLoadInjected(filename, addPanel, ctx, onNote) {
14724
14639
  if (filename === void 0) {
14725
- const usage = "usage: /loadinjected <filename.md> [--add]";
14726
- onNote?.(false, usage);
14640
+ const note = "/loadinjected retired — open injected documents with the Injected button on the conversation anchor";
14641
+ onNote?.(false, note);
14727
14642
  return {
14728
14643
  ok: false,
14729
- note: usage
14644
+ note
14730
14645
  };
14731
14646
  }
14732
14647
  if (ctx.panelId == null) {
@@ -15503,23 +15418,42 @@ function deriveSessionStats(entries) {
15503
15418
  * no module state. Imports only the parser primitives; the compiler is
15504
15419
  * table-testable precisely because its whole surface is this function.
15505
15420
  */
15506
- /** Is this trimmed line an instruction? (parseCommand-known or `?`.) */
15507
- function isInstruction(line) {
15508
- return parseCommand(line) !== null || line.startsWith("?");
15421
+ /** Is this trimmed line an instruction? (parseCommand-known or `?`.)
15422
+ * Under `slash` (the typed-run flag, ADR The Typed Run D2) a leading-`/`
15423
+ * line is ALWAYS a boundary — even when parseCommand returns null — so
15424
+ * one unknown `/token` line feeds as its own one-line send. */
15425
+ function isInstruction(line, slash = false) {
15426
+ return parseCommand(line) !== null || line.startsWith("?") || slash && line.startsWith("/");
15509
15427
  }
15510
15428
  /** Close the mention's message at the first blank line, the next
15511
15429
  * instruction line, or the end of the row — whichever comes first. */
15512
- function messageEndsAt(lines, from) {
15430
+ function messageEndsAt(lines, from, slash = false) {
15513
15431
  let j = from;
15514
- while (j < lines.length && lines[j] !== "" && !isInstruction(lines[j])) j += 1;
15432
+ while (j < lines.length && lines[j] !== "" && !isInstruction(lines[j], slash)) j += 1;
15515
15433
  return j;
15516
15434
  }
15517
15435
  /**
15436
+ * Is this draft a TYPED RUN? (ADR The Typed Run 2026-09-16, D1.) Pure:
15437
+ * true only when EVERY non-blank line is directive — starts with `/`
15438
+ * (command-or-skill attempt), `@` (mention), or `?` (shelf lookup) —
15439
+ * and there are at least TWO non-blank lines. Prose, mixed prose+slash
15440
+ * drafts, and every single-line draft fail and take today's whole-draft
15441
+ * path unchanged. Blank lines never count and never break the match;
15442
+ * per-line trim (same as the compiler) absorbs CRLF. No I/O, no
15443
+ * reactivity — table-testable like compileRow.
15444
+ */
15445
+ function isTypedRun(text) {
15446
+ const directives = text.split("\n").map((l) => l.trim()).filter((l) => l !== "");
15447
+ if (directives.length < 2) return false;
15448
+ return directives.every((l) => l.startsWith("/") || l.startsWith("@") || l.startsWith("?"));
15449
+ }
15450
+ /**
15518
15451
  * Compile a saved row's text into sections. Pure: the same text always
15519
15452
  * yields the same records; blank-only input yields an empty list (the
15520
15453
  * runner refuses an empty compile at start — no fetch fires, Wave 2).
15521
15454
  */
15522
- function compileRow(text) {
15455
+ function compileRow(text, opts = {}) {
15456
+ const slash = opts.slashBoundaries === true;
15523
15457
  const lines = text.split("\n").map((l) => l.trim());
15524
15458
  const sections = [];
15525
15459
  let i = 0;
@@ -15532,7 +15466,7 @@ function compileRow(text) {
15532
15466
  const command = parseCommand(line);
15533
15467
  if (command !== null) {
15534
15468
  if (command.type === "mention") {
15535
- const end = messageEndsAt(lines, i + 1);
15469
+ const end = messageEndsAt(lines, i + 1, slash);
15536
15470
  const tail = lines.slice(i + 1, end);
15537
15471
  const messageLines = command.args === "" ? tail : [command.args, ...tail];
15538
15472
  sections.push({
@@ -15565,8 +15499,18 @@ function compileRow(text) {
15565
15499
  i += 1;
15566
15500
  continue;
15567
15501
  }
15502
+ if (slash && line.startsWith("/")) {
15503
+ sections.push({
15504
+ kind: "send-block",
15505
+ display: line,
15506
+ sendText: line,
15507
+ lineCount: 1
15508
+ });
15509
+ i += 1;
15510
+ continue;
15511
+ }
15568
15512
  let end = i + 1;
15569
- while (end < lines.length && !isInstruction(lines[end])) end += 1;
15513
+ while (end < lines.length && !isInstruction(lines[end], slash)) end += 1;
15570
15514
  const block = lines.slice(i, end);
15571
15515
  while (block.length > 0 && block[0] === "") block.shift();
15572
15516
  while (block.length > 0 && block[block.length - 1] === "") block.pop();
@@ -15648,7 +15592,7 @@ function start(row, ctx, opts = {}) {
15648
15592
  ...state,
15649
15593
  note: "a macro is already running — abort it first"
15650
15594
  };
15651
- const sections = compileRow(row.text);
15595
+ const sections = compileRow(row.text, row.id === -1 ? { slashBoundaries: true } : {});
15652
15596
  if (sections.length === 0) return {
15653
15597
  ...idleState(),
15654
15598
  phase: "failed",
@@ -16209,6 +16153,13 @@ function ConversationPanel($$renderer, $$props) {
16209
16153
  * sends flow through sendPrompt so the /use record guard lives in ONE
16210
16154
  * place (Suggest Strip ADR D5 contract, byte-identical). */
16211
16155
  async function onsubmit(text, images) {
16156
+ if (isTypedRun(text)) {
16157
+ startMacro({
16158
+ text,
16159
+ id: -1
16160
+ }, false);
16161
+ return true;
16162
+ }
16212
16163
  const command = parseCommand(text);
16213
16164
  if (command !== null) {
16214
16165
  let bannered = false;
@@ -16779,13 +16730,8 @@ function WorkspaceExplorerButtons($$renderer, $$props) {
16779
16730
  function WorkspaceExplorerToolbar($$renderer, $$props) {
16780
16731
  $$renderer.component(($$renderer) => {
16781
16732
  let columnEl = null;
16782
- let { showTabs = false, activeTab = "explorer"} = $$props;
16783
- $$renderer.push(`<div data-svelte="WorkspaceExplorerToolbar" class="explorer-toolbar svelte-b18z79" data-testid="explorer-toolbar">`);
16784
- if (showTabs) {
16785
- $$renderer.push("<!--[0-->");
16786
- $$renderer.push(`<div class="tabs svelte-b18z79" role="tablist" data-testid="explorer-tabs"><button role="tab"${attr_class("tab svelte-b18z79", void 0, { "active": activeTab === "explorer" })}${attr("aria-selected", activeTab === "explorer")} data-testid="git-tab-explorer">${escape_html(t(giteyetabexplorer3))}</button> <button role="tab"${attr_class("tab svelte-b18z79", void 0, { "active": activeTab === "changes" })}${attr("aria-selected", activeTab === "changes")} data-testid="git-tab-changes">${escape_html(t(giteyetabchanges3))}</button></div>`);
16787
- } else $$renderer.push("<!--[-1-->");
16788
- $$renderer.push(`<!--]--> `);
16733
+ let { activeTab = "explorer"} = $$props;
16734
+ $$renderer.push(`<div data-svelte="WorkspaceExplorerToolbar" class="explorer-toolbar svelte-b18z79" data-testid="explorer-toolbar"><div class="tabs svelte-b18z79" role="tablist" data-testid="explorer-tabs"><button role="tab"${attr_class("tab svelte-b18z79", void 0, { "active": activeTab === "explorer" })}${attr("aria-selected", activeTab === "explorer")} data-testid="git-tab-explorer">${escape_html(t(giteyetabexplorer3))}</button> <button role="tab"${attr_class("tab svelte-b18z79", void 0, { "active": activeTab === "changes" })}${attr("aria-selected", activeTab === "changes")} data-testid="git-tab-changes">${escape_html(t(giteyetabchanges3))}</button></div> `);
16789
16735
  WorkspaceExplorerButtons($$renderer, {
16790
16736
  container: columnEl});
16791
16737
  $$renderer.push(`<!----></div>`);
@@ -16827,8 +16773,15 @@ var workspacePanelCopy = {
16827
16773
  save: "Save",
16828
16774
  /** Save button label while the delegated turn is unanswered. */
16829
16775
  saving: "Saving…",
16776
+ /** Failure line when the save turn is refused. */
16777
+ saveFailed: "Save failed",
16830
16778
  /** Post-receipt note — the receipt is acceptance, not proof of bytes. */
16831
- stale: "Saved — refresh to confirm the written file"},
16779
+ stale: "Saved — refresh to confirm the written file",
16780
+ /** Failure line when the file read refuses. */
16781
+ loadFailed: "Could not read this file",
16782
+ /** Suffix when the fetched page stops before the file's end. */
16783
+ truncated: " — first page only"
16784
+ },
16832
16785
  common: {
16833
16786
  /** Canvas-copy button tooltip — captures the whole panel column. */
16834
16787
  copyColumn: "Copy panel column as image (Shift+Click to save)"
@@ -17477,82 +17430,44 @@ function WorkspaceExplorerChanges($$renderer, $$props) {
17477
17430
  });
17478
17431
  }
17479
17432
  //#endregion
17480
- //#region src/lib/components/panels/WorkspaceExplorerPanel.svelte
17481
- function WorkspaceExplorerPanel($$renderer, $$props) {
17433
+ //#region src/lib/components/panels/WorkspaceExplorer.svelte
17434
+ function WorkspaceExplorer($$renderer, $$props) {
17482
17435
  $$renderer.component(($$renderer) => {
17483
- let { root, expanded, onCollapseAll, tab = "explorer", collapsedRepos, onCollapseAllRepos } = $$props;
17484
- /** Rel path → level state. Key '' is the root level. */
17485
- let levels = {};
17486
- let repoMaps = {};
17487
- const activeTab = derived(() => tab);
17488
- let changesStatuses = {};
17489
- /** Repos under the workspace root, as rel paths ('' first when the root
17490
- * is one). Empty until the root probe reports an open gate. */
17491
- const changesRepos = derived(() => {
17492
- const rootMap = repoMaps[""];
17493
- if (!rootMap) return [];
17494
- const out = [];
17495
- if (rootMap.rootIsRepo) out.push("");
17496
- for (const [name, flag] of Object.entries(rootMap.repos)) if (flag) out.push(name);
17497
- return out;
17498
- });
17499
- const showTabs = derived(() => changesRepos().length > 0);
17500
- /** Root-relative paths reported changed by git-status — the Explorer
17501
- * tree tints these file rows red, the same contract as the Changes rows. */
17502
- const changedPaths = derived(() => {
17503
- const out = /* @__PURE__ */ new Set();
17504
- for (const [repoRel, st] of Object.entries(changesStatuses)) {
17505
- if (st.kind !== "ready") continue;
17506
- for (const f of st.files) out.add(repoRel === "" ? f.path : repoRel + "/" + f.path);
17507
- }
17508
- return out;
17509
- });
17510
- /** Every ancestor directory of a changed path — a parent of a changed
17511
- * file is highlighted exactly like a repo row (bold + #FF4500). */
17512
- const changedDirs = derived(() => {
17513
- const out = /* @__PURE__ */ new Set();
17514
- for (const p of changedPaths()) {
17515
- const parts = p.split("/");
17516
- parts.pop();
17517
- let cur = "";
17518
- for (const part of parts) {
17519
- cur = cur === "" ? part : cur + "/" + part;
17520
- out.add(cur);
17521
- }
17522
- }
17523
- return out;
17524
- });
17525
- let pulseSource = null;
17526
- function closePulse() {
17527
- pulseSource?.close();
17528
- pulseSource = null;
17529
- }
17530
- onDestroy(closePulse);
17531
- $$renderer.push(`<div data-svelte="WorkspaceExplorerPanel" class="explorer svelte-v4uero" data-testid="workspace-explorer">`);
17436
+ /**
17437
+ * WorkspaceExplorer — the explorer/changes tab strip + scroll body
17438
+ * (extracted 2026-09-13 from WorkspaceExplorerPanel's markup, same
17439
+ * extraction series as Tree and Changes). Presentation only: every
17440
+ * value arrives as a prop, every intent leaves as a callback — the
17441
+ * panel keeps all state, fetches, and persistence.
17442
+ *
17443
+ * Layout contract: .explorer is the toolbar's absolute ANCHOR and must
17444
+ * never scroll itself; the scroll lives on .tree-scroll so the toolbar
17445
+ * stays pinned (the toolbar-scrolls-away bug).
17446
+ */
17447
+ let { root, activeTab, levels, expanded, repoMaps, changedPaths, changedDirs, changesRepos, changesStatuses, collapsedRepos} = $$props;
17448
+ $$renderer.push(`<div data-svelte="WorkspaceExplorer" class="explorer svelte-1l3v1je">`);
17532
17449
  WorkspaceExplorerToolbar($$renderer, {
17533
- onCollapseAll: activeTab() === "changes" ? () => onCollapseAllRepos?.([...changesRepos()]) : onCollapseAll,
17534
- showTabs: showTabs(),
17535
- activeTab: activeTab()});
17450
+ activeTab});
17536
17451
  $$renderer.push(`<!----> `);
17537
- if (activeTab() === "changes") {
17452
+ if (activeTab === "changes") {
17538
17453
  $$renderer.push("<!--[0-->");
17539
- $$renderer.push(`<div class="changes svelte-v4uero" data-testid="git-changes"><div class="tree-scroll svelte-v4uero">`);
17454
+ $$renderer.push(`<div class="changes svelte-1l3v1je" data-testid="git-changes"><div class="tree-scroll svelte-1l3v1je">`);
17540
17455
  WorkspaceExplorerChanges($$renderer, {
17541
17456
  root,
17542
- changesRepos: changesRepos(),
17457
+ changesRepos,
17543
17458
  changesStatuses,
17544
17459
  collapsedRepos: collapsedRepos ?? []});
17545
17460
  $$renderer.push(`<!----></div></div>`);
17546
17461
  } else {
17547
17462
  $$renderer.push("<!--[-1-->");
17548
- $$renderer.push(`<div class="tree-scroll svelte-v4uero">`);
17463
+ $$renderer.push(`<div class="tree-scroll svelte-1l3v1je">`);
17549
17464
  WorkspaceExplorerTree($$renderer, {
17550
17465
  root,
17551
17466
  levels,
17552
17467
  expanded,
17553
17468
  repoMaps,
17554
- changedPaths: changedPaths(),
17555
- changedDirs: changedDirs()});
17469
+ changedPaths,
17470
+ changedDirs});
17556
17471
  $$renderer.push(`<!----></div>`);
17557
17472
  }
17558
17473
  $$renderer.push(`<!--]--></div>`);
@@ -17563,16 +17478,16 @@ function WorkspaceExplorerPanel($$renderer, $$props) {
17563
17478
  function WorkspaceFileToolbar($$renderer, $$props) {
17564
17479
  $$renderer.component(($$renderer) => {
17565
17480
  let columnEl = null;
17566
- let { dirty, saveState = "idle", stale = false, staleNote = "", hasTabs = false, tab = "preview", view = "edit", showToggle = false, diffDisabled = false, readonly = false, onviewchange, onsave } = $$props;
17567
- $$renderer.push(`<div data-svelte="WorkspaceFileToolbar" class="file-toolbar svelte-cqlxfy">`);
17568
- if (!readonly) {
17481
+ let { dirty, saveState = "idle", stale = false, staleNote = "", hasTabs = false, tab = "preview", view = "edit", showToggle = false, diffDisabled = false, readonly = false, onviewchange, onsave, path } = $$props;
17482
+ $$renderer.push(`<div data-svelte="WorkspaceFileToolbar" class="file-toolbar svelte-cqlxfy"><span class="path svelte-cqlxfy"${attr("title", path)}>${escape_html(path)}</span> `);
17483
+ if (showToggle) {
17569
17484
  $$renderer.push("<!--[0-->");
17570
- $$renderer.push(`<button type="button" class="save-btn svelte-cqlxfy" data-testid="file-save"${attr("disabled", !dirty || saveState === "saving" || view === "diff", true)}>${escape_html(saveState === "saving" ? workspacePanelCopy.file.saving : workspacePanelCopy.file.save)}</button>`);
17485
+ $$renderer.push(`<div class="seg-group svelte-cqlxfy" role="group"${attr("aria-label", t(fileeyetogglelabel3))} data-testid="file-view-toggle"><button type="button"${attr_class("seg left svelte-cqlxfy", void 0, { "on": view === "edit" })}${attr("aria-pressed", view === "edit")} data-testid="file-view-edit">${escape_html(t(fileeyeedit2))}</button> <button type="button"${attr_class("seg right svelte-cqlxfy", void 0, { "on": view === "diff" })}${attr("disabled", diffDisabled, true)}${attr("aria-pressed", view === "diff")} data-testid="file-view-diff">${escape_html(t(fileeyediff2))}</button></div>`);
17571
17486
  } else $$renderer.push("<!--[-1-->");
17572
17487
  $$renderer.push(`<!--]--> `);
17573
- if (showToggle) {
17488
+ if (!readonly) {
17574
17489
  $$renderer.push("<!--[0-->");
17575
- $$renderer.push(`<div class="seg-group svelte-cqlxfy" role="group"${attr("aria-label", t(fileeyetogglelabel3))} data-testid="file-view-toggle"><button type="button"${attr_class("seg left svelte-cqlxfy", void 0, { "on": view === "edit" })}${attr("aria-pressed", view === "edit")} data-testid="file-view-edit">${escape_html(t(fileeyeedit2))}</button> <button type="button"${attr_class("seg right svelte-cqlxfy", void 0, { "on": view === "diff" })}${attr("disabled", diffDisabled, true)}${attr("aria-pressed", view === "diff")} data-testid="file-view-diff">${escape_html(t(fileeyediff2))}</button></div>`);
17490
+ $$renderer.push(`<button type="button" class="save-btn svelte-cqlxfy" data-testid="file-save"${attr("disabled", !dirty || saveState === "saving" || view === "diff", true)}>${escape_html(saveState === "saving" ? workspacePanelCopy.file.saving : workspacePanelCopy.file.save)}</button>`);
17576
17491
  } else $$renderer.push("<!--[-1-->");
17577
17492
  $$renderer.push(`<!--]--> `);
17578
17493
  if (dirty) {
@@ -17594,7 +17509,7 @@ function WorkspaceFileToolbar($$renderer, $$props) {
17594
17509
  $$renderer.push("<!--[0-->");
17595
17510
  $$renderer.push(`<div class="tabs svelte-cqlxfy" role="tablist"><button role="tab"${attr("aria-selected", tab === "preview")} data-testid="tab-preview"${attr_class("svelte-cqlxfy", void 0, { "active": tab === "preview" })}>${escape_html(workspacePanelCopy.file.preview)}</button> <button role="tab"${attr("aria-selected", tab === "edit")} data-testid="tab-edit"${attr_class("svelte-cqlxfy", void 0, { "active": tab === "edit" })}>${escape_html(workspacePanelCopy.file.edit)}</button></div>`);
17596
17511
  } else $$renderer.push("<!--[-1-->");
17597
- $$renderer.push(`<!--]--> <span${attr_class("copy-slot svelte-cqlxfy", void 0, { "push": !hasTabs })}>`);
17512
+ $$renderer.push(`<!--]--> <span class="copy-slot svelte-cqlxfy">`);
17598
17513
  CanvasCopyButton($$renderer, {
17599
17514
  container: columnEl,
17600
17515
  title: workspacePanelCopy.common.copyColumn,
@@ -17605,6 +17520,105 @@ function WorkspaceFileToolbar($$renderer, $$props) {
17605
17520
  });
17606
17521
  }
17607
17522
  //#endregion
17523
+ //#region src/lib/components/panels/WorkspaceFile.svelte
17524
+ function WorkspaceFile($$renderer, $$props) {
17525
+ $$renderer.component(($$renderer) => {
17526
+ /**
17527
+ * WorkspaceFile — the file body's view switch (extracted 2026-09-13
17528
+ * from WorkspaceFilePanel's markup, same extraction series as the
17529
+ * explorer components). Presentation only: every value arrives as a
17530
+ * prop, every intent leaves as a callback — the panel keeps the text
17531
+ * state, the Monaco lifecycle, and the save logic.
17532
+ *
17533
+ * Layout contract (the height fix that shipped with this extraction):
17534
+ * .file-panel fills its column (height: 100%) and NEVER scrolls
17535
+ * itself (overflow: hidden) — the scroll lives on .file-scroll for
17536
+ * the content views that need it (image, markdown preview). The
17537
+ * editor/iframe hosts take flex: 1 with min-height: 0 so the Monaco
17538
+ * editor sizes to the remaining space and scrolls INTERNALLY; the
17539
+ * old overflow:auto container made the hosts content-sized and the
17540
+ * page-level scrollbar stole the height.
17541
+ */
17542
+ let { tab = void 0, dirty, fullPath, saveState, fileStale, staleNote, hasTabs, activeView, showToggle, diffDisabled, readonly, onViewChange, onSave, saveError, saveFailedNote, loadingNote, loadFailedNote, truncatedNote, newFileNote, phase, errorCode, title, isImage, bytesSrc, isHtml, text, isMarkdown, noHead, eof, editorHost = null, diffHost = null } = $$props;
17543
+ let $$settled = true;
17544
+ let $$inner_renderer;
17545
+ function $$render_inner($$renderer) {
17546
+ $$renderer.push(`<div data-svelte="WorkspaceFile" class="file-panel svelte-1ewqqbd">`);
17547
+ WorkspaceFileToolbar($$renderer, {
17548
+ dirty,
17549
+ path: fullPath,
17550
+ saveState,
17551
+ stale: fileStale,
17552
+ staleNote,
17553
+ hasTabs,
17554
+ view: activeView,
17555
+ showToggle,
17556
+ diffDisabled,
17557
+ readonly,
17558
+ onviewchange: onViewChange,
17559
+ onsave: onSave,
17560
+ get tab() {
17561
+ return tab;
17562
+ },
17563
+ set tab($$value) {
17564
+ tab = $$value;
17565
+ $$settled = false;
17566
+ }
17567
+ });
17568
+ $$renderer.push(`<!----> `);
17569
+ if (saveState === "failed") {
17570
+ $$renderer.push("<!--[0-->");
17571
+ $$renderer.push(`<p class="note fail svelte-1ewqqbd" data-testid="file-save-failed">${escape_html(saveFailedNote)}${escape_html(saveError ? " — " + saveError : "")}</p>`);
17572
+ } else $$renderer.push("<!--[-1-->");
17573
+ $$renderer.push(`<!--]--> `);
17574
+ if (phase === "loading") {
17575
+ $$renderer.push("<!--[0-->");
17576
+ $$renderer.push(`<p class="note svelte-1ewqqbd">${escape_html(loadingNote)}</p>`);
17577
+ } else if (phase === "failed") {
17578
+ $$renderer.push("<!--[1-->");
17579
+ $$renderer.push(`<p class="note fail svelte-1ewqqbd" data-testid="file-failed">${escape_html(loadFailedNote)} (${escape_html(errorCode)})</p>`);
17580
+ } else if (isImage) {
17581
+ $$renderer.push("<!--[2-->");
17582
+ $$renderer.push(`<div class="file-scroll svelte-1ewqqbd"><img class="img-preview svelte-1ewqqbd" data-testid="file-image"${attr("src", bytesSrc)}${attr("alt", title)}/></div>`);
17583
+ } else if (isHtml && tab === "preview") {
17584
+ $$renderer.push("<!--[3-->");
17585
+ $$renderer.push(`<iframe class="html-frame svelte-1ewqqbd" data-testid="file-html-frame" sandbox=""${attr("srcdoc", text ?? "")}${attr("title", title)}></iframe>`);
17586
+ } else if (activeView === "diff") {
17587
+ $$renderer.push("<!--[4-->");
17588
+ if (noHead) {
17589
+ $$renderer.push("<!--[0-->");
17590
+ $$renderer.push(`<p class="note svelte-1ewqqbd" data-testid="file-diff-new-file">${escape_html(newFileNote)}</p>`);
17591
+ } else $$renderer.push("<!--[-1-->");
17592
+ $$renderer.push(`<!--]--> <div class="file-editor-host svelte-1ewqqbd" data-testid="file-diff-view"></div>`);
17593
+ } else if (isMarkdown && tab === "preview") {
17594
+ $$renderer.push("<!--[5-->");
17595
+ $$renderer.push(`<div class="file-scroll svelte-1ewqqbd">`);
17596
+ MarkdownPanel($$renderer, { content: text ?? "" });
17597
+ $$renderer.push(`<!----></div>`);
17598
+ } else {
17599
+ $$renderer.push("<!--[-1-->");
17600
+ if (!eof) {
17601
+ $$renderer.push("<!--[0-->");
17602
+ $$renderer.push(`<p class="note svelte-1ewqqbd">${escape_html(truncatedNote)}</p>`);
17603
+ } else $$renderer.push("<!--[-1-->");
17604
+ $$renderer.push(`<!--]--> <div class="file-editor-host svelte-1ewqqbd"></div>`);
17605
+ }
17606
+ $$renderer.push(`<!--]--></div>`);
17607
+ }
17608
+ do {
17609
+ $$settled = true;
17610
+ $$inner_renderer = $$renderer.copy();
17611
+ $$render_inner($$inner_renderer);
17612
+ } while (!$$settled);
17613
+ $$renderer.subsume($$inner_renderer);
17614
+ bind_props($$props, {
17615
+ tab,
17616
+ editorHost,
17617
+ diffHost
17618
+ });
17619
+ });
17620
+ }
17621
+ //#endregion
17608
17622
  //#region src/lib/components/panels/WorkspaceFilePanel.svelte
17609
17623
  function WorkspaceFilePanel($$renderer, $$props) {
17610
17624
  $$renderer.component(($$renderer) => {
@@ -17631,23 +17645,31 @@ function WorkspaceFilePanel($$renderer, $$props) {
17631
17645
  * outside-workspace, too-large, not-text, not-regular-file.
17632
17646
  */
17633
17647
  /** The read receipt (api/dsh/workspace-file body, DshWorkspaceFileText). */
17634
- let { path, view = "edit", onviewchange} = $$props;
17648
+ let { sessionId, path, root, view = "edit", onviewchange, onclose } = $$props;
17635
17649
  let viewOverride = null;
17636
17650
  const activeView = derived(() => viewOverride ?? view);
17637
17651
  let tab = "preview";
17652
+ let phase = "loading";
17653
+ let errorCode = null;
17654
+ let text = null;
17655
+ let eof = true;
17638
17656
  /** Dirty = the edit buffer differs from the baseline. */
17639
17657
  let dirty = false;
17640
17658
  /** The delegated save's progress (ADR D4): idle → saving → saved /
17641
17659
  * failed; a further edit re-arms the button from any state. */
17642
17660
  let saveState = "idle";
17661
+ let saveError = null;
17643
17662
  /** Index Pulse: an external change landed on disk while a draft exists
17644
17663
  * in the buffer — surfaced as a visible note; the draft is kept. */
17645
17664
  let fileStale = false;
17665
+ let noHead = false;
17666
+ let diffHost = null;
17646
17667
  /** Toggle intent: bridge the click, then hand the choice to the owner. */
17647
17668
  function setView(next) {
17648
17669
  viewOverride = next;
17649
17670
  onviewchange?.(next);
17650
17671
  }
17672
+ let editorHost = null;
17651
17673
  const isMarkdown = derived(() => path.toLowerCase().endsWith(".md"));
17652
17674
  /** HTML documents preview like markdown (two tabs): the preview tab
17653
17675
  * mounts a SANDBOXED iframe (srcdoc, no scripts — a read-only render
@@ -17655,6 +17677,36 @@ function WorkspaceFilePanel($$renderer, $$props) {
17655
17677
  * every text file (2026-09-10). */
17656
17678
  const isHtml = derived(() => /\.html?$/.test(path.toLowerCase()));
17657
17679
  const hasTabs = derived(() => isMarkdown() || isHtml());
17680
+ /** Image extensions preview through /api/dsh/workspace-file-bytes with
17681
+ * an <img> element — READ-ONLY by nature (no tabs, no edit buffer);
17682
+ * `read` refuses binaries workspace-file/not-text (2026-09-10). */
17683
+ const IMAGE_EXT = [
17684
+ "png",
17685
+ "jpg",
17686
+ "jpeg",
17687
+ "gif",
17688
+ "webp",
17689
+ "svg",
17690
+ "bmp",
17691
+ "ico",
17692
+ "avif"
17693
+ ];
17694
+ const isImage = derived(() => IMAGE_EXT.includes(path.split(".").pop()?.toLowerCase() ?? ""));
17695
+ const bytesSrc = derived(() => "/api/dsh/workspace-file-bytes?sessionId=" + encodeURIComponent(sessionId) + "&path=" + encodeURIComponent(path));
17696
+ const title = derived(() => path.split("/").filter((p) => p.length > 0).pop() ?? path);
17697
+ /** The FULL display path (root + relative path) — the toolbar's
17698
+ * left-side readout (the SettingsHomeToolbar pattern), with the
17699
+ * same tilde discipline: the operator's home directory collapses
17700
+ * to `~` before anything hits the screen. The homedir is derived
17701
+ * from the config store's server homedir truth (the settingsHomes
17702
+ * roots, The Settings Tree ADR D2) — never guessed client-side. */
17703
+ const fullPath = derived(() => {
17704
+ const full = root.replace(/\/+$/, "") + "/" + path.replace(/^\/+/, "");
17705
+ const dsi = appConfig().settingsHomes.dsi;
17706
+ const marker = dsi.lastIndexOf("/.dsi");
17707
+ const homeDir = marker > 0 ? dsi.slice(0, marker) : null;
17708
+ return homeDir !== null && full.startsWith(homeDir + "/") ? "~" + full.slice(homeDir.length) : full;
17709
+ });
17658
17710
  /** The DIRECT save (The File Eye ADR D4): POST the buffer to the
17659
17711
  * gated write route; the written receipt advances the baseline so
17660
17712
  * dirty clears — bytes on disk, not an agent's promise. */
@@ -17662,33 +17714,59 @@ function WorkspaceFilePanel($$renderer, $$props) {
17662
17714
  let $$settled = true;
17663
17715
  let $$inner_renderer;
17664
17716
  function $$render_inner($$renderer) {
17665
- $$renderer.push(`<div data-svelte="WorkspaceFilePanel" class="file-panel svelte-1pgc8hn" data-testid="workspace-file-panel"${attr("data-file-path", path)}>`);
17666
- WorkspaceFileToolbar($$renderer, {
17717
+ $$renderer.push(`<div data-svelte="WorkspaceFilePanel" data-testid="workspace-file-panel"${attr("data-file-path", path)} class="file-body svelte-1pgc8hn">`);
17718
+ WorkspaceFile($$renderer, {
17667
17719
  dirty,
17720
+ fullPath: fullPath(),
17668
17721
  saveState,
17669
- stale: fileStale,
17722
+ fileStale,
17670
17723
  staleNote: t(fileeyeexternalchange3),
17671
17724
  hasTabs: hasTabs(),
17672
- view: activeView(),
17725
+ activeView: activeView(),
17673
17726
  showToggle: false,
17674
17727
  diffDisabled: dirty,
17675
17728
  readonly: false,
17676
- onviewchange: setView,
17677
- onsave: save,
17729
+ onViewChange: setView,
17730
+ onSave: save,
17731
+ saveError,
17732
+ saveFailedNote: workspacePanelCopy.file.saveFailed,
17733
+ loadingNote: workspacePanelCopy.explorer.loading,
17734
+ loadFailedNote: workspacePanelCopy.file.loadFailed,
17735
+ truncatedNote: workspacePanelCopy.file.truncated,
17736
+ newFileNote: t(fileeyenewfile3),
17737
+ phase,
17738
+ errorCode,
17739
+ title: title(),
17740
+ isImage: isImage(),
17741
+ bytesSrc: bytesSrc(),
17742
+ isHtml: isHtml(),
17743
+ text,
17744
+ isMarkdown: isMarkdown(),
17745
+ noHead,
17746
+ eof,
17678
17747
  get tab() {
17679
17748
  return tab;
17680
17749
  },
17681
17750
  set tab($$value) {
17682
17751
  tab = $$value;
17683
17752
  $$settled = false;
17753
+ },
17754
+ get editorHost() {
17755
+ return editorHost;
17756
+ },
17757
+ set editorHost($$value) {
17758
+ editorHost = $$value;
17759
+ $$settled = false;
17760
+ },
17761
+ get diffHost() {
17762
+ return diffHost;
17763
+ },
17764
+ set diffHost($$value) {
17765
+ diffHost = $$value;
17766
+ $$settled = false;
17684
17767
  }
17685
17768
  });
17686
- $$renderer.push(`<!----> `);
17687
- $$renderer.push("<!--[-1-->");
17688
- $$renderer.push(`<!--]--> `);
17689
- $$renderer.push("<!--[0-->");
17690
- $$renderer.push(`<p class="note svelte-1pgc8hn">${escape_html(workspacePanelCopy.explorer.loading)}</p>`);
17691
- $$renderer.push(`<!--]--></div>`);
17769
+ $$renderer.push(`<!----></div>`);
17692
17770
  }
17693
17771
  do {
17694
17772
  $$settled = true;
@@ -17699,27 +17777,457 @@ function WorkspaceFilePanel($$renderer, $$props) {
17699
17777
  });
17700
17778
  }
17701
17779
  //#endregion
17702
- //#region src/lib/components/panels/ResizeGutter.svelte
17703
- function ResizeGutter($$renderer, $$props) {
17780
+ //#region src/lib/components/panels/SettingsHomeToolbar.svelte
17781
+ function SettingsHomeToolbar($$renderer, $$props) {
17704
17782
  $$renderer.component(($$renderer) => {
17705
- /**
17706
- * ResizeGutter — the 6px draggable seam after every panel (Panel Floor
17707
- * W3 task 3.2, ADR-0006 R8; OCI port, every-panel-has-a-right-gutter
17708
- * rule — with individual resize (2026-08-28) each gutter owns its
17709
- * OWN panel's width, so the rule now just means no panel is the
17710
- * only one without a seam).
17783
+ let columnEl = null;
17784
+ let { fullPath, dirty, saving} = $$props;
17785
+ $$renderer.push(`<div data-svelte="SettingsHomeToolbar" class="bar svelte-9whorb"><span class="path svelte-9whorb"${attr("title", fullPath)}>${escape_html(fullPath)}</span> `);
17786
+ if (dirty) {
17787
+ $$renderer.push("<!--[0-->");
17788
+ $$renderer.push(`<span class="dirty svelte-9whorb" data-testid="settings-home-dirty">●</span>`);
17789
+ } else $$renderer.push("<!--[-1-->");
17790
+ $$renderer.push(`<!--]--> <button class="save svelte-9whorb"${attr("disabled", !dirty || saving, true)}>${escape_html(t(save))}</button> <span class="copy-slot svelte-9whorb">`);
17791
+ CanvasCopyButton($$renderer, {
17792
+ container: columnEl,
17793
+ title: workspacePanelCopy.common.copyColumn,
17794
+ size: 12
17795
+ });
17796
+ $$renderer.push(`<!----></span></div>`);
17797
+ });
17798
+ }
17799
+ //#endregion
17800
+ //#region src/lib/components/panels/SettingsHomeFile.svelte
17801
+ function SettingsHomeFile($$renderer, $$props) {
17802
+ $$renderer.component(($$renderer) => {
17803
+ /**
17804
+ * SettingsHomeFile — The Settings Tree ADR (2026-09-18, D2): the file
17805
+ * body of a settings-home explorer tab. Edits ride the DSI-local
17806
+ * /api/settings-home plane: load, edit, save, with an honest dirty
17807
+ * indicator. Deliberately NOT the WorkspaceFilePanel — that surface is
17808
+ * session-scoped and git-faced, and a settings home has neither a
17809
+ * session nor git guarantees.
17810
+ *
17811
+ * The editor itself IS the shared lazy Monaco seam (settings-monaco.ts,
17812
+ * createTextEditor) — the RCA of 2026-09-18 ("The Home Tab That Lost Its
17813
+ * Colors") records why the first cut shipped a textarea: the seam was
17814
+ * never named in the ADR's file map. No component imports monaco-editor
17815
+ * directly; the dynamic import keeps the lazy chunk (monaco-lazy-chunk
17816
+ * build gate). Language is guessed from the file extension.
17817
+ */
17818
+ let { home, path } = $$props;
17819
+ let loaded = false;
17820
+ let saving = false;
17821
+ const dirty = derived(() => loaded);
17822
+ /** Full path for display, tilde-abbreviated: the home root (server
17823
+ * homedir truth via /api/config, The Settings Tree ADR 2026-09-18
17824
+ * D2) joined with the home-relative `path` prop, then the operator's
17825
+ * home directory collapses to `~` — the readout never exposes the
17826
+ * real home path to the wire/screen. A $derived so a late config
17827
+ * load re-renders the toolbar's path readout. */
17828
+ const fullPath = derived(() => {
17829
+ const root = appConfig().settingsHomes[home];
17830
+ const full = root.endsWith("/") ? root + path : root + "/" + path;
17831
+ const marker = full.indexOf(home === "dsi" ? "/.dsi/" : "/.dsh/");
17832
+ return marker > 0 ? "~" + full.slice(marker) : full;
17833
+ });
17834
+ $$renderer.push(`<div data-svelte="SettingsHomeFile" class="home-file svelte-bq960k" data-testid="settings-home-file">`);
17835
+ SettingsHomeToolbar($$renderer, {
17836
+ fullPath: fullPath(),
17837
+ dirty: dirty(),
17838
+ saving});
17839
+ $$renderer.push(`<!----> `);
17840
+ $$renderer.push("<!--[-1-->");
17841
+ $$renderer.push(`<!--]--> <div class="editor-host svelte-bq960k" data-testid="settings-home-editor"></div></div>`);
17842
+ });
17843
+ }
17844
+ //#endregion
17845
+ //#region src/lib/components/panels/WorkspaceFileTabs.svelte
17846
+ function WorkspaceFileTabs($$renderer, $$props) {
17847
+ $$renderer.component(($$renderer) => {
17848
+ /**
17849
+ * WorkspaceFileTabs — the Explorer Layout's tab strip + file container
17850
+ * (ADR 2026-09-17 D3/D5, extracted from day one per the ADR's §7
17851
+ * give-up: the panel must not grow into the new monolith).
17852
+ *
17853
+ * PRESENTATION ONLY (Module Communication Map): the open-tab list, the
17854
+ * active path, and every fetch arrive as PROPS from the owning
17855
+ * WorkspaceExplorerPanel; the only things leaving are INTENT callbacks
17856
+ * — onActivate (focus an open tab), onClose (the per-tab [x]). The
17857
+ * content region is the parent's children snippet, so the panel hosts
17858
+ * WorkspaceFile itself and this component never fetches.
17859
+ *
17860
+ * A closed panel drops the tabs with it (D5) — nothing here persists.
17861
+ */
17862
+ /** One open file tab — the panel's component state, deduped by path. */
17863
+ /** Root-relative path — identity within the panel's tab list. */
17864
+ let { tabs, activePath, children } = $$props;
17865
+ /** Tab label — the leaf name; the full root-relative path rides the
17866
+ * title attribute (ADR D5). */
17867
+ function leaf(path) {
17868
+ const idx = path.lastIndexOf("/");
17869
+ return idx === -1 ? path : path.slice(idx + 1);
17870
+ }
17871
+ $$renderer.push(`<div data-svelte="WorkspaceFileTabs" class="file-tabs svelte-1hdf0bx" data-testid="workspace-file-tabs">`);
17872
+ if (tabs.length > 0) {
17873
+ $$renderer.push("<!--[0-->");
17874
+ $$renderer.push(`<div class="strip svelte-1hdf0bx" role="tablist" data-testid="workspace-file-tabstrip"><!--[-->`);
17875
+ const each_array = ensure_array_like(tabs);
17876
+ for (let $$index = 0, $$length = each_array.length; $$index < $$length; $$index++) {
17877
+ let tab = each_array[$$index];
17878
+ $$renderer.push(`<button type="button" role="tab"${attr_class("tab svelte-1hdf0bx", void 0, { "active": tab.path === activePath })}${attr("data-testid", `workspace-file-tab-${stringify(tab.path)}`)}${attr("title", tab.path)}${attr("aria-selected", tab.path === activePath)}><span class="label svelte-1hdf0bx">${escape_html(leaf(tab.path))}</span> <span role="button" tabindex="-1" class="close svelte-1hdf0bx"${attr("data-testid", `workspace-file-tabclose-${stringify(tab.path)}`)}${attr("aria-label", t(close))}${attr("title", t(close))}>×</span></button>`);
17879
+ }
17880
+ $$renderer.push(`<!--]--></div> <div class="body svelte-1hdf0bx">`);
17881
+ children($$renderer);
17882
+ $$renderer.push(`<!----></div>`);
17883
+ } else {
17884
+ $$renderer.push("<!--[-1-->");
17885
+ $$renderer.push(`<div class="empty svelte-1hdf0bx" data-testid="workspace-file-tabs-empty"></div>`);
17886
+ }
17887
+ $$renderer.push(`<!--]--></div>`);
17888
+ });
17889
+ }
17890
+ //#endregion
17891
+ //#region src/lib/components/panels/ResizeGutter.svelte
17892
+ function ResizeGutter($$renderer, $$props) {
17893
+ $$renderer.component(($$renderer) => {
17894
+ /**
17895
+ * ResizeGutter — the 6px draggable seam after every panel (Panel Floor
17896
+ * W3 task 3.2, ADR-0006 R8; OCI port, every-panel-has-a-right-gutter
17897
+ * rule — with individual resize (2026-08-28) each gutter owns its
17898
+ * OWN panel's width, so the rule now just means no panel is the
17899
+ * only one without a seam).
17711
17900
  *
17712
17901
  * The gutter does not resize anything itself — mousedown reports
17713
17902
  * (event, index) upward through the panel registry (the ONLY
17714
17903
  * leaf→root action channel, commitment 2), and the workspace route
17715
17904
  * (the single drag owner) snapshots widths and runs the individual /
17716
17905
  * proportional math on window mousemove.
17906
+ *
17907
+ * The Explorer Layout (ADR 2026-09-17 D4): an OPTIONAL onDragStart
17908
+ * owner prop — when given, the mousedown fires IT instead of the
17909
+ * registry import and the calling component is the drag owner (the
17910
+ * gutter instance lives inside one component, so no leaf→root hop is
17911
+ * needed). When absent, the floor path above runs verbatim — every
17912
+ * existing call site is untouched.
17717
17913
  */
17718
- let { index } = $$props;
17914
+ let { index, onDragStart } = $$props;
17719
17915
  $$renderer.push(`<button data-svelte="ResizeGutter" type="button" class="gutter svelte-gujdub"${attr("data-testid", `panel-gutter-${stringify(index)}`)}${attr("aria-label", t(resizepanel1))}${attr("title", t(dragresize1))}></button>`);
17720
17916
  });
17721
17917
  }
17722
17918
  //#endregion
17919
+ //#region src/lib/utils/panel-prefs.ts
17920
+ /** localStorage base key — one JSON blob `{ panels, selectedPanelId, panelWidth, zoom }`.
17921
+ * A workspace profile (`?profile=…` on the URL) suffixes EVERY dsi-* key
17922
+ * (see storage-profile.ts): the default desk lives under `dsi-panels`,
17923
+ * profile `widi` under `dsi-panels_widi`. */
17924
+ var PANEL_PREFS_KEY = "dsi-panels";
17925
+ /**
17926
+ * The storage key for a workspace profile.
17927
+ * @param profile sanitized profile segment, or null/undefined for the
17928
+ * default desk (`dsi-panels`).
17929
+ */
17930
+ function panelPrefsKey(profile) {
17931
+ return dsiKey(PANEL_PREFS_KEY, profile);
17932
+ }
17933
+ var PANEL_WORKSPACE_FILE_MAX_WIDTH = 1436;
17934
+ /** The single treePct bound site — sanitize a stored or dragged value
17935
+ * into [TREE_PCT_MIN, TREE_PCT_MAX]; junk (non-finite) falls back to
17936
+ * TREE_PCT_DEFAULT. Mirrors clampPanelZoom's discipline. */
17937
+ function clampTreePct(pct) {
17938
+ if (typeof pct !== "number" || !Number.isFinite(pct)) return 30;
17939
+ return Math.max(15, Math.min(70, Math.round(pct)));
17940
+ }
17941
+ /**
17942
+ * The single panel-width bound site — load-side sanitizing, gutter drags,
17943
+ * and the width slider all route through here. Bounds read the config
17944
+ * store (layout load has landed them before any mount; defaults before).
17945
+ */
17946
+ function clampPanelWidth(width, kind) {
17947
+ const { minWidth, maxWidth } = appConfig().panel;
17948
+ if (kind === "workspace-explorer") return Math.max(520, Math.min(PANEL_WORKSPACE_FILE_MAX_WIDTH, width));
17949
+ if (kind === "workspace-file") return Math.max(520, Math.min(PANEL_WORKSPACE_FILE_MAX_WIDTH, width));
17950
+ return Math.max(minWidth, Math.min(maxWidth, width));
17951
+ }
17952
+ /** The single zoom bound site — load-side sanitizing and the zoom slider. */
17953
+ function clampPanelZoom(zoom) {
17954
+ const { minZoom, maxZoom } = appConfig().panel;
17955
+ return Math.max(minZoom, Math.min(maxZoom, zoom));
17956
+ }
17957
+ /** Config-driven default width (clamped through the single bound site). */
17958
+ function defaultPanelWidth() {
17959
+ return clampPanelWidth(appConfig().panel.defaultWidth);
17960
+ }
17961
+ /**
17962
+ * Clamp a stored explorer `expanded` list to sane paths (Workspace
17963
+ * Explorer restore): non-string members drop and a repeated path keeps
17964
+ * its FIRST occurrence — the tree treats `expanded` as a set of
17965
+ * root-relative directories, and a duplicate would double-render a row.
17966
+ */
17967
+ function clampExpanded(value) {
17968
+ if (!Array.isArray(value)) return [];
17969
+ const seen = /* @__PURE__ */ new Set();
17970
+ const out = [];
17971
+ for (const member of value) {
17972
+ if (typeof member !== "string" || seen.has(member)) continue;
17973
+ seen.add(member);
17974
+ out.push(member);
17975
+ }
17976
+ return out;
17977
+ }
17978
+ /** Persisted defaults when nothing (usable) is stored. */
17979
+ function defaultPanelPrefs() {
17980
+ return {
17981
+ panels: [],
17982
+ selectedPanelId: null,
17983
+ panelWidth: defaultPanelWidth(),
17984
+ zoom: 1,
17985
+ treePct: 30
17986
+ };
17987
+ }
17988
+ /**
17989
+ * Hard shape check for a stored panel entry (the parse boundary).
17990
+ * KIND (2026-09-06, ADR D6): absent or 'conversation' is the legacy shape —
17991
+ * `sessionId` required; 'prompt-manager' carries no session fields;
17992
+ * 'injected-doc' (Loadinjected ADR D2) requires non-empty `sourceSessionId`
17993
+ * + `displayPath` — the dedupe key. Any other kind value is hard junk
17994
+ * (entry drops). `agentPreset` and `width`
17995
+ * may be absent (defaulted later); `agentPreset` when present must be
17996
+ * string|null, `width` when present must be a finite number. Unknown
17997
+ * extra keys are fine — they survive sanitizing.
17998
+ */
17999
+ function isPanelEntry(value) {
18000
+ if (value === null || typeof value !== "object") return false;
18001
+ const entry = value;
18002
+ if (typeof entry.id !== "string" || entry.id.length === 0) return false;
18003
+ if (entry.kind !== void 0 && entry.kind !== "conversation" && entry.kind !== "prompt-manager" && entry.kind !== "settings-editor" && entry.kind !== "injected-doc" && entry.kind !== "workspace-explorer" && entry.kind !== "workspace-file") return false;
18004
+ if (entry.kind === "settings-editor" && entry.target !== "dsi" && entry.target !== "dsh") return false;
18005
+ if (entry.kind === "workspace-explorer") {
18006
+ if (entry.sessionId !== null && (typeof entry.sessionId !== "string" || entry.sessionId.length === 0)) return false;
18007
+ if (entry.sessionId === null && entry.home !== "dsi" && entry.home !== "dsh") return false;
18008
+ }
18009
+ if (entry.kind === "workspace-file") {
18010
+ if (typeof entry.sessionId !== "string" || entry.sessionId.length === 0) return false;
18011
+ }
18012
+ if (entry.kind === "workspace-explorer" && typeof entry.root !== "string") return false;
18013
+ if (entry.kind === "workspace-file") {
18014
+ if (typeof entry.path !== "string" || entry.path.length === 0) return false;
18015
+ }
18016
+ if (entry.kind === "injected-doc") {
18017
+ if (typeof entry.sourceSessionId !== "string" || entry.sourceSessionId.length === 0) return false;
18018
+ if (typeof entry.displayPath !== "string" || entry.displayPath.length === 0) return false;
18019
+ }
18020
+ if (entry.kind !== "prompt-manager" && entry.kind !== "settings-editor" && entry.kind !== "injected-doc" && entry.kind !== "workspace-explorer" && entry.kind !== "workspace-file") {
18021
+ if (typeof entry.sessionId !== "string" || entry.sessionId.length === 0) return false;
18022
+ if (entry.agentPreset !== void 0 && typeof entry.agentPreset !== "string" && entry.agentPreset !== null) return false;
18023
+ }
18024
+ if (entry.width !== void 0 && (typeof entry.width !== "number" || !Number.isFinite(entry.width))) return false;
18025
+ return true;
18026
+ }
18027
+ /** Drop hard-junk entries; default missing keys; clamp widths. Unknown
18028
+ * keys on surviving entries pass through untouched.
18029
+ *
18030
+ * Duplicate ids (2026-08-24 crash fix): a corrupted blob carrying the
18031
+ * same panel-N twice would reach the keyed each in SidebarOpenPanels
18032
+ * and crash Svelte (each_key_duplicate) at render — the each's key is
18033
+ * the panel id. Duplicates drop to the FIRST occurrence (order is the
18034
+ * floor's left-to-right truth); a selectedPanelId left dangling by the
18035
+ * dedupe re-sanitizes to the first panel in loadPanelPrefs. */
18036
+ function sanitizePanels(raw) {
18037
+ if (!Array.isArray(raw)) return [];
18038
+ const seen = /* @__PURE__ */ new Set();
18039
+ return raw.filter((entry) => isPanelEntry(entry)).map((entry) => {
18040
+ const kindDefault = entry.kind === "workspace-explorer" ? 850 : entry.kind === "workspace-file" ? 850 : defaultPanelWidth();
18041
+ const width = clampPanelWidth(typeof entry.width === "number" && Number.isFinite(entry.width) ? entry.width : kindDefault, entry.kind);
18042
+ if (entry.kind === "prompt-manager") return {
18043
+ id: entry.id,
18044
+ kind: "prompt-manager",
18045
+ width
18046
+ };
18047
+ if (entry.kind === "settings-editor") return {
18048
+ id: entry.id,
18049
+ kind: "settings-editor",
18050
+ target: entry.target,
18051
+ width
18052
+ };
18053
+ if (entry.kind === "injected-doc") return {
18054
+ id: entry.id,
18055
+ kind: "injected-doc",
18056
+ sourceSessionId: entry.sourceSessionId,
18057
+ displayPath: entry.displayPath,
18058
+ width
18059
+ };
18060
+ if (entry.kind === "workspace-explorer") {
18061
+ const home = entry.home === "dsi" || entry.home === "dsh" ? entry.home : void 0;
18062
+ return {
18063
+ id: entry.id,
18064
+ kind: "workspace-explorer",
18065
+ sessionId: entry.sessionId ?? null,
18066
+ root: entry.root,
18067
+ ...typeof entry.title === "string" && entry.title.length > 0 ? { title: entry.title } : {},
18068
+ ...home !== void 0 ? { home } : {},
18069
+ expanded: clampExpanded(entry.expanded),
18070
+ tab: entry.tab === "changes" ? "changes" : "explorer",
18071
+ collapsedRepos: clampExpanded(entry.collapsedRepos),
18072
+ openTabs: clampExpanded(entry.openTabs),
18073
+ activeFile: typeof entry.activeFile === "string" ? entry.activeFile : null,
18074
+ width
18075
+ };
18076
+ }
18077
+ if (entry.kind === "workspace-file") return {
18078
+ id: entry.id,
18079
+ kind: "workspace-file",
18080
+ sessionId: entry.sessionId,
18081
+ path: entry.path,
18082
+ explorerPanelId: typeof entry.explorerPanelId === "string" && entry.explorerPanelId.length > 0 ? entry.explorerPanelId : null,
18083
+ view: entry.view === "diff" ? "diff" : "edit",
18084
+ width
18085
+ };
18086
+ return {
18087
+ ...entry,
18088
+ id: entry.id,
18089
+ kind: "conversation",
18090
+ sessionId: entry.sessionId,
18091
+ agentPreset: entry.agentPreset ?? null,
18092
+ width
18093
+ };
18094
+ }).filter((entry) => {
18095
+ if (seen.has(entry.id)) return false;
18096
+ seen.add(entry.id);
18097
+ return true;
18098
+ });
18099
+ }
18100
+ /**
18101
+ * Read the workspace state from localStorage.
18102
+ * @param profile sanitized workspace profile (null/omitted = default desk).
18103
+ * @returns stored state when parseable (sanitized), defaults when absent,
18104
+ * junk, or SSR. A `selectedPanelId` pointing at a missing panel
18105
+ * sanitizes to the first panel's id (null when no panels remain).
18106
+ */
18107
+ function loadPanelPrefs(profile) {
18108
+ if (typeof localStorage === "undefined") return defaultPanelPrefs();
18109
+ try {
18110
+ const raw = localStorage.getItem(panelPrefsKey(profile));
18111
+ if (!raw) return defaultPanelPrefs();
18112
+ const parsed = JSON.parse(raw);
18113
+ if (parsed === null || typeof parsed !== "object") return defaultPanelPrefs();
18114
+ if (!Array.isArray(parsed.panels)) return defaultPanelPrefs();
18115
+ const panels = sanitizePanels(parsed.panels);
18116
+ const panelWidth = typeof parsed.panelWidth === "number" && Number.isFinite(parsed.panelWidth) ? clampPanelWidth(parsed.panelWidth) : defaultPanelWidth();
18117
+ const zoom = typeof parsed.zoom === "number" && Number.isFinite(parsed.zoom) ? clampPanelZoom(parsed.zoom) : 1;
18118
+ const treePct = typeof parsed.treePct === "number" && Number.isFinite(parsed.treePct) ? clampTreePct(parsed.treePct) : 30;
18119
+ return {
18120
+ panels,
18121
+ selectedPanelId: typeof parsed.selectedPanelId === "string" && panels.some((panel) => panel.id === parsed.selectedPanelId) ? parsed.selectedPanelId : panels[0]?.id ?? null,
18122
+ panelWidth,
18123
+ zoom,
18124
+ treePct
18125
+ };
18126
+ } catch {
18127
+ return defaultPanelPrefs();
18128
+ }
18129
+ }
18130
+ //#endregion
18131
+ //#region src/lib/components/panels/WorkspaceExplorerPanel.svelte
18132
+ function WorkspaceExplorerPanel($$renderer, $$props) {
18133
+ $$renderer.component(($$renderer) => {
18134
+ let { sessionId, root, home, expanded, onToggle, onOpenFile, onCollapseAll, tab = "explorer", onTabChange, collapsedRepos, onToggleRepo, onCollapseAllRepos, treePct = 30, onTreePctChange, pendingOpenFile = null, onPendingOpenConsumed, openTabs = [], activeFile = null, onOpenTab, onCloseTab, onActivateTab } = $$props;
18135
+ /** Rel path → level state. Key '' is the root level. */
18136
+ let levels = {};
18137
+ let repoMaps = {};
18138
+ const activeTab = derived(() => tab);
18139
+ let changesStatuses = {};
18140
+ /** Repos under the workspace root, as rel paths ('' first when the root
18141
+ * is one). Empty until the root probe answers. The tab strip is
18142
+ * unconditional (Always Tabs D1) — this list drives ROWS, not tabs. */
18143
+ const changesRepos = derived(() => {
18144
+ const rootMap = repoMaps[""];
18145
+ if (!rootMap) return [];
18146
+ const out = [];
18147
+ if (rootMap.rootIsRepo) out.push("");
18148
+ for (const [name, flag] of Object.entries(rootMap.repos)) if (flag) out.push(name);
18149
+ return out;
18150
+ });
18151
+ /** Root-relative paths reported changed by git-status — the Explorer
18152
+ * tree tints these file rows red, the same contract as the Changes rows. */
18153
+ const changedPaths = derived(() => {
18154
+ const out = /* @__PURE__ */ new Set();
18155
+ for (const [repoRel, st] of Object.entries(changesStatuses)) {
18156
+ if (st.kind !== "ready") continue;
18157
+ for (const f of st.files) out.add(repoRel === "" ? f.path : repoRel + "/" + f.path);
18158
+ }
18159
+ return out;
18160
+ });
18161
+ /** Every ancestor directory of a changed path — a parent of a changed
18162
+ * file is highlighted exactly like a repo row (bold + #FF4500). */
18163
+ const changedDirs = derived(() => {
18164
+ const out = /* @__PURE__ */ new Set();
18165
+ for (const p of changedPaths()) {
18166
+ const parts = p.split("/");
18167
+ parts.pop();
18168
+ let cur = "";
18169
+ for (const part of parts) {
18170
+ cur = cur === "" ? part : cur + "/" + part;
18171
+ out.add(cur);
18172
+ }
18173
+ }
18174
+ return out;
18175
+ });
18176
+ let pulseSource = null;
18177
+ function closePulse() {
18178
+ pulseSource?.close();
18179
+ pulseSource = null;
18180
+ }
18181
+ onDestroy(closePulse);
18182
+ function startSplitDrag(event) {}
18183
+ const splitPct = derived(() => clampTreePct(treePct));
18184
+ $$renderer.push(`<div data-svelte="WorkspaceExplorerPanel" data-testid="workspace-explorer" class="split svelte-v4uero"${attr_style(`--tree-basis: ${stringify(splitPct())}%; --file-basis: ${stringify(100 - splitPct())}%`)}>`);
18185
+ WorkspaceExplorer($$renderer, {
18186
+ root,
18187
+ activeTab: activeTab(),
18188
+ levels,
18189
+ expanded,
18190
+ repoMaps,
18191
+ changedPaths: changedPaths(),
18192
+ changedDirs: changedDirs(),
18193
+ changesRepos: changesRepos(),
18194
+ changesStatuses,
18195
+ collapsedRepos});
18196
+ $$renderer.push(`<!----> `);
18197
+ ResizeGutter($$renderer, {
18198
+ index: 0,
18199
+ onDragStart: startSplitDrag
18200
+ });
18201
+ $$renderer.push(`<!----> `);
18202
+ WorkspaceFileTabs($$renderer, {
18203
+ tabs: openTabs.map((p) => ({ path: p })),
18204
+ activePath: activeFile,
18205
+ children: ($$renderer) => {
18206
+ if (activeFile !== null) {
18207
+ $$renderer.push("<!--[0-->");
18208
+ if (home !== void 0) {
18209
+ $$renderer.push("<!--[0-->");
18210
+ SettingsHomeFile($$renderer, {
18211
+ home,
18212
+ path: activeFile
18213
+ });
18214
+ } else if (sessionId !== null) {
18215
+ $$renderer.push("<!--[1-->");
18216
+ WorkspaceFilePanel($$renderer, {
18217
+ sessionId,
18218
+ path: activeFile,
18219
+ root,
18220
+ onclose: () => {}
18221
+ });
18222
+ } else $$renderer.push("<!--[-1-->");
18223
+ $$renderer.push(`<!--]-->`);
18224
+ } else $$renderer.push("<!--[-1-->");
18225
+ $$renderer.push(`<!--]-->`);
18226
+ }});
18227
+ $$renderer.push(`<!----></div>`);
18228
+ });
18229
+ }
18230
+ //#endregion
17723
18231
  //#region src/lib/components/panels/PanelsZoom.svelte
17724
18232
  function PanelsZoom($$renderer, $$props) {
17725
18233
  $$renderer.component(($$renderer) => {
@@ -17971,197 +18479,6 @@ function EmptyFloor($$renderer, $$props) {
17971
18479
  });
17972
18480
  }
17973
18481
  //#endregion
17974
- //#region src/lib/utils/panel-prefs.ts
17975
- /** localStorage base key — one JSON blob `{ panels, selectedPanelId, panelWidth, zoom }`.
17976
- * A workspace profile (`?profile=…` on the URL) suffixes EVERY dsi-* key
17977
- * (see storage-profile.ts): the default desk lives under `dsi-panels`,
17978
- * profile `widi` under `dsi-panels_widi`. */
17979
- var PANEL_PREFS_KEY = "dsi-panels";
17980
- /**
17981
- * The storage key for a workspace profile.
17982
- * @param profile sanitized profile segment, or null/undefined for the
17983
- * default desk (`dsi-panels`).
17984
- */
17985
- function panelPrefsKey(profile) {
17986
- return dsiKey(PANEL_PREFS_KEY, profile);
17987
- }
17988
- var PANEL_WORKSPACE_FILE_MAX_WIDTH = 1436;
17989
- /**
17990
- * The single panel-width bound site — load-side sanitizing, gutter drags,
17991
- * and the width slider all route through here. Bounds read the config
17992
- * store (layout load has landed them before any mount; defaults before).
17993
- */
17994
- function clampPanelWidth(width, kind) {
17995
- const { minWidth, maxWidth } = appConfig().panel;
17996
- if (kind === "workspace-explorer") return Math.max(280, Math.min(maxWidth, width));
17997
- if (kind === "workspace-file") return Math.max(850, Math.min(PANEL_WORKSPACE_FILE_MAX_WIDTH, width));
17998
- return Math.max(minWidth, Math.min(maxWidth, width));
17999
- }
18000
- /** The single zoom bound site — load-side sanitizing and the zoom slider. */
18001
- function clampPanelZoom(zoom) {
18002
- const { minZoom, maxZoom } = appConfig().panel;
18003
- return Math.max(minZoom, Math.min(maxZoom, zoom));
18004
- }
18005
- /** Config-driven default width (clamped through the single bound site). */
18006
- function defaultPanelWidth() {
18007
- return clampPanelWidth(appConfig().panel.defaultWidth);
18008
- }
18009
- /**
18010
- * Clamp a stored explorer `expanded` list to sane paths (Workspace
18011
- * Explorer restore): non-string members drop and a repeated path keeps
18012
- * its FIRST occurrence — the tree treats `expanded` as a set of
18013
- * root-relative directories, and a duplicate would double-render a row.
18014
- */
18015
- function clampExpanded(value) {
18016
- if (!Array.isArray(value)) return [];
18017
- const seen = /* @__PURE__ */ new Set();
18018
- const out = [];
18019
- for (const member of value) {
18020
- if (typeof member !== "string" || seen.has(member)) continue;
18021
- seen.add(member);
18022
- out.push(member);
18023
- }
18024
- return out;
18025
- }
18026
- /** Persisted defaults when nothing (usable) is stored. */
18027
- function defaultPanelPrefs() {
18028
- return {
18029
- panels: [],
18030
- selectedPanelId: null,
18031
- panelWidth: defaultPanelWidth(),
18032
- zoom: 1
18033
- };
18034
- }
18035
- /**
18036
- * Hard shape check for a stored panel entry (the parse boundary).
18037
- * KIND (2026-09-06, ADR D6): absent or 'conversation' is the legacy shape —
18038
- * `sessionId` required; 'prompt-manager' carries no session fields;
18039
- * 'injected-doc' (Loadinjected ADR D2) requires non-empty `sourceSessionId`
18040
- * + `displayPath` — the dedupe key. Any other kind value is hard junk
18041
- * (entry drops). `agentPreset` and `width`
18042
- * may be absent (defaulted later); `agentPreset` when present must be
18043
- * string|null, `width` when present must be a finite number. Unknown
18044
- * extra keys are fine — they survive sanitizing.
18045
- */
18046
- function isPanelEntry(value) {
18047
- if (value === null || typeof value !== "object") return false;
18048
- const entry = value;
18049
- if (typeof entry.id !== "string" || entry.id.length === 0) return false;
18050
- if (entry.kind !== void 0 && entry.kind !== "conversation" && entry.kind !== "prompt-manager" && entry.kind !== "settings-editor" && entry.kind !== "injected-doc" && entry.kind !== "workspace-explorer" && entry.kind !== "workspace-file") return false;
18051
- if (entry.kind === "settings-editor" && entry.target !== "dsi" && entry.target !== "dsh") return false;
18052
- if (entry.kind === "workspace-explorer" || entry.kind === "workspace-file") {
18053
- if (typeof entry.sessionId !== "string" || entry.sessionId.length === 0) return false;
18054
- }
18055
- if (entry.kind === "workspace-explorer" && typeof entry.root !== "string") return false;
18056
- if (entry.kind === "workspace-file") {
18057
- if (typeof entry.path !== "string" || entry.path.length === 0) return false;
18058
- }
18059
- if (entry.kind === "injected-doc") {
18060
- if (typeof entry.sourceSessionId !== "string" || entry.sourceSessionId.length === 0) return false;
18061
- if (typeof entry.displayPath !== "string" || entry.displayPath.length === 0) return false;
18062
- }
18063
- if (entry.kind !== "prompt-manager" && entry.kind !== "settings-editor" && entry.kind !== "injected-doc" && entry.kind !== "workspace-explorer" && entry.kind !== "workspace-file") {
18064
- if (typeof entry.sessionId !== "string" || entry.sessionId.length === 0) return false;
18065
- if (entry.agentPreset !== void 0 && typeof entry.agentPreset !== "string" && entry.agentPreset !== null) return false;
18066
- }
18067
- if (entry.width !== void 0 && (typeof entry.width !== "number" || !Number.isFinite(entry.width))) return false;
18068
- return true;
18069
- }
18070
- /** Drop hard-junk entries; default missing keys; clamp widths. Unknown
18071
- * keys on surviving entries pass through untouched.
18072
- *
18073
- * Duplicate ids (2026-08-24 crash fix): a corrupted blob carrying the
18074
- * same panel-N twice would reach the keyed each in SidebarOpenPanels
18075
- * and crash Svelte (each_key_duplicate) at render — the each's key is
18076
- * the panel id. Duplicates drop to the FIRST occurrence (order is the
18077
- * floor's left-to-right truth); a selectedPanelId left dangling by the
18078
- * dedupe re-sanitizes to the first panel in loadPanelPrefs. */
18079
- function sanitizePanels(raw) {
18080
- if (!Array.isArray(raw)) return [];
18081
- const seen = /* @__PURE__ */ new Set();
18082
- return raw.filter((entry) => isPanelEntry(entry)).map((entry) => {
18083
- const kindDefault = entry.kind === "workspace-explorer" ? 280 : entry.kind === "workspace-file" ? 860 : defaultPanelWidth();
18084
- const width = clampPanelWidth(typeof entry.width === "number" && Number.isFinite(entry.width) ? entry.width : kindDefault, entry.kind);
18085
- if (entry.kind === "prompt-manager") return {
18086
- id: entry.id,
18087
- kind: "prompt-manager",
18088
- width
18089
- };
18090
- if (entry.kind === "settings-editor") return {
18091
- id: entry.id,
18092
- kind: "settings-editor",
18093
- target: entry.target,
18094
- width
18095
- };
18096
- if (entry.kind === "injected-doc") return {
18097
- id: entry.id,
18098
- kind: "injected-doc",
18099
- sourceSessionId: entry.sourceSessionId,
18100
- displayPath: entry.displayPath,
18101
- width
18102
- };
18103
- if (entry.kind === "workspace-explorer") return {
18104
- id: entry.id,
18105
- kind: "workspace-explorer",
18106
- sessionId: entry.sessionId,
18107
- root: entry.root,
18108
- expanded: clampExpanded(entry.expanded),
18109
- tab: entry.tab === "changes" ? "changes" : "explorer",
18110
- collapsedRepos: clampExpanded(entry.collapsedRepos),
18111
- width
18112
- };
18113
- if (entry.kind === "workspace-file") return {
18114
- id: entry.id,
18115
- kind: "workspace-file",
18116
- sessionId: entry.sessionId,
18117
- path: entry.path,
18118
- explorerPanelId: typeof entry.explorerPanelId === "string" && entry.explorerPanelId.length > 0 ? entry.explorerPanelId : null,
18119
- view: entry.view === "diff" ? "diff" : "edit",
18120
- width
18121
- };
18122
- return {
18123
- ...entry,
18124
- id: entry.id,
18125
- kind: "conversation",
18126
- sessionId: entry.sessionId,
18127
- agentPreset: entry.agentPreset ?? null,
18128
- width
18129
- };
18130
- }).filter((entry) => {
18131
- if (seen.has(entry.id)) return false;
18132
- seen.add(entry.id);
18133
- return true;
18134
- });
18135
- }
18136
- /**
18137
- * Read the workspace state from localStorage.
18138
- * @param profile sanitized workspace profile (null/omitted = default desk).
18139
- * @returns stored state when parseable (sanitized), defaults when absent,
18140
- * junk, or SSR. A `selectedPanelId` pointing at a missing panel
18141
- * sanitizes to the first panel's id (null when no panels remain).
18142
- */
18143
- function loadPanelPrefs(profile) {
18144
- if (typeof localStorage === "undefined") return defaultPanelPrefs();
18145
- try {
18146
- const raw = localStorage.getItem(panelPrefsKey(profile));
18147
- if (!raw) return defaultPanelPrefs();
18148
- const parsed = JSON.parse(raw);
18149
- if (parsed === null || typeof parsed !== "object") return defaultPanelPrefs();
18150
- if (!Array.isArray(parsed.panels)) return defaultPanelPrefs();
18151
- const panels = sanitizePanels(parsed.panels);
18152
- const panelWidth = typeof parsed.panelWidth === "number" && Number.isFinite(parsed.panelWidth) ? clampPanelWidth(parsed.panelWidth) : defaultPanelWidth();
18153
- const zoom = typeof parsed.zoom === "number" && Number.isFinite(parsed.zoom) ? clampPanelZoom(parsed.zoom) : 1;
18154
- return {
18155
- panels,
18156
- selectedPanelId: typeof parsed.selectedPanelId === "string" && panels.some((panel) => panel.id === parsed.selectedPanelId) ? parsed.selectedPanelId : panels[0]?.id ?? null,
18157
- panelWidth,
18158
- zoom
18159
- };
18160
- } catch {
18161
- return defaultPanelPrefs();
18162
- }
18163
- }
18164
- //#endregion
18165
18482
  //#region src/lib/components/panels/control-bar/ControlBar.svelte
18166
18483
  function ControlBar($$renderer, $$props) {
18167
18484
  $$renderer.component(($$renderer) => {
@@ -18281,25 +18598,26 @@ function loadSidebarPrefs(profile) {
18281
18598
  }
18282
18599
  }
18283
18600
  /**
18284
- * The open workspace explorer for a session — Workspace Explorer D4's
18285
- * dedupe key: ONE explorer per sessionId, so a repeat chip click FOCUSES
18286
- * instead of adding (a duplicate is a bug, not a feature). Two sessions
18287
- * sharing one workspace hold distinct explorers.
18601
+ * The open workspace explorer for a workspace ROOT — The Shared Tree ADR
18602
+ * (2026-09-17) D1's dedupe key: ONE explorer per ROOT across ALL sessions,
18603
+ * so a chip click on any session sharing the root FOCUSES instead of
18604
+ * adding (a duplicate is a bug, not a feature). The retired per-session
18605
+ * key ("two sessions sharing one workspace hold distinct explorers",
18606
+ * Workspace Explorer D4) produced N byte-identical trees for N sessions.
18288
18607
  */
18289
- function findWorkspaceExplorerPanel(panels, sessionId) {
18290
- return panels.find((p) => p.kind === "workspace-explorer" && p.sessionId === sessionId);
18608
+ function findWorkspaceExplorerPanel(panels, root) {
18609
+ return panels.find((p) => p.kind === "workspace-explorer" && p.root === root);
18291
18610
  }
18292
18611
  /**
18293
- * The explorer's slot (Workspace Explorer D3; 2026-09-10 parent-first
18294
- * fix): the AFTER-SOURCE slot of its session — the same below-source rule
18295
- * every lineage child uses (afterSourceSlot = the conversation's family
18296
- * unit end + 1, documents and workspace tail included) — so the floor
18297
- * reads Parent → explorer → file. No open conversation panel → the
18298
- * focused-slot fallback (the chip's session may hold no open
18299
- * conversation, so there is no source anchor to honor).
18612
+ * The explorer's slot (The Shared Tree ADR 2026-09-17, D3): the FOCUSED
18613
+ * slot — the explorer is NOT a lineage child; a workspace tree has no
18614
+ * parent session, only an opener. The retired after-source rule
18615
+ * (Workspace Explorer D3, "Parent → explorer → file") interposed the
18616
+ * panel inside a conversation's family unit — the family clamp inside
18617
+ * focusedInsertionSlot still keeps any real family unsplit.
18300
18618
  */
18301
- function workspaceExplorerSlot(panels, sessionId, selectedIndex, facts) {
18302
- return afterSourceSlot(panels, sessionId, facts) ?? focusedInsertionSlot(panels, selectedIndex, facts.parentOf);
18619
+ function workspaceExplorerSlot(panels, selectedIndex, facts) {
18620
+ return focusedInsertionSlot(panels, selectedIndex, facts.parentOf);
18303
18621
  }
18304
18622
  /**
18305
18623
  * Insert-before-focused slot (operator spec, 2026-08-31): a fresh panel
@@ -18444,9 +18762,8 @@ function _page($$renderer, $$props) {
18444
18762
  /** A workspace-explorer floor slot (Workspace Explorer W3 task 3.2,
18445
18763
  * ADR D2/D4): the LIVE tree over the session's workspace root.
18446
18764
  * expanded starts empty — F5 restore refills it from the blob.
18447
- * Width = the MINIMUM resize width (2026-09-10): a tree needs no
18448
- * wide columns — the uniform preset (panelWidth) wastes empty
18449
- * space; the gutter drag grows it like any panel. */
18765
+ * Width = the WIDE file band, unconditionally (The Settings Tree
18766
+ * ADR 2026-09-18 D1: the explorer layout is the only layout). */
18450
18767
  function makeWorkspaceExplorerPanel(sessionId, root) {
18451
18768
  return {
18452
18769
  id: nextPanelId(),
@@ -18454,20 +18771,43 @@ function _page($$renderer, $$props) {
18454
18771
  sessionId,
18455
18772
  root,
18456
18773
  expanded: [],
18457
- width: clampPanelWidth(280, "workspace-explorer")
18774
+ width: clampPanelWidth(850, "workspace-explorer")
18458
18775
  };
18459
18776
  }
18460
- /** Chip click action (Workspace Explorer ADR D4): ONE explorer per
18461
- * session — a repeat click FOCUSES the existing panel (a duplicate
18462
- * is a bug, not a feature); a fresh one lands at the focused slot
18463
- * with the lineage family clamp. */
18777
+ /** Chip click action (The Shared Tree ADR 2026-09-17, D2): ONE
18778
+ * explorer per WORKSPACE root across ALL sessions — a click on any
18779
+ * session sharing the root FOCUSES the open explorer (dedupe
18780
+ * key = root, D1); a fresh root lands at the focused slot (D3,
18781
+ * non-lineage — the explorer is not a lineage child). sessionId
18782
+ * stays in the signature (the chip supplies it) and rides the fresh
18783
+ * panel as PROVENANCE (D4), never as identity. */
18464
18784
  function openWorkspaceExplorer(sessionId, workspace) {
18465
- const open = findWorkspaceExplorerPanel(panels, sessionId);
18785
+ const open = findWorkspaceExplorerPanel(panels, workspace);
18466
18786
  if (open) {
18467
18787
  selectedPanelId = open.id;
18468
18788
  return;
18469
18789
  }
18470
- insertPanel(makeWorkspaceExplorerPanel(sessionId, workspace), workspaceExplorerSlot(panels, sessionId, selectedIndex(), moveFacts()));
18790
+ insertPanel(makeWorkspaceExplorerPanel(sessionId, workspace), workspaceExplorerSlot(panels, selectedIndex(), moveFacts()));
18791
+ }
18792
+ /** The open explorer for a session's workspace (The Shared Tree D1):
18793
+ * matched by ROOT across sessions — the panel's provenance sessionId
18794
+ * is not the key. */
18795
+ function sessionWorkspaceExplorer(sessionId) {
18796
+ const root = panelWorkspaceFor(sessionId);
18797
+ return root === null ? void 0 : findWorkspaceExplorerPanel(panels, root);
18798
+ }
18799
+ /** Explorer tree intents (Module Communication Map): the panel emits
18800
+ * toggle/open-file; the owner mutates expanded (the persisted part) —
18801
+ * the levels cache stays component-local. File open is W4's wiring. */
18802
+ function toggleExplorerPath(panelId, path) {
18803
+ const idx = panels.findIndex((p) => p.id === panelId);
18804
+ if (idx < 0 || panels[idx].kind !== "workspace-explorer") return;
18805
+ const explorer = panels[idx];
18806
+ const next = explorer.expanded.includes(path) ? explorer.expanded.filter((p) => p !== path) : [...explorer.expanded, path];
18807
+ panels = panels.with(idx, {
18808
+ ...explorer,
18809
+ expanded: next
18810
+ });
18471
18811
  }
18472
18812
  /** Explorer collapse-all intent (Module Communication Map): the panel
18473
18813
  * emits; the owner empties the expanded list (the persisted part) —
@@ -18480,6 +18820,79 @@ function _page($$renderer, $$props) {
18480
18820
  expanded: []
18481
18821
  });
18482
18822
  }
18823
+ /** Git Eye tab intent (2026-09-12 operator request): the active tab is
18824
+ * PERSISTED on the entry — a hard reload restores it (supersedes the
18825
+ * ADR D5 remount-Explorer clause, amendment logged). */
18826
+ function setExplorerTab(panelId, tab) {
18827
+ const idx = panels.findIndex((p) => p.id === panelId);
18828
+ if (idx < 0 || panels[idx].kind !== "workspace-explorer") return;
18829
+ panels = panels.with(idx, {
18830
+ ...panels[idx],
18831
+ tab
18832
+ });
18833
+ }
18834
+ /** Explorer Layout tabs (amendment 2026-09-16): the tab list and the
18835
+ * active tab are OWNER-PERSISTED entry facts — panel-prefs saves the
18836
+ * blob, so a hard reload restores the open tabs exactly like
18837
+ * expanded/tab/collapsedRepos. The file tabs die with the panel (D5). */
18838
+ function openExplorerTab(panelId, path) {
18839
+ const idx = panels.findIndex((p) => p.id === panelId);
18840
+ if (idx < 0 || panels[idx].kind !== "workspace-explorer") return;
18841
+ const ex = panels[idx];
18842
+ const tabs = ex.openTabs ?? [];
18843
+ const openTabs = tabs.includes(path) ? tabs : [...tabs, path];
18844
+ panels = panels.with(idx, {
18845
+ ...ex,
18846
+ openTabs,
18847
+ activeFile: path
18848
+ });
18849
+ }
18850
+ function closeExplorerTab(panelId, path) {
18851
+ const idx = panels.findIndex((p) => p.id === panelId);
18852
+ if (idx < 0 || panels[idx].kind !== "workspace-explorer") return;
18853
+ const ex = panels[idx];
18854
+ const tabs = ex.openTabs ?? [];
18855
+ const closedAt = tabs.indexOf(path);
18856
+ if (closedAt === -1) return;
18857
+ const openTabs = tabs.filter((p) => p !== path);
18858
+ const activeFile = ex.activeFile === path ? openTabs.length === 0 ? null : openTabs[Math.max(0, closedAt - 1)] : ex.activeFile ?? null;
18859
+ panels = panels.with(idx, {
18860
+ ...ex,
18861
+ openTabs,
18862
+ activeFile
18863
+ });
18864
+ }
18865
+ function activateExplorerTab(panelId, path) {
18866
+ const idx = panels.findIndex((p) => p.id === panelId);
18867
+ if (idx < 0 || panels[idx].kind !== "workspace-explorer") return;
18868
+ const ex = panels[idx];
18869
+ if (!(ex.openTabs ?? []).includes(path)) return;
18870
+ panels = panels.with(idx, {
18871
+ ...ex,
18872
+ activeFile: path
18873
+ });
18874
+ }
18875
+ /** The Explorer Layout drag settle (ADR D4): one DESK fact on the
18876
+ * route — the split is desk state, persisted with the floor blob,
18877
+ * NOT per entry. The panel already clamped; clamp again at the
18878
+ * single bound site for defense in depth. */
18879
+ function setExplorerTreePct(_panelId, pct) {
18880
+ treePct = clampTreePct(pct);
18881
+ }
18882
+ /** Git Eye repo-collapse intent (2026-09-13): the collapsed repo set is
18883
+ * PERSISTED on the entry — a hard reload restores it (the explorer-tab
18884
+ * pattern). Default is EXPANDED: an absent/empty list collapses nothing. */
18885
+ function toggleExplorerRepo(panelId, repoRel) {
18886
+ const idx = panels.findIndex((p) => p.id === panelId);
18887
+ if (idx < 0 || panels[idx].kind !== "workspace-explorer") return;
18888
+ const explorer = panels[idx];
18889
+ const prev = explorer.collapsedRepos ?? [];
18890
+ const next = prev.includes(repoRel) ? prev.filter((r) => r !== repoRel) : [...prev, repoRel];
18891
+ panels = panels.with(idx, {
18892
+ ...explorer,
18893
+ collapsedRepos: next
18894
+ });
18895
+ }
18483
18896
  /** Git Eye collapse-all-repos intent (2026-09-13): on the Changes tab
18484
18897
  * the toolbar's collapse-all collapses EVERY known repo — persisted
18485
18898
  * like the single-repo toggle, so a hard reload restores it. */
@@ -18504,9 +18917,20 @@ function _page($$renderer, $$props) {
18504
18917
  });
18505
18918
  }
18506
18919
  /** The file panel's root (the copy fallback): its explorer's root
18507
- * when open, else the session's workspace from the spine/cold rows. */
18920
+ * when open (Shared Tree D1 — root-keyed, any session's explorer),
18921
+ * else the session's workspace from the spine/cold rows. */
18508
18922
  function filePanelRoot(sessionId) {
18509
- return findWorkspaceExplorerPanel(panels, sessionId)?.root ?? panelWorkspaceFor(sessionId) ?? "";
18923
+ return sessionWorkspaceExplorer(sessionId)?.root ?? panelWorkspaceFor(sessionId) ?? "";
18924
+ }
18925
+ /** Tree file-click action (The Settings Tree ADR 2026-09-18 D1: the
18926
+ * explorer layout is the ONLY layout — the retired floor-file branch
18927
+ * of the 2026-09-17 ADR is gone). The intent publishes to the
18928
+ * session's explorer, which opens/focuses a tab. No open explorer
18929
+ * ⇒ the intent drops (the browsing surface that opens files IS the
18930
+ * explorer; the floor keeps conversations only). */
18931
+ function openWorkspaceFile(sessionId, path) {
18932
+ const host = sessionWorkspaceExplorer(sessionId);
18933
+ if (host) publishFileIntent(host.id, path);
18510
18934
  }
18511
18935
  const seedData = page.data;
18512
18936
  const seedCold = seedData && typeof seedData.sessionId === "string" && Array.isArray(seedData.entries) ? {
@@ -18527,6 +18951,7 @@ function _page($$renderer, $$props) {
18527
18951
  const initialPrefs = loadPanelPrefs(activeProfile);
18528
18952
  let panelWidth = initialPrefs.panelWidth;
18529
18953
  let zoom = clampPanelZoom(initialPrefs.zoom);
18954
+ let treePct = clampTreePct(initialPrefs.treePct);
18530
18955
  let panels = seededInitially ? [makePanel(seedSessionId(), seedCold?.agentPreset ?? null)] : initialPrefs.panels;
18531
18956
  syncPanelIdSeq(panels);
18532
18957
  let selectedPanelId = seededInitially ? panels[0]?.id ?? null : initialPrefs.selectedPanelId;
@@ -18537,6 +18962,20 @@ function _page($$renderer, $$props) {
18537
18962
  let loupePanelId = null;
18538
18963
  /** Runtime cold-load results per sessionId (dual fetch results). */
18539
18964
  let coldCache = {};
18965
+ let pendingFileIntents = {};
18966
+ let fileIntentSeq = 0;
18967
+ /** Publish a file intent to an explorer panel and focus it. */
18968
+ function publishFileIntent(explorerId, path) {
18969
+ pendingFileIntents[explorerId] = {
18970
+ path,
18971
+ nonce: ++fileIntentSeq
18972
+ };
18973
+ selectedPanelId = explorerId;
18974
+ }
18975
+ /** The panel consumed intent nonce N — clear exactly that publish. */
18976
+ function consumeFileIntent(explorerId, nonce) {
18977
+ if (pendingFileIntents[explorerId]?.nonce === nonce) pendingFileIntents[explorerId] = null;
18978
+ }
18540
18979
  if (seededInitially && seedCold !== void 0 && seedData !== void 0) coldCache = {
18541
18980
  ...coldCache,
18542
18981
  [seedData.sessionId]: seedCold
@@ -18601,7 +19040,7 @@ function _page($$renderer, $$props) {
18601
19040
  if (opts.family !== false && root.kind === "workspace-explorer") {
18602
19041
  for (const p of panels) if (p.kind === "workspace-file" && (p.explorerPanelId === root.id || p.explorerPanelId === null && p.sessionId === root.sessionId)) doomedPanelIds.add(p.id);
18603
19042
  }
18604
- panels = panels.filter((p) => p.id !== root.id && !doomedPanelIds.has(p.id) && (p.kind === "conversation" ? !doomed.has(p.sessionId) : !("sessionId" in p) || opts.family !== false && !doomed.has(p.sessionId)));
19043
+ panels = panels.filter((p) => p.id !== root.id && !doomedPanelIds.has(p.id) && (p.kind === "conversation" ? !doomed.has(p.sessionId) : !("sessionId" in p) || p.sessionId === null || opts.family !== false && !doomed.has(p.sessionId)));
18605
19044
  if (selectedPanelId !== null && !panels.some((p) => p.id === selectedPanelId)) selectedPanelId = panels[Math.min(idx, panels.length - 1)]?.id ?? null;
18606
19045
  clearLoupeIfGone();
18607
19046
  }
@@ -18848,11 +19287,31 @@ function _page($$renderer, $$props) {
18848
19287
  WorkspaceExplorerPanel($$renderer, {
18849
19288
  sessionId: panel.sessionId,
18850
19289
  root: panel.root,
19290
+ home: panel.home,
18851
19291
  expanded: panel.expanded,
18852
19292
  tab: panel.tab ?? "explorer",
19293
+ onToggle: (path) => toggleExplorerPath(panel.id, path),
19294
+ onOpenFile: (path) => {
19295
+ if (panel.home !== void 0) {
19296
+ openExplorerTab(panel.id, path);
19297
+ return;
19298
+ }
19299
+ if (panel.sessionId !== null) openWorkspaceFile(panel.sessionId, path);
19300
+ },
18853
19301
  onCollapseAll: () => collapseExplorerPaths(panel.id),
19302
+ onTabChange: (tab) => setExplorerTab(panel.id, tab),
18854
19303
  collapsedRepos: panel.collapsedRepos ?? [],
18855
- onCollapseAllRepos: (repos) => collapseExplorerRepos(panel.id, repos)
19304
+ onToggleRepo: (repoRel) => toggleExplorerRepo(panel.id, repoRel),
19305
+ onCollapseAllRepos: (repos) => collapseExplorerRepos(panel.id, repos),
19306
+ treePct,
19307
+ onTreePctChange: (pct) => setExplorerTreePct(panel.id, pct),
19308
+ pendingOpenFile: pendingFileIntents[panel.id] ?? null,
19309
+ onPendingOpenConsumed: (nonce) => consumeFileIntent(panel.id, nonce),
19310
+ openTabs: panel.openTabs ?? [],
19311
+ activeFile: panel.activeFile ?? null,
19312
+ onOpenTab: (path) => openExplorerTab(panel.id, path),
19313
+ onCloseTab: (path) => closeExplorerTab(panel.id, path),
19314
+ onActivateTab: (path) => activateExplorerTab(panel.id, path)
18856
19315
  });
18857
19316
  } else if (panel.kind === "workspace-file") {
18858
19317
  $$renderer.push("<!--[4-->");
@@ -18861,7 +19320,9 @@ function _page($$renderer, $$props) {
18861
19320
  path: panel.path,
18862
19321
  root: filePanelRoot(panel.sessionId),
18863
19322
  view: panel.view ?? "edit",
18864
- onviewchange: (v) => setFileView(panel.id, v)});
19323
+ onviewchange: (v) => setFileView(panel.id, v),
19324
+ onclose: () => removePanel(panel.id)
19325
+ });
18865
19326
  } else if (panel.kind === "conversation") {
18866
19327
  $$renderer.push("<!--[5-->");
18867
19328
  const p = panel;
@@ -19023,4 +19484,4 @@ function _page($$renderer, $$props) {
19023
19484
  }
19024
19485
 
19025
19486
  export { _page as default };
19026
- //# sourceMappingURL=_page.svelte.js-CYT_NWww.js.map
19487
+ //# sourceMappingURL=_page.svelte.js-BSt3-Adz.js.map