dsh-coding-sidebar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +524 -0
  3. package/cordis.patch.yml +49 -0
  4. package/lib/client-editor.js +48229 -0
  5. package/lib/client-mermaid.js +200745 -0
  6. package/lib/client-registry.js +21867 -0
  7. package/lib/client-terminal.js +9361 -0
  8. package/lib/client.js +21867 -0
  9. package/lib/index.js +4556 -0
  10. package/lib/invariant.js +22 -0
  11. package/lib/types/agent-opens.d.ts +65 -0
  12. package/lib/types/agent-pty.d.ts +226 -0
  13. package/lib/types/browser-probe.d.ts +15 -0
  14. package/lib/types/bundle-route.d.ts +12 -0
  15. package/lib/types/client/BrowserView.d.ts +39 -0
  16. package/lib/types/client/DiffTab.d.ts +6 -0
  17. package/lib/types/client/DiffView.d.ts +51 -0
  18. package/lib/types/client/EditorHost.d.ts +13 -0
  19. package/lib/types/client/FileTree.d.ts +38 -0
  20. package/lib/types/client/FreeWindow.d.ts +29 -0
  21. package/lib/types/client/GitView.d.ts +10 -0
  22. package/lib/types/client/MarkdownHtml.d.ts +32 -0
  23. package/lib/types/client/OrphanedTab.d.ts +11 -0
  24. package/lib/types/client/PdfView.d.ts +6 -0
  25. package/lib/types/client/RenderBoundary.d.ts +32 -0
  26. package/lib/types/client/SandboxStatusBar.d.ts +10 -0
  27. package/lib/types/client/SideCardSection.d.ts +74 -0
  28. package/lib/types/client/SideChatView.d.ts +16 -0
  29. package/lib/types/client/Sidebar.d.ts +6 -0
  30. package/lib/types/client/SubagentView.d.ts +16 -0
  31. package/lib/types/client/TabBar.d.ts +54 -0
  32. package/lib/types/client/TerminalView.d.ts +24 -0
  33. package/lib/types/client/TextEditor.d.ts +10 -0
  34. package/lib/types/client/TreePanel.d.ts +24 -0
  35. package/lib/types/client/UploadOverlay.d.ts +20 -0
  36. package/lib/types/client/add-plugin-modal.d.ts +15 -0
  37. package/lib/types/client/api.d.ts +275 -0
  38. package/lib/types/client/binary-download.d.ts +12 -0
  39. package/lib/types/client/breakpoints.d.ts +17 -0
  40. package/lib/types/client/browser.d.ts +60 -0
  41. package/lib/types/client/builtins/index.d.ts +18 -0
  42. package/lib/types/client/builtins/tabs.d.ts +11 -0
  43. package/lib/types/client/builtins/viewers.d.ts +3 -0
  44. package/lib/types/client/chunk-loader.d.ts +131 -0
  45. package/lib/types/client/chunks/editor.d.ts +9 -0
  46. package/lib/types/client/chunks/mermaid.d.ts +10 -0
  47. package/lib/types/client/chunks/terminal.d.ts +9 -0
  48. package/lib/types/client/cm-themes.d.ts +24 -0
  49. package/lib/types/client/conversation-draft.d.ts +14 -0
  50. package/lib/types/client/desktop-env.d.ts +35 -0
  51. package/lib/types/client/editor-load.d.ts +66 -0
  52. package/lib/types/client/frame-batcher.d.ts +24 -0
  53. package/lib/types/client/icons.d.ts +59 -0
  54. package/lib/types/client/image-types.d.ts +3 -0
  55. package/lib/types/client/ime-guard.d.ts +36 -0
  56. package/lib/types/client/index.d.ts +25 -0
  57. package/lib/types/client/intercept.d.ts +46 -0
  58. package/lib/types/client/lang.d.ts +14 -0
  59. package/lib/types/client/layout-push.d.ts +15 -0
  60. package/lib/types/client/lazy-chunk.d.ts +25 -0
  61. package/lib/types/client/link-intercept.d.ts +42 -0
  62. package/lib/types/client/locales-ar.d.ts +18 -0
  63. package/lib/types/client/locales-de.d.ts +4 -0
  64. package/lib/types/client/locales-fr.d.ts +10 -0
  65. package/lib/types/client/locales-hi.d.ts +17 -0
  66. package/lib/types/client/locales-id.d.ts +15 -0
  67. package/lib/types/client/locales-it.d.ts +8 -0
  68. package/lib/types/client/locales-ja.d.ts +17 -0
  69. package/lib/types/client/locales-ko.d.ts +9 -0
  70. package/lib/types/client/locales-nl.d.ts +16 -0
  71. package/lib/types/client/locales-pl.d.ts +19 -0
  72. package/lib/types/client/locales-pt.d.ts +1 -0
  73. package/lib/types/client/locales-ru.d.ts +13 -0
  74. package/lib/types/client/locales-sv.d.ts +1 -0
  75. package/lib/types/client/locales-th.d.ts +17 -0
  76. package/lib/types/client/locales-tr.d.ts +17 -0
  77. package/lib/types/client/locales-vi.d.ts +17 -0
  78. package/lib/types/client/locales-zh-HK.d.ts +32 -0
  79. package/lib/types/client/locales-zh-MO.d.ts +32 -0
  80. package/lib/types/client/locales-zh-TW.d.ts +32 -0
  81. package/lib/types/client/locales.d.ts +452 -0
  82. package/lib/types/client/markdown-html.d.ts +96 -0
  83. package/lib/types/client/markdown-images.d.ts +45 -0
  84. package/lib/types/client/markdown-labels.d.ts +38 -0
  85. package/lib/types/client/md-toc.d.ts +4 -0
  86. package/lib/types/client/mermaid-blocks.d.ts +47 -0
  87. package/lib/types/client/mermaid-sanitize.d.ts +2 -0
  88. package/lib/types/client/mermaid.d.ts +12 -0
  89. package/lib/types/client/open-when-sized.d.ts +20 -0
  90. package/lib/types/client/open-with-settings.d.ts +4 -0
  91. package/lib/types/client/open-with.d.ts +87 -0
  92. package/lib/types/client/openpath-intercept.d.ts +85 -0
  93. package/lib/types/client/paths.d.ts +43 -0
  94. package/lib/types/client/pdf-types.d.ts +2 -0
  95. package/lib/types/client/pinned.d.ts +86 -0
  96. package/lib/types/client/plugin-settings.d.ts +8 -0
  97. package/lib/types/client/plugins-shared.d.ts +27 -0
  98. package/lib/types/client/plugins-tabs.d.ts +3 -0
  99. package/lib/types/client/plugins-viewers.d.ts +3 -0
  100. package/lib/types/client/prefs.d.ts +41 -0
  101. package/lib/types/client/produced-files.d.ts +29 -0
  102. package/lib/types/client/selection-payload.d.ts +27 -0
  103. package/lib/types/client/service.d.ts +439 -0
  104. package/lib/types/client/settings-nav-icon.d.ts +19 -0
  105. package/lib/types/client/shell-presets.d.ts +48 -0
  106. package/lib/types/client/sidechat-transcript.d.ts +103 -0
  107. package/lib/types/client/split-pane.d.ts +36 -0
  108. package/lib/types/client/state.d.ts +432 -0
  109. package/lib/types/client/subagent-detect.d.ts +54 -0
  110. package/lib/types/client/subagent-jobs.d.ts +63 -0
  111. package/lib/types/client/tab-content-memo.d.ts +40 -0
  112. package/lib/types/client/terminal-font.d.ts +78 -0
  113. package/lib/types/client/terminal-links.d.ts +119 -0
  114. package/lib/types/client/theme.d.ts +45 -0
  115. package/lib/types/client/titlebar-strip.d.ts +23 -0
  116. package/lib/types/client/upload.d.ts +69 -0
  117. package/lib/types/client/wco.d.ts +47 -0
  118. package/lib/types/config.d.ts +64 -0
  119. package/lib/types/context-types.d.ts +605 -0
  120. package/lib/types/fs-operations.d.ts +28 -0
  121. package/lib/types/fs-search.d.ts +24 -0
  122. package/lib/types/fs-tree.d.ts +51 -0
  123. package/lib/types/git.d.ts +124 -0
  124. package/lib/types/html-route.d.ts +59 -0
  125. package/lib/types/index.d.ts +47 -0
  126. package/lib/types/invariant.d.ts +15 -0
  127. package/lib/types/jobs-routes.d.ts +45 -0
  128. package/lib/types/open-external.d.ts +24 -0
  129. package/lib/types/path-security.d.ts +20 -0
  130. package/lib/types/prefs-shared.d.ts +236 -0
  131. package/lib/types/pty-deps.d.ts +78 -0
  132. package/lib/types/pty-manager.d.ts +168 -0
  133. package/lib/types/sidechat-core.d.ts +172 -0
  134. package/lib/types/sidechat-routes.d.ts +30 -0
  135. package/lib/types/subagent-activity.d.ts +44 -0
  136. package/lib/types/subagent-live-route.d.ts +44 -0
  137. package/lib/types/tools.d.ts +34 -0
  138. package/lib/types/trust-fence.d.ts +24 -0
  139. package/lib/types/wire.d.ts +38 -0
  140. package/package.json +172 -0
  141. package/scripts/install.ps1 +306 -0
  142. package/scripts/install.sh +287 -0
  143. package/src/agent-opens.ts +265 -0
  144. package/src/agent-pty.ts +527 -0
  145. package/src/browser-probe.ts +26 -0
  146. package/src/bundle-route.ts +129 -0
  147. package/src/client/BrowserView.tsx +289 -0
  148. package/src/client/DiffTab.tsx +114 -0
  149. package/src/client/DiffView.tsx +305 -0
  150. package/src/client/EditorHost.tsx +558 -0
  151. package/src/client/FileTree.tsx +669 -0
  152. package/src/client/FreeWindow.tsx +337 -0
  153. package/src/client/GitView.tsx +717 -0
  154. package/src/client/MarkdownHtml.tsx +296 -0
  155. package/src/client/OrphanedTab.tsx +27 -0
  156. package/src/client/PdfView.tsx +110 -0
  157. package/src/client/RenderBoundary.tsx +49 -0
  158. package/src/client/SandboxStatusBar.tsx +60 -0
  159. package/src/client/SideCardSection.module.css +960 -0
  160. package/src/client/SideCardSection.tsx +1163 -0
  161. package/src/client/SideChatView.module.css +437 -0
  162. package/src/client/SideChatView.tsx +645 -0
  163. package/src/client/Sidebar.tsx +1370 -0
  164. package/src/client/SubagentView.module.css +547 -0
  165. package/src/client/SubagentView.tsx +883 -0
  166. package/src/client/TabBar.tsx +365 -0
  167. package/src/client/TerminalView.tsx +424 -0
  168. package/src/client/TextEditor.tsx +468 -0
  169. package/src/client/TreePanel.tsx +275 -0
  170. package/src/client/UploadOverlay.tsx +62 -0
  171. package/src/client/add-plugin-modal.tsx +199 -0
  172. package/src/client/api.ts +357 -0
  173. package/src/client/binary-download.tsx +23 -0
  174. package/src/client/breakpoints.ts +61 -0
  175. package/src/client/browser.ts +167 -0
  176. package/src/client/builtins/index.ts +37 -0
  177. package/src/client/builtins/tabs.tsx +340 -0
  178. package/src/client/builtins/viewers.tsx +125 -0
  179. package/src/client/chunk-loader.ts +358 -0
  180. package/src/client/chunks/editor.tsx +9 -0
  181. package/src/client/chunks/mermaid.tsx +10 -0
  182. package/src/client/chunks/terminal.tsx +9 -0
  183. package/src/client/cm-themes.ts +129 -0
  184. package/src/client/conversation-draft.ts +29 -0
  185. package/src/client/css-modules.d.ts +5 -0
  186. package/src/client/desktop-env.ts +72 -0
  187. package/src/client/editor-load.ts +92 -0
  188. package/src/client/frame-batcher.ts +56 -0
  189. package/src/client/icons.tsx +152 -0
  190. package/src/client/image-types.ts +8 -0
  191. package/src/client/ime-guard.ts +47 -0
  192. package/src/client/index.tsx +427 -0
  193. package/src/client/intercept.tsx +188 -0
  194. package/src/client/lang.ts +225 -0
  195. package/src/client/layout-push.ts +24 -0
  196. package/src/client/layout.css +90 -0
  197. package/src/client/lazy-chunk.tsx +89 -0
  198. package/src/client/link-intercept.ts +73 -0
  199. package/src/client/locales-ar.ts +368 -0
  200. package/src/client/locales-de.ts +353 -0
  201. package/src/client/locales-fr.ts +360 -0
  202. package/src/client/locales-hi.ts +367 -0
  203. package/src/client/locales-id.ts +365 -0
  204. package/src/client/locales-it.ts +358 -0
  205. package/src/client/locales-ja.ts +367 -0
  206. package/src/client/locales-ko.ts +359 -0
  207. package/src/client/locales-nl.ts +365 -0
  208. package/src/client/locales-pl.ts +369 -0
  209. package/src/client/locales-pt.ts +350 -0
  210. package/src/client/locales-ru.ts +365 -0
  211. package/src/client/locales-sv.ts +350 -0
  212. package/src/client/locales-th.ts +367 -0
  213. package/src/client/locales-tr.ts +367 -0
  214. package/src/client/locales-vi.ts +367 -0
  215. package/src/client/locales-zh-HK.ts +382 -0
  216. package/src/client/locales-zh-MO.ts +382 -0
  217. package/src/client/locales-zh-TW.ts +382 -0
  218. package/src/client/locales.ts +894 -0
  219. package/src/client/markdown-html.ts +331 -0
  220. package/src/client/markdown-images.ts +137 -0
  221. package/src/client/markdown-labels.tsx +52 -0
  222. package/src/client/md-toc.tsx +127 -0
  223. package/src/client/mermaid-blocks.ts +110 -0
  224. package/src/client/mermaid-sanitize.ts +93 -0
  225. package/src/client/mermaid.tsx +406 -0
  226. package/src/client/open-when-sized.ts +43 -0
  227. package/src/client/open-with-settings.tsx +135 -0
  228. package/src/client/open-with.ts +221 -0
  229. package/src/client/openpath-intercept.ts +143 -0
  230. package/src/client/paths.ts +63 -0
  231. package/src/client/pdf-types.ts +4 -0
  232. package/src/client/pinned.ts +183 -0
  233. package/src/client/plugin-settings.ts +43 -0
  234. package/src/client/plugins-shared.ts +29 -0
  235. package/src/client/plugins-tabs.ts +96 -0
  236. package/src/client/plugins-viewers.ts +30 -0
  237. package/src/client/prefs.ts +210 -0
  238. package/src/client/produced-files.ts +120 -0
  239. package/src/client/selection-payload.ts +86 -0
  240. package/src/client/service.ts +859 -0
  241. package/src/client/settings-nav-icon.ts +45 -0
  242. package/src/client/shell-presets.ts +83 -0
  243. package/src/client/sidebar.module.css +2788 -0
  244. package/src/client/sidechat-transcript.ts +321 -0
  245. package/src/client/split-pane.tsx +331 -0
  246. package/src/client/state.ts +1497 -0
  247. package/src/client/subagent-detect.ts +137 -0
  248. package/src/client/subagent-jobs.ts +161 -0
  249. package/src/client/tab-content-memo.ts +55 -0
  250. package/src/client/terminal-font.ts +224 -0
  251. package/src/client/terminal-links.ts +194 -0
  252. package/src/client/theme.ts +110 -0
  253. package/src/client/titlebar-strip.ts +37 -0
  254. package/src/client/upload.ts +187 -0
  255. package/src/client/wco.ts +120 -0
  256. package/src/config.ts +154 -0
  257. package/src/context-types.ts +611 -0
  258. package/src/fs-operations.ts +95 -0
  259. package/src/fs-search.ts +113 -0
  260. package/src/fs-tree.ts +158 -0
  261. package/src/git.ts +446 -0
  262. package/src/html-route.ts +106 -0
  263. package/src/index.ts +1291 -0
  264. package/src/invariant.ts +32 -0
  265. package/src/jobs-routes.ts +257 -0
  266. package/src/open-external.ts +90 -0
  267. package/src/path-security.ts +75 -0
  268. package/src/prefs-shared.ts +280 -0
  269. package/src/pty-deps.ts +240 -0
  270. package/src/pty-manager.ts +376 -0
  271. package/src/sidechat-core.ts +468 -0
  272. package/src/sidechat-routes.ts +344 -0
  273. package/src/subagent-activity.ts +91 -0
  274. package/src/subagent-live-route.ts +93 -0
  275. package/src/tools.ts +482 -0
  276. package/src/trust-fence.ts +84 -0
  277. package/src/wire.ts +100 -0
@@ -0,0 +1,439 @@
1
+ /**
2
+ * The BetterSidebar client service: a registry that external plugins use
3
+ * to contribute sidebar tab types and file previewers. The service is
4
+ * published to the cordis context as `ctx.betterSidebar` (see
5
+ * {@link ../context-types.ts}); consumers declare it in `inject` and call
6
+ * `registerTab` / `registerFileViewer`, both returning a disposer that
7
+ * cordis auto-invokes on fiber disposal (HMR-safe).
8
+ *
9
+ * Design notes:
10
+ * - The registry is synchronous-snapshot (Map + listener set) so React
11
+ * can read it through `useSyncExternalStore` without tearing.
12
+ * - `dedupeKey` unifies the three open-tab strategies the builtins used to
13
+ * hardcode: single-instance (`() => type`), per-path (`tab => tab.path`),
14
+ * and per-id (`tab => tab.id` for diff tabs whose id is change-derived).
15
+ * `single: true` is sugar for `dedupeKey: () => id`.
16
+ * - `createTab` lets a descriptor own tab instantiation (the terminal
17
+ * builtin uses it to mint `terminal:<n>` ids and bump `nextTerminal`).
18
+ * - `matchFileViewer` walks descriptors in priority order (desc, stable):
19
+ * per descriptor it tries `detect` first (when `head` bytes are given),
20
+ * then `exts`; `exts: []` is a catch-all that matches any path.
21
+ */
22
+ import type { ReactNode } from 'react';
23
+ import type { Context } from '../context-types.ts';
24
+ import { type SidebarSnapshot, type SidebarState, type SidebarStore, type SidebarTab } from './state.ts';
25
+ import type { SessionScope } from './api.ts';
26
+ import type { SidebarPrefs } from '../prefs-shared.ts';
27
+ /**
28
+ * Public state vocabulary re-exported for consumers (type-only; the values
29
+ * stay internal). External plugins name these types in their descriptors —
30
+ * e.g. `dedupeKey: (tab: SidebarTab) => tab.id`, `createTab: (state: SidebarState) => …`,
31
+ * or `badge: (…, state: SidebarState) => …`.
32
+ */
33
+ export type { SidebarTab, SidebarState, SidebarStore, SidebarSnapshot, SidebarDiffRef, TabType, } from './state.ts';
34
+ export type { SessionScope } from './api.ts';
35
+ export type { SidebarPrefs } from '../prefs-shared.ts';
36
+ /** The row control a declarative setting renders as in the settings popup. */
37
+ export type SidebarSettingToggleType = 'switch' | 'text' | 'number' | 'select';
38
+ /** One option of a `type: 'select'` setting row. */
39
+ export interface SidebarSettingSelectOption {
40
+ /** The value written to the setting key when this option is picked
41
+ * (JSON-serializable: string / number / boolean). */
42
+ value: string | number | boolean;
43
+ /** Option title (i18n friendly: string or () => string). */
44
+ title: string | (() => string);
45
+ /** Option description (i18n friendly); rendered under the title in the
46
+ * icon dropdown. */
47
+ desc?: string | (() => string);
48
+ /** Option icon: when ANY option declares one, the dropdown renders
49
+ * big-icon option cards and the closed control shows the selected
50
+ * option's icon too; without icons both are a single line of text. */
51
+ icon?: ReactNode | ((size: number) => ReactNode);
52
+ }
53
+ /** One declarative setting of a tab/viewer, rendered as a nested row in the
54
+ * Side card settings page (e.g. the Subagent page's "auto-open when a
55
+ * subagent appears" switch, or the terminal's custom font rows). `type`
56
+ * selects the control: 'switch' (default) renders the custom switch,
57
+ * 'text' a free-form input committed on blur/Enter, 'number' a numeric
58
+ * input clamped to `min`/`max`, 'select' a dropdown over the declared
59
+ * `options` (single-pick writes the option's value; `multi: true` writes
60
+ * the array of picked values and defaults to false). */
61
+ export interface SidebarSettingToggle {
62
+ /** The SidebarPrefs field this toggle reads and writes ('autoOpenSubagent'). */
63
+ key: string;
64
+ /** Row title (i18n friendly: string or () => string). */
65
+ title: string | (() => string);
66
+ /** Row description (i18n friendly). */
67
+ desc?: string | (() => string);
68
+ /** Row control type; defaults to 'switch' (backward compatible). */
69
+ type?: SidebarSettingToggleType;
70
+ /** Lower bound for `type: 'number'` rows (clamped on commit). */
71
+ min?: number;
72
+ /** Upper bound for `type: 'number'` rows (clamped on commit). */
73
+ max?: number;
74
+ /** Input placeholder for `type: 'text'` rows. */
75
+ placeholder?: string;
76
+ /** Unit suffix rendered after the input (e.g. 'px' for a size row). */
77
+ unit?: string;
78
+ /** Options of a `type: 'select'` row. */
79
+ options?: readonly SidebarSettingSelectOption[];
80
+ /** Whether a `type: 'select'` row allows picking several options (the
81
+ * stored value is then an array of option values); defaults to false. */
82
+ multi?: boolean;
83
+ }
84
+ /** Props of a descriptor's custom settings panel (`settings.render`). */
85
+ export interface SidebarSettingsRenderProps {
86
+ store: SidebarStore;
87
+ service: BetterSidebarService;
88
+ prefs: SidebarPrefs;
89
+ /** This descriptor's own persisted settings blob (from `pluginSettings[id]`). */
90
+ pluginSettings: Record<string, unknown>;
91
+ /** Persist one plugin-owned setting of this descriptor. */
92
+ updatePluginSetting(key: string, value: unknown): void;
93
+ /** Close the settings popup. */
94
+ close(): void;
95
+ }
96
+ /** Declarative settings of one registered tab or file viewer. */
97
+ export interface SidebarSettingsDeclaration {
98
+ /**
99
+ * Extra settings rows rendered under the feature's own row in the
100
+ * settings page (only while the feature is enabled). Keys must be fields
101
+ * of the host's PrefsSchema (built-ins: 'autoOpenSubagent',
102
+ * 'agentTerminalTools', 'agentOpenTools', 'terminalFontFamily'); unknown
103
+ * keys are dropped by the settings seam.
104
+ */
105
+ toggles?: readonly SidebarSettingToggle[];
106
+ /**
107
+ * Plugin-owned settings rows (v0.12.0+): same row controls as `toggles`
108
+ * (switch/text/number), but the keys are plugin-local and persisted in
109
+ * the sidebar's own prefs document under `pluginSettings[<descriptor id>]`
110
+ * — no host PrefsSchema field needed. Values must be JSON-serializable
111
+ * (the row controls produce strings / numbers / booleans).
112
+ */
113
+ pluginToggles?: readonly SidebarSettingToggle[];
114
+ /**
115
+ * Custom settings panel (v0.12.0+): when given, the gear popup renders
116
+ * this instead of the row lists (`toggles` / `pluginToggles`). Receives
117
+ * the shared store/service, the live prefs, the descriptor's own
118
+ * `pluginSettings` blob, and a persistence helper.
119
+ */
120
+ render?: (props: SidebarSettingsRenderProps) => ReactNode;
121
+ }
122
+ /** Props every tab component receives (builtins and external alike). */
123
+ export interface TabComponentProps {
124
+ ctx: Context;
125
+ store: SidebarStore;
126
+ scope: SessionScope;
127
+ tab: SidebarTab;
128
+ /** Whether this tab is the active one AND the panel is open (live views pause otherwise). */
129
+ visible: boolean;
130
+ /** The explorer's expanded directory set (ExplorerView). */
131
+ expanded?: string[];
132
+ /** The explorer's reveal-highlight set (ExplorerView; "Show in folder" targets). */
133
+ revealed?: string[];
134
+ onToggleDir?: (path: string) => void;
135
+ onReferenceFile?: (path: string) => void;
136
+ onOpenFile?: (path: string) => void;
137
+ onOpenDiff?: (tab: SidebarTab) => void;
138
+ onSubagentJump?: (childSessionId: string) => void;
139
+ }
140
+ /** Describes one kind of sidebar tab (builtins register themselves too). */
141
+ export interface TabDescriptor {
142
+ /** Unique id; also the `SidebarTab.type` value (`'explorer'`, `'my-plugin:db'`). */
143
+ id: string;
144
+ title: string | (() => string);
145
+ icon?: ReactNode | ((size: number) => ReactNode);
146
+ /** + menu sort order (ascending); default 100. */
147
+ order?: number;
148
+ /** Hide from the + menu (the editor tab is opened by file-open, not by the menu). */
149
+ hidden?: boolean;
150
+ /**
151
+ * + menu disabled predicate (e.g. terminal at capacity). Receives the
152
+ * session scope and the live sidebar state (counts, expansions).
153
+ */
154
+ available?: (ctx: Context, scope: SessionScope, state: SidebarState) => boolean;
155
+ /**
156
+ * Single-instance sugar: `true` is shorthand for `dedupeKey: () => id`
157
+ * (opening the tab focuses an existing one of the same type instead of
158
+ * creating a duplicate). An explicit `dedupeKey` always wins when both
159
+ * are given. Builtins: explorer/git/subagent use `single: true`.
160
+ */
161
+ single?: boolean;
162
+ /**
163
+ * If provided, opening a tab whose `dedupeKey(tab)` matches an existing
164
+ * tab's key focuses the existing one instead of creating a new one.
165
+ * Returning `undefined` means "no dedup — always open a new tab".
166
+ * Builtins: editor uses `tab => tab.path`; diff uses `tab => tab.id`
167
+ * (openDiffTab mints change-derived ids).
168
+ */
169
+ dedupeKey?: (tab: SidebarTab) => string | undefined;
170
+ /**
171
+ * Custom tab creation (minting the `SidebarTab` and any state patches).
172
+ * Return `null` to refuse creation. The terminal builtin uses this to
173
+ * mint `terminal:<n>` ids and bump `nextTerminal`.
174
+ * When omitted, a default `{ id, type, title }` tab is created.
175
+ */
176
+ createTab?: (state: SidebarState) => {
177
+ tab: SidebarTab;
178
+ patch?: Partial<SidebarState>;
179
+ } | null;
180
+ /**
181
+ * External-link target claim (v0.13.0+): when a GUI external-link click
182
+ * is taken over (the `browserInterceptLinks` master AND the URL's
183
+ * protocol flag — `browserInterceptHttp` / `browserInterceptHttps` —
184
+ * are on), the first registered tab whose `urlTarget(url)` returns true
185
+ * is opened with `openTab({ type, url, title: hostname })` — the URL is
186
+ * the whole payload (the tab reads it from `tab.path`). Registration
187
+ * order wins (first claim first served); a disabled tab type is skipped;
188
+ * a throwing predicate is swallowed (console.error, the type is skipped).
189
+ * The built-in browser tab declares NO urlTarget — it stays the implicit
190
+ * fallback target, so plugins can never be shadowed by it. To host more
191
+ * than one URL at a time, mint per-URL ids through `createTab` (the
192
+ * browser builtin's pattern); otherwise the id safety net focuses the
193
+ * existing tab of the same type and the new URL is not applied.
194
+ */
195
+ urlTarget?: (url: URL) => boolean;
196
+ /**
197
+ * Declarative settings shown in the Side card settings page: every
198
+ * registered tab gets an enable/disable switch (icon + title + id), and
199
+ * `settings.toggles` adds nested switches tied to SidebarPrefs fields
200
+ * (e.g. the subagent tab's 'autoOpenSubagent').
201
+ */
202
+ settings?: SidebarSettingsDeclaration;
203
+ /**
204
+ * Tab-strip badge (v0.12.0+): a small pill rendered on the tab next to
205
+ * the icon — a number renders as a count (99+ capped), a string renders
206
+ * as-is, null/undefined hides the badge. Called on every tab-bar render,
207
+ * so keep it cheap; a throw is swallowed (no badge shown).
208
+ */
209
+ badge?: (ctx: Context, scope: SessionScope, state: SidebarState) => string | number | null | undefined;
210
+ /**
211
+ * Lifecycle callbacks (v0.12.0+). Fired by the SERVICE paths only:
212
+ * `onOpen` when an open actually creates a tab (a dedupe/id-safety-net
213
+ * focus is NOT an open — it fires `onActivate` instead), `onActivate`
214
+ * when a tab is focused (dedupe focus, id-safety-net focus, or the
215
+ * tab-bar activation), `onClose` when a tab is closed through
216
+ * `closeTab`. Builtin-only flows that mutate state directly (the diff
217
+ * split placement, agent-terminal reconcile) never touch external tabs
218
+ * and fire no callbacks. A throwing callback is logged and never breaks
219
+ * the open/close/activate flow.
220
+ */
221
+ onOpen?: (tab: SidebarTab, scope: SessionScope) => void;
222
+ onActivate?: (tab: SidebarTab, scope: SessionScope) => void;
223
+ onClose?: (tab: SidebarTab, scope: SessionScope) => void;
224
+ component: (props: TabComponentProps) => ReactNode;
225
+ }
226
+ /** How the host loads a file's bytes for one viewer. */
227
+ export type FileFetchStrategy = 'none' | 'fsRead' | 'mediaUrl' | 'custom' | 'binary-download';
228
+ /** Props every file viewer component receives. */
229
+ export interface FileViewerProps {
230
+ ctx: Context;
231
+ store: SidebarStore;
232
+ scope: SessionScope;
233
+ path: string;
234
+ title: string;
235
+ /** The matching descriptor's id (`'code'`, `'my-plugin:csv'`). */
236
+ viewerId: string;
237
+ /** fsRead text content (fetchStrategy='fsRead'). */
238
+ content?: string;
239
+ truncated?: boolean;
240
+ /** mediaUrl for the path (fetchStrategy='mediaUrl'). */
241
+ mediaUrl?: string;
242
+ /** custom load() return value (fetchStrategy='custom'). */
243
+ customData?: unknown;
244
+ /** Internal (built-in text editor): 'host' asks the viewer to skip its own
245
+ * toolbar row — the editor host's merged-mode header renders it instead,
246
+ * fed through the two callbacks below. Viewers that ignore these fields
247
+ * render exactly as before. */
248
+ toolbar?: 'self' | 'host';
249
+ /** Internal: the viewer reports its toolbar state (mode/dirty/save). */
250
+ onToolbarState?: (state: EditorToolbarState) => void;
251
+ /** Internal: the viewer registers its toolbar commands on mount (null on
252
+ * unmount). */
253
+ onToolbarControls?: (controls: EditorToolbarControls | null) => void;
254
+ }
255
+ /** The toolbar state a text editor reports to the host's merged-mode header. */
256
+ export interface EditorToolbarState {
257
+ /** Whether the preview/edit mode toggle applies (markdown/html). */
258
+ modes: boolean;
259
+ mode: 'preview' | 'edit';
260
+ dirty: boolean;
261
+ /** Whether saving applies (text content loaded). */
262
+ editable: boolean;
263
+ saveState: 'idle' | 'saving' | 'saved' | 'failed';
264
+ }
265
+ /** The commands the host's merged-mode header sends back to the viewer. */
266
+ export interface EditorToolbarControls {
267
+ setMode(mode: 'preview' | 'edit'): void;
268
+ save(): void;
269
+ }
270
+ /** Describes one file previewer (builtins register themselves too). */
271
+ export interface FileViewerDescriptor {
272
+ /** Unique id (`'image'`, `'pdf'`, `'my-plugin:csv'`). */
273
+ id: string;
274
+ /** Display name for the settings inventory (falls back to `id` when absent). */
275
+ title?: string | (() => string);
276
+ /** Icon shown in the settings inventory. */
277
+ icon?: ReactNode | ((size: number) => ReactNode);
278
+ /** Lowercase extensions without leading dot (`['png','jpg']`). `[]` = match any (catch-all). */
279
+ exts: readonly string[];
280
+ /** Higher wins; default 0. Builtins use 0; the catch-all `code` viewer uses -100. */
281
+ priority?: number;
282
+ fetchStrategy: FileFetchStrategy;
283
+ /**
284
+ * Content sniff: when `head` bytes are available the descriptor's `detect`
285
+ * is consulted before its `exts` (per-descriptor, in priority order).
286
+ */
287
+ detect?: (path: string, head: Uint8Array) => boolean;
288
+ /** fetchStrategy='custom' loader. `signal` (v0.12.0+) aborts on viewer
289
+ * teardown / re-match; loaders that ignore it keep working. */
290
+ load?: (path: string, scope: SessionScope, signal?: AbortSignal) => Promise<unknown>;
291
+ /**
292
+ * Declarative settings shown in the Side card settings page: every
293
+ * registered viewer gets an enable/disable switch (icon + title + exts).
294
+ */
295
+ settings?: SidebarSettingsDeclaration;
296
+ component: (props: FileViewerProps) => ReactNode;
297
+ }
298
+ /** One `openTab` request. */
299
+ export interface OpenTabSeed {
300
+ type: string;
301
+ /** Overrides the descriptor's title when given (the editor tab shows the file name). */
302
+ title?: string;
303
+ /** A file path (the editor tab's content seed). */
304
+ path?: string;
305
+ /** A diff reference (the diff tab's content seed). */
306
+ diff?: SidebarTab['diff'];
307
+ /** Explicit tab id (defaults to the type). */
308
+ id?: string;
309
+ /** A URL the tab navigates to on mount (the browser tab's seed). */
310
+ url?: string;
311
+ /** JSON-serializable custom state carried on the minted tab (persisted across reloads; v0.12.0+). */
312
+ meta?: unknown;
313
+ }
314
+ /**
315
+ * The registry service published as `ctx.betterSidebar`.
316
+ */
317
+ export interface BetterSidebarService {
318
+ registerTab(descriptor: TabDescriptor): () => void;
319
+ registerFileViewer(descriptor: FileViewerDescriptor): () => void;
320
+ getTabs(): readonly TabDescriptor[];
321
+ getFileViewers(): readonly FileViewerDescriptor[];
322
+ /** Find a tab descriptor by id (undefined if not registered). */
323
+ getTab(id: string): TabDescriptor | undefined;
324
+ /**
325
+ * Whether a tab type is enabled in the side card prefs. An absent
326
+ * `tabsEnabled[id]` entry means enabled — only an explicit `false`
327
+ * disables the type (hidden from the + menu, `openTab` refuses, and
328
+ * derived flows gate on it).
329
+ */
330
+ isTabEnabled(id: string): boolean;
331
+ /** Whether a file viewer is enabled (absent `viewersEnabled[id]` = enabled). */
332
+ isViewerEnabled(id: string): boolean;
333
+ /**
334
+ * Find a file viewer for a path (priority desc; detect first, then exts).
335
+ * Disabled viewers are skipped, so files fall through to the next match.
336
+ */
337
+ matchFileViewer(path: string, head?: Uint8Array): FileViewerDescriptor | undefined;
338
+ /**
339
+ * Open a tab (used by external tabs and the + menu). `title` overrides
340
+ * the descriptor's title when given (the editor tab shows the file name);
341
+ * when the descriptor provides `createTab` it mints the tab itself and
342
+ * `title`/`path`/`id` are ignored. `url` lands the tab with its `path`
343
+ * pre-set to the URL (the browser tab's navigation seed; the caller
344
+ * usually pairs it with a hostname `title`). A disabled tab type is a
345
+ * no-op.
346
+ *
347
+ * `scope` (v0.12.0+) targets a specific session: when given, the open
348
+ * lands in THAT session's sidebar state (loading it if it has none yet)
349
+ * without switching the UI's active session; when absent the open lands
350
+ * in the currently active session (the pre-0.12 behavior).
351
+ *
352
+ * A CONTENT open (a `path` or `url` seed) must land in sight: when the
353
+ * panel is collapsed, it is expanded automatically. Type-only opens (the
354
+ * + menu, agent-terminal auto-tabs) never expand — the panel behavior is
355
+ * their caller's business.
356
+ *
357
+ * Note: `available` gates the + menu's disabled state only — it does NOT
358
+ * refuse `openTab` (only the settings disable switch does).
359
+ */
360
+ openTab(seed: OpenTabSeed, scope?: SessionScope): void;
361
+ /**
362
+ * Close a tab by id (fires descriptor.onClose). An unknown tab id is a
363
+ * strict no-op (no state churn, no callbacks). `scope` (v0.12.0+) rides
364
+ * to the callback (its optional cwd included); absent, the callback gets
365
+ * `{ sessionId }` of the active session.
366
+ */
367
+ closeTab(tabId: string, scope?: SessionScope): void;
368
+ /** Subscribe to registry changes (register/dispose). */
369
+ subscribe(listener: () => void): () => void;
370
+ /** The plugin version this service instance was built from ('0.12.0'). */
371
+ readonly version: string;
372
+ /**
373
+ * Monotonic capability list (v0.12.0+): 'badge' | 'tabLifecycle' |
374
+ * 'updateTab' | 'openFile' | 'targetedOpen' | 'stateSubscription' |
375
+ * 'tabMeta' | 'pluginSettings'. Features are never removed — consumers
376
+ * gate new API usage on membership.
377
+ */
378
+ readonly features: readonly string[];
379
+ /**
380
+ * The current sidebar snapshot: the active session id, its state (panel
381
+ * geometry, open tabs, expansions), and the side card prefs (v0.12.0+).
382
+ * `state`/`sessionId` are undefined until a session becomes active.
383
+ */
384
+ getSnapshot(): SidebarSnapshot;
385
+ /** Subscribe to snapshot changes (session switch, state changes, prefs changes). Returns the disposer. */
386
+ subscribeState(listener: () => void): () => void;
387
+ /** Update an open tab's display fields (title / path / meta); a missing tab id is a no-op. */
388
+ updateTab(tabId: string, patch: {
389
+ title?: string;
390
+ path?: string;
391
+ meta?: unknown;
392
+ }): void;
393
+ /**
394
+ * Activate an open tab (the tab-bar activation path; fires
395
+ * descriptor.onActivate). An unknown tab id is a strict no-op. `scope`
396
+ * (v0.12.0+) rides to the callback like `closeTab`'s.
397
+ */
398
+ activateTab(tabId: string, scope?: SessionScope): void;
399
+ /** Open a file in the sidebar editor of `scope`'s session (title defaults to the file name). */
400
+ openFile(scope: SessionScope, path: string, title?: string): void;
401
+ }
402
+ /**
403
+ * Find the tab type that claims an intercepted external-link URL (v0.13.0+).
404
+ * Walks the descriptors in REGISTRATION order and returns the first one
405
+ * that declares `urlTarget` and matches `url`; a throwing predicate is
406
+ * swallowed (console.error, type skipped) so one broken plugin can never
407
+ * break the whole link pipeline. The caller passes the ENABLED tab
408
+ * descriptors (enablement is the caller's prefs domain — filter
409
+ * `service.getTabs()` through `tabsEnabled` before matching) and falls
410
+ * back to the built-in browser tab when nothing claims the URL (the
411
+ * browser never declares `urlTarget` itself, so it can never shadow a
412
+ * plugin claim).
413
+ */
414
+ export declare function matchUrlTarget(tabs: readonly TabDescriptor[], url: URL): TabDescriptor | undefined;
415
+ export declare const SIDEBAR_SERVICE_VERSION: string;
416
+ /**
417
+ * Monotonic capability list consumers use to gate new API usage (features
418
+ * are never removed). Each string names a v0.12.0+ capability:
419
+ * - 'badge': TabDescriptor.badge
420
+ * - 'tabLifecycle': TabDescriptor.onOpen/onActivate/onClose
421
+ * - 'updateTab': BetterSidebarService.updateTab
422
+ * - 'openFile': BetterSidebarService.openFile
423
+ * - 'targetedOpen': BetterSidebarService.openTab(seed, scope?)
424
+ * - 'stateSubscription': getSnapshot/subscribeState
425
+ * - 'tabMeta': SidebarTab.meta (seeds, createTab, updateTab, persistence)
426
+ * - 'pluginSettings': SidebarSettingsDeclaration.pluginToggles/render
427
+ * - 'urlTarget' (v0.13.0): TabDescriptor.urlTarget (external-link claims)
428
+ * - 'settingSelect': SidebarSettingToggle type 'select' (options/multi)
429
+ * - 'floatWindows' (v0.16.0): tabs float as free windows — openTab's dedupe/
430
+ * id focus targets RAISE the floating window (never duplicate the tab or
431
+ * expand panels), closeTab on a floating tab closes it with its window.
432
+ */
433
+ export declare const SIDEBAR_FEATURES: readonly ["badge", "tabLifecycle", "updateTab", "openFile", "targetedOpen", "stateSubscription", "tabMeta", "pluginSettings", "urlTarget", "settingSelect", "floatWindows"];
434
+ /**
435
+ * Create one BetterSidebar service bound to a store. The service owns the
436
+ * tab/viewer registries (Map + listener set) and proxies openTab/closeTab
437
+ * to the store's reducer. One instance per client plugin activation.
438
+ */
439
+ export declare function createBetterSidebarService(store: SidebarStore): BetterSidebarService;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Mark this plugin's row in the DSH settings navigation so its bundled CSS
3
+ * can replace the shell's fallback gear with the Side card glyph.
4
+ *
5
+ * DSH 0.1.x projects only `id`, `order`, and `label` from a
6
+ * `settings.section` registration, then chooses icons inside the settings
7
+ * shell from a closed list of built-in ids. Until that public contract grows
8
+ * an icon field, the plugin identifies only its own localized row after the
9
+ * dialog mounts. The marker owns no shell structure and is removed on fiber
10
+ * disposal, so the adaptation remains HMR-safe.
11
+ */
12
+ export declare const SETTINGS_NAV_MARKER = "data-dsh-coding-sidebar-settings-nav";
13
+ /**
14
+ * Keep the marker on the settings-nav button whose visible text is this
15
+ * plugin's current localized section label.
16
+ * @param label - locale-aware label resolver used by the section registration.
17
+ * @returns disposer that disconnects observation and removes owned markers.
18
+ */
19
+ export declare function registerSettingsNavIcon(label: () => string): () => void;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Built-in desktop-shell presets for the "位置兼容模式" secondary setting
3
+ * (scheme `preset`). DATA-DRIVEN by design: each shell's adaptation lives
4
+ * in one entry (strip values / optional CSS), the core applies it through
5
+ * the SAME generic strip variable and CSS-injection mechanism — adding a
6
+ * shell is adding data, never a code path.
7
+ *
8
+ * Inclusion rule (maintained in AGENTS.md §8): only shells that (a) appear
9
+ * in this repo's issues/PRs (a user actually hit a problem) and (b) have
10
+ * 100+ GitHub stars (a real user base). The mechanism is opt-in: auto
11
+ * detection never applies a preset — the settings badge only SUGGESTS it.
12
+ */
13
+ import type { DesktopEnv } from './desktop-env.ts';
14
+ export interface ShellPreset {
15
+ /** Stable preset id (persisted in `titleBarPresetId`). */
16
+ readonly id: string;
17
+ /** User-facing name of the shell. */
18
+ readonly title: string;
19
+ /** One-line description shown in the settings popup. */
20
+ readonly desc: string;
21
+ /**
22
+ * The top strip (px) this shell reserves over web content, per
23
+ * environment — the fallback used when neither the standard WCO API nor
24
+ * the `dsh-desktop-titlebar-inset` contract parameter is available.
25
+ * Return undefined when the shell needs no strip in that environment.
26
+ * MUST be pure (called during render).
27
+ */
28
+ readonly stripFor?: (env: DesktopEnv) => number | undefined;
29
+ /**
30
+ * Extra CSS applied while the preset is enabled (injected last, after the
31
+ * plugin's own styles). Targets the plugin's stable data attributes and
32
+ * shell-declared body/URL markers only — never other shells' class names.
33
+ * Empty for presets fully covered by the strip variable.
34
+ */
35
+ readonly css?: string;
36
+ /**
37
+ * Whether this shell's marker is currently visible (URL stamps / preload
38
+ * markers — see desktop-env.ts). Used ONLY for the settings "已检测"
39
+ * badge; never auto-applies anything. MUST be pure.
40
+ */
41
+ readonly detect?: (env: DesktopEnv) => boolean;
42
+ }
43
+ /** All built-in shell presets (registration order = settings list order). */
44
+ export declare function getShellPresets(): readonly ShellPreset[];
45
+ /** One preset by id, or undefined for an unknown/empty id. */
46
+ export declare function getShellPreset(id: string): ShellPreset | undefined;
47
+ /** The strip the active preset contributes for the given environment. */
48
+ export declare function presetStripFor(preset: ShellPreset | undefined, env: DesktopEnv): number | undefined;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Side Chat transcript mapping (browser half): turns a thread child's
3
+ * history rows (`session.history` — the generic RPC, which reads the durable
4
+ * log without activating the child) into compact display rows.
5
+ *
6
+ * A thread child's log starts with the ENTIRE inherited parent log as its
7
+ * fork seed. The mapping therefore cuts everything up to the LAST
8
+ * `session/end-seed` marker and maps context injections (the "Side
9
+ * conversation boundary" prompt, plugin-sourced context) onto a collapsible
10
+ * injection row, so the view shows only the thread's own conversation.
11
+ *
12
+ * Live streaming: `assistant/message` events only land when a step
13
+ * completes, but `assistant/chunk` events stream token-level text and
14
+ * reasoning deltas. The mapping accumulates both per block and supersedes
15
+ * them with the assembled message once it lands (settled rows).
16
+ */
17
+ import type { SidebarHistoryEntry } from '../context-types.ts';
18
+ /** One compact transcript row rendered in the thread view. `seq` is the
19
+ * source event's log sequence — stable row identity for React keys across
20
+ * polls (streaming caches ride the key, so window slides must not re-key
21
+ * rows). */
22
+ export type SidechatTranscriptRow = {
23
+ kind: 'user';
24
+ seq: number;
25
+ text: string;
26
+ }
27
+ /** A context injection (the side boundary prompt + the parked in-progress
28
+ * snapshot, or any plugin-sourced context): rendered as one collapsible
29
+ * row, never as a user bubble. */
30
+ | {
31
+ kind: 'injection';
32
+ seq: number;
33
+ text: string;
34
+ }
35
+ /** `settled` distinguishes an assembled message from a still-streaming
36
+ * chunk accumulation (streaming rows are superseded by the settle). */
37
+ | {
38
+ kind: 'assistant';
39
+ seq: number;
40
+ text: string;
41
+ settled: boolean;
42
+ } | {
43
+ kind: 'reasoning';
44
+ seq: number;
45
+ text: string;
46
+ settled: boolean;
47
+ } | {
48
+ kind: 'tool';
49
+ seq: number;
50
+ name: string;
51
+ failed: boolean;
52
+ /** Raw arguments JSON as the model produced it. */
53
+ args?: string;
54
+ /** Plain text of the paired result. */
55
+ resultText?: string;
56
+ /** True while the call's result has not landed yet. */
57
+ executing?: boolean;
58
+ };
59
+ /** Extract the visible text of a content-block list (`text` blocks verbatim,
60
+ * joined by blank lines); empty reads `…` so rows never render blank. */
61
+ export declare function blockText(content: readonly unknown[]): string;
62
+ /**
63
+ * One-line summary of a tool call's raw arguments JSON for the collapsed
64
+ * row: the first identifying string field when the JSON parses, else the
65
+ * flattened raw text; empty when there is nothing worth showing.
66
+ */
67
+ export declare function toolArgsSummary(args: string | undefined): string;
68
+ /**
69
+ * Collect the thread's OWN events on first attach: walk backward from the
70
+ * log tail (oldest-first accumulation) until the `session/end-seed` marker
71
+ * surfaces, then keep everything after it.
72
+ *
73
+ * Page size matters: cold reads re-expand persisted chunk-rows into one
74
+ * `assistant/chunk` event per delta, so a single streamed answer can be
75
+ * HUNDREDS of events. A small walk window (the old 8×32 = 256 events) let
76
+ * earlier `tool/call` events fall out of the loaded window — the tool rows
77
+ * vanished on re-entry while the settled text survived. The walk therefore
78
+ * pages big; tail polls stay small.
79
+ *
80
+ * Exhaustion (log start reached without a marker — a thread created before
81
+ * seeding existed, or a pathological log) returns `seedBoundary: 0` so the
82
+ * caller stops re-walking and renders the window as-is.
83
+ *
84
+ * @param fetchPage - one history page (newest-first window ending at
85
+ * `beforeSeq`, exclusive; omit for the tail page).
86
+ * @param pageCap - safety bound on backward pages.
87
+ */
88
+ export declare function collectOwnEvents(fetchPage: (beforeSeq?: number) => Promise<readonly SidebarHistoryEntry[]>, pageCap?: number): Promise<{
89
+ seedBoundary: number;
90
+ entries: SidebarHistoryEntry[];
91
+ }>;
92
+ /**
93
+ * Map a thread child's history rows onto compact transcript rows: the
94
+ * inherited fork seed is cut at the last `session/end-seed`, context
95
+ * injections map onto a collapsible injection row, `assistant/chunk`
96
+ * deltas accumulate into streaming rows per (turn, step, block) and are
97
+ * superseded by the assembled `assistant/message`, and tool invocations
98
+ * render one expandable line each (arguments, paired result text, failure
99
+ * marker; a still-executing call is marked until its result lands).
100
+ * @param entries - history rows (event + host-computed view) in seq order.
101
+ * @returns display rows in log order.
102
+ */
103
+ export declare function transcriptRows(entries: readonly SidebarHistoryEntry[]): SidechatTranscriptRow[];
@@ -0,0 +1,36 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { SidebarState, SidebarTab, SplitNode } from './state.ts';
3
+ import type { DropZone } from './state.ts';
4
+ import { type NewTabOption, type TabDragPayload } from './TabBar.tsx';
5
+ /** Actions the workbench needs (bound to the store by the sidebar shell). */
6
+ export interface WorkbenchActions {
7
+ closeTab: (paneId: string, tabId: string) => void;
8
+ activateTab: (paneId: string, tabId: string) => void;
9
+ /** Make a pane the target of newly opened tabs (click focus). */
10
+ focusPane: (paneId: string) => void;
11
+ /** VSCode drag gesture: edge → split the target pane, center → merge. */
12
+ moveTabToEdge: (payload: TabDragPayload, toPane: string, zone: DropZone) => void;
13
+ /** Reorder within a pane (drop onto another tab inserts before it). */
14
+ moveTabBefore: (payload: TabDragPayload, toPane: string, beforeTabId: string) => void;
15
+ resizeSplit: (splitId: string, index: number, deltaFrac: number) => void;
16
+ /** Float a docked tab out as a free window (tab context menu entry). */
17
+ floatTab: (tabId: string) => void;
18
+ /**
19
+ * Pin/unpin a terminal tab (v0.17.0+). The shell snapshots the home cwd
20
+ * at pin time; null clears the pin. Optional: when undefined the tab
21
+ * context menu hides the pin entry (legacy callers).
22
+ */
23
+ pinTab?: (tabId: string, scope: 'workspace' | 'global' | null) => void;
24
+ }
25
+ /** The workbench: the split tree filling the sidebar body (`tree` overrides
26
+ * `state.splits` — the pinned-injection augmented tree). */
27
+ export declare function Workbench(props: {
28
+ state: SidebarState;
29
+ tree?: SplitNode;
30
+ newTabOptions: NewTabOption[];
31
+ actions: WorkbenchActions;
32
+ onNewTab: (optionId: string) => void;
33
+ renderTab: (tab: SidebarTab, active: boolean, paneId: string) => ReactNode;
34
+ getTabIcon?: (tab: SidebarTab) => ReactNode;
35
+ getTabBadge?: (tab: SidebarTab) => ReactNode;
36
+ }): import("react").JSX.Element;