@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,193 @@
1
+ /**
2
+ * ChatCode self-update for the account menu's "check for updates / update now"
3
+ * action. Mirrors the semantics of the upstream `yuanjing-wanma-cli /update`
4
+ * flow (query the npm registry for the latest version, compare against the
5
+ * globally installed version, then `npm install -f -g <pkg>@<latest>`), applied
6
+ * to the CLI package this sidebar is responsible for.
7
+ *
8
+ * All subprocess work goes through `execFile` with a plain argv array (no
9
+ * shell), the same posture the rest of the host uses. The npm registry (and
10
+ * the global install target) is the machine running the DSH host, not the
11
+ * browser.
12
+ */
13
+ import { execFile } from 'node:child_process'
14
+ import { readFile } from 'node:fs/promises'
15
+ import { homedir } from 'node:os'
16
+ import { join } from 'node:path'
17
+ import { SidebarError } from './wire.ts'
18
+
19
+ /** CLI package the sidebar keeps up to date. */
20
+ export const CHATCODE_CLI_PKG = '@chatcode/chatcode-cli'
21
+ /** The single package a "one-click update" upgrades. */
22
+ export const CHATCODE_UPDATE_PACKAGES = [CHATCODE_CLI_PKG] as const
23
+
24
+ /** npm registry used for both the version lookup and the global install. */
25
+ export const NPM_REGISTRY = 'https://registry.npmjs.org'
26
+
27
+ /** One package's version-check result. */
28
+ export interface ChatCodeUpdateCheck {
29
+ packageName: string
30
+ /** Globally installed version (null when not globally present). */
31
+ current: string | null
32
+ /** Latest version on npm (null when the lookup failed). */
33
+ latest: string | null
34
+ needsUpdate: boolean
35
+ }
36
+
37
+ /** One package's perform-update result. */
38
+ export interface ChatCodeUpdateResult {
39
+ packageName: string
40
+ from: string | null
41
+ to: string | null
42
+ ok: boolean
43
+ /** Human message; on failure it carries the pasteable manual command. */
44
+ message: string
45
+ }
46
+
47
+ interface RunResult {
48
+ code: number
49
+ stdout: string
50
+ stderr: string
51
+ }
52
+
53
+ /** Windows ships npm as a `npm.cmd` shim, which `execFile` cannot spawn
54
+ * directly (EINVAL) — it must run through the system shell. We keep args as an
55
+ * array so Node quotes each one (no string interpolation / injection), and
56
+ * stay shell-free on POSIX, matching the repo's git runner. */
57
+ function npmSpawnArgs(args: readonly string[]): { file: string; args: readonly string[] } {
58
+ if (process.platform === 'win32') {
59
+ // `cmd.exe /d /s /c` — the `/s` + array form lets Node do the argument
60
+ // escaping so `npm …` is invoked as a single anchored command.
61
+ return { file: process.env.comspec ?? 'cmd.exe', args: ['/d', '/s', '/c', 'npm', ...args] }
62
+ }
63
+ return { file: 'npm', args }
64
+ }
65
+
66
+ /** npm command runner (bounded timeout). Errors are returned, not thrown, so
67
+ * callers can distinguish "npm exited non-zero" from npm missing. */
68
+ function runNpm(args: readonly string[], cwd: string, timeoutMs: number): Promise<RunResult> {
69
+ return new Promise((resolvePromise) => {
70
+ const spawnSpec = npmSpawnArgs(args)
71
+ const timer = setTimeout(() => {
72
+ child.kill('SIGTERM')
73
+ }, timeoutMs)
74
+ const child = execFile(
75
+ spawnSpec.file,
76
+ [...spawnSpec.args],
77
+ { cwd, encoding: 'utf8', windowsHide: true, maxBuffer: 32 * 1024 * 1024 },
78
+ (error, stdout, stderr) => {
79
+ clearTimeout(timer)
80
+ if (error !== null) {
81
+ const raw = (error as NodeJS.ErrnoException).code
82
+ resolvePromise({ code: typeof raw === 'number' ? raw : 1, stdout, stderr })
83
+ return
84
+ }
85
+ resolvePromise({ code: 0, stdout, stderr })
86
+ },
87
+ )
88
+ })
89
+ }
90
+
91
+ /**
92
+ * Read the globally installed version of one package. Resolves the npm global
93
+ * modules dir via `npm root -g` and reads `<dir>/<pkg>/package.json` directly —
94
+ * robust against `npm ls --json` packaging quirks. Returns the version string,
95
+ * or null when the package is not globally installed.
96
+ */
97
+ export async function installedGlobalVersion(pkg: string): Promise<string | null> {
98
+ const { code, stdout } = await runNpm(['root', '-g'], homedir(), 30_000)
99
+ const root = stdout.trim()
100
+ if (code !== 0 || root === '') return null
101
+ const packageJsonPath = pkg.startsWith('@')
102
+ ? join(root, pkg, 'package.json')
103
+ : join(root, join(...pkg.split('/')), 'package.json')
104
+ try {
105
+ const raw = await readFile(packageJsonPath, 'utf8')
106
+ const parsed: unknown = JSON.parse(raw)
107
+ const record = parsed !== null && typeof parsed === 'object' ? parsed as Record<string, unknown> : null
108
+ const version = record?.version
109
+ return typeof version === 'string' && version !== '' ? version : null
110
+ } catch {
111
+ return null
112
+ }
113
+ }
114
+
115
+ /** Query npm for the latest published version of one package. Throws a
116
+ * SidebarError on any network / shape failure (mirrors chatcodeReleaseNote). */
117
+ export async function npmLatestVersion(pkg: string): Promise<string> {
118
+ // `encodeURI` (not `encodeURIComponent`) keeps scoped packages' leading `@`
119
+ // and `/` intact (`@scope/name`), which the npm registry requires verbatim.
120
+ const url = `https://registry.npmjs.org/${encodeURI(pkg)}/latest`
121
+ let response: Response
122
+ try {
123
+ response = await fetch(url, { signal: AbortSignal.timeout(10_000) })
124
+ } catch {
125
+ throw new SidebarError('chatcode-update-fetch', `the npm registry is unreachable for ${pkg}`, 502)
126
+ }
127
+ if (!response.ok) {
128
+ throw new SidebarError('chatcode-update-fetch', `the npm registry returned HTTP ${response.status} for ${pkg}`, 502)
129
+ }
130
+ const parsed: unknown = await response.json().catch(() => null)
131
+ const record = parsed !== null && typeof parsed === 'object' ? parsed as Record<string, unknown> : null
132
+ const version = record?.version
133
+ if (typeof version !== 'string' || version === '') {
134
+ throw new SidebarError('chatcode-update-fetch', `the npm registry returned an invalid response for ${pkg}`, 502)
135
+ }
136
+ return version
137
+ }
138
+
139
+ /** One package's version check (the pure comparison extracted for tests). */
140
+ export function chatcodeNeedsUpdate(current: string | null, latest: string | null): boolean {
141
+ // Unknown current version counts as needing an update; equality is up-to-date.
142
+ return current === null ? latest !== null : latest !== null && latest !== current
143
+ }
144
+
145
+ /** Check one package against npm. A registry failure surfaces as a thrown
146
+ * SidebarError; a missing local install degrades to `current: null`. */
147
+ export async function checkOneUpdate(pkg: string): Promise<ChatCodeUpdateCheck> {
148
+ const [current, latest] = await Promise.all([installedGlobalVersion(pkg), npmLatestVersion(pkg)])
149
+ return { packageName: pkg, current, latest, needsUpdate: chatcodeNeedsUpdate(current, latest) }
150
+ }
151
+
152
+ /** The npm registry flag appended to every global install (mirrors upstream). */
153
+ function installArgs(pkg: string, version: string): string[] {
154
+ return ['install', '-f', '-g', `${pkg}@${version}`, '--registry', NPM_REGISTRY]
155
+ }
156
+
157
+ /** The pasteable manual fallback shown when a global install fails. */
158
+ export function manualInstallCommand(pkg: string, version: string): string {
159
+ return `npm i -f -g ${pkg}@${version} --registry=${NPM_REGISTRY}`
160
+ }
161
+
162
+ /** Install one package at an exact latest version. Never throws on npm
163
+ * failure — the failure is encoded in the result with its manual command. */
164
+ export async function performOneUpdate(pkg: string, from: string | null, latest: string): Promise<ChatCodeUpdateResult> {
165
+ const { code, stderr, stdout } = await runNpm(installArgs(pkg, latest), homedir(), 120_000)
166
+ if (code !== 0) {
167
+ const detail = (stderr || stdout || 'unknown npm error').trim()
168
+ const message = `update failed: ${detail}\n${manualInstallCommand(pkg, latest)}`
169
+ return { packageName: pkg, from, to: latest, ok: false, message }
170
+ }
171
+ return { packageName: pkg, from, to: latest, ok: true, message: `${from ?? 'none'} -> ${latest}` }
172
+ }
173
+
174
+ /** Aggregate update-result shape returned by the `chatcode.update.perform`
175
+ * route (one entry per package, in update order). */
176
+ export async function performAllUpdates(checks: ChatCodeUpdateCheck[]): Promise<ChatCodeUpdateResult[]> {
177
+ const results: ChatCodeUpdateResult[] = []
178
+ for (const check of checks) {
179
+ const target = check.latest
180
+ if (target === null) {
181
+ results.push({
182
+ packageName: check.packageName,
183
+ from: check.current,
184
+ to: null,
185
+ ok: false,
186
+ message: 'no target version (the latest version lookup failed)',
187
+ })
188
+ continue
189
+ }
190
+ results.push(await performOneUpdate(check.packageName, check.current, target))
191
+ }
192
+ return results
193
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Bottom-left account control.
3
+ *
4
+ * DSH's settings launcher is currently a closed host-shell surface, so this
5
+ * component intentionally leaves its structure alone: it hides only the
6
+ * lower-left launcher while mounted and invokes that exact native button for
7
+ * the Settings menu item. This keeps the existing settings dialog and all
8
+ * host-owned navigation behavior intact.
9
+ */
10
+ import { useEffect, useRef, useState } from 'react'
11
+ import { api } from './api.ts'
12
+ import { useChatcodeAccount } from './useChatcodeAccount.ts'
13
+ import { t } from './locales.ts'
14
+ import css from './sidebar.module.css'
15
+
16
+ const SETTINGS_LABELS = new Set([
17
+ 'settings', '设置', '設定', 'paramètres', 'einstellungen', 'impostazioni',
18
+ 'configurações', 'instellingen', 'inställningar', 'ayarlar', '설정', 'настройки',
19
+ ])
20
+
21
+ function nativeSettingsButton(except: HTMLElement | null): HTMLButtonElement | undefined {
22
+ return [...document.querySelectorAll<HTMLButtonElement>('button')]
23
+ .filter(button => except === null || !except.contains(button))
24
+ .filter(button => {
25
+ const text = `${button.getAttribute('aria-label') ?? ''} ${button.title} ${button.textContent ?? ''}`.trim().toLowerCase()
26
+ if (![...SETTINGS_LABELS].some(label => text === label || text.includes(label))) return false
27
+ const rect = button.getBoundingClientRect()
28
+ // The host launcher lives in the lower-left rail. Avoid matching a
29
+ // Settings item inside an already-open dialog or an unrelated panel.
30
+ return rect.left < window.innerWidth * 0.4 && rect.bottom > window.innerHeight - 120
31
+ })
32
+ .sort((a, b) => b.getBoundingClientRect().bottom - a.getBoundingClientRect().bottom)[0]
33
+ }
34
+
35
+ /** Find the left navigation rail's usable width from the native launcher. */
36
+ function railWidthOf(button: HTMLButtonElement | undefined): number | undefined {
37
+ let element: HTMLElement | null = button?.parentElement ?? null
38
+ for (let depth = 0; element !== null && depth < 8; depth += 1, element = element.parentElement) {
39
+ const rect = element.getBoundingClientRect()
40
+ if (rect.left <= 8 && rect.width >= 160 && rect.width <= window.innerWidth * 0.5) return Math.round(rect.width)
41
+ }
42
+ return undefined
43
+ }
44
+
45
+ function LogoutIcon(): JSX.Element {
46
+ return (
47
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
48
+ <path d="M6.5 2.5h-2A1.5 1.5 0 0 0 3 4v8a1.5 1.5 0 0 0 1.5 1.5h2M8 5l3 3-3 3M5.5 8h5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
49
+ </svg>
50
+ )
51
+ }
52
+
53
+ /** Settings glyph kept local so every account action shares the same SVG treatment. */
54
+ function SettingsIcon(): JSX.Element {
55
+ return (
56
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" aria-hidden="true">
57
+ <path d="M6.9 2.2h2.2l.35 1.28c.33.14.64.32.92.54l1.26-.38 1.1 1.9-.93.94c.03.17.05.35.05.54s-.02.37-.05.54l.93.94-1.1 1.9-1.26-.38c-.28.22-.59.4-.92.54L9.1 13.8H6.9l-.35-1.28a4.1 4.1 0 0 1-.92-.54l-1.26.38-1.1-1.9.93-.94a3.2 3.2 0 0 1 0-1.08l-.93-.94 1.1-1.9 1.26.38c.28-.22.59-.4.92-.54L6.9 2.2Z" stroke="currentColor" strokeWidth="1.25" strokeLinejoin="round" />
58
+ <circle cx="8" cy="8" r="1.75" stroke="currentColor" strokeWidth="1.25" />
59
+ </svg>
60
+ )
61
+ }
62
+
63
+ /** Preserve the sidebar's existing browser-session exit when the optional ChatCode service is absent. */
64
+ function logout(): void {
65
+ for (const cookie of document.cookie.split(';')) {
66
+ const name = cookie.split('=')[0]?.trim()
67
+ if (name !== undefined && name !== '') document.cookie = `${name}=; Max-Age=0; Path=/; SameSite=Lax`
68
+ }
69
+ window.localStorage.clear()
70
+ window.sessionStorage.clear()
71
+ window.location.assign('/')
72
+ }
73
+
74
+ export function AccountMenu(): JSX.Element {
75
+ const rootRef = useRef<HTMLDivElement>(null)
76
+ const [open, setOpen] = useState(false)
77
+ const [userName, setUserName] = useState('User')
78
+ const { chatcode, busy, error, loginUrl, startLogin, logout: logoutChatcode } = useChatcodeAccount()
79
+
80
+ useEffect(() => {
81
+ let active = true
82
+ void api.accountGet()
83
+ .then(({ userName: next }) => { if (active && next.trim() !== '') setUserName(next) })
84
+ .catch(() => { /* The menu remains usable with its local fallback. */ })
85
+ return () => { active = false }
86
+ }, [])
87
+
88
+ useEffect(() => {
89
+ const onPointerDown = (event: PointerEvent): void => {
90
+ if (!rootRef.current?.contains(event.target as Node)) setOpen(false)
91
+ }
92
+ document.addEventListener('pointerdown', onPointerDown)
93
+ return () => { document.removeEventListener('pointerdown', onPointerDown) }
94
+ }, [])
95
+
96
+ // The host has no slot for replacing this one closed-shell control. Keep
97
+ // the original fully recoverable: its inline style is restored on disposal
98
+ // and it remains the target invoked by our Settings menu item.
99
+ useEffect(() => {
100
+ let current: HTMLButtonElement | undefined
101
+ let originalStyle: string | undefined
102
+ const sync = (): void => {
103
+ const next = nativeSettingsButton(rootRef.current)
104
+ const width = railWidthOf(next)
105
+ if (width !== undefined) rootRef.current?.style.setProperty('--dsh-account-menu-width', `${width}px`)
106
+ if (next === current) return
107
+ if (current !== undefined && originalStyle !== undefined) current.style.cssText = originalStyle
108
+ current = next
109
+ originalStyle = current?.style.cssText
110
+ if (current !== undefined) current.style.visibility = 'hidden'
111
+ }
112
+ sync()
113
+ const observer = new MutationObserver(sync)
114
+ observer.observe(document.body, { childList: true, subtree: true, characterData: true })
115
+ return () => {
116
+ observer.disconnect()
117
+ if (current !== undefined && originalStyle !== undefined) current.style.cssText = originalStyle
118
+ }
119
+ }, [])
120
+
121
+ const openSettings = (): void => {
122
+ setOpen(false)
123
+ nativeSettingsButton(rootRef.current)?.click()
124
+ }
125
+
126
+ const chatcodeName = chatcode?.userName?.trim() || chatcode?.emailAddress?.trim() || userName
127
+ const signedIn = chatcode?.validation === 'valid' && chatcode.expired !== true
128
+ // A saved credential remains usable for the next check when the company
129
+ // network is unavailable. Do not turn that transient state into a login CTA.
130
+ const canStartChatcodeLogin = chatcode?.available === true && !signedIn
131
+ && !(chatcode.configured === true && chatcode.validation === 'unavailable')
132
+
133
+ return (
134
+ <div ref={rootRef} className={css.accountMenu}>
135
+ {open && (
136
+ <div className={css.accountDropdown} role="menu" aria-label={t('accountMenuLabel')}>
137
+ <div className={css.accountDropdownHeader}>
138
+ <span className={css.accountAvatar}>{chatcodeName.slice(0, 1).toUpperCase()}</span>
139
+ <span className={css.accountName}>{chatcodeName}</span>
140
+ <span className={css.accountPresence} style={{ opacity: signedIn ? 1 : 0.25 }} aria-hidden="true" />
141
+ </div>
142
+ <div className={css.accountMenuDivider} />
143
+ <button type="button" role="menuitem" className={css.accountItem} onClick={openSettings}>
144
+ <SettingsIcon />
145
+ {t('accountMenuSettings')}
146
+ </button>
147
+ {error && <div role="alert" className={css.accountItem}>{t(error === 'status' ? 'accountMenuChatcodeStatusError' : 'accountMenuChatcodeError')}</div>}
148
+ {(chatcode?.login?.state === 'failed' || chatcode?.login?.state === 'timed-out') && <div role="alert" className={css.accountItem}>{t('accountMenuChatcodeRetry')}</div>}
149
+ {loginUrl && <a data-dsh-external="true" className={css.accountItem} href={loginUrl} target="_blank" rel="noopener noreferrer" onClick={event => { event.stopPropagation() }}>{t('accountMenuChatcodeOpen')}</a>}
150
+ {chatcode?.available === true && <>
151
+ {canStartChatcodeLogin && <button type="button" role="menuitem" className={css.accountItem} disabled={busy} onClick={startLogin}>
152
+ <LogoutIcon />{busy ? t('accountMenuChatcodeStarting') : t('accountMenuChatcodeLogin')}
153
+ </button>}
154
+ {chatcode.configured && <button type="button" role="menuitem" className={css.accountItem} disabled={busy} onClick={logoutChatcode}>
155
+ <LogoutIcon />{t('accountMenuChatcodeLogout')}
156
+ </button>}
157
+ </>}
158
+ {chatcode?.available === false && <button type="button" role="menuitem" className={css.accountItem} onClick={logout}>
159
+ <LogoutIcon />{t('accountMenuLogout')}
160
+ </button>}
161
+ </div>
162
+ )}
163
+ <button
164
+ type="button"
165
+ className={css.accountTrigger}
166
+ aria-expanded={open}
167
+ aria-haspopup="menu"
168
+ aria-label={t('accountMenuLabel')}
169
+ onClick={() => { setOpen(value => !value) }}
170
+ >
171
+ <span className={css.accountAvatar}>{chatcodeName.slice(0, 1).toUpperCase()}</span>
172
+ <span className={css.accountName}>{chatcodeName}</span>
173
+ <span className={css.accountPresence} style={{ opacity: signedIn ? 1 : 0.25 }} aria-hidden="true" />
174
+ </button>
175
+ </div>
176
+ )
177
+ }
@@ -0,0 +1,329 @@
1
+ .section {
2
+ container-type: inline-size;
3
+ width: 100%;
4
+ max-width: 920px;
5
+ min-width: 0;
6
+ }
7
+
8
+ /* Query row: date range – 查询 – 导出. */
9
+ .queryRow {
10
+ display: flex;
11
+ align-items: center;
12
+ flex-wrap: wrap;
13
+ gap: 8px;
14
+ }
15
+
16
+ .dateInput {
17
+ flex: 1 1 140px;
18
+ min-width: 0;
19
+ max-width: 180px;
20
+ }
21
+
22
+ .dash {
23
+ color: var(--dsw-alias-label-tertiary);
24
+ }
25
+
26
+ /* The 查询 button: the settings primary-button recipe (label-primary fill,
27
+ inverted surface text). */
28
+ .primaryButton {
29
+ appearance: none;
30
+ border: 1px solid transparent;
31
+ border-radius: 8px;
32
+ padding: 5px 14px;
33
+ font: inherit;
34
+ font-size: 13px;
35
+ line-height: 1.5;
36
+ cursor: pointer;
37
+ background: var(--dsw-alias-state-business-primary);
38
+ color: #fff;
39
+ }
40
+
41
+ .section.section .primaryButton:hover:not(:disabled),
42
+ .section.section .primaryButton:focus:not(:disabled),
43
+ .section.section .primaryButton:focus-visible:not(:disabled),
44
+ .section.section .primaryButton:active:not(:disabled) {
45
+ border-color: transparent !important;
46
+ background: var(--dsw-alias-state-business-primary) !important;
47
+ color: #fff !important;
48
+ outline: none !important;
49
+ box-shadow: none !important;
50
+ filter: none !important;
51
+ transform: none !important;
52
+ }
53
+
54
+ .primaryButton:disabled {
55
+ opacity: 0.45;
56
+ cursor: default;
57
+ }
58
+
59
+ /* The 导出 / retry / pager buttons: the hairline-button recipe. */
60
+ .exportButton {
61
+ appearance: none;
62
+ display: inline-flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ padding: 5px 14px;
66
+ border: 1px solid var(--dsw-alias-border-l2);
67
+ border-radius: 8px;
68
+ font: inherit;
69
+ font-size: 13px;
70
+ line-height: 1.5;
71
+ color: var(--dsw-alias-label-primary);
72
+ background: transparent;
73
+ cursor: pointer;
74
+ }
75
+
76
+ .section.section .exportButton:hover:not(:disabled),
77
+ .section.section .exportButton:focus:not(:disabled),
78
+ .section.section .exportButton:focus-visible:not(:disabled),
79
+ .section.section .exportButton:active:not(:disabled) {
80
+ border-color: var(--dsw-alias-border-l2) !important;
81
+ background: transparent !important;
82
+ color: var(--dsw-alias-label-primary) !important;
83
+ outline: none !important;
84
+ box-shadow: none !important;
85
+ filter: none !important;
86
+ transform: none !important;
87
+ }
88
+
89
+ .exportButton:disabled {
90
+ opacity: 0.45;
91
+ cursor: default;
92
+ }
93
+
94
+ /* The 4-card overview strip. */
95
+ .overview {
96
+ display: grid;
97
+ grid-template-columns: repeat(4, minmax(0, 1fr));
98
+ gap: 12px;
99
+ margin-top: 24px;
100
+ }
101
+
102
+ .card {
103
+ display: flex;
104
+ flex-direction: column;
105
+ gap: 8px;
106
+ min-width: 0;
107
+ padding: 14px 16px;
108
+ border: 1px solid var(--dsw-alias-border-l2);
109
+ border-radius: 12px;
110
+ background: var(--dsw-alias-bg-layer-3);
111
+ }
112
+
113
+ .cardValue {
114
+ font-size: 22px;
115
+ line-height: 28px;
116
+ font-weight: 600;
117
+ font-variant-numeric: tabular-nums;
118
+ overflow: hidden;
119
+ text-overflow: ellipsis;
120
+ white-space: nowrap;
121
+ }
122
+
123
+ .addedValue {
124
+ color: var(--dsw-alias-state-business-primary);
125
+ }
126
+
127
+ .adoptedValue,
128
+ .ratioCardHigh .cardRatio {
129
+ color: var(--dsw-alias-state-success-primary);
130
+ }
131
+
132
+ .commitsValue {
133
+ color: var(--dsw-alias-state-warn-primary);
134
+ }
135
+
136
+ .ratioCardHigh {
137
+ border-color: color-mix(in srgb, var(--dsw-alias-state-success-primary) 35%, var(--dsw-alias-border-l2));
138
+ background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 12%, var(--dsw-alias-bg-layer-3));
139
+ }
140
+
141
+ .ratioCardLow {
142
+ border-color: color-mix(in srgb, var(--dsw-alias-state-error-primary) 30%, var(--dsw-alias-border-l2));
143
+ background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, var(--dsw-alias-bg-layer-3));
144
+ }
145
+
146
+ .ratioCardLow .cardRatio {
147
+ color: var(--dsw-alias-state-error-primary);
148
+ }
149
+
150
+ .cardLabel {
151
+ font-size: 12px;
152
+ line-height: 18px;
153
+ font-weight: 500;
154
+ color: var(--dsw-alias-label-secondary);
155
+ overflow: hidden;
156
+ text-overflow: ellipsis;
157
+ white-space: nowrap;
158
+ }
159
+
160
+ /* Commit table heading. */
161
+ .tableHeading {
162
+ margin-top: 30px;
163
+ margin-bottom: 12px;
164
+ font-size: 13px;
165
+ line-height: 20px;
166
+ font-weight: 600;
167
+ color: var(--dsw-alias-label-primary);
168
+ }
169
+
170
+ .tableWrap {
171
+ max-width: 100%;
172
+ max-height: 44vh;
173
+ overflow: auto;
174
+ border: 1px solid var(--dsw-alias-border-l2);
175
+ border-radius: 12px;
176
+ }
177
+
178
+ .table {
179
+ border-collapse: separate;
180
+ border-spacing: 0;
181
+ width: 100%;
182
+ min-width: 1180px;
183
+ font-size: 12px;
184
+ line-height: 18px;
185
+ }
186
+
187
+ .table th {
188
+ position: sticky;
189
+ top: 0;
190
+ z-index: 1;
191
+ padding: 8px 10px;
192
+ text-align: left;
193
+ font-weight: 600;
194
+ white-space: nowrap;
195
+ color: var(--dsw-alias-label-secondary);
196
+ background: var(--dsw-alias-bg-layer-2);
197
+ border-bottom: 1px solid var(--dsw-alias-border-l2);
198
+ border-right: 1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 75%, transparent);
199
+ }
200
+
201
+ .table td {
202
+ padding: 7px 10px;
203
+ max-width: 180px;
204
+ overflow: hidden;
205
+ text-overflow: ellipsis;
206
+ white-space: nowrap;
207
+ color: var(--dsw-alias-label-primary);
208
+ border-bottom: 1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 55%, transparent);
209
+ border-right: 1px solid color-mix(in srgb, var(--dsw-alias-border-l2) 65%, transparent);
210
+ }
211
+
212
+ .table :is(th, td):last-child {
213
+ border-right: none;
214
+ }
215
+
216
+ .table tbody tr:last-child td {
217
+ border-bottom: none;
218
+ }
219
+
220
+ .table tbody tr:hover td {
221
+ background: var(--dsw-alias-interactive-bg-hover);
222
+ }
223
+
224
+ /* Numeric columns: right-aligned tabular figures. */
225
+ .num {
226
+ text-align: right;
227
+ font-variant-numeric: tabular-nums;
228
+ }
229
+
230
+ /* AI-adopted value ink when non-zero (the portal's green-if-positive). */
231
+ .aiValue {
232
+ color: var(--dsw-alias-state-success-primary);
233
+ }
234
+
235
+ /* The per-row ratio: value + mini progress bar. Both carry the threshold
236
+ color (≥70 success / ≥50 business / else warn) inline from the component. */
237
+ .ratioValue {
238
+ display: inline-block;
239
+ min-width: 34px;
240
+ font-variant-numeric: tabular-nums;
241
+ }
242
+
243
+ .ratioBar {
244
+ display: inline-block;
245
+ vertical-align: middle;
246
+ width: 56px;
247
+ height: 5px;
248
+ margin-left: 6px;
249
+ border-radius: 999px;
250
+ background: color-mix(in srgb, var(--dsw-alias-border-l2) 60%, transparent);
251
+ overflow: hidden;
252
+ }
253
+
254
+ .ratioBar > span {
255
+ display: block;
256
+ height: 100%;
257
+ border-radius: 999px;
258
+ }
259
+
260
+ .dateCell {
261
+ font-variant-numeric: tabular-nums;
262
+ }
263
+
264
+ /* Pager: ‹ info ›. */
265
+ .pager {
266
+ display: flex;
267
+ align-items: center;
268
+ justify-content: flex-end;
269
+ flex-wrap: wrap;
270
+ gap: 10px;
271
+ margin-top: 12px;
272
+ }
273
+
274
+ .pager .exportButton {
275
+ padding: 3px 10px;
276
+ font-size: 14px;
277
+ line-height: 1.2;
278
+ }
279
+
280
+ .pageInfo {
281
+ font-size: 12px;
282
+ line-height: 18px;
283
+ color: var(--dsw-alias-label-secondary);
284
+ font-variant-numeric: tabular-nums;
285
+ }
286
+
287
+ /* Loading / sign-in / empty placeholder rows. */
288
+ .state {
289
+ min-height: 120px;
290
+ display: flex;
291
+ align-items: center;
292
+ font-size: 13px;
293
+ line-height: 20px;
294
+ color: var(--dsw-alias-label-secondary);
295
+ }
296
+
297
+ /* Error row with the inline retry. */
298
+ .stateError {
299
+ min-height: 120px;
300
+ display: flex;
301
+ align-items: center;
302
+ justify-content: space-between;
303
+ gap: 12px;
304
+ font-size: 13px;
305
+ line-height: 20px;
306
+ color: var(--dsw-alias-label-secondary);
307
+ }
308
+
309
+ .stateError > span {
310
+ min-width: 0;
311
+ overflow-wrap: anywhere;
312
+ }
313
+
314
+ @container (max-width: 600px) {
315
+ .overview {
316
+ grid-template-columns: repeat(2, minmax(0, 1fr));
317
+ }
318
+ }
319
+
320
+ @container (max-width: 320px) {
321
+ .overview {
322
+ grid-template-columns: minmax(0, 1fr);
323
+ }
324
+
325
+ .stateError {
326
+ align-items: flex-start;
327
+ flex-direction: column;
328
+ }
329
+ }