@chatcode/cco-better-sidebar 0.18.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +554 -0
  3. package/README_EN.md +563 -0
  4. package/assets/logo.svg +1 -0
  5. package/cordis.patch.yml +79 -0
  6. package/lib/client-editor.js +55744 -0
  7. package/lib/client-mermaid.js +208256 -0
  8. package/lib/client-registry.js +30378 -0
  9. package/lib/client-terminal.js +16894 -0
  10. package/lib/client.js +30378 -0
  11. package/lib/index.js +6897 -0
  12. package/lib/invariant.js +22 -0
  13. package/lib/types/agent-opens.d.ts +65 -0
  14. package/lib/types/agent-pty.d.ts +226 -0
  15. package/lib/types/browser-probe.d.ts +15 -0
  16. package/lib/types/bundle-route.d.ts +12 -0
  17. package/lib/types/chatcode-update.d.ts +47 -0
  18. package/lib/types/client/AccountMenu.d.ts +1 -0
  19. package/lib/types/client/AiRatioSection.d.ts +3 -0
  20. package/lib/types/client/BrowserView.d.ts +39 -0
  21. package/lib/types/client/ChatcodeGate.d.ts +6 -0
  22. package/lib/types/client/CodingPlanModelsSettings.d.ts +9 -0
  23. package/lib/types/client/DiffTab.d.ts +6 -0
  24. package/lib/types/client/DiffView.d.ts +51 -0
  25. package/lib/types/client/EditorHost.d.ts +13 -0
  26. package/lib/types/client/FenceErrorNotice.d.ts +5 -0
  27. package/lib/types/client/FileTree.d.ts +41 -0
  28. package/lib/types/client/FreeWindow.d.ts +29 -0
  29. package/lib/types/client/GitView.d.ts +12 -0
  30. package/lib/types/client/MarkdownHtml.d.ts +32 -0
  31. package/lib/types/client/OrphanedTab.d.ts +11 -0
  32. package/lib/types/client/PdfView.d.ts +6 -0
  33. package/lib/types/client/ReleaseNotesDialog.d.ts +11 -0
  34. package/lib/types/client/RenderBoundary.d.ts +32 -0
  35. package/lib/types/client/SandboxStatusBar.d.ts +10 -0
  36. package/lib/types/client/SideCardSection.d.ts +74 -0
  37. package/lib/types/client/Sidebar.d.ts +8 -0
  38. package/lib/types/client/TabBar.d.ts +54 -0
  39. package/lib/types/client/TerminalView.d.ts +24 -0
  40. package/lib/types/client/TextEditor.d.ts +10 -0
  41. package/lib/types/client/TreePanel.d.ts +27 -0
  42. package/lib/types/client/UpdateDialog.d.ts +10 -0
  43. package/lib/types/client/UpdateSettingsPanel.d.ts +1 -0
  44. package/lib/types/client/UpdateSettingsSection.d.ts +10 -0
  45. package/lib/types/client/UploadOverlay.d.ts +20 -0
  46. package/lib/types/client/UsageHeatmapSection.d.ts +4 -0
  47. package/lib/types/client/add-plugin-modal.d.ts +15 -0
  48. package/lib/types/client/api.d.ts +345 -0
  49. package/lib/types/client/binary-download.d.ts +12 -0
  50. package/lib/types/client/breakpoints.d.ts +17 -0
  51. package/lib/types/client/browser.d.ts +60 -0
  52. package/lib/types/client/builtins/index.d.ts +18 -0
  53. package/lib/types/client/builtins/tabs.d.ts +11 -0
  54. package/lib/types/client/builtins/viewers.d.ts +3 -0
  55. package/lib/types/client/chat-presentation.d.ts +13 -0
  56. package/lib/types/client/chunk-loader.d.ts +124 -0
  57. package/lib/types/client/chunks/editor.d.ts +9 -0
  58. package/lib/types/client/chunks/mermaid.d.ts +10 -0
  59. package/lib/types/client/chunks/terminal.d.ts +9 -0
  60. package/lib/types/client/cm-themes.d.ts +24 -0
  61. package/lib/types/client/conversation-draft.d.ts +14 -0
  62. package/lib/types/client/desktop-env.d.ts +35 -0
  63. package/lib/types/client/editor-load.d.ts +66 -0
  64. package/lib/types/client/frame-batcher.d.ts +24 -0
  65. package/lib/types/client/icons.d.ts +66 -0
  66. package/lib/types/client/image-types.d.ts +3 -0
  67. package/lib/types/client/ime-guard.d.ts +36 -0
  68. package/lib/types/client/index.d.ts +19 -0
  69. package/lib/types/client/intercept.d.ts +44 -0
  70. package/lib/types/client/lang.d.ts +14 -0
  71. package/lib/types/client/layout-push.d.ts +15 -0
  72. package/lib/types/client/lazy-chunk.d.ts +25 -0
  73. package/lib/types/client/link-intercept.d.ts +42 -0
  74. package/lib/types/client/locales-ar.d.ts +18 -0
  75. package/lib/types/client/locales-de.d.ts +4 -0
  76. package/lib/types/client/locales-fr.d.ts +10 -0
  77. package/lib/types/client/locales-hi.d.ts +17 -0
  78. package/lib/types/client/locales-id.d.ts +15 -0
  79. package/lib/types/client/locales-it.d.ts +8 -0
  80. package/lib/types/client/locales-ja.d.ts +17 -0
  81. package/lib/types/client/locales-ko.d.ts +9 -0
  82. package/lib/types/client/locales-nl.d.ts +16 -0
  83. package/lib/types/client/locales-pl.d.ts +19 -0
  84. package/lib/types/client/locales-pt.d.ts +1 -0
  85. package/lib/types/client/locales-ru.d.ts +13 -0
  86. package/lib/types/client/locales-sv.d.ts +1 -0
  87. package/lib/types/client/locales-th.d.ts +17 -0
  88. package/lib/types/client/locales-tr.d.ts +17 -0
  89. package/lib/types/client/locales-vi.d.ts +17 -0
  90. package/lib/types/client/locales-zh-HK.d.ts +32 -0
  91. package/lib/types/client/locales-zh-MO.d.ts +32 -0
  92. package/lib/types/client/locales-zh-TW.d.ts +32 -0
  93. package/lib/types/client/locales.d.ts +571 -0
  94. package/lib/types/client/markdown-html.d.ts +96 -0
  95. package/lib/types/client/markdown-images.d.ts +45 -0
  96. package/lib/types/client/markdown-labels.d.ts +21 -0
  97. package/lib/types/client/md-toc.d.ts +4 -0
  98. package/lib/types/client/mermaid-blocks.d.ts +47 -0
  99. package/lib/types/client/mermaid-sanitize.d.ts +2 -0
  100. package/lib/types/client/mermaid.d.ts +12 -0
  101. package/lib/types/client/open-when-sized.d.ts +20 -0
  102. package/lib/types/client/open-with-settings.d.ts +4 -0
  103. package/lib/types/client/open-with.d.ts +87 -0
  104. package/lib/types/client/openpath-intercept.d.ts +52 -0
  105. package/lib/types/client/paths.d.ts +43 -0
  106. package/lib/types/client/pdf-types.d.ts +2 -0
  107. package/lib/types/client/pinned.d.ts +86 -0
  108. package/lib/types/client/plugin-settings.d.ts +8 -0
  109. package/lib/types/client/plugins-shared.d.ts +27 -0
  110. package/lib/types/client/plugins-tabs.d.ts +3 -0
  111. package/lib/types/client/plugins-viewers.d.ts +3 -0
  112. package/lib/types/client/prefs.d.ts +41 -0
  113. package/lib/types/client/produced-files.d.ts +29 -0
  114. package/lib/types/client/selection-payload.d.ts +27 -0
  115. package/lib/types/client/service.d.ts +445 -0
  116. package/lib/types/client/session-pins.d.ts +14 -0
  117. package/lib/types/client/settings-nav-icon.d.ts +27 -0
  118. package/lib/types/client/shell-presets.d.ts +48 -0
  119. package/lib/types/client/split-pane.d.ts +38 -0
  120. package/lib/types/client/state.d.ts +480 -0
  121. package/lib/types/client/tab-content-memo.d.ts +40 -0
  122. package/lib/types/client/terminal-font.d.ts +78 -0
  123. package/lib/types/client/terminal-links.d.ts +119 -0
  124. package/lib/types/client/theme.d.ts +45 -0
  125. package/lib/types/client/titlebar-strip.d.ts +23 -0
  126. package/lib/types/client/ui-tweaks/appicons.d.ts +16 -0
  127. package/lib/types/client/ui-tweaks/archive.d.ts +42 -0
  128. package/lib/types/client/ui-tweaks/branding.d.ts +45 -0
  129. package/lib/types/client/ui-tweaks/cachehit.d.ts +41 -0
  130. package/lib/types/client/ui-tweaks/gitbar.d.ts +103 -0
  131. package/lib/types/client/ui-tweaks/index.d.ts +279 -0
  132. package/lib/types/client/ui-tweaks/mcp.d.ts +28 -0
  133. package/lib/types/client/ui-tweaks/notifier.d.ts +125 -0
  134. package/lib/types/client/ui-tweaks/whale.d.ts +55 -0
  135. package/lib/types/client/upload.d.ts +69 -0
  136. package/lib/types/client/useChatcodeAccount.d.ts +10 -0
  137. package/lib/types/client/wco.d.ts +47 -0
  138. package/lib/types/codingplan.d.ts +37 -0
  139. package/lib/types/config.d.ts +95 -0
  140. package/lib/types/context-types.d.ts +540 -0
  141. package/lib/types/fs-operations.d.ts +30 -0
  142. package/lib/types/fs-search.d.ts +24 -0
  143. package/lib/types/fs-tree.d.ts +51 -0
  144. package/lib/types/git.d.ts +124 -0
  145. package/lib/types/html-route.d.ts +59 -0
  146. package/lib/types/index.d.ts +47 -0
  147. package/lib/types/invariant.d.ts +15 -0
  148. package/lib/types/login-route.d.ts +2 -0
  149. package/lib/types/open-external.d.ts +24 -0
  150. package/lib/types/path-security.d.ts +26 -0
  151. package/lib/types/prefs-shared.d.ts +257 -0
  152. package/lib/types/pty-deps.d.ts +79 -0
  153. package/lib/types/pty-manager.d.ts +168 -0
  154. package/lib/types/tools.d.ts +34 -0
  155. package/lib/types/trust-fence.d.ts +24 -0
  156. package/lib/types/ui-tweaks/archive.d.ts +71 -0
  157. package/lib/types/ui-tweaks/config.d.ts +186 -0
  158. package/lib/types/ui-tweaks/git-web.d.ts +43 -0
  159. package/lib/types/ui-tweaks/git.d.ts +311 -0
  160. package/lib/types/ui-tweaks/index.d.ts +17 -0
  161. package/lib/types/ui-tweaks/mcp.d.ts +103 -0
  162. package/lib/types/ui-tweaks/turn-outcome.d.ts +57 -0
  163. package/lib/types/ui-tweaks/web.d.ts +55 -0
  164. package/lib/types/wire.d.ts +38 -0
  165. package/package.json +213 -0
  166. package/scripts/install.ps1 +310 -0
  167. package/scripts/install.sh +287 -0
  168. package/src/agent-opens.ts +265 -0
  169. package/src/agent-pty.ts +527 -0
  170. package/src/browser-probe.ts +26 -0
  171. package/src/bundle-route.ts +129 -0
  172. package/src/chatcode-update.ts +193 -0
  173. package/src/client/AccountMenu.tsx +177 -0
  174. package/src/client/AiRatioSection.module.css +329 -0
  175. package/src/client/AiRatioSection.tsx +292 -0
  176. package/src/client/BrowserView.tsx +289 -0
  177. package/src/client/ChatcodeGate.tsx +35 -0
  178. package/src/client/CodingPlanModelsSettings.module.css +118 -0
  179. package/src/client/CodingPlanModelsSettings.tsx +101 -0
  180. package/src/client/DiffTab.tsx +114 -0
  181. package/src/client/DiffView.tsx +305 -0
  182. package/src/client/EditorHost.tsx +563 -0
  183. package/src/client/FenceErrorNotice.tsx +40 -0
  184. package/src/client/FileTree.tsx +701 -0
  185. package/src/client/FreeWindow.tsx +337 -0
  186. package/src/client/GitView.tsx +722 -0
  187. package/src/client/MarkdownHtml.tsx +296 -0
  188. package/src/client/OrphanedTab.tsx +27 -0
  189. package/src/client/PdfView.tsx +110 -0
  190. package/src/client/ReleaseNotesDialog.tsx +91 -0
  191. package/src/client/RenderBoundary.tsx +49 -0
  192. package/src/client/SandboxStatusBar.tsx +60 -0
  193. package/src/client/SideCardSection.module.css +932 -0
  194. package/src/client/SideCardSection.tsx +1169 -0
  195. package/src/client/Sidebar.tsx +1857 -0
  196. package/src/client/TabBar.tsx +365 -0
  197. package/src/client/TerminalView.tsx +424 -0
  198. package/src/client/TextEditor.tsx +468 -0
  199. package/src/client/TreePanel.tsx +279 -0
  200. package/src/client/UpdateDialog.tsx +186 -0
  201. package/src/client/UpdateSettingsPanel.tsx +29 -0
  202. package/src/client/UpdateSettingsSection.tsx +22 -0
  203. package/src/client/UploadOverlay.tsx +62 -0
  204. package/src/client/UsageHeatmapSection.module.css +440 -0
  205. package/src/client/UsageHeatmapSection.tsx +301 -0
  206. package/src/client/add-plugin-modal.tsx +199 -0
  207. package/src/client/api.ts +433 -0
  208. package/src/client/binary-download.tsx +23 -0
  209. package/src/client/breakpoints.ts +61 -0
  210. package/src/client/browser.ts +167 -0
  211. package/src/client/builtins/index.ts +37 -0
  212. package/src/client/builtins/tabs.tsx +275 -0
  213. package/src/client/builtins/viewers.tsx +125 -0
  214. package/src/client/chat-presentation.ts +95 -0
  215. package/src/client/chunk-loader.ts +350 -0
  216. package/src/client/chunks/editor.tsx +9 -0
  217. package/src/client/chunks/mermaid.tsx +10 -0
  218. package/src/client/chunks/terminal.tsx +9 -0
  219. package/src/client/cm-themes.ts +129 -0
  220. package/src/client/conversation-draft.ts +29 -0
  221. package/src/client/css-modules.d.ts +5 -0
  222. package/src/client/desktop-env.ts +72 -0
  223. package/src/client/editor-load.ts +92 -0
  224. package/src/client/frame-batcher.ts +56 -0
  225. package/src/client/icons.tsx +170 -0
  226. package/src/client/image-types.ts +8 -0
  227. package/src/client/ime-guard.ts +47 -0
  228. package/src/client/index.tsx +501 -0
  229. package/src/client/intercept.tsx +172 -0
  230. package/src/client/lang.ts +225 -0
  231. package/src/client/layout-push.ts +37 -0
  232. package/src/client/layout.css +156 -0
  233. package/src/client/lazy-chunk.tsx +89 -0
  234. package/src/client/link-intercept.ts +73 -0
  235. package/src/client/locales-ar.ts +387 -0
  236. package/src/client/locales-de.ts +372 -0
  237. package/src/client/locales-fr.ts +379 -0
  238. package/src/client/locales-hi.ts +386 -0
  239. package/src/client/locales-id.ts +384 -0
  240. package/src/client/locales-it.ts +377 -0
  241. package/src/client/locales-ja.ts +486 -0
  242. package/src/client/locales-ko.ts +378 -0
  243. package/src/client/locales-nl.ts +384 -0
  244. package/src/client/locales-pl.ts +388 -0
  245. package/src/client/locales-pt.ts +369 -0
  246. package/src/client/locales-ru.ts +384 -0
  247. package/src/client/locales-sv.ts +369 -0
  248. package/src/client/locales-th.ts +386 -0
  249. package/src/client/locales-tr.ts +386 -0
  250. package/src/client/locales-vi.ts +386 -0
  251. package/src/client/locales-zh-HK.ts +401 -0
  252. package/src/client/locales-zh-MO.ts +401 -0
  253. package/src/client/locales-zh-TW.ts +401 -0
  254. package/src/client/locales.ts +1229 -0
  255. package/src/client/markdown-html.ts +331 -0
  256. package/src/client/markdown-images.ts +137 -0
  257. package/src/client/markdown-labels.tsx +31 -0
  258. package/src/client/md-toc.tsx +127 -0
  259. package/src/client/mermaid-blocks.ts +110 -0
  260. package/src/client/mermaid-sanitize.ts +93 -0
  261. package/src/client/mermaid.tsx +406 -0
  262. package/src/client/open-when-sized.ts +43 -0
  263. package/src/client/open-with-settings.tsx +135 -0
  264. package/src/client/open-with.ts +221 -0
  265. package/src/client/openpath-intercept.ts +79 -0
  266. package/src/client/paths.ts +63 -0
  267. package/src/client/pdf-types.ts +4 -0
  268. package/src/client/pinned.ts +184 -0
  269. package/src/client/plugin-settings.ts +43 -0
  270. package/src/client/plugins-shared.ts +29 -0
  271. package/src/client/plugins-tabs.ts +153 -0
  272. package/src/client/plugins-viewers.ts +44 -0
  273. package/src/client/prefs.ts +216 -0
  274. package/src/client/produced-files.ts +120 -0
  275. package/src/client/selection-payload.ts +86 -0
  276. package/src/client/service.ts +873 -0
  277. package/src/client/session-pins.tsx +334 -0
  278. package/src/client/settings-nav-icon.ts +55 -0
  279. package/src/client/shell-presets.ts +83 -0
  280. package/src/client/sidebar.module.css +3607 -0
  281. package/src/client/split-pane.tsx +333 -0
  282. package/src/client/state.ts +1646 -0
  283. package/src/client/tab-content-memo.ts +55 -0
  284. package/src/client/terminal-font.ts +224 -0
  285. package/src/client/terminal-links.ts +194 -0
  286. package/src/client/theme.ts +110 -0
  287. package/src/client/titlebar-strip.ts +37 -0
  288. package/src/client/ui-tweaks/appicons.ts +24 -0
  289. package/src/client/ui-tweaks/archive.tsx +281 -0
  290. package/src/client/ui-tweaks/branding.tsx +232 -0
  291. package/src/client/ui-tweaks/cachehit.tsx +122 -0
  292. package/src/client/ui-tweaks/gitbar.tsx +2559 -0
  293. package/src/client/ui-tweaks/index.tsx +1696 -0
  294. package/src/client/ui-tweaks/mcp.tsx +582 -0
  295. package/src/client/ui-tweaks/notifier.ts +514 -0
  296. package/src/client/ui-tweaks/whale.tsx +171 -0
  297. package/src/client/upload.ts +187 -0
  298. package/src/client/useChatcodeAccount.ts +103 -0
  299. package/src/client/wco.ts +120 -0
  300. package/src/codingplan.ts +146 -0
  301. package/src/config.ts +207 -0
  302. package/src/context-types.ts +559 -0
  303. package/src/fs-operations.ts +97 -0
  304. package/src/fs-search.ts +113 -0
  305. package/src/fs-tree.ts +158 -0
  306. package/src/git.ts +446 -0
  307. package/src/html-route.ts +106 -0
  308. package/src/index.ts +1524 -0
  309. package/src/invariant.ts +32 -0
  310. package/src/login-route.ts +26 -0
  311. package/src/open-external.ts +90 -0
  312. package/src/path-security.ts +81 -0
  313. package/src/prefs-shared.ts +303 -0
  314. package/src/pty-deps.ts +241 -0
  315. package/src/pty-manager.ts +376 -0
  316. package/src/tools.ts +482 -0
  317. package/src/trust-fence.ts +84 -0
  318. package/src/ui-tweaks/archive.ts +293 -0
  319. package/src/ui-tweaks/config.ts +257 -0
  320. package/src/ui-tweaks/git-web.ts +421 -0
  321. package/src/ui-tweaks/git.ts +1524 -0
  322. package/src/ui-tweaks/index.ts +61 -0
  323. package/src/ui-tweaks/mcp.ts +535 -0
  324. package/src/ui-tweaks/turn-outcome.ts +105 -0
  325. package/src/ui-tweaks/web.ts +225 -0
  326. package/src/wire.ts +110 -0
  327. package/vendor/addon-fit.js +2 -0
  328. package/vendor/xterm.css +218 -0
  329. package/vendor/xterm.js +5 -0
@@ -0,0 +1,480 @@
1
+ /**
2
+ * Per-session sidebar state: the panel geometry, the split-pane workbench
3
+ * tree, open tabs, and the explorer expansion set. One state instance per
4
+ * conversation id, persisted to localStorage under `dsh-sidebar:v1:<id>` so
5
+ * a reload restores the exact layout of the session it belongs to — switching
6
+ * conversations swaps the whole state (memory + isolation).
7
+ *
8
+ * The split tree is a recursive structure: a leaf holds a tab group, a split
9
+ * divides the space row- or column-wise with fractional sizes. All tree
10
+ * operations are pure functions over the node, unit-tested in tests/state.spec.ts.
11
+ */
12
+ import { type SidebarPrefs } from '../prefs-shared.ts';
13
+ /**
14
+ * Tab type identifier. Builtins register their ids (editor / git / terminal
15
+ * / browser / diff) through the sidebar service; external
16
+ * plugins register their own (e.g. `'my-plugin:db'`). Kept as `string` so
17
+ * the registry stays open.
18
+ */
19
+ export type TabType = string;
20
+ /** What a diff tab shows: a worktree/index change of one path, or one commit's full patch. */
21
+ export type SidebarDiffRef = {
22
+ kind: 'worktree';
23
+ path: string;
24
+ staged: boolean;
25
+ untracked?: boolean;
26
+ worktree?: string;
27
+ repoRoot?: string;
28
+ } | {
29
+ kind: 'commit';
30
+ hash: string;
31
+ hashFull: string;
32
+ subject: string;
33
+ worktree?: string;
34
+ repoRoot?: string;
35
+ };
36
+ /** One open tab. `path` carries the file (editor) or is absent (git/terminal);
37
+ * `diff` carries the change a diff tab shows; `meta` (v0.12.0+) carries
38
+ * plugin-owned JSON-serializable state, preserved across reloads. */
39
+ export interface SidebarTab {
40
+ id: string;
41
+ type: TabType;
42
+ title: string;
43
+ path?: string;
44
+ diff?: SidebarDiffRef;
45
+ /** Plugin-owned state (v0.12.0+): MUST be JSON-serializable — it is
46
+ * persisted with the layout and restored verbatim on reload. */
47
+ meta?: unknown;
48
+ /** Pinned-terminal marker (v0.17.0+): a pinned terminal tab survives a
49
+ * session switch in its home session's state and surfaces in the
50
+ * PinnedRail of every session the scope allows. `homeCwd` is the cwd
51
+ * snapshot at pin time — a `workspace`-scoped pin is only visible to
52
+ * sessions whose cwd matches it. Absent = unpinned (legacy states). */
53
+ pin?: {
54
+ scope: 'workspace' | 'global';
55
+ homeCwd?: string;
56
+ };
57
+ }
58
+ /** A tab group. */
59
+ export interface SidebarLeaf {
60
+ kind: 'leaf';
61
+ id: string;
62
+ tabs: SidebarTab[];
63
+ active: string | null;
64
+ }
65
+ /** A recursive split between child panes (fractional sizes summing to 1). */
66
+ export interface SidebarSplit {
67
+ kind: 'split';
68
+ id: string;
69
+ dir: 'row' | 'col';
70
+ sizes: number[];
71
+ children: SplitNode[];
72
+ }
73
+ export type SplitNode = SidebarLeaf | SidebarSplit;
74
+ /**
75
+ * One free window: a tab dragged out of the workbench onto the conversation
76
+ * area floats in the panel host at viewport coordinates. The tab is OWNED by
77
+ * the window exactly like a pane owns its tabs (moved, not copied); geometry
78
+ * persists with the session so a reload restores the window in place.
79
+ * Stacking order is the array order (last = topmost).
80
+ */
81
+ export interface FloatWindow {
82
+ id: string;
83
+ tab: SidebarTab;
84
+ /** Viewport coordinates of the window's top-left corner. */
85
+ x: number;
86
+ y: number;
87
+ w: number;
88
+ h: number;
89
+ }
90
+ /** The full per-session state. */
91
+ export interface SidebarState {
92
+ panelOpen: boolean;
93
+ width: number;
94
+ /** The pane receiving newly opened tabs (last pane the user touched).
95
+ * Pane ids are globally unique across BOTH trees (shared uid counter), so
96
+ * one field resolves into either tree — see {@link treeOf}. */
97
+ activePane: string | null;
98
+ /** Monotonic terminal tab counter (ids survive reloads). */
99
+ nextTerminal: number;
100
+ /** Monotonic browser tab counter (ids survive reloads; mirrors nextTerminal). */
101
+ nextBrowser: number;
102
+ /** Explorer expansion set (absolute directory paths). */
103
+ expanded: string[];
104
+ /**
105
+ * Explorer rows highlighted by a "Show in folder" reveal (absolute paths).
106
+ * Transient by design: sanitizeState never restores it, so a reload starts
107
+ * unhighlighted.
108
+ */
109
+ revealed: string[];
110
+ /** The right sidebar's split tree (the original workbench). */
111
+ splits: SplitNode;
112
+ /** Whether the bottom panel (a second, independent workbench) is open. */
113
+ bottomOpen: boolean;
114
+ /** The bottom panel's height (clamped to the contract range). */
115
+ bottomHeight: number;
116
+ /**
117
+ * Whether the bottom panel has been expanded at least once in this
118
+ * session — the FIRST expansion tries to auto-open a terminal tab (gated
119
+ * on the bottomPanelAutoTerminal pref); later expansions never do.
120
+ */
121
+ bottomOpenedOnce: boolean;
122
+ /** The bottom panel's own split tree (panes/tabs live only in ONE tree;
123
+ * tabs never cross panels — the two panels only share panel-size drags). */
124
+ bottomSplits: SplitNode;
125
+ /** Free windows (tabs dragged out onto the conversation area). */
126
+ floats: FloatWindow[];
127
+ }
128
+ export declare const PANEL_MIN = 280;
129
+ export declare const PANEL_MAX = 640;
130
+ export declare const PANEL_DEFAULT = 400;
131
+ export declare const TAB_MAX_WIDTH = 160;
132
+ /** Bottom panel geometry contract (mirrors the width contract; the upper
133
+ * bound is the viewport, enforced by {@link setBottomHeight}). */
134
+ export declare const BOTTOM_MIN = 120;
135
+ export declare const BOTTOM_DEFAULT = 220;
136
+ /** Free-window geometry contract: the floor keeps the window usable (a
137
+ * header plus some content), the ceiling is the viewport. */
138
+ export declare const FLOAT_MIN_W = 320;
139
+ export declare const FLOAT_MIN_H = 200;
140
+ /** Geometry a fresh free window starts with: a phone-like portrait ratio
141
+ * (390×780 ≈ 1:2). The creation path additionally caps the size to the
142
+ * viewport (minus a 24px margin), so a short viewport gets a shorter —
143
+ * not overflowing — window instead of an exact ratio. */
144
+ export declare const FLOAT_DEFAULT_W = 390;
145
+ export declare const FLOAT_DEFAULT_H = 780;
146
+ /** Mint a fresh uid-based tab id. The `'editor:' + path` convention only
147
+ * covers openSidebarFile opens (per-path dedupe); opens that must not
148
+ * dedupe (the tree's "open to the side") mint through here. */
149
+ export declare function mintTabId(): string;
150
+ /** The default tab a fresh session seeds. */
151
+ export type DefaultSeed = 'editor-home' | 'none';
152
+ /** A fresh default state: one seeded tab in one pane, open per the caller's
153
+ * preference. `width` is the caller's preferred panel width (default
154
+ * PANEL_DEFAULT) and `panelOpen` whether the panel starts expanded (default
155
+ * true); the store seeds new sessions from the user's side card prefs.
156
+ * `seed` picks the seeded tab: 'editor-home' places the EMPTY files window
157
+ * (an editor tab with no path whose tree panel starts open,
158
+ * `meta.treeOpen: true`) — in BOTH editorExplorer modes that window is the
159
+ * file explorer page — and 'none' starts with an empty pane (the store
160
+ * passes it when the user disabled the editor tab type in settings). */
161
+ export declare function makeDefaultState(width?: number, panelOpen?: boolean, seed?: DefaultSeed): SidebarState;
162
+ /** Which tree owns a pane/split id: 'bottomSplits' when the id lives in the
163
+ * bottom panel's tree, else 'splits' (the right panel's tree). Ids are
164
+ * globally unique (the shared uid counter), so an id in neither tree falls
165
+ * back to the right tree, where tree operations no-op on a missing node —
166
+ * the pre-bottom-panel behavior. */
167
+ export declare function treeOf(state: SidebarState, id: string): 'splits' | 'bottomSplits';
168
+ /** Walk the tree and apply `visit` to the leaf with the given id. */
169
+ export declare function mapLeaf(node: SplitNode, paneId: string, visit: (leaf: SidebarLeaf) => void): SplitNode;
170
+ /** The first leaf of the tree (fallback pane when activePane is gone). */
171
+ export declare function firstLeaf(node: SplitNode): SidebarLeaf;
172
+ /**
173
+ * Narrow-viewport migration: the bottom panel's tabs are thrown INTO the
174
+ * right sidebar — the "merged display" on mobile is the right panel alone,
175
+ * whose tab strips now carry the bottom tree's tabs (depth-first order,
176
+ * appended to the right tree's FIRST leaf). The bottom tree is emptied (its
177
+ * structure stays — the desktop bottom panel re-renders its welcome cards)
178
+ * and the panel closes. The active pane moves to the right tree's first
179
+ * leaf so every new tab lands in the visible panel.
180
+ *
181
+ * Idempotent: a bottom tree with no tabs and a closed panel returns the
182
+ * same reference. Runs when the viewport enters narrow (see the Sidebar
183
+ * shell); migrating is permanent for the session — the tabs now live in the
184
+ * right tree, exactly like the user "threw them in".
185
+ */
186
+ export declare function migrateBottomTabs(state: SidebarState): SidebarState;
187
+ /** Find the leaf containing a tab id, if any. */
188
+ export declare function leafWithTab(node: SplitNode, tabId: string): SidebarLeaf | undefined;
189
+ /** All leaves of the tree, depth-first. */
190
+ export declare function allLeaves(node: SplitNode): SidebarLeaf[];
191
+ /** Whether a tab exists anywhere in a state (either tree, any pane, or any
192
+ * free window — a floating tab is as open as a docked one). */
193
+ export declare function tabOpenIn(state: SidebarState, tabId: string): boolean;
194
+ /** The free window holding a tab id, if any. */
195
+ export declare function floatWithTab(state: SidebarState, tabId: string): FloatWindow | undefined;
196
+ /** The free window with the given window id, if any. */
197
+ export declare function floatById(state: SidebarState, floatId: string): FloatWindow | undefined;
198
+ /** Replace a leaf with a split of it plus a fresh empty leaf. */
199
+ export declare function splitLeafAt(node: SplitNode, paneId: string, dir: 'row' | 'col'): SplitNode;
200
+ /**
201
+ * Split a leaf by inserting a fresh leaf holding `tab` beside it — the
202
+ * VSCode drag-to-edge gesture. `dir` is the split direction ('row' for
203
+ * left/right, 'col' for up/down); `front` places the new leaf first (left/
204
+ * up) or second (right/down).
205
+ * @returns the new tree plus the fresh leaf's id (the drop's active pane).
206
+ */
207
+ export declare function insertLeafAt(node: SplitNode, paneId: string, dir: 'row' | 'col', tab: SidebarTab, front: boolean): {
208
+ node: SplitNode;
209
+ leafId: string;
210
+ };
211
+ /** Where a tab drop lands on a pane: an edge creates a split, center merges. */
212
+ export type DropZone = 'left' | 'right' | 'up' | 'down' | 'center';
213
+ /**
214
+ * The VSCode drag gesture: move a tab out of its pane and either merge it
215
+ * into the target pane (center) or split the target pane with the tab in a
216
+ * fresh leaf (edge). The source pane collapses when it empties.
217
+ *
218
+ * The panes may live in DIFFERENT trees (dragging a tab between the two
219
+ * panels): the tab then leaves its own tree and lands in the other one.
220
+ */
221
+ export declare function moveTabToEdge(state: SidebarState, fromPane: string, tabId: string, toPane: string, zone: DropZone): SidebarState;
222
+ /**
223
+ * Remove a leaf from the tree. A split left with one child promotes that
224
+ * child; removing the last leaf yields an empty leaf.
225
+ */
226
+ export declare function removeLeafAt(node: SplitNode, paneId: string): SplitNode;
227
+ /** Close a tab; an emptied leaf is removed (unless it is the only pane). */
228
+ export declare function closeTab(state: SidebarState, paneId: string, tabId: string): SidebarState;
229
+ /** Activate a tab in its pane (the pane's own tree). */
230
+ export declare function activateTab(state: SidebarState, paneId: string, tabId: string): SidebarState;
231
+ /** Update the display fields of one open tab (title / path / meta) without
232
+ * re-opening it. The browser tab persists its current URL and hostname
233
+ * title through this reducer so a reload restores the visited page. A
234
+ * missing tab id is a no-op. The tab may live in either tree or a free
235
+ * window. */
236
+ export declare function patchTab(state: SidebarState, tabId: string, patch: {
237
+ title?: string;
238
+ path?: string;
239
+ meta?: unknown;
240
+ }): SidebarState;
241
+ /**
242
+ * Set or clear the pin marker on one open tab (v0.17.0+). A pin marker is
243
+ * structural metadata (NOT display fields like title/path), so it walks
244
+ * both split trees AND the free windows exactly like {@link patchTab} —
245
+ * the tab may live in either tree or float. Passing `null` clears the pin
246
+ * (the tab stays open in its home session); passing a `{ scope, homeCwd }`
247
+ * object sets it. An unknown tab id is a strict no-op (same reference
248
+ * returned) so a stale pin request never churns the state or rewrites
249
+ * localStorage.
250
+ * @param state - the current per-session sidebar state.
251
+ * @param tabId - the tab to pin/unpin.
252
+ * @param pin - the pin marker to set, or null to clear.
253
+ * @returns the next state (or the same reference when the tab is missing
254
+ * or the pin marker is already the requested value).
255
+ */
256
+ export declare function setTabPin(state: SidebarState, tabId: string, pin: {
257
+ scope: 'workspace' | 'global';
258
+ homeCwd?: string;
259
+ } | null): SidebarState;
260
+ /**
261
+ * Land a tab in the active pane (or focus its existing instance by id).
262
+ * Dedup strategies (single-instance, per-path, per-change) are owned by the
263
+ * tab descriptor through {@link BetterSidebarService.openTab} / `dedupeKey`;
264
+ * this reducer only handles the id-based safety net (reconcile and
265
+ * openDiffTab already check existence before calling) and the landing
266
+ * itself — the service's dedupe path delegates here after its dedupeKey
267
+ * check misses.
268
+ *
269
+ * The active pane may live in EITHER tree (pane ids are globally unique):
270
+ * a stale id that survives in neither tree falls back to the right tree's
271
+ * first pane instead of swallowing the open.
272
+ */
273
+ export declare function openTabInActivePane(state: SidebarState, tab: SidebarTab): SidebarState;
274
+ /** Move a tab from one pane to another (insert at index; -1 appends).
275
+ * The panes may live in DIFFERENT trees — dragging a tab between the two
276
+ * panels removes it from its own tree and lands it in the other one. */
277
+ export declare function moveTab(state: SidebarState, fromPane: string, tabId: string, toPane: string, index?: number): SidebarState;
278
+ /** Split the active pane (or the pane containing the active tab). */
279
+ export declare function splitPane(state: SidebarState, dir: 'row' | 'col'): SidebarState;
280
+ /**
281
+ * Open a diff tab the VSCode way: an existing instance of the same change is
282
+ * focused wherever it lives; otherwise the tab joins the first pane that
283
+ * already holds diff tabs (diff panes are sticky — repeated clicks stack
284
+ * there); on the FIRST diff of a layout the source pane splits vertically so
285
+ * the diff lands in a fresh pane below it ("默认在下半栏新增一个").
286
+ *
287
+ * This is split-tree placement surgery, not registry dispatch: the diff tab
288
+ * descriptor's `dedupeKey` is `(tab) => tab.id`, and the existing-instance
289
+ * check below is exactly that rule — the two agree by construction (asserted
290
+ * in tests). Diff tabs minted by the Git view carry change-derived ids, so
291
+ * the id check is the per-change dedupe.
292
+ * @returns the new state, with the diff pane active.
293
+ */
294
+ export declare function openDiffTab(state: SidebarState, sourcePaneId: string, tab: SidebarTab): SidebarState;
295
+ /** Toggle the panel open/closed (opening restores the previous layout). */
296
+ export declare function togglePanel(state: SidebarState): SidebarState;
297
+ /** Toggle the bottom panel open/closed (independent of the right panel). */
298
+ export declare function toggleBottomPanel(state: SidebarState): SidebarState;
299
+ /** Set the panel width (clamped to the contract range; the upper bound is
300
+ * the viewport so the fullscreen expansion can fill the window). */
301
+ export declare function setWidth(state: SidebarState, width: number): SidebarState;
302
+ /** Set the bottom panel height (clamped to the contract range). The upper
303
+ * bound leaves the center column (the agent output area) at least PANEL_MIN
304
+ * tall — without the cap the bottom panel could swallow the whole viewport
305
+ * and squeeze the conversation to zero height. */
306
+ export declare function setBottomHeight(state: SidebarState, height: number): SidebarState;
307
+ /** Toggle a directory in the explorer expansion set. */
308
+ export declare function toggleExpanded(state: SidebarState, path: string): SidebarState;
309
+ /**
310
+ * Reveal files in the explorer: expand every ancestor directory between the
311
+ * explorer root and each file (so the lazy tree actually shows the row) and
312
+ * record the paths for highlighting. The reveal set is transient —
313
+ * sanitizeState never restores it, so a reload starts unhighlighted.
314
+ * @param state - current sidebar state.
315
+ * @param cwd - the explorer's root (session working directory).
316
+ * @param files - absolute paths to highlight (parent dirs are expanded).
317
+ * @returns the next state, or the same reference when nothing is revealed.
318
+ */
319
+ export declare function revealPaths(state: SidebarState, cwd: string | undefined, files: readonly string[]): SidebarState;
320
+ /** Adjust one split divider: `i` is the left/top child index, delta in fractions. */
321
+ export declare function resizeSplit(node: SplitNode, splitId: string, index: number, delta: number): SplitNode;
322
+ /** State-level {@link resizeSplit} route: the divider may live in either
323
+ * tree (split ids are globally unique). */
324
+ export declare function resizeSplitIn(state: SidebarState, splitId: string, index: number, delta: number): SidebarState;
325
+ /** Clamp free-window geometry: sizes respect the floor and the viewport, and
326
+ * the position keeps the whole window inside the viewport. Without a window
327
+ * (unit tests) only the floor applies — the caller's values pass through. */
328
+ export declare function clampFloatGeometry(x: number, y: number, w: number, h: number): Pick<FloatWindow, 'x' | 'y' | 'w' | 'h'>;
329
+ /**
330
+ * Float a docked tab: remove it from its pane (either tree; an emptied pane
331
+ * collapses like any move) and append a free window centered on the drop
332
+ * point, with the default size clamped to the viewport. The stacking order
333
+ * is the array order, so a fresh window is born topmost. An unknown tab id
334
+ * (or one already floating) is a strict no-op.
335
+ */
336
+ export declare function floatTab(state: SidebarState, tabId: string, x: number, y: number): SidebarState;
337
+ /** Move a free window (clamped to the viewport); unknown ids are a no-op. */
338
+ export declare function moveFloat(state: SidebarState, floatId: string, x: number, y: number): SidebarState;
339
+ /** Resize a free window from its SE corner: the top-left corner stays
340
+ * anchored, sizes clamp to the floor and to the viewport's remaining room. */
341
+ export declare function resizeFloat(state: SidebarState, floatId: string, w: number, h: number): SidebarState;
342
+ /** Bring a free window to the top (the array's end). Already topmost (or the
343
+ * only window) returns the same reference — no persist churn on every click. */
344
+ export declare function raiseFloat(state: SidebarState, floatId: string): SidebarState;
345
+ /** Dock a free window back into a pane (center merge): the tab joins the
346
+ * target pane and activates. `toPane` defaults to the active pane with the
347
+ * right tree's first leaf as the stale-id fallback (mirrors
348
+ * {@link openTabInActivePane}). Unknown window ids are a no-op. */
349
+ export declare function dockFloat(state: SidebarState, floatId: string, toPane?: string): SidebarState;
350
+ /** Close the free window holding a tab (the tab closes WITH the window —
351
+ * the caller fires the descriptor's onClose lifecycle). */
352
+ export declare function closeFloatByTab(state: SidebarState, tabId: string): SidebarState;
353
+ /** Prefix marking a tab id as an agent-owned terminal (suffix is the uuid). */
354
+ export declare const AGENT_TAB_PREFIX = "agent:";
355
+ /** Whether a tab id refers to an agent-owned terminal. */
356
+ export declare function isAgentTabId(tabId: string): boolean;
357
+ /** Extract the agent terminal uuid from an `agent:<uuid>` tab id. */
358
+ export declare function agentUuidOf(tabId: string): string;
359
+ /** Build the sidebar tab id for one agent terminal uuid. */
360
+ export declare function agentTabId(uuid: string): string;
361
+ /**
362
+ * Reconcile the sidebar's agent-terminal tabs with the host's live list.
363
+ * The host pushes the current list of agent terminals (created by the model
364
+ * through the `terminal_create` tool) over a dedicated WebSocket; this
365
+ * reducer mirrors that list into tabs: new uuids get a tab, vanished uuids
366
+ * lose theirs. The agent owns the lifetime — the user closing a tab sends a
367
+ * WS close frame that kills the pty, which fires a change, which converges
368
+ * the view. Idempotent: a no-op when the lists already match.
369
+ * @param state - the current per-session sidebar state.
370
+ * @param agentTerminals - the live agent terminal snapshots from the host.
371
+ * @returns the next state (or the same reference if no change was needed).
372
+ */
373
+ export declare function reconcileAgentTerminals(state: SidebarState, agentTerminals: ReadonlyArray<{
374
+ uuid: string;
375
+ title: string;
376
+ }>): SidebarState;
377
+ /** Immutable snapshot handed to React (replaced only on real changes). */
378
+ export interface SidebarSnapshot {
379
+ sessionId: string | undefined;
380
+ state: SidebarState | undefined;
381
+ /**
382
+ * The current side card prefs. Carried IN the snapshot (not a separate
383
+ * subscription) so prefs changes re-render the consumers that gate on
384
+ * them — the + menu hides a tab type the moment its switch flips.
385
+ */
386
+ prefs: SidebarPrefs;
387
+ }
388
+ /** Default panel width for one viewport: the prefs percent of the window,
389
+ * clamped to the panel floor (a tiny percent must stay usable) and to the
390
+ * viewport (a large one must never cover the whole window). */
391
+ export declare function defaultWidthFor(viewport: number, percent: number): number;
392
+ /**
393
+ * Structural validation of one persisted state. A malformed or stale shape
394
+ * (older layouts, hand-edited storage) must fall back to the default instead
395
+ * of crashing the panel on every reload; the restored width is also clamped
396
+ * to the current viewport so a stale fullscreen width can never crush the
397
+ * app shell (margin-right larger than the window) or cover the whole screen.
398
+ * @returns a clean state, or undefined to fall back to the default.
399
+ */
400
+ export declare function sanitizeState(parsed: unknown): SidebarState | undefined;
401
+ /** The session-scoped store: one state per conversation, localStorage-backed. */
402
+ export declare class SidebarStore {
403
+ private readonly bySession;
404
+ private snapshot;
405
+ private readonly listeners;
406
+ /** Per-session persist debounce timers (v0.12.0+: one per session, so a
407
+ * targeted open never cancels another session's pending write). */
408
+ private readonly persistTimers;
409
+ /** User-facing side card prefs seeding brand-new session states (defaults until the settings RPC resolves). */
410
+ private prefs;
411
+ /**
412
+ * External disable (the dsh-web-ui family's aionui-panel provider choice):
413
+ * while true the sidebar must not mount at all. Not part of the snapshot —
414
+ * nothing renders on it; the mount gate and the intercept predicates read
415
+ * it directly.
416
+ */
417
+ private suspended;
418
+ /**
419
+ * Set the external-disable flag (from the settings route) and remember it
420
+ * for the mount gate and the intercept predicates.
421
+ */
422
+ setSuspended(suspended: boolean): void;
423
+ /** Whether the sidebar is externally disabled (aionui-panel chosen). */
424
+ getSuspended(): boolean;
425
+ /**
426
+ * Replace the side card prefs (the settings RPC result / settings page
427
+ * write). Notifies like any store change: the snapshot carries the prefs,
428
+ * so consumers that gate on enable switches (the + menu, derived flows)
429
+ * re-render with the new values immediately.
430
+ */
431
+ setPrefs(prefs: SidebarPrefs): void;
432
+ /** The current side card prefs (seeds new sessions; persisted states win). */
433
+ getPrefs(): SidebarPrefs;
434
+ /** Select a session (or none); loads its persisted state. */
435
+ setSession(sessionId: string | undefined): void;
436
+ subscribe(listener: () => void): () => void;
437
+ getSnapshot(): SidebarSnapshot;
438
+ /** Mutate the current session's state (no-op without a session). */
439
+ update(mutator: (draft: SidebarState) => void): void;
440
+ /**
441
+ * Whether a tab still exists in its session's state. Views use this on
442
+ * unmount to tell "the tab was closed" (release the terminal now) from
443
+ * "the tree re-rendered / the conversation switched" (the tab is still
444
+ * open — keep the terminal alive through the host's reconnect grace).
445
+ * Checks the session's own map entry (the current snapshot may already
446
+ * point at another session when a conversation switch unmounts the old
447
+ * one's tabs).
448
+ */
449
+ tabOpen(sessionId: string, tabId: string): boolean;
450
+ /**
451
+ * Read-only view of EVERY cached session's state (v0.17.0+). The
452
+ * PinnedRail uses this to collect pinned terminals across sessions
453
+ * without each render reading private fields. The map is the live
454
+ * `bySession` reference — callers MUST treat it as read-only (mutations
455
+ * go through {@link reduce} / {@link reduceFor}). A session that has
456
+ * never been visited in this run is absent (its pinned tabs are not
457
+ * visible until first load — accepted as YAGNI by the design).
458
+ */
459
+ getSessionStates(): ReadonlyMap<string, SidebarState>;
460
+ /** Apply a pure reducer (returns the next state). */
461
+ reduce(reducer: (state: SidebarState) => SidebarState): void;
462
+ /**
463
+ * Apply a pure reducer to a TARGET session's state (not the active one),
464
+ * loading it on demand and persisting the result — WITHOUT switching the
465
+ * active snapshot or notifying (the UI must not follow along). Used by the
466
+ * service's targeted `openTab(seed, scope)`: the open lands in the target
467
+ * session's layout and is visible whenever the user switches to it.
468
+ */
469
+ reduceFor(sessionId: string, reducer: (state: SidebarState) => SidebarState): void;
470
+ private schedulePersist;
471
+ private notify;
472
+ }
473
+ /**
474
+ * Create one sidebar store instance. Production code calls this only from
475
+ * the client plugin's `apply` (the instance is handed to components as a
476
+ * prop); tests call it directly. No module-level singleton: the store's
477
+ * lifetime belongs to the plugin activation, exactly like the official
478
+ * `createXXXStore()` factory rule.
479
+ */
480
+ export declare function createSidebarStore(): SidebarStore;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The memo comparator for one tab's content cell (TabContent in Sidebar.tsx).
3
+ *
4
+ * Kept as a pure module so the "what re-renders a tab cell" contract is unit
5
+ * testable: geometry/store re-renders of the Sidebar shell must NOT reconcile
6
+ * every mounted tab (xterm/CodeMirror/FileTree subtrees — issue #315), while
7
+ * locale switches (localeRevision), tab-registry updates (tabsVersion), and
8
+ * a tab MOVING BETWEEN PANES (paneId — onOpenDiff closes over the pane id,
9
+ * and moveTab reuses the SAME tab object, so only the pane id identifies the
10
+ * move) must re-render the cell.
11
+ */
12
+ import type { SidebarTab } from './state.ts';
13
+ /** The fields whose change invalidates a tab cell's memo. Anything a cell's
14
+ * render (or its callbacks) depends on must be listed here. */
15
+ export interface TabContentMemoKey {
16
+ tab: SidebarTab;
17
+ /** The pane this tab currently lives in. onOpenDiff closes over it, and
18
+ * moveTab reuses the same tab object when the tab moves panes — without
19
+ * comparing paneId the cell would keep the source pane's callback. */
20
+ paneId: string;
21
+ sessionId: string;
22
+ cwd: string | undefined;
23
+ visible: boolean;
24
+ expanded: string[];
25
+ /** Files highlighted in the file tree (the "Show in folder" reveal). */
26
+ revealed: string[];
27
+ localeRevision: string;
28
+ tabsVersion: number;
29
+ /** When the tab is a pinned virtual tab (injected from another session),
30
+ * this overrides `tab.id` passed to the tab descriptor's component so
31
+ * TerminalView connects to the home session's PTY by the original id.
32
+ * Undefined for regular tabs (no override). */
33
+ effectiveTabId: string | undefined;
34
+ }
35
+ /** True when the cell may skip a re-render (all render-affecting fields
36
+ * unchanged). Callback/context identities are deliberately ignored: their
37
+ * captured dependencies are stable or covered by the compared fields
38
+ * (onReferenceFile → sessionId/cwd, onSubagentJump/onToggleDir → stable
39
+ * refs/closures, onOpenDiff → paneId). */
40
+ export declare function tabContentCompare(prev: TabContentMemoKey, next: TabContentMemoKey): boolean;
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Terminal font resolution: the user's custom font prefs (SidebarPrefs,
3
+ * configured under the terminal card's secondary settings) turned into the
4
+ * xterm options. Kept as a pure module (no DOM, no xterm) so the fallback
5
+ * chain and clamping are unit-testable without mounting a terminal.
6
+ */
7
+ import { type SidebarPrefs } from '../prefs-shared.ts';
8
+ /** The built-in fallback stack when neither the user nor the theme sets one. */
9
+ export declare const DEFAULT_TERMINAL_FONT_FAMILY = "\"SF Mono\", Menlo, Consolas, \"Liberation Mono\", monospace";
10
+ /**
11
+ * Icon fonts appended to whichever base stack wins, so shell prompts that
12
+ * draw glyphs from the Nerd Font Private Use Areas resolve to a real glyph
13
+ * instead of the missing-glyph box (aka tofu).
14
+ *
15
+ * Why this is needed even though the OS "should" fall back automatically:
16
+ * prompt frameworks (starship, powerlevel10k, oh-my-posh) take their icons
17
+ * from the PUA. Chromium's implicit system fallback reliably covers the
18
+ * *BMP* PUA (U+E000–U+F8FF — e.g. the U+E0B0 powerline separator) but NOT
19
+ * the *supplementary-plane* PUA-B (U+F0000+) where Nerd Fonts v3 relocated
20
+ * the Material Design icon set. Naming the families explicitly makes the
21
+ * browser consult them per character, which covers both planes.
22
+ *
23
+ * Deliberately NOT listed: color-emoji families. Chromium routes genuine
24
+ * emoji code points through a dedicated emoji fallback path (which is why
25
+ * emoji already render), so naming them buys nothing here — while placing a
26
+ * color font ahead of the generic family risks capturing BMP symbols the
27
+ * prompt expects in monospace (U+26A0 ⚠, U+2714 ✔ …) and rendering them as
28
+ * wide color glyphs that break the cell grid.
29
+ *
30
+ * Ordering rationale: the symbols-only patches ship glyphs without Latin,
31
+ * so they can never hijack ASCII metrics — the safest first hop. The
32
+ * fully-patched distributions follow for users who installed one of those
33
+ * instead. Both the `… Mono` and proportional family names are listed
34
+ * because the Nerd Fonts installers register them as distinct families.
35
+ *
36
+ * These are strictly *appended*, never prepended: xterm derives its cell
37
+ * metrics from the first entry, so the base font must stay in front or the
38
+ * whole grid would be re-measured against an icon font.
39
+ */
40
+ export declare const ICON_FONT_FALLBACKS: readonly string[];
41
+ /**
42
+ * Append {@link ICON_FONT_FALLBACKS} to a CSS font-family stack, keeping
43
+ * the caller's own entries and order intact.
44
+ *
45
+ * - Families already named in `stack` are not duplicated (quote-, case- and
46
+ * whitespace-insensitive), so a user who already lists their Nerd Font
47
+ * keeps their exact priority.
48
+ * - The icon fonts are spliced in ahead of the *first* generic family
49
+ * (`monospace` etc.), because a generic always resolves: anything after it
50
+ * would never be consulted. A stack that OPENS with a generic is the one
51
+ * exception: only the symbols-only patches (no Latin) may precede it — a
52
+ * fully-patched Nerd Font there would become xterm's measuring base font
53
+ * and override the user/theme family precedence, so it is placed after the
54
+ * generic instead.
55
+ * - Idempotent — re-applying to an already-topped-up stack is a no-op, which
56
+ * matters because `TerminalView` diffs the resolved value against the live
57
+ * `term.options.fontFamily` before reflowing.
58
+ *
59
+ * @param stack - a CSS font-family stack (base font first).
60
+ * @returns the stack with icon fallbacks merged in.
61
+ */
62
+ export declare function withIconFontFallbacks(stack: string): string;
63
+ /**
64
+ * Resolve the xterm font options for the given prefs.
65
+ *
66
+ * The base family keeps its existing precedence — user pref > theme code
67
+ * font > built-in stack — and then {@link withIconFontFallbacks} tops it up
68
+ * so prompt icons resolve regardless of which base won.
69
+ *
70
+ * @param prefs - the current side card preferences.
71
+ * @param themeFontFamily - the app's theme code font (`--ds-font-family-code`
72
+ * token value, read live by the caller); undefined when the token is absent.
73
+ * @returns the `fontFamily` / `fontSize` xterm options.
74
+ */
75
+ export declare function resolveTerminalFont(prefs: SidebarPrefs, themeFontFamily: string | undefined): {
76
+ fontFamily: string;
77
+ fontSize: number;
78
+ };