@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,501 @@
1
+ /**
2
+ * Client half of dsh-better-sidebar: resolves the user's "Side card"
3
+ * preferences through the plugin's own fenced settings route, mounts the
4
+ * right sidebar portal (inside an error boundary so a rendering failure
5
+ * shows an error strip instead of a blank panel), registers the turn-tail
6
+ * interception, and contributes the Side card settings section to the DSH
7
+ * Settings shell. Requires the runtime's slots and sessions services; the
8
+ * bundle itself is a module-table consumer only (react + ui-primitives +
9
+ * xterm, all provided or inlined).
10
+ */
11
+ import { createElement, Fragment } from 'react'
12
+ import { createRoot, type Root } from 'react-dom/client'
13
+ import type { Context } from '../context-types.ts'
14
+ import { allLeaves, createSidebarStore, isAgentTabId } from './state.ts'
15
+ import { createBetterSidebarService, matchUrlTarget, SIDEBAR_SERVICE_VERSION } from './service.ts'
16
+ import { revalidateChunksOnReactivate, setChunkModuleSystem } from './chunk-loader.ts'
17
+ import { registerBuiltins } from './builtins/index.ts'
18
+ import { Sidebar } from './Sidebar.tsx'
19
+ import { ChatcodeGate } from './ChatcodeGate.tsx'
20
+ import { hasPriorSidebarInstall, ReleaseNotesDialog } from './ReleaseNotesDialog.tsx'
21
+ import { RenderBoundary } from './RenderBoundary.tsx'
22
+ import { registerOpenPathInterception, registerTurnTailInterception } from './intercept.tsx'
23
+ import { registerLinkInterception } from './link-intercept.ts'
24
+ import { registerImeGuard } from './ime-guard.ts'
25
+ import { AI_RATIO_NAV_MARKER, registerSettingsNavIcon, UPDATE_NAV_MARKER, USAGE_NAV_MARKER } from './settings-nav-icon.ts'
26
+ import { installSessionPins } from './session-pins.tsx'
27
+ import { registerChatPresentationCompatibility } from './chat-presentation.ts'
28
+ import { loadExternalDisable, loadPrefs } from './prefs.ts'
29
+ import { SideCardSection } from './SideCardSection.tsx'
30
+ import { UsageHeatmapSection } from './UsageHeatmapSection.tsx'
31
+ import { AiRatioSection } from './AiRatioSection.tsx'
32
+ import { UpdateSettingsSection } from './UpdateSettingsSection.tsx'
33
+ import { api } from './api.ts'
34
+ import { LOCALE_NS, attachLocale, attachBetterLocale, t, zh, en,
35
+ ja, de, fr, pt, ko, ar, hi, id, tr, vi, th, ru, it, nl, sv, pl,
36
+ zhHK, zhTW, zhMO,
37
+ } from './locales.ts'
38
+ import css from './sidebar.module.css'
39
+ import './layout.css'
40
+ import { apply as applyUiTweaksClient } from './ui-tweaks/index.tsx'
41
+
42
+ /** Services required before mounting (provided by the client runtime; the
43
+ * locale service backs the sidebar's copy — see locales.ts). `modules`
44
+ * (rc.8+) is the client module system the chunk loader resolves its
45
+ * externals through — Cordis guards service access without inject. */
46
+ export const inject = ['slots', 'sessions', 'workspaces', 'locale', 'modules', 'commandUi', 'conversation']
47
+
48
+ /**
49
+ * Error boundary over the sidebar tree (root scope): a render error in the
50
+ * sidebar SHELL itself must never blank the page silently — the shared
51
+ * RenderBoundary shows a dismissible error strip and logs the stack. The
52
+ * per-tab scope (Sidebar.tsx) catches viewer/editor crashes first; this root
53
+ * boundary stays as the last resort for Workbench/shell errors.
54
+ */
55
+ /**
56
+ * Client plugin body.
57
+ * @param ctx - the client cordis context (slots, sessions).
58
+ */
59
+ export function apply(ctx: Context): void {
60
+ // Capture this before createSidebarStore reads or writes persistence, so a
61
+ // genuinely fresh install is seeded silently while an upgrade can display its note.
62
+ let priorInstall = false
63
+ try { priorInstall = hasPriorSidebarInstall(window.localStorage) } catch { /* storage unavailable */ }
64
+ // Keep the former dsh-ui-tweaks browser half intact inside this one client
65
+ // activation. Its settings pages, GitBar, notifications, branding and
66
+ // optional UI contributions retain their original ids and persistence.
67
+ applyUiTweaksClient(ctx as never)
68
+
69
+ // The sidebar follows the DSH i18n system: attach the locale service so
70
+ // the module-level t()/isZh() resolve the Host-backed language preference
71
+ // (and switch live — the Sidebar root subscribes to it), and register the
72
+ // plugin's dictionaries into the shared locale registry. The disposers
73
+ // run on fiber disposal, so re-activation (HMR) re-registers cleanly.
74
+ attachLocale(ctx.locale)
75
+ ctx.effect(() => {
76
+ const offZh = ctx.locale.register(LOCALE_NS, 'zh', zh)
77
+ const offEn = ctx.locale.register(LOCALE_NS, 'en', en)
78
+ return () => { offZh(); offEn() }
79
+ }, 'dsh-better-sidebar: dictionaries')
80
+
81
+ // Opt-in third-language support through @huanlin/dsh-plugin-better-locale.
82
+ // When that plugin is installed, it publishes `ctx.betterLocale` (the
83
+ // override store) and patches LocaleRuntime.prototype.lookup to consult
84
+ // it. We mirror the same override awareness into the sidebar's own `t()`:
85
+ // attachBetterLocale() makes t() consult the store's getOverride first,
86
+ // so the sidebar's chrome (which bypasses ctx.locale and calls t()
87
+ // directly) also switches to the override language. We also register
88
+ // the ja dict with the better-locale store so external callers of
89
+ // ctx.locale.lookup('betterSidebar', key) get the override text too.
90
+ //
91
+ // Activation-order-safe: ctx.get('betterLocale') is a non-reactive read
92
+ // (cordis only re-evaluates declared `inject` deps). If better-locale
93
+ // activates after better-sidebar, the initial read returns undefined.
94
+ // We subscribe to the locale revision — better-locale bumps it on
95
+ // activation (when a persisted override exists) and on every override
96
+ // switch — and re-check ctx.get on each bump, attaching + registering
97
+ // the ja dict once the store becomes available.
98
+ ctx.effect(() => {
99
+ let dispose: (() => void) | undefined
100
+ const sync = (): void => {
101
+ dispose?.()
102
+ dispose = undefined
103
+ const store = ctx.get('betterLocale') as
104
+ | {
105
+ readonly active: string | undefined
106
+ getOverride(dshActive: string, ns: string, key: string): string | undefined
107
+ isOverrideActive(dshActive: string): boolean
108
+ register(ns: string, dicts: Record<string, Record<string, string>>): () => void
109
+ subscribe(listener: () => void): () => void
110
+ }
111
+ | undefined
112
+ attachBetterLocale(store)
113
+ if (store !== undefined) {
114
+ dispose = store.register(LOCALE_NS, {
115
+ ja, de, fr, pt, ko, ar, hi, id, tr, vi, th, ru, it, nl, sv, pl,
116
+ 'zh-HK': zhHK, 'zh-TW': zhTW, 'zh-MO': zhMO,
117
+ })
118
+ }
119
+ }
120
+ // Initial check (picks up the store if better-locale activated first).
121
+ sync()
122
+ // Re-check on every locale revision bump (better-locale bumps when it
123
+ // activates with a persisted override, and when the user switches).
124
+ const unsubscribe = ctx.locale.subscribe(sync)
125
+ return () => {
126
+ unsubscribe()
127
+ dispose?.()
128
+ attachBetterLocale(undefined)
129
+ }
130
+ }, 'dsh-better-sidebar: better-locale lazy integration')
131
+ // One store instance per activation: production code creates it only here,
132
+ // then hands it to the mounted panel and closes over it in the slot
133
+ // registrations (the official createXXXStore() factory rule — no
134
+ // module-level singleton).
135
+ const sidebarStore = createSidebarStore()
136
+ // Extend (rather than replace) DSH's single-owner Workspace browser with a
137
+ // durable Pinned sessions peer group and an extra native row-menu action.
138
+ // The native browser keeps ownership of search, workspace management and
139
+ // its rename/fork/archive flows.
140
+ ctx.effect(
141
+ () => installSessionPins(ctx, sidebarStore),
142
+ 'dsh-better-sidebar: session pins',
143
+ )
144
+ // The sidebar registry service: external plugins register tab types and
145
+ // file previewers through `ctx.betterSidebar.registerTab/registerFileViewer`.
146
+ // Published before the panel mounts so consumers injecting 'betterSidebar'
147
+ // are ready by the time the sidebar renders.
148
+ const service = createBetterSidebarService(sidebarStore)
149
+ ctx.provide('betterSidebar', service)
150
+ // Terminal tab titles use the host's effective shell name (e.g. bash/zsh)
151
+ // instead of "Terminal 1". Start with a safe fallback and replace it as
152
+ // soon as the host shell info resolves. Tabs created before the response
153
+ // arrives keep the fallback title, so also retitle any already-open UI
154
+ // terminal tabs that still carry it.
155
+ const fallbackTitle = t('terminal')
156
+ let terminalTitle = fallbackTitle
157
+ void api.shellGet().then(({ name }) => {
158
+ terminalTitle = name
159
+ const snapshot = service.getSnapshot()
160
+ if (snapshot.state === undefined) return
161
+ const tabs = allLeaves(snapshot.state.splits)
162
+ .concat(allLeaves(snapshot.state.bottomSplits))
163
+ .flatMap(leaf => leaf.tabs)
164
+ for (const tab of tabs) {
165
+ if (tab.type === 'terminal' && !isAgentTabId(tab.id) && tab.title === fallbackTitle) {
166
+ service.updateTab(tab.id, { title: name })
167
+ }
168
+ }
169
+ }).catch(() => { /* keep fallback */ })
170
+ // Register the plugin's own built-in tabs and viewers through the same
171
+ // service (eating our own dogfood). The disposer unregisters them on
172
+ // fiber disposal (HMR-safe).
173
+ ctx.effect(
174
+ () => registerBuiltins(ctx, service, { terminalTitle: () => terminalTitle }),
175
+ 'dsh-better-sidebar: register built-in tabs and viewers',
176
+ )
177
+ // A failure anywhere in the client lifecycle must never take the app down
178
+ // silently: log with the plugin prefix and pin a visible diagnostic strip
179
+ // to the page so a blank panel is never the only symptom.
180
+ const fail = (phase: string, error: unknown): void => {
181
+ console.error(`[dsh-better-sidebar] ${phase} error:`, error)
182
+ try {
183
+ const bar = document.createElement('div')
184
+ bar.style.cssText = 'position:fixed;left:8px;bottom:8px;z-index:2147483000;max-width:70vw;padding:8px 12px;'
185
+ + 'font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace;color:#f2a1a1;background:#1b1b22;'
186
+ + 'border:1px solid #f2a1a1;border-radius:8px;white-space:pre-wrap'
187
+ bar.textContent = `[dsh-better-sidebar] ${phase} error: ${error instanceof Error ? error.message : String(error)}`
188
+ document.body.appendChild(bar)
189
+ } catch {
190
+ // Nothing left to report with.
191
+ }
192
+ }
193
+ try {
194
+ // rc.8+ exposes the client module system as the `ctx.modules` service
195
+ // (no window.__DSH_MODULES__ page global anymore); the chunk loader needs
196
+ // it to resolve its externals, so inject it before anything can load a
197
+ // lazy chunk. The loader falls back to the rc.7 global when absent.
198
+ setChunkModuleSystem(ctx.modules)
199
+ // Fresh chunk state for this activation: drop per-test fixtures and
200
+ // revalidate loaded chunk scripts against the bundle route's ETags —
201
+ // unchanged chunks keep their resolved exports (no re-inject /
202
+ // re-execute on HMR), changed ones are dropped for a clean re-fetch.
203
+ void revalidateChunksOnReactivate()
204
+ ctx.effect(
205
+ registerChatPresentationCompatibility,
206
+ 'dsh-better-sidebar: Chat presentation compatibility',
207
+ )
208
+ ctx.effect(() => {
209
+ let disposed = false
210
+ let root: Root | undefined
211
+ let host: HTMLDivElement | undefined
212
+ let mounted = false
213
+ let bodyObserver: MutationObserver | undefined
214
+ let hostCheckFrame: number | null = null
215
+ const unmount = (): void => {
216
+ if (!mounted) return
217
+ mounted = false
218
+ bodyObserver?.disconnect()
219
+ bodyObserver = undefined
220
+ if (hostCheckFrame !== null) {
221
+ cancelAnimationFrame(hostCheckFrame)
222
+ hostCheckFrame = null
223
+ }
224
+ root?.unmount()
225
+ root = undefined
226
+ host?.remove()
227
+ host = undefined
228
+ }
229
+ /** Re-attach the host if the page (a desktop shell wrapper, SPA
230
+ * navigation, …) ever removes it from <body>. Cheap: childList only,
231
+ * no subtree, no attribute filtering. */
232
+ const guardAnchor = (): void => {
233
+ if (bodyObserver !== undefined) return
234
+ bodyObserver = new MutationObserver(() => {
235
+ if (host !== undefined && !document.body.contains(host)) {
236
+ document.body.appendChild(host)
237
+ }
238
+ })
239
+ bodyObserver.observe(document.body, { childList: true })
240
+ }
241
+ /** One-shot geometry self-check: if the host page transforms
242
+ * <html>/<body> itself (exotic shells), a fixed panel host would
243
+ * track the transformed box instead of the viewport. Flip the
244
+ * degraded mode and pin the host to the viewport every frame until
245
+ * the ancestor transform is actually gone. The normal path (no
246
+ * page-level transform) never runs the sync loop. */
247
+ const scheduleHostCheck = (): void => {
248
+ hostCheckFrame ??= requestAnimationFrame(() => {
249
+ hostCheckFrame = null
250
+ const layer = host?.querySelector<HTMLElement>('[data-dsh-panel-host]')
251
+ if (layer === null || layer === undefined) return
252
+ const rect = layer.getBoundingClientRect()
253
+ const mismatched = Math.abs(rect.left) > 8 || Math.abs(rect.top) > 8
254
+ || Math.abs(rect.width - window.innerWidth) > 8 || Math.abs(rect.height - window.innerHeight) > 8
255
+ if (!mismatched) {
256
+ layer.removeAttribute('data-dsh-panel-host-degraded')
257
+ layer.style.transform = ''
258
+ return
259
+ }
260
+ layer.setAttribute('data-dsh-panel-host-degraded', '')
261
+ console.warn('[dsh-better-sidebar] panel host geometry mismatch — a page-level transform was detected; using degraded viewport sync')
262
+ // Track our own compensating translation so the loop judges the
263
+ // UNCORRECTED geometry: clearing degraded mode must wait for the
264
+ // ancestor transform to actually disappear — the frame right after
265
+ // our correction applies would otherwise look "fixed" and the
266
+ // offset would return immediately (CR #232 P1).
267
+ let applied = { x: 0, y: 0 }
268
+ const sync = (): void => {
269
+ const r = layer.getBoundingClientRect()
270
+ const rawLeft = r.left - applied.x
271
+ const rawTop = r.top - applied.y
272
+ if (Math.abs(rawLeft) <= 1 && Math.abs(rawTop) <= 1
273
+ && Math.abs(r.width - window.innerWidth) <= 1 && Math.abs(r.height - window.innerHeight) <= 1) {
274
+ layer.removeAttribute('data-dsh-panel-host-degraded')
275
+ layer.style.transform = ''
276
+ return
277
+ }
278
+ const next = { x: -rawLeft, y: -rawTop }
279
+ if (next.x !== applied.x || next.y !== applied.y) {
280
+ applied = next
281
+ layer.style.transform = `translate(${applied.x}px, ${applied.y}px)`
282
+ }
283
+ hostCheckFrame = requestAnimationFrame(sync)
284
+ }
285
+ hostCheckFrame = requestAnimationFrame(sync)
286
+ })
287
+ }
288
+ const mount = (): void => {
289
+ if (mounted || disposed) return
290
+ try {
291
+ host = document.createElement('div')
292
+ host.setAttribute('data-dsh-better-sidebar', '')
293
+ document.body.appendChild(host)
294
+ root = createRoot(host)
295
+ root.render(createElement(RenderBoundary, { className: css.boundaryError }, createElement(Fragment, null,
296
+ createElement(Sidebar, { ctx, store: sidebarStore }),
297
+ createElement(ChatcodeGate),
298
+ createElement(ReleaseNotesDialog, {
299
+ packageName: 'dsh-better-sidebar',
300
+ version: SIDEBAR_SERVICE_VERSION,
301
+ priorInstall,
302
+ }),
303
+ )))
304
+ mounted = true
305
+ guardAnchor()
306
+ scheduleHostCheck()
307
+ } catch (error) {
308
+ fail('mount', error)
309
+ }
310
+ }
311
+ const sync = async (): Promise<void> => {
312
+ if (disposed) return
313
+ // Resolve the user's side card prefs BEFORE the first session seeds,
314
+ // so a brand-new conversation opens (or stays closed) at the chosen
315
+ // width from first paint. A settings route failure falls back to the
316
+ // schema defaults; the sidebar still mounts (a stalled wire gives up
317
+ // after the timeout and mounts on the defaults).
318
+ const prefs = await Promise.race([
319
+ loadPrefs(api),
320
+ new Promise<null>(resolve => { const timer = window.setTimeout(() => resolve(null), 2000) }),
321
+ ])
322
+ if (prefs !== null) sidebarStore.setPrefs(prefs)
323
+ if (disposed) return
324
+ // Mutual exclusion with the dsh-web-ui family right panel: while the
325
+ // aionui-panel provider is selected, the sidebar must not mount at
326
+ // all. Re-evaluated on every settings-document update (live switch).
327
+ const suspended = await loadExternalDisable(api)
328
+ if (disposed) return
329
+ sidebarStore.setSuspended(suspended)
330
+ if (suspended) unmount()
331
+ else mount()
332
+ }
333
+ void sync()
334
+ // Live re-evaluation: the runtime broadcasts settings-document updates
335
+ // (the aionui card saves through the same document). Best effort —
336
+ // deployments without the 'remote' service fall back to boot-time
337
+ // evaluation only.
338
+ const remote = ctx.get('remote') as { $on?: (event: string, listener: () => void) => () => void } | undefined
339
+ const offRemote = remote?.$on?.('settings/document-updated', () => { void sync() })
340
+ return () => {
341
+ disposed = true
342
+ offRemote?.()
343
+ unmount()
344
+ }
345
+ }, 'dsh-better-sidebar: sidebar mount')
346
+
347
+ ctx.effect(
348
+ () => {
349
+ try {
350
+ return registerTurnTailInterception(ctx, sidebarStore)
351
+ } catch (error) {
352
+ fail('interception', error)
353
+ return () => {}
354
+ }
355
+ },
356
+ 'dsh-better-sidebar: turn-tail interception',
357
+ )
358
+
359
+ ctx.effect(
360
+ () => {
361
+ try {
362
+ return registerOpenPathInterception(ctx, sidebarStore)
363
+ } catch (error) {
364
+ fail('interception', error)
365
+ return () => {}
366
+ }
367
+ },
368
+ 'dsh-better-sidebar: open-path interception',
369
+ )
370
+
371
+ ctx.effect(
372
+ () => {
373
+ try {
374
+ // External http(s) links in the chat/GUI open the sidebar instead
375
+ // of a new window. Gated on the browserInterceptLinks MASTER pref,
376
+ // the URL's protocol flag (browserInterceptHttp / Https — https
377
+ // defaults OFF: most https sites refuse iframe embedding), and the
378
+ // target tab's enable switch; Ctrl/Cmd+click always bypasses. The
379
+ // target is the first registered tab whose `urlTarget` claims the
380
+ // URL (enabled tabs only), else the built-in browser tab.
381
+ const urlTargetOf = (url: URL): string | undefined => {
382
+ const prefs = sidebarStore.getPrefs()
383
+ const enabled = service.getTabs().filter(tab => prefs.tabsEnabled[tab.id] !== false)
384
+ return matchUrlTarget(enabled, url)?.id
385
+ }
386
+ return registerLinkInterception({
387
+ takeoverEnabled: (url) => {
388
+ if (sidebarStore.getSuspended()) return false
389
+ const prefs = sidebarStore.getPrefs()
390
+ if (prefs.browserInterceptLinks === false) return false
391
+ const protocolOn = url.protocol === 'https:'
392
+ ? prefs.browserInterceptHttps !== false
393
+ : prefs.browserInterceptHttp !== false
394
+ if (!protocolOn) return false
395
+ // A plugin claim is the target (already enabled-filtered);
396
+ // otherwise the built-in browser must be enabled.
397
+ return urlTargetOf(url) !== undefined || prefs.tabsEnabled['browser'] !== false
398
+ },
399
+ openInSidebar: (url) => {
400
+ let title: string | undefined
401
+ try { title = new URL(url).hostname } catch { /* keep the default title */ }
402
+ const type = urlTargetOf(new URL(url)) ?? 'browser'
403
+ ctx.get('betterSidebar')?.openTab({ type, url, title })
404
+ },
405
+ selfOrigin: window.location.origin,
406
+ })
407
+ } catch (error) {
408
+ fail('interception', error)
409
+ return () => {}
410
+ }
411
+ },
412
+ 'dsh-better-sidebar: link interception',
413
+ )
414
+
415
+ // The IME guard: composition keys (candidate arrows, confirm, cancel)
416
+ // belong to the input method, never to page JS. Inlined third-party UI
417
+ // (formerly Univer's office controls, #562 regression) has shipped
418
+ // unguarded keydown handlers that hijack ArrowUp/ArrowDown and break
419
+ // Chinese input; the document-capture guard neutralizes the whole class
420
+ // before React or any native listener sees the event. Registered as
421
+ // early as possible so no other capture-phase listener can win the
422
+ // ordering race.
423
+ ctx.effect(
424
+ () => {
425
+ try {
426
+ return registerImeGuard()
427
+ } catch (error) {
428
+ fail('ime guard', error)
429
+ return () => {}
430
+ }
431
+ },
432
+ 'dsh-better-sidebar: IME composition guard',
433
+ )
434
+
435
+ // DSH 0.1.x does not yet carry an icon through the settings.section
436
+ // registration contract: its shell renders a generic gear for every
437
+ // external section. Mark only this plugin's localized nav row so
438
+ // layout.css can paint the requested Side card SVG; the disposer clears
439
+ // the marker for HMR / plugin disable.
440
+ ctx.effect(
441
+ () => registerSettingsNavIcon(() => t('settingsNav')),
442
+ 'dsh-better-sidebar: settings navigation icon',
443
+ )
444
+ // Same adaptation for the "Token 用量" heatmap row, under its own marker
445
+ // so the two registrations never strip each other's rows.
446
+ ctx.effect(
447
+ () => registerSettingsNavIcon(() => t('usageNav'), USAGE_NAV_MARKER),
448
+ 'dsh-better-sidebar: usage settings navigation icon',
449
+ )
450
+ ctx.effect(
451
+ () => registerSettingsNavIcon(() => t('aiRatioNav'), AI_RATIO_NAV_MARKER),
452
+ 'dsh-better-sidebar: AI ratio settings navigation icon',
453
+ )
454
+ ctx.effect(
455
+ () => registerSettingsNavIcon(() => t('updateNav'), UPDATE_NAV_MARKER),
456
+ 'dsh-better-sidebar: update settings navigation icon',
457
+ )
458
+
459
+ // The "Side card" settings section: appears in the DSH Settings shell
460
+ // once the shell's declaration is on the ledger (slots.inject waits for
461
+ // it); the section reads/writes the prefs through the plugin's own
462
+ // fenced settings route, keeps the shared store in sync, and renders the
463
+ // declarative enable/disable inventory from the tab/viewer registry.
464
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
465
+ name: 'settings.section',
466
+ id: 'better-sidebar',
467
+ order: 100,
468
+ label: () => t('settingsNav'),
469
+ inject: () => ({ store: sidebarStore, service }),
470
+ }, SideCardSection))
471
+
472
+ // The "Token 用量" heatmap section: the sibling nav entry right after
473
+ // the Side card section. Its data arrives through the plugin's own
474
+ // fenced stats routes (server-side ChatCode proxy), so it needs no
475
+ // injected face of its own.
476
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
477
+ name: 'settings.section',
478
+ id: 'better-sidebar-usage',
479
+ order: 110,
480
+ label: () => t('usageNav'),
481
+ }, UsageHeatmapSection))
482
+
483
+ // The "更新" section: a dedicated nav entry (after Token 用量) hosting
484
+ // the one-click ChatCode upgrade panel.
485
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
486
+ name: 'settings.section',
487
+ id: 'better-sidebar-update',
488
+ order: 115,
489
+ label: () => t('updateNav'),
490
+ }, UpdateSettingsSection))
491
+
492
+ ctx.slots.inject('settings.section', () => ctx.slots.register({
493
+ name: 'settings.section',
494
+ id: 'better-sidebar-ai-ratio',
495
+ order: 120,
496
+ label: () => t('aiRatioNav'),
497
+ }, AiRatioSection))
498
+ } catch (error) {
499
+ fail('load', error)
500
+ }
501
+ }
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Interception of the chat's produced-files row: the turn-tail chain entry
3
+ * that replaces ui-deliverables' row when the closing turn produced files.
4
+ * The takeover looks identical (same chip row); the chips open the file in
5
+ * the sidebar instead of the host OS. Priority -1 runs before the default-0
6
+ * deliverables entry; when nothing was produced the selector returns null
7
+ * and the original row renders unchanged.
8
+ */
9
+ import { IconCodeOutline16 } from '@deepseek-ai/dsh-client-ui-primitives'
10
+ import type { Context } from '../context-types.ts'
11
+ import { firstLeaf, revealPaths, togglePanel, type SidebarStore } from './state.ts'
12
+ import { t } from './locales.ts'
13
+ import { resolveSidebarPath, selectProducedFiles } from './produced-files.ts'
14
+ import { wrapOpenPath } from './openpath-intercept.ts'
15
+ import css from './sidebar.module.css'
16
+
17
+ /** Open a file in the sidebar's editor (used by the intercepted row and the explorer). */
18
+ export function openSidebarFile(ctx: Context, store: SidebarStore, sessionId: string, path: string): void {
19
+ const summary = ctx.sessions.list.getSnapshot().byId[sessionId]
20
+ const absolute = resolveSidebarPath(summary?.cwd, path)
21
+ const at = Math.max(absolute.lastIndexOf('/'), absolute.lastIndexOf('\\'))
22
+ const title = at === -1 ? absolute : absolute.slice(at + 1)
23
+ // Route through the sidebar service so the editor descriptor's dedupeKey
24
+ // (per-path) applies; the id is path-derived so multiple editors coexist.
25
+ ctx.get('betterSidebar')?.openTab({ type: 'editor', title, path: absolute, id: `editor:${absolute}` })
26
+ }
27
+
28
+ /**
29
+ * The produced files the turn-tail selector last matched for the visible
30
+ * session. The "Show in folder" gesture carries no file path of its own
31
+ * (`'.'`), so the reveal highlights exactly these rows when available.
32
+ */
33
+ let lastProduced: readonly string[] = []
34
+
35
+ /**
36
+ * Reveal the produced files in the sidebar explorer: expand their parent
37
+ * directories, highlight the rows, and focus the explorer tab (expanding the
38
+ * hosting panel when it is collapsed). Unknown files fall back to revealing
39
+ * the workspace root itself.
40
+ */
41
+ export function revealInExplorer(
42
+ ctx: Context,
43
+ store: SidebarStore,
44
+ sessionId: string,
45
+ files: readonly string[],
46
+ ): void {
47
+ const summary = ctx.sessions.list.getSnapshot().byId[sessionId]
48
+ const cwd = summary?.cwd
49
+ // Deliverables report paths as-is (often relative to the session cwd), but
50
+ // the explorer tree and revealPaths work on absolute paths — resolve every
51
+ // target so the ancestors expand and the row actually matches.
52
+ const targets = files.length > 0
53
+ ? files.map(path => resolveSidebarPath(cwd, path))
54
+ : cwd === undefined ? [] : [cwd]
55
+ store.reduce(state => revealPaths(state, cwd, targets))
56
+ // A type-only open never auto-expands the panel (only content opens do,
57
+ // see service.openTab) — so a reveal opens the panel itself when it is
58
+ // collapsed, or the highlight
59
+ // would be set on an invisible panel.
60
+ store.reduce(s => (s.panelOpen ? s : togglePanel(s)))
61
+ // Pin the landing to the right panel: the files window must appear where
62
+ // the panel just expanded, not in a bottom-panel pane the user last
63
+ // touched.
64
+ store.reduce(s => ({ ...s, activePane: firstLeaf(s.splits).id }))
65
+ // Focus the single-instance editor home tab (the files window) where the
66
+ // reveal highlight renders. Read via ctx.get like every other internal
67
+ // consumer (#357): the provider is not on this fiber chain, so a direct
68
+ // ctx.betterSidebar read can throw before optional chaining applies.
69
+ ctx.get('betterSidebar')?.openTab({ type: 'editor', title: t('files') })
70
+ }
71
+
72
+ /** The intercepted produced-files row (visual twin of the deliverables chips). */
73
+ export function SidebarProducedFiles(props: {
74
+ matched: readonly string[]
75
+ openInSidebar: (path: string) => void
76
+ /** Reveal the produced files in the explorer ("Show in folder" twin). */
77
+ onShowInFolder: (files: readonly string[]) => void
78
+ }) {
79
+ const { matched, openInSidebar, onShowInFolder } = props
80
+ const shown = matched.slice(0, 6)
81
+ const hidden = matched.length - shown.length
82
+ return (
83
+ <div className={css.producedRow}>
84
+ <span className={css.producedLabel}>{t('produced')}</span>
85
+ {shown.map(path => {
86
+ const at = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'))
87
+ const name = at === -1 ? path : path.slice(at + 1)
88
+ return (
89
+ <button
90
+ key={path}
91
+ type="button"
92
+ className={css.producedChip}
93
+ title={path}
94
+ onClick={() => { openInSidebar(path) }}
95
+ >
96
+ <IconCodeOutline16 size={12} />
97
+ <span>{name}</span>
98
+ </button>
99
+ )
100
+ })}
101
+ {hidden > 0 && <span className={css.producedMore}>+{hidden}</span>}
102
+ {hidden > 0 && (
103
+ <button
104
+ type="button"
105
+ className={css.producedMore}
106
+ style={{ cursor: 'pointer', textDecoration: 'underline', textUnderlineOffset: 2 }}
107
+ onClick={() => { onShowInFolder(matched) }}
108
+ >
109
+ {t('showInFolder')}
110
+ </button>
111
+ )}
112
+ </div>
113
+ )
114
+ }
115
+
116
+ /**
117
+ * Register the turn-tail interception (returns the disposer).
118
+ *
119
+ * The slot is a CHILD slot the host's ui-conversation declares in its
120
+ * `conversation.chat.node` children table (kind: chain, scope: session).
121
+ * Registering it directly races the declaration — the ui-slots core's
122
+ * load-time validation throws "not declared (a parent entry's children
123
+ * table must declare it)" when the parent entry is not on the ledger yet.
124
+ * slots.inject waits for the declaration: the callback runs synchronously
125
+ * when the slot is already declared, otherwise it runs inside the declaring
126
+ * register() call once the declaration commits; declaration collapse
127
+ * disposes the entry and a later declaration re-registers it. This mirrors
128
+ * @deepseek-ai/dsh-client-ui-deliverables' registration of the same slot.
129
+ */
130
+ export function registerTurnTailInterception(ctx: Context, store: SidebarStore): () => void {
131
+ return ctx.slots.inject('conversation.chat.turnTail', () => ctx.slots.register({
132
+ name: 'conversation.chat.turnTail',
133
+ // Decline the takeover while the editor tab type is disabled in the side
134
+ // card settings: the produced-files row falls back to the default
135
+ // deliverables behavior instead of offering chips that cannot open. Also
136
+ // while the sidebar is externally disabled (aionui-panel chosen).
137
+ select: (owner) => {
138
+ if (store.getSuspended()) return null
139
+ if (store.getPrefs().tabsEnabled['editor'] === false) return null
140
+ const matched = selectProducedFiles(owner)
141
+ if (matched !== null) lastProduced = matched
142
+ return matched
143
+ },
144
+ priority: -1,
145
+ registrant: 'dsh-better-sidebar',
146
+ inject: (sessionId: string) => ({
147
+ openInSidebar: (path: string) => { openSidebarFile(ctx, store, sessionId, path) },
148
+ onShowInFolder: (files: readonly string[]) => { revealInExplorer(ctx, store, sessionId, files) },
149
+ }),
150
+ }, SidebarProducedFiles))
151
+ }
152
+
153
+ /**
154
+ * Register the chat file-open interception: wraps `ctx.workspaces.openPath`
155
+ * — the single funnel every chat-side file open goes through (tool-row path
156
+ * links, the produced-files row, prose mentions) — so opens land in the
157
+ * sidebar editor instead of the Host OS. The folder-reveal gesture ("Show in
158
+ * folder" passes `'.'`) is the one exception: it is routed to the explorer.
159
+ * Gated by BOTH the `interceptOpenPath` pref and the editor tab's enable
160
+ * switch; declined opens fall through to the original method. Returns the
161
+ * disposer restoring the original (HMR-safe).
162
+ */
163
+ export function registerOpenPathInterception(ctx: Context, store: SidebarStore): () => void {
164
+ return wrapOpenPath(ctx.workspaces, {
165
+ takeoverEnabled: () => !store.getSuspended()
166
+ && store.getPrefs().interceptOpenPath !== false
167
+ && store.getPrefs().tabsEnabled['editor'] !== false,
168
+ currentSessionId: () => ctx.sessions.list.getSnapshot().current,
169
+ openInSidebar: (path, sessionId) => { openSidebarFile(ctx, store, sessionId, path) },
170
+ revealInExplorer: (_path, sessionId) => { revealInExplorer(ctx, store, sessionId, lastProduced) },
171
+ })
172
+ }