@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,32 @@
1
+ /**
2
+ * Package-owned invariant companion for `dsh-better-sidebar`.
3
+ * @module dsh-better-sidebar/invariant
4
+ */
5
+
6
+ /* jscpd:ignore-start */
7
+ import type { Context } from './context-types.ts'
8
+
9
+ const PACKAGE_NAME = 'dsh-better-sidebar'
10
+
11
+ /** Cordis companion plugin name. */
12
+ export const name = 'dsh-better-sidebar-invariant'
13
+ /** Service required before the companion can reserve package ownership. */
14
+ export const inject = ['invariants']
15
+
16
+ /**
17
+ * No runtime invariant: the sidebar owns no service state or event protocol
18
+ * of its own — every route is mounted under the host's webServer fence, the
19
+ * pty lifecycle is exercised by the smoke spec, and the panel's store is a
20
+ * plain snapshot registry asserted by the behavior specs. The route fence,
21
+ * pty quota, and store semantics are each observed through their seams.
22
+ */
23
+ const install: () => void = () => {}
24
+
25
+ /**
26
+ * Register this package's invariant companion.
27
+ * @param ctx - Cordis context carrying the invariant service.
28
+ * @returns the installed registration's disposer after setup succeeds.
29
+ */
30
+ export const apply = (ctx: Context): Promise<() => void> =>
31
+ Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install))
32
+ /* jscpd:ignore-end */
@@ -0,0 +1,26 @@
1
+ import { readFile } from 'node:fs/promises'
2
+ import { dirname, join } from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ import type { Context, SidebarHttpRequest, SidebarHttpResponse } from './context-types.ts'
5
+
6
+ const assetPath = join(dirname(fileURLToPath(import.meta.url)), '..', 'assets', 'logo.svg')
7
+
8
+ function page(logo: string): string {
9
+ return `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>请登录 ChatCode</title><style>
10
+ *{box-sizing:border-box}body{margin:0;min-width:320px;min-height:100vh;display:grid;place-items:center;font-family:Inter,"Microsoft YaHei",system-ui,sans-serif;color:#172b4d;background:radial-gradient(circle at 18% 18%,#e5edff 0,transparent 30rem),linear-gradient(135deg,#edf2fb,#dfe8f7)}.card{width:min(490px,calc(100vw - 40px));padding:34px 44px 36px;border:1px solid #dce4ef;border-radius:18px;background:#fff;box-shadow:0 20px 48px rgba(36,62,103,.18),0 2px 8px rgba(36,62,103,.06)}.brand{display:flex;align-items:center;justify-content:center;gap:12px;padding-bottom:27px;border-bottom:1px solid #edf0f5}.brand img{width:34px;height:34px}.brand strong{font-size:22px;letter-spacing:-.02em}.content{padding-top:40px;text-align:center}.content h1{margin:0;font-size:28px;line-height:1.3;font-weight:650}.content p{min-height:24px;margin:12px 0 30px;color:#71809a;font-size:14px;line-height:1.7}.action{width:100%;min-height:46px;border:0;border-radius:8px;background:#9B0B09;color:#fff;font:600 15px/1 Inter,"Microsoft YaHei",sans-serif;cursor:pointer;box-shadow:0 8px 18px rgba(155,11,9,.25);transition:transform .18s ease,box-shadow .18s ease,background .18s ease}.action:hover:not(:disabled){transform:translateY(-2px);background:#b20d0a;box-shadow:0 12px 24px rgba(155,11,9,.38)}.action:focus-visible{outline:3px solid #f0b7b5;outline-offset:3px}.action:disabled{cursor:default;opacity:.7}.error{color:#b42318}@media(max-width:480px){.card{padding:30px 26px}.content{padding-top:32px}}
11
+ </style></head><body><main class="card"><div class="brand"><img src="data:image/svg+xml;base64,${logo}" alt=""><strong>ChatCode</strong></div><section class="content"><h1>请登录 ChatCode</h1><p id="message">请登录后继续使用。</p><button id="login" class="action" type="button">去登录</button></section></main><script>
12
+ const message=document.getElementById('message'),button=document.getElementById('login');const current=new URL(location.href);const returnTo=(()=>{const v=current.searchParams.get('return');return v&&v.startsWith('/')&&!v.startsWith('//')?v:'/'} )();const call=async(name,body={})=>{const r=await fetch('/sidebar/api/'+name,{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify(body)});const d=await r.json();if(!r.ok||!d.ok)throw new Error(d.error?.message||'操作失败');return d.value};let waiting=false;async function status(){try{const s=await call('chatcode.status');if(s.available&&s.validation==='valid'&&!s.expired){location.replace(returnTo);return}if(waiting)message.textContent='正在等待浏览器完成登录…'}catch{message.textContent='无法读取登录状态,请稍后重试。';message.classList.add('error')}}async function login(){const tab=window.open('','_blank');button.disabled=true;button.textContent='正在打开登录页…';message.classList.remove('error');try{const s=await call('chatcode.start');waiting=true;if(tab&&!tab.closed){tab.opener=null;tab.location.href=s.url;message.textContent='请在新打开的浏览器窗口完成登录…'}else{message.innerHTML='浏览器拦截了登录窗口,请 <a href="'+s.url+'" target="_blank" rel="noopener noreferrer">打开登录页</a> 后完成登录。'}}catch{try{tab&&tab.close()}catch{}button.disabled=false;button.textContent='去登录';message.textContent='登录页打开失败,请重试。';message.classList.add('error')}}button.addEventListener('click',login);status();setInterval(status,2000);
13
+ </script></body></html>`
14
+ }
15
+
16
+ export function registerLoginRoute(ctx: Context, fence: (req: SidebarHttpRequest) => boolean): () => void {
17
+ return ctx.webServer.register({ kind: 'exact', path: '/sidebar/login', handler: async (req, res) => {
18
+ if (!fence(req)) { res.writeHead(403); res.end('forbidden'); return }
19
+ if (req.method !== 'GET' && req.method !== 'HEAD') { res.writeHead(405); res.end(); return }
20
+ try {
21
+ const logo = (await readFile(assetPath)).toString('base64')
22
+ res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store', 'content-security-policy': "default-src 'self'; img-src 'self' data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'" })
23
+ res.end(req.method === 'HEAD' ? undefined : page(logo))
24
+ } catch { res.writeHead(500); res.end('login page unavailable') }
25
+ } })
26
+ }
@@ -0,0 +1,90 @@
1
+ /**
2
+ * External open actions for the file tree's "open with" menu: hand a path to
3
+ * the OS file manager (reveal/select) or launch a URL scheme's registered
4
+ * handler (vscode://, cursor://, zed://, custom schemes).
5
+ *
6
+ * The client runs in a browser / DSH Desktop renderer where a raw `vscode://`
7
+ * navigation is unreliable, so both actions fan out through this host route
8
+ * and spawn the platform opener with an argv array (no shell interpolation).
9
+ * The command builders are pure — the platform is injectable — so every
10
+ * per-platform branch is unit-testable without spawning anything.
11
+ */
12
+ import { spawn } from 'node:child_process'
13
+ import { parentOf, requireAbsolute } from './fs-tree.ts'
14
+ import { SidebarError } from './wire.ts'
15
+
16
+ /** The two external open actions the route accepts. */
17
+ export type OpenExternalAction = 'reveal' | 'url'
18
+
19
+ /** One platform opener invocation (argv array — never a shell string). */
20
+ export interface ExternalCommand {
21
+ command: string
22
+ args: string[]
23
+ }
24
+
25
+ /** Reveal/select a path in the OS file manager. On Linux there is no common
26
+ * select protocol — the containing directory is opened instead (KISS). */
27
+ export function revealCommand(path: string, platform: NodeJS.Platform = process.platform): ExternalCommand {
28
+ switch (platform) {
29
+ case 'darwin':
30
+ return { command: 'open', args: ['-R', path] }
31
+ // `explorer /select,<path>` — the classic "show in folder" incantation.
32
+ // Passed as separate argv entries (no shell); verify on Windows, with
33
+ // `cmd /c start "" explorer.exe "/select,<path>"` as the fallback.
34
+ case 'win32':
35
+ return { command: 'explorer.exe', args: ['/select,', path] }
36
+ default: {
37
+ const parent = parentOf(path)
38
+ return { command: 'xdg-open', args: [parent ?? path] }
39
+ }
40
+ }
41
+ }
42
+
43
+ /** Hand a custom-scheme URL to the OS protocol handler. */
44
+ export function urlCommand(url: string, platform: NodeJS.Platform = process.platform): ExternalCommand {
45
+ switch (platform) {
46
+ case 'darwin':
47
+ return { command: 'open', args: [url] }
48
+ // url.dll,FileProtocolHandler launches the registered protocol handler;
49
+ // `cmd /c start "" <url>` is the fallback if rundll32 misbehaves.
50
+ case 'win32':
51
+ return { command: 'rundll32.exe', args: ['url.dll,FileProtocolHandler', url] }
52
+ default:
53
+ return { command: 'xdg-open', args: [url] }
54
+ }
55
+ }
56
+
57
+ /** Validate a URL-scheme open target: a parseable custom-scheme URL (never
58
+ * http/https — those would only dump the URL into a browser tab). */
59
+ export function validateExternalUrl(raw: string): string {
60
+ if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(raw)) {
61
+ throw new SidebarError('bad-request', 'url must be a custom-scheme URL')
62
+ }
63
+ let url: URL
64
+ try {
65
+ url = new URL(raw)
66
+ } catch {
67
+ throw new SidebarError('bad-request', 'invalid url')
68
+ }
69
+ if (url.protocol === 'http:' || url.protocol === 'https:') {
70
+ throw new SidebarError('bad-request', 'only custom-scheme urls can be opened externally')
71
+ }
72
+ return raw
73
+ }
74
+
75
+ /**
76
+ * Launch one external open action and return immediately (detached, no
77
+ * stdio). Spawn failures are reported through the child's 'error' event —
78
+ * by then the route already returned, so the event is swallowed (the OS
79
+ * dialog about a missing handler is the user-visible outcome either way).
80
+ */
81
+ export function launchExternal(action: OpenExternalAction, value: string): { started: true } {
82
+ const platform = process.platform
83
+ const spec = action === 'reveal'
84
+ ? revealCommand(requireAbsolute(value), platform)
85
+ : urlCommand(validateExternalUrl(value), platform)
86
+ const child = spawn(spec.command, spec.args, { detached: true, stdio: 'ignore' })
87
+ child.on('error', () => { /* opener missing/denied: handled by the OS */ })
88
+ child.unref()
89
+ return { started: true }
90
+ }
@@ -0,0 +1,81 @@
1
+ /** Filesystem path guards shared by sidebar APIs that access a session workspace. */
2
+ import { realpath } from 'node:fs/promises'
3
+ import { basename, dirname, join } from 'node:path'
4
+ import { isWithin, requireAbsolute } from './fs-tree.ts'
5
+ import { SidebarError } from './wire.ts'
6
+
7
+ /** Resolve a path and convert filesystem resolution failures to an API error. */
8
+ async function resolveRealPath(path: string, label: string): Promise<string> {
9
+ try {
10
+ return await realpath(path)
11
+ } catch (error) {
12
+ throw new SidebarError('fs-error', `cannot resolve ${label} "${path}": ${error instanceof Error ? error.message : String(error)}`, 400)
13
+ }
14
+ }
15
+
16
+ /** Reject a resolved path whose real filesystem target escapes the workspace. */
17
+ function assertWithinWorkspace(workspace: string, target: string): void {
18
+ if (!isWithin(workspace, target)) {
19
+ throw new SidebarError('forbidden', `path "${target}" is outside workspace`, 403)
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Resolve an existing workspace path through symlinks and (unless disarmed)
25
+ * enforce containment.
26
+ *
27
+ * @param cwd - Session workspace directory.
28
+ * @param target - Client-supplied absolute path.
29
+ * @param fence - Whether containment is enforced (the settings-page
30
+ * `workspaceFence` switch). Even when false the paths are still resolved
31
+ * through symlinks so callers always receive the canonical target.
32
+ * @returns The canonical absolute path used for the filesystem operation.
33
+ */
34
+ export async function ensureWorkspacePath(cwd: string, target: string, fence = true): Promise<string> {
35
+ const absolute = requireAbsolute(target)
36
+ const [realCwd, realTarget] = await Promise.all([
37
+ resolveRealPath(cwd, 'workspace'),
38
+ resolveRealPath(absolute, 'target'),
39
+ ])
40
+ if (fence) assertWithinWorkspace(realCwd, realTarget)
41
+ return realTarget
42
+ }
43
+
44
+ /**
45
+ * Validate a write destination, including destinations that do not exist yet.
46
+ * Existing targets are resolved to catch symlinks; missing targets are checked
47
+ * against the nearest existing ancestor before the caller creates or renames.
48
+ * The returned path is rebuilt from that canonical ancestor, so an existing
49
+ * symlink is never left in the path passed to the write operation.
50
+ *
51
+ * @param cwd - Session workspace directory.
52
+ * @param target - Client-supplied absolute destination path.
53
+ * @param fence - Whether containment is enforced (the settings-page
54
+ * `workspaceFence` switch). Resolution/canonicalization is identical either way.
55
+ * @returns A canonical path for an existing target or its nearest existing ancestor.
56
+ */
57
+ export async function ensureWorkspaceWritePath(cwd: string, target: string, fence = true): Promise<string> {
58
+ const absolute = requireAbsolute(target)
59
+ const realCwd = await resolveRealPath(cwd, 'workspace')
60
+ let existingPath = absolute
61
+ const missingSegments: string[] = []
62
+
63
+ for (;;) {
64
+ try {
65
+ const realTarget = await realpath(existingPath)
66
+ if (fence) assertWithinWorkspace(realCwd, realTarget)
67
+ return missingSegments.reduce((path, segment) => join(path, segment), realTarget)
68
+ } catch (error) {
69
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
70
+ if (error instanceof SidebarError) throw error
71
+ throw new SidebarError('fs-error', `cannot resolve target "${existingPath}": ${error instanceof Error ? error.message : String(error)}`, 400)
72
+ }
73
+ const parent = dirname(existingPath)
74
+ if (parent === existingPath) {
75
+ throw new SidebarError('fs-error', `cannot resolve target "${absolute}"`, 400)
76
+ }
77
+ missingSegments.unshift(basename(existingPath))
78
+ existingPath = parent
79
+ }
80
+ }
81
+ }
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Shared "Side card" preference vocabulary (types + constants), consumed by
3
+ * BOTH halves: the host registers the schemastery schema over these values
4
+ * (config.ts) and the client reads/writes them through the settings RPC
5
+ * (client/prefs.ts, client/SideCardSection.tsx). Kept free of schemastery so
6
+ * the browser bundle never pulls the schema runtime in.
7
+ */
8
+
9
+ /** The user-settings namespace holding the side card preferences. */
10
+ export const SIDEBAR_PREFS_NS = 'dsh-better-sidebar'
11
+
12
+ /** User-facing side card preferences (new-conversation defaults). */
13
+ export interface SidebarPrefs {
14
+ /** Whether a brand-new conversation opens the side card by default. */
15
+ openByDefault: boolean
16
+ /** Default panel width as a percent of the window width (20–60). */
17
+ defaultWidthPercent: number
18
+ /**
19
+ * Whether the sidebar auto-activates (opens the panel) and expands the
20
+ * Subagent page when the current conversation spawns a new subagent.
21
+ */
22
+ autoOpenSubagent: boolean
23
+ /**
24
+ * Whether the sidebar auto-activates (opens the panel) and expands the
25
+ * Jobs page when a NEW background job appears for the current
26
+ * conversation (any new job id, not just the first one).
27
+ */
28
+ autoOpenJobs: boolean
29
+ /**
30
+ * Whether the model-facing agent terminal tools (terminal_create / list /
31
+ * send / read / wait_for / resize / signal / close) are injected into the
32
+ * model's toolset. Off by default: the feature stays dormant until the
33
+ * user explicitly enables it in the side card settings.
34
+ */
35
+ agentTerminalTools: boolean
36
+ /**
37
+ * Whether the model-facing `sidebar_open` tool is injected into the
38
+ * model's toolset — one tool that lets the model actively open a local
39
+ * file, a local folder (as a tree rooted there), or an HTTP(S) page in
40
+ * the calling session's sidebar. Off by default: the feature stays
41
+ * dormant until the user explicitly enables it in the side card settings.
42
+ */
43
+ agentOpenTools: boolean
44
+ /**
45
+ * Custom terminal font-family stack (a CSS font-family value, e.g.
46
+ * `'JetBrains Mono', monospace`). Empty string follows the app's theme
47
+ * monospace font (`--ds-font-family-code`). Applied live to every
48
+ * terminal tab; configured under the terminal card's secondary settings.
49
+ */
50
+ terminalFontFamily: string
51
+ /**
52
+ * Custom terminal font size in px (9–32). Applied live to every terminal
53
+ * tab; configured under the terminal card's secondary settings.
54
+ */
55
+ terminalFontSize: number
56
+ /**
57
+ * Whether expanding the bottom panel for the FIRST time in a session tries
58
+ * to open a fresh terminal tab there (the terminal quota/type still gates
59
+ * the attempt). On by default; the switch lives under the terminal tab's
60
+ * row in the Side card settings.
61
+ */
62
+ bottomPanelAutoTerminal: boolean
63
+ /**
64
+ * Whether chat-side file opens (tool-row path links, the produced-files
65
+ * row, prose file mentions — every path that funnels through the client
66
+ * runtime's `ctx.workspaces.openPath`) open in the sidebar editor instead
67
+ * of the Host OS's default application. On by default; the editor tab's
68
+ * own enable switch gates it too (both must be on for the takeover).
69
+ */
70
+ interceptOpenPath: boolean
71
+ /**
72
+ * Whether the editor tab runs in merged mode: a path input replaces the
73
+ * plain header and a toggleable file-tree panel (with a global name
74
+ * search) docks at the tab's right edge. On by default; also makes brand
75
+ * new sessions seed an empty editor tab (tree panel open) instead of the
76
+ * explorer tab. The switch lives under the editor card's gear in the
77
+ * Side card settings; off restores the pre-merge editor exactly.
78
+ */
79
+ editorExplorer: boolean
80
+ /**
81
+ * Whether the sidebar's filesystem routes enforce the workspace fence:
82
+ * every client-supplied path must resolve (through symlinks) inside the
83
+ * session workspace, else the route answers 403 "outside workspace". On
84
+ * by default; turning it OFF lets the file tree / editor read+write /
85
+ * media / HTML preview / upload routes reach ANY host path (e.g. the
86
+ * global ~/.chatcode-cli/AGENTS.md or a linked worktree outside the session cwd)
87
+ * — the trade-off being that any same-origin script (including
88
+ * third-party consumer plugins) can read/write outside the workspace
89
+ * through those routes while it is off. The switch lives under the files
90
+ * tab's gear in the Side card settings; the fence error surfaces offer a
91
+ * one-click global off + retry.
92
+ */
93
+ workspaceFence: boolean
94
+ /**
95
+ * The shell the UI and agent terminals spawn (absolute path or bare
96
+ * executable name). Empty (default) keeps the legacy resolution order:
97
+ * `cordis.patch.yml` `config.shell`, then `$SHELL` / login shell /
98
+ * `powershell.exe` on Windows. Set it from the terminal card's gear in
99
+ * the Side card settings (or the yaml) to pin a specific shell — takes
100
+ * effect for terminals opened afterwards.
101
+ */
102
+ terminalShell: string
103
+ /**
104
+ * Explicit arguments for `terminalShell`, space-separated (empty keeps
105
+ * the platform defaults; when set, they fully replace them — same
106
+ * contract as the yaml `shellArgs`).
107
+ */
108
+ terminalShellArgs: string
109
+ /**
110
+ * Title-bar / shell compatibility scheme (the "位置兼容模式" setting):
111
+ * - `auto` (default): CONSERVATIVE — only the standard Window Controls
112
+ * Overlay API (present in frameless Chromium shells that draw the
113
+ * native caption buttons over web content) contributes real geometry;
114
+ * without it nothing is modified, so plain-browser (web) behavior is
115
+ * untouched.
116
+ * - `web`: EXPLICIT "ChatCode CLI Web" — never adapt, not even WCO
117
+ * geometry (the user declares they run the plain web UI).
118
+ * - `preset`: apply the built-in shell preset named by
119
+ * `titleBarPresetId` (data-driven, opt-in — see shell-presets.ts).
120
+ * - `custom`: apply the free-form `customCss` (and the legacy
121
+ * `titleBarStripPx` strip).
122
+ */
123
+ titleBarScheme: TitleBarScheme
124
+ /**
125
+ * The built-in shell preset id applied while `titleBarScheme` is
126
+ * `preset` ('' = no preset — nothing extra is applied).
127
+ */
128
+ titleBarPresetId: string
129
+ /**
130
+ * Free-form CSS injected into the page (last in the cascade, so it can
131
+ * override the plugin's styles; use `!important` to override JS-written
132
+ * inline CSS variables). Applied while `titleBarScheme` is `custom`.
133
+ */
134
+ customCss: string
135
+ /**
136
+ * LEGACY (kept for read-migration and downgrade mirroring only): position
137
+ * compatibility mode flag. The UI writes `titleBarScheme` instead; a
138
+ * stored `true` without a scheme migrates to the `custom` scheme (with
139
+ * `titleBarStripPx` preserved).
140
+ */
141
+ titleBarCompat: boolean
142
+ /**
143
+ * LEGACY (kept for read-migration and downgrade mirroring only): the
144
+ * reserved top strip height in px used by the `custom` scheme (0–120,
145
+ * default 40). Drives the `--dsh-title-bar-strip` CSS variable: the
146
+ * toggle cluster drops `strip + 3px` and the right panel's content
147
+ * starts `strip` px below its top edge.
148
+ */
149
+ titleBarStripPx: number
150
+ /**
151
+ * Whether the HTML previewer drops its sandboxed iframe. Sandbox ON (the
152
+ * default) renders previewed HTML in an opaque-origin iframe that cannot
153
+ * touch the GUI; turning it OFF runs the previewed page with the GUI's
154
+ * own origin — full read/write access to session files and internal
155
+ * APIs. Only for trusted local content; the setting copy warns.
156
+ */
157
+ htmlViewerNoSandbox: boolean
158
+ /**
159
+ * Whether a newly opened HTML preview starts UNSANDBOXED (the per-surface
160
+ * temporary unlock pre-applied). Off by default: previews open sandboxed
161
+ * and the status row offers the one-tap unlock; when on, previews open
162
+ * in the red unsandboxed state and the status row offers a one-tap
163
+ * restore for the current file.
164
+ */
165
+ htmlViewerDefaultUnsafe: boolean
166
+ /**
167
+ * Whether the browser tab drops its sandboxed iframe. Sandbox ON (the
168
+ * default) keeps browsed sites in an opaque origin with no GUI access;
169
+ * turning it OFF runs any visited site with the GUI's own origin — it
170
+ * can read session data and act as the logged-in GUI. Only for trusted
171
+ * sites; the setting copy warns.
172
+ */
173
+ browserNoSandbox: boolean
174
+ /**
175
+ * MASTER switch: whether clicking an EXTERNAL link in the GUI (chat
176
+ * messages, tool rows, prose mentions) is taken over into the sidebar at
177
+ * all. On by default; the per-protocol granularity lives in
178
+ * `browserInterceptHttp` / `browserInterceptHttps` (the protocol flag
179
+ * must also be on), and the target tab's own enable switch gates it too.
180
+ * Ctrl/Cmd+click always bypasses the takeover. Kept as the master so old
181
+ * documents keep their meaning with no migration (an explicit `false`
182
+ * stays "never take over").
183
+ */
184
+ browserInterceptLinks: boolean
185
+ /**
186
+ * Whether clicking an http EXTERNAL link in the GUI opens the sidebar
187
+ * (the built-in browser tab, or a plugin tab that declares `urlTarget`)
188
+ * instead of a new browser tab. On by default; gated on the
189
+ * `browserInterceptLinks` master and the target tab's own enable switch.
190
+ */
191
+ browserInterceptHttp: boolean
192
+ /**
193
+ * Whether clicking an https EXTERNAL link in the GUI opens the sidebar
194
+ * instead of a new browser tab. OFF by default — most https sites (e.g.
195
+ * GitHub) refuse iframe embedding, so the system browser is the smoother
196
+ * default; gated on the `browserInterceptLinks` master and the target
197
+ * tab's own enable switch.
198
+ */
199
+ browserInterceptHttps: boolean
200
+ /**
201
+ * Comma-separated allowlist of local (loopback) authorities the browser
202
+ * tab may navigate to — `localhost`, `127.0.0.1`, `127.0.0.1:5174`, or
203
+ * host:port pairs. Empty by default: loopback addresses stay blocked so a
204
+ * browsed page cannot probe local services. Each entry is either a bare
205
+ * hostname (all ports) or host:port; the GUI's own origin is always
206
+ * allowed regardless. The iframe sandbox still renders allowed local
207
+ * pages in an opaque origin, exactly like any other site.
208
+ */
209
+ browserAllowedLoopback: string
210
+ /**
211
+ * Per-tab enable switches, keyed by tab descriptor id (`'explorer'`,
212
+ * `'my-plugin:db'`). An ABSENT key means enabled — only an explicit
213
+ * `false` disables a tab type (hidden from the + menu, `openTab` refuses,
214
+ * and derived flows like subagent auto-open / agent-terminal tabs stop).
215
+ * Already-open tabs of a disabled type keep rendering (closing one
216
+ * prevents reopening), matching the "existing conversations keep their
217
+ * own layouts" rule.
218
+ */
219
+ tabsEnabled: Record<string, boolean>
220
+ /**
221
+ * Per-viewer enable switches, keyed by file viewer descriptor id
222
+ * (`'image'`, `'my-plugin:csv'`). An ABSENT key means enabled; a disabled
223
+ * viewer is skipped by `matchFileViewer` so files fall through to the
224
+ * next matching viewer (or the download button when none match).
225
+ */
226
+ viewersEnabled: Record<string, boolean>
227
+ /**
228
+ * Plugin-owned settings blobs (v0.12.0+), keyed by descriptor id: each
229
+ * registered tab/viewer that declares `settings.pluginToggles` (or writes
230
+ * through `settings.render`'s `updatePluginSetting`) persists its values
231
+ * here — an open map, so third-party keys need no host PrefsSchema field.
232
+ * Values are JSON-serializable (the row controls produce strings /
233
+ * numbers / booleans; custom panels are responsible for their own).
234
+ */
235
+ pluginSettings: Record<string, Record<string, unknown>>
236
+ }
237
+
238
+ /** Range contract of {@link SidebarPrefs.defaultWidthPercent}. */
239
+ export const WIDTH_PERCENT_MIN = 20
240
+ export const WIDTH_PERCENT_MAX = 60
241
+ export const WIDTH_PERCENT_DEFAULT = 35
242
+
243
+ /** Range contract of {@link SidebarPrefs.terminalFontSize}. */
244
+ export const TERMINAL_FONT_SIZE_MIN = 9
245
+ export const TERMINAL_FONT_SIZE_MAX = 32
246
+ export const TERMINAL_FONT_SIZE_DEFAULT = 13
247
+
248
+ /** Range contract of {@link SidebarPrefs.titleBarStripPx}. */
249
+ export const TITLE_BAR_STRIP_MIN = 0
250
+ export const TITLE_BAR_STRIP_MAX = 120
251
+ export const TITLE_BAR_STRIP_DEFAULT = 40
252
+
253
+ /** The title-bar / shell compatibility schemes (see {@link SidebarPrefs.titleBarScheme}). */
254
+ export const TITLE_BAR_SCHEMES = ['auto', 'web', 'preset', 'custom'] as const
255
+ export type TitleBarScheme = typeof TITLE_BAR_SCHEMES[number]
256
+
257
+ /** Fallback prefs used whenever the settings document is unreachable or malformed. */
258
+ export const SIDEBAR_PREFS_DEFAULTS: SidebarPrefs = {
259
+ openByDefault: false,
260
+ defaultWidthPercent: WIDTH_PERCENT_DEFAULT,
261
+ autoOpenSubagent: true,
262
+ autoOpenJobs: true,
263
+ agentTerminalTools: false,
264
+ agentOpenTools: false,
265
+ bottomPanelAutoTerminal: true,
266
+ terminalFontFamily: '',
267
+ terminalFontSize: TERMINAL_FONT_SIZE_DEFAULT,
268
+ interceptOpenPath: true,
269
+ editorExplorer: false,
270
+ workspaceFence: true,
271
+ terminalShell: '',
272
+ terminalShellArgs: '',
273
+ titleBarScheme: 'auto',
274
+ titleBarPresetId: '',
275
+ customCss: '',
276
+ titleBarCompat: false,
277
+ titleBarStripPx: TITLE_BAR_STRIP_DEFAULT,
278
+ htmlViewerNoSandbox: false,
279
+ htmlViewerDefaultUnsafe: false,
280
+ browserNoSandbox: false,
281
+ browserInterceptLinks: true,
282
+ browserInterceptHttp: true,
283
+ browserInterceptHttps: false,
284
+ browserAllowedLoopback: '',
285
+ tabsEnabled: {},
286
+ viewersEnabled: {},
287
+ pluginSettings: {},
288
+ }
289
+
290
+ /** Clamp one width percent into the contract range (shared by schema and client reads). */
291
+ export function clampWidthPercent(value: number): number {
292
+ return Math.min(WIDTH_PERCENT_MAX, Math.max(WIDTH_PERCENT_MIN, Math.round(value)))
293
+ }
294
+
295
+ /** Clamp one terminal font size into the contract range (shared by schema and client reads). */
296
+ export function clampTerminalFontSize(value: number): number {
297
+ return Math.min(TERMINAL_FONT_SIZE_MAX, Math.max(TERMINAL_FONT_SIZE_MIN, Math.round(value)))
298
+ }
299
+
300
+ /** Clamp one title-bar strip height into the contract range (shared by schema and client reads). */
301
+ export function clampTitleBarStrip(value: number): number {
302
+ return Math.min(TITLE_BAR_STRIP_MAX, Math.max(TITLE_BAR_STRIP_MIN, Math.round(value)))
303
+ }