dsh-over-ssh 0.1.0 → 0.2.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 (250) hide show
  1. package/README.md +19 -17
  2. package/agents/agent-manifest.json +6 -6
  3. package/agents/linux-amd64/dsh-over-ssh-agent +0 -0
  4. package/agents/linux-arm64/dsh-over-ssh-agent +0 -0
  5. package/lib/client.js +422 -337
  6. package/lib/file-references.js +92 -35
  7. package/lib/fs.js +295 -113
  8. package/lib/index.js +6912 -4330
  9. package/lib/terminal-wrapper.js +9 -4
  10. package/lib/types/src/config.d.ts +156 -9
  11. package/lib/types/src/file-references.d.ts +0 -1
  12. package/lib/types/src/host/app/backend.d.ts +44 -3
  13. package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +0 -1
  14. package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +0 -1
  15. package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +12 -1
  16. package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +0 -1
  17. package/lib/types/src/host/app/transport/transport-selector.d.ts +0 -1
  18. package/lib/types/src/host/app/transport/transport-service.d.ts +0 -1
  19. package/lib/types/src/host/domain/capability.d.ts +0 -1
  20. package/lib/types/src/host/domain/errors.d.ts +0 -1
  21. package/lib/types/src/host/domain/roots/connection.d.ts +0 -1
  22. package/lib/types/src/host/domain/roots/root.d.ts +24 -9
  23. package/lib/types/src/host/domain/roots/session-roots.d.ts +23 -1
  24. package/lib/types/src/host/domain/roots/ssh-config.d.ts +0 -1
  25. package/lib/types/src/host/domain/session/session-admin.d.ts +0 -1
  26. package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +0 -1
  27. package/lib/types/src/host/domain/transfer/transfer.d.ts +0 -1
  28. package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +0 -1
  29. package/lib/types/src/host/infra/transport/forward.d.ts +75 -0
  30. package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +5 -1
  31. package/lib/types/src/host/infra/transport/transport-base.d.ts +0 -1
  32. package/lib/types/src/host/infra/transport/transport.d.ts +42 -2
  33. package/lib/types/src/host/integration/agent/agent-preflight.d.ts +0 -1
  34. package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +0 -1
  35. package/lib/types/src/host/integration/fs/fs-provider.d.ts +18 -3
  36. package/lib/types/src/host/integration/transfer/transfer-service.d.ts +0 -1
  37. package/lib/types/src/index.d.ts +23 -1
  38. package/lib/types/src/local-bridge-refs.d.ts +0 -1
  39. package/lib/types/src/local-bridge.d.ts +0 -1
  40. package/lib/types/src/shared/root-keys.d.ts +120 -0
  41. package/lib/types/src/wiring/helpers.d.ts +0 -1
  42. package/package.json +5 -3
  43. package/lib/types/src/client/controls.d.ts +0 -131
  44. package/lib/types/src/client/core/card-focus-trap.d.ts +0 -45
  45. package/lib/types/src/client/core/controls.d.ts +0 -164
  46. package/lib/types/src/client/core/latest.d.ts +0 -48
  47. package/lib/types/src/client/core/locale.d.ts +0 -38
  48. package/lib/types/src/client/core/message.d.ts +0 -37
  49. package/lib/types/src/client/core/panel-model.d.ts +0 -134
  50. package/lib/types/src/client/core/portal.d.ts +0 -266
  51. package/lib/types/src/client/core/remote.d.ts +0 -84
  52. package/lib/types/src/client/core/styles.d.ts +0 -458
  53. package/lib/types/src/client/file-tree-io.d.ts +0 -19
  54. package/lib/types/src/client/file-tree.d.ts +0 -271
  55. package/lib/types/src/client/flows/directory-browser.d.ts +0 -587
  56. package/lib/types/src/client/flows/session-directory.d.ts +0 -415
  57. package/lib/types/src/client/flows/workspace-directory-flow.d.ts +0 -480
  58. package/lib/types/src/client/locale.d.ts +0 -31
  59. package/lib/types/src/client/panel-model.d.ts +0 -37
  60. package/lib/types/src/client/panel.d.ts +0 -77
  61. package/lib/types/src/client/roots/connection-test.d.ts +0 -69
  62. package/lib/types/src/client/roots/roots-panel-state.d.ts +0 -307
  63. package/lib/types/src/client/roots/roots-panel-view.d.ts +0 -280
  64. package/lib/types/src/client/roots/roots-panel.d.ts +0 -364
  65. package/lib/types/src/client/roots/transfer-panel-view.d.ts +0 -35
  66. package/lib/types/src/client/roots/transfer-panel.d.ts +0 -241
  67. package/lib/types/src/client/roots/transfer-progress.d.ts +0 -67
  68. package/lib/types/src/client/roots-panel-view.d.ts +0 -236
  69. package/lib/types/src/client/roots-panel.d.ts +0 -347
  70. package/lib/types/src/client/session/session-panel-view.d.ts +0 -336
  71. package/lib/types/src/client/session/session-panel.d.ts +0 -322
  72. package/lib/types/src/client/session/session-remote-mark.d.ts +0 -110
  73. package/lib/types/src/client/session/session-row-actions.d.ts +0 -398
  74. package/lib/types/src/client/session-panel.d.ts +0 -152
  75. package/lib/types/src/client/styles.d.ts +0 -252
  76. package/lib/types/src/client/workspace-directory-flow.d.ts +0 -342
  77. package/lib/types/src/fs.d.ts +0 -46
  78. package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +0 -18
  79. package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +0 -38
  80. package/lib/types/src/host/app/recycle-bin-contract.d.ts +0 -215
  81. package/lib/types/src/host/app/recycle-bin-move.d.ts +0 -18
  82. package/lib/types/src/host/app/recycle-bin-service.d.ts +0 -45
  83. package/lib/types/src/host/app/recycle-bin-world.d.ts +0 -35
  84. package/lib/types/src/host/app/roots/roots-api-connections.d.ts +0 -49
  85. package/lib/types/src/host/app/roots/roots-api-contract.d.ts +0 -369
  86. package/lib/types/src/host/app/roots/roots-api-directories.d.ts +0 -13
  87. package/lib/types/src/host/app/roots/roots-api-http.d.ts +0 -77
  88. package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +0 -130
  89. package/lib/types/src/host/app/roots/roots-api-projections.d.ts +0 -102
  90. package/lib/types/src/host/app/roots/roots-api-roots.d.ts +0 -13
  91. package/lib/types/src/host/app/roots/roots-api-state.d.ts +0 -22
  92. package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +0 -26
  93. package/lib/types/src/host/app/roots/roots-api.d.ts +0 -55
  94. package/lib/types/src/host/app/roots/user-roots.d.ts +0 -552
  95. package/lib/types/src/host/app/roots-api-connections.d.ts +0 -49
  96. package/lib/types/src/host/app/roots-api-contract.d.ts +0 -339
  97. package/lib/types/src/host/app/roots-api-directories.d.ts +0 -13
  98. package/lib/types/src/host/app/roots-api-http.d.ts +0 -77
  99. package/lib/types/src/host/app/roots-api-projections.d.ts +0 -102
  100. package/lib/types/src/host/app/roots-api-roots.d.ts +0 -13
  101. package/lib/types/src/host/app/roots-api-state.d.ts +0 -22
  102. package/lib/types/src/host/app/roots-api-support.d.ts +0 -182
  103. package/lib/types/src/host/app/roots-api-transfers.d.ts +0 -13
  104. package/lib/types/src/host/app/roots-api.d.ts +0 -55
  105. package/lib/types/src/host/app/routed-fs.d.ts +0 -239
  106. package/lib/types/src/host/app/serialize.d.ts +0 -37
  107. package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +0 -36
  108. package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +0 -34
  109. package/lib/types/src/host/app/session-admin-context.d.ts +0 -36
  110. package/lib/types/src/host/app/session-admin-contract.d.ts +0 -214
  111. package/lib/types/src/host/app/session-admin-delete.d.ts +0 -34
  112. package/lib/types/src/host/app/session-admin-service.d.ts +0 -38
  113. package/lib/types/src/host/app/takeover/takeover-plan.d.ts +0 -227
  114. package/lib/types/src/host/app/takeover/takeover-rows.d.ts +0 -55
  115. package/lib/types/src/host/app/transport-selector.d.ts +0 -98
  116. package/lib/types/src/host/app/transport-service.d.ts +0 -75
  117. package/lib/types/src/host/app/user-roots.d.ts +0 -461
  118. package/lib/types/src/host/domain/bin-location.d.ts +0 -79
  119. package/lib/types/src/host/domain/connection.d.ts +0 -95
  120. package/lib/types/src/host/domain/glob-pattern.d.ts +0 -62
  121. package/lib/types/src/host/domain/literal-text.d.ts +0 -63
  122. package/lib/types/src/host/domain/path-grammar.d.ts +0 -84
  123. package/lib/types/src/host/domain/recycle-bin.d.ts +0 -337
  124. package/lib/types/src/host/domain/root.d.ts +0 -74
  125. package/lib/types/src/host/domain/roots/bin-location.d.ts +0 -91
  126. package/lib/types/src/host/domain/roots/route-table.d.ts +0 -102
  127. package/lib/types/src/host/domain/route-table.d.ts +0 -80
  128. package/lib/types/src/host/domain/session/terminal-target.d.ts +0 -136
  129. package/lib/types/src/host/domain/session-admin.d.ts +0 -330
  130. package/lib/types/src/host/domain/shell.d.ts +0 -47
  131. package/lib/types/src/host/domain/ssh-config.d.ts +0 -73
  132. package/lib/types/src/host/domain/terminal-target.d.ts +0 -132
  133. package/lib/types/src/host/domain/tool-requests.d.ts +0 -369
  134. package/lib/types/src/host/domain/transfer.d.ts +0 -260
  135. package/lib/types/src/host/infra/backend/local-backend.d.ts +0 -84
  136. package/lib/types/src/host/infra/backend/local-version.d.ts +0 -43
  137. package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +0 -89
  138. package/lib/types/src/host/infra/backend/remote-backend.d.ts +0 -239
  139. package/lib/types/src/host/infra/cache/lru.d.ts +0 -52
  140. package/lib/types/src/host/infra/cache/versioned.d.ts +0 -77
  141. package/lib/types/src/host/infra/temp-name.d.ts +0 -19
  142. package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +0 -132
  143. package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +0 -140
  144. package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +0 -41
  145. package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +0 -197
  146. package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +0 -564
  147. package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +0 -418
  148. package/lib/types/src/host/infra/transport/agent-installer.d.ts +0 -121
  149. package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +0 -123
  150. package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +0 -131
  151. package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +0 -41
  152. package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +0 -197
  153. package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +0 -509
  154. package/lib/types/src/host/infra/transport/agent-transport.d.ts +0 -353
  155. package/lib/types/src/host/infra/transport/alias-transport.d.ts +0 -206
  156. package/lib/types/src/host/infra/transport/askpass-fs.d.ts +0 -20
  157. package/lib/types/src/host/infra/transport/askpass.d.ts +0 -133
  158. package/lib/types/src/host/infra/transport/command-pool.d.ts +0 -61
  159. package/lib/types/src/host/infra/transport/fs-commands.d.ts +0 -140
  160. package/lib/types/src/host/infra/transport/git-status-route.d.ts +0 -63
  161. package/lib/types/src/host/infra/transport/git-status.d.ts +0 -101
  162. package/lib/types/src/host/infra/transport/node-spawn.d.ts +0 -26
  163. package/lib/types/src/host/infra/transport/session-window.d.ts +0 -51
  164. package/lib/types/src/host/infra/transport/shell-quote.d.ts +0 -59
  165. package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +0 -206
  166. package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +0 -20
  167. package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +0 -133
  168. package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +0 -62
  169. package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +0 -43
  170. package/lib/types/src/host/infra/transport/ssh-argv.d.ts +0 -43
  171. package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +0 -302
  172. package/lib/types/src/host/integration/agent/agent-install-io.d.ts +0 -33
  173. package/lib/types/src/host/integration/agent/askpass-registry.d.ts +0 -66
  174. package/lib/types/src/host/integration/agent-install-io.d.ts +0 -33
  175. package/lib/types/src/host/integration/agent-preflight.d.ts +0 -56
  176. package/lib/types/src/host/integration/anchor-directory.d.ts +0 -56
  177. package/lib/types/src/host/integration/askpass-registry.d.ts +0 -59
  178. package/lib/types/src/host/integration/bin-root-resolver.d.ts +0 -81
  179. package/lib/types/src/host/integration/connection-test.d.ts +0 -73
  180. package/lib/types/src/host/integration/file-reference-provider.d.ts +0 -115
  181. package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +0 -21
  182. package/lib/types/src/host/integration/fs/platform-errors.d.ts +0 -94
  183. package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +0 -369
  184. package/lib/types/src/host/integration/fs/remote-index.d.ts +0 -177
  185. package/lib/types/src/host/integration/fs-provider.d.ts +0 -226
  186. package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +0 -38
  187. package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +0 -67
  188. package/lib/types/src/host/integration/lsp/lsp-session.d.ts +0 -130
  189. package/lib/types/src/host/integration/management-tools.d.ts +0 -64
  190. package/lib/types/src/host/integration/recycle-bin-service.d.ts +0 -247
  191. package/lib/types/src/host/integration/remote/connection-test.d.ts +0 -73
  192. package/lib/types/src/host/integration/remote/remote-face.d.ts +0 -127
  193. package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +0 -108
  194. package/lib/types/src/host/integration/remote-face.d.ts +0 -127
  195. package/lib/types/src/host/integration/remote-file-tools.d.ts +0 -241
  196. package/lib/types/src/host/integration/remote-home.d.ts +0 -69
  197. package/lib/types/src/host/integration/remote-index.d.ts +0 -165
  198. package/lib/types/src/host/integration/remote-job-tools.d.ts +0 -295
  199. package/lib/types/src/host/integration/remote-tool-backend.d.ts +0 -97
  200. package/lib/types/src/host/integration/remote-tools.d.ts +0 -206
  201. package/lib/types/src/host/integration/root-registry.d.ts +0 -115
  202. package/lib/types/src/host/integration/roots/anchor-directory.d.ts +0 -56
  203. package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +0 -81
  204. package/lib/types/src/host/integration/roots/remote-home.d.ts +0 -69
  205. package/lib/types/src/host/integration/roots/root-registry.d.ts +0 -144
  206. package/lib/types/src/host/integration/roots/roots-wiring.d.ts +0 -219
  207. package/lib/types/src/host/integration/roots/session-root-index.d.ts +0 -52
  208. package/lib/types/src/host/integration/roots-api.d.ts +0 -407
  209. package/lib/types/src/host/integration/roots-wiring.d.ts +0 -211
  210. package/lib/types/src/host/integration/session/session-api.d.ts +0 -88
  211. package/lib/types/src/host/integration/session/session-bin-lock.d.ts +0 -80
  212. package/lib/types/src/host/integration/session/session-header.d.ts +0 -151
  213. package/lib/types/src/host/integration/session/session-labels.d.ts +0 -56
  214. package/lib/types/src/host/integration/session/session-route-wiring.d.ts +0 -50
  215. package/lib/types/src/host/integration/session-admin-service.d.ts +0 -236
  216. package/lib/types/src/host/integration/session-api.d.ts +0 -81
  217. package/lib/types/src/host/integration/session-bin-lock.d.ts +0 -80
  218. package/lib/types/src/host/integration/session-header.d.ts +0 -118
  219. package/lib/types/src/host/integration/session-labels.d.ts +0 -56
  220. package/lib/types/src/host/integration/session-route-wiring.d.ts +0 -50
  221. package/lib/types/src/host/integration/session-scope.d.ts +0 -113
  222. package/lib/types/src/host/integration/session-tools.d.ts +0 -44
  223. package/lib/types/src/host/integration/strict-arguments.d.ts +0 -79
  224. package/lib/types/src/host/integration/takeover/takeover-api.d.ts +0 -62
  225. package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +0 -60
  226. package/lib/types/src/host/integration/takeover/takeover-service.d.ts +0 -167
  227. package/lib/types/src/host/integration/terminal-manifest.d.ts +0 -94
  228. package/lib/types/src/host/integration/tools/management-tools.d.ts +0 -245
  229. package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +0 -316
  230. package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +0 -43
  231. package/lib/types/src/host/integration/tools/remote-tools.d.ts +0 -236
  232. package/lib/types/src/host/integration/tools/session-target.d.ts +0 -52
  233. package/lib/types/src/host/integration/tools/session-tools.d.ts +0 -46
  234. package/lib/types/src/host/integration/tools/strict-arguments.d.ts +0 -79
  235. package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +0 -94
  236. package/lib/types/src/host/integration/transfer/remote-copy.d.ts +0 -66
  237. package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +0 -48
  238. package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +0 -106
  239. package/lib/types/src/host/integration/transfer-adapters.d.ts +0 -44
  240. package/lib/types/src/host/integration/transfer-service.d.ts +0 -164
  241. package/lib/types/src/host/integration/transfer-tracker.d.ts +0 -106
  242. package/lib/types/src/host/integration/user-roots.d.ts +0 -461
  243. package/lib/types/src/wiring/connections.d.ts +0 -189
  244. package/lib/types/src/wiring/fs.d.ts +0 -13
  245. package/lib/types/src/wiring/install-memo.d.ts +0 -32
  246. package/lib/types/src/wiring/legacy-dirs.d.ts +0 -62
  247. package/lib/types/src/wiring/package-paths.d.ts +0 -35
  248. package/lib/types/src/wiring/roots.d.ts +0 -21
  249. package/lib/types/src/wiring/state-dir.d.ts +0 -29
  250. package/lib/types/src/wiring/tools.d.ts +0 -35
@@ -1,364 +0,0 @@
1
- /**
2
- * The roots panel's whole decision surface: wire parsing, the HTTP calls to the host route, input validation
3
- * and the view model. **No React, no DOM, no `@deepseek-ai/*`** — the component renders what this module
4
- * returns and nothing else, which is what makes the panel testable without a browser (红线 2).
5
- *
6
- * Two rules this module owns:
7
- *
8
- * 1. **No credentials, ever.** {@link parseRootsSnapshot} copies field by field and drops anything else, so a
9
- * host answer that leaked `keyPath` / `password` still cannot reach the DOM. The host route filters its
10
- * side; this is the client's half of the same rule.
11
- * 2. **Password authentication is not editable here.** A password must never round-trip through a browser
12
- * form and a same-origin POST, so {@link validateConnectionDraft} refuses it outright rather than
13
- * growing a field that leaks secrets into web storage.
14
- *
15
- * @module dsh-over-ssh/client/roots-panel
16
- */
17
- import type { MessageKey } from '../core/message.ts';
18
- /**
19
- * Where the host route lives, as the browser must request it.
20
- *
21
- * **Relative on purpose, and this is the platform's own convention.** The GUI is served from two different
22
- * origins depending on the carrier, and only a relative path is correct in both:
23
- *
24
- * - `dsh web` (browser): the page origin *is* the host, so `/api/…` reaches
25
- * `ctx.webServer` directly;
26
- * - the Desktop shell: the page is `dsh-app://app/` and `/api/…` resolves to
27
- * `dsh-app://app/api/…`, which Electron's `protocol.handle('dsh-app')` forwards to the owned host with
28
- * the browser-session cookie the shell keeps out of the page
29
- * (`resources/app.asar/lib/main.js`, `forwardWebRequest`: non-asset paths are proxied with
30
- * `headers.set('cookie', cookie)`; `serveWebDocument` never sees `/api/…`).
31
- *
32
- * The absolute host origin (`globalThis.__DSH_TRANSPORT__.streamBaseUrl`) is deliberately **not** used here:
33
- * the shell strips `set-cookie` from proxied responses and the session cookie is host-only + `HttpOnly`, so
34
- * a direct fetch to the host origin cannot authenticate — it would turn a working request into a 401. The
35
- * platform uses that origin for the WebSocket only (`dsh-api-gateway`, `REMOTE_STREAM_MUX_PATH`), and its
36
- * own HTTP callers resolve against `document.baseURI` (`dsh-client-file-upload`, `new URL(request.path,
37
- * document.baseURI)`).
38
- */
39
- export declare const ROOTS_API_PATH = "/api/over-ssh/roots";
40
- /** A root either lives on this machine or behind a connection. */
41
- export type RootLocation = 'local' | 'remote';
42
- /** One root as the panel shows it. */
43
- export interface RootRow {
44
- /** Stable identity: `<location>:<connectionId or '-'>:<path>`. */
45
- readonly key: string;
46
- readonly location: RootLocation;
47
- readonly path: string;
48
- readonly connectionId?: string;
49
- readonly label?: string;
50
- readonly writable: boolean;
51
- /** `config` rows come from the profile patch; `user` rows were added in this panel. */
52
- readonly source: 'config' | 'user';
53
- /** Whether this root also appears in the panel's own file — only those may be deleted. */
54
- readonly inUserFile: boolean;
55
- /**
56
- * The workspaces that list this root; absent means **no session lists it yet**.
57
- *
58
- * The composer's card shows exactly the roots whose list names its own workspace (one session must not
59
- * inherit another's directories). The settings panel ignores it, because that panel manages the whole list
60
- * and registers roots with **no** scope of its own — its form has no `workspaces` member (`RootsPanelDrafts`),
61
- * so a root added there is not listed by any session until one adds it (audit 0056).
62
- */
63
- readonly workspaces?: readonly string[];
64
- /**
65
- * The **sessions** that added this root (ADR-0013); absent on entries written before it.
66
- *
67
- * The composer's card prefers this over `workspaces`: two conversations opened on the same directory share
68
- * the workspace path and differ only here.
69
- */
70
- readonly sessions?: readonly string[];
71
- }
72
- /** A connection, narrowed to what the panel may display. **No credential fields exist on this type.** */
73
- export interface ConnectionSummary {
74
- readonly id: string;
75
- readonly host: string;
76
- readonly user?: string;
77
- readonly port?: number;
78
- readonly authKind: 'agent' | 'key' | 'password';
79
- /**
80
- * The transport in force, **when the host reports it**.
81
- *
82
- * `publicRoot`/`publicUserState` do not carry it today (the transport is a live fact of the platform's remote
83
- * namespace, not of the profile), so production answers leave this `undefined` and the panel says 「未知」
84
- * rather than claiming `unknown` — but a host that starts publishing it is honoured without another round
85
- * (audit 2026-11-01b ui P2-6).
86
- */
87
- readonly transport?: ConnectionTransport;
88
- /** True when that transport is a degraded substitute, when the host reports it. */
89
- readonly degraded?: boolean;
90
- }
91
- /** The transports a connection row may name; `unknown` means "the host did not say". */
92
- /**
93
- * Re-exported from `core/panel-model.ts`, which **owns** it: `core` must not depend on `roots`, so the
94
- * definition lives in the lower layer and this file points up at it. `export type {…} from` alone does not put
95
- * the name in this module's scope, hence the separate `import type` as well.
96
- */
97
- export type { ConnectionTransport } from '../core/panel-model.ts';
98
- import type { ConnectionTransport } from '../core/panel-model.ts';
99
- /** Everything one GET answers. */
100
- export interface RootsSnapshot {
101
- readonly roots: readonly RootRow[];
102
- readonly connections: readonly ConnectionSummary[];
103
- readonly warnings: readonly string[];
104
- /**
105
- * The plugin's anchor root (`<home>/.dsh/over-ssh/roots`), when the host publishes one.
106
- *
107
- * Not a row: it is what lets a client recognise that a session's cwd *is* a remote workspace (see
108
- * `client/session-remote-mark.ts`). Absent means "the host did not say", and every caller must treat that
109
- * as unknown rather than as "no remote sessions".
110
- */
111
- readonly anchorRoot?: string | undefined;
112
- }
113
- /** The outcome of one call: the panel never throws on a host failure, it shows it. */
114
- export type ApiResult<T> = {
115
- readonly ok: true;
116
- readonly value: T;
117
- } | {
118
- readonly ok: false;
119
- readonly error: string;
120
- };
121
- /** What this module needs from `fetch`; injected so tests run without a server (红线 3). */
122
- export type FetchLike = (input: string, init?: {
123
- readonly method?: string;
124
- readonly headers?: Record<string, string>;
125
- readonly body?: string;
126
- readonly signal?: AbortSignal;
127
- }) => Promise<{
128
- readonly ok: boolean;
129
- readonly status: number;
130
- json: () => Promise<unknown>;
131
- }>;
132
- /**
133
- * A message is a key plus its parameters; the component resolves it against the locale dictionaries.
134
- *
135
- * The shape (and the resolver, `resolveMessage`) live in `../core/message.ts` because the session panel and the
136
- * transfer panel produce messages too, and both are pure models that must not import React. Re-exported here so
137
- * every existing `import { MessageKey } from './roots-panel.ts'` keeps working.
138
- */
139
- export type { MessageKey };
140
- /** A validated draft, or the reason it was refused. */
141
- export type Validation<T> = {
142
- readonly ok: true;
143
- readonly value: T;
144
- } | {
145
- readonly ok: false;
146
- readonly message: MessageKey;
147
- };
148
- /** The payload the panel sends to add a root. */
149
- export interface RootDraft {
150
- readonly location: RootLocation;
151
- readonly path: string;
152
- readonly connectionId?: string;
153
- readonly label?: string;
154
- readonly writable?: boolean;
155
- }
156
- /**
157
- * The transport a connection record asks for.
158
- *
159
- * It stays in the wire payload type because the host still accepts and persists it, but the **panel no
160
- * longer offers a choice**: every save asks for `agent`, and the host's selector/preflight degrades to the
161
- * zero-install transports automatically when the Tier 2 ladder cannot serve the connection. Keeping the type
162
- * here documents what the payload can contain without re-opening the UI decision.
163
- */
164
- export type ConnectionTransportChoice = 'alias' | 'ssh2' | 'agent';
165
- /**
166
- * The payload the panel sends to add a connection.
167
- *
168
- * `password` is the **only** secret this client ever holds, and only long enough to post it: the host writes
169
- * it into `ctx.credentials` and answers with a projection that never contains it (FR-2.1.2).
170
- */
171
- export interface ConnectionDraft {
172
- readonly id: string;
173
- readonly host: string;
174
- readonly user: string;
175
- readonly authKind: 'agent' | 'key' | 'password';
176
- /**
177
- * The transport the payload asks for.
178
- *
179
- * Always `agent` now: the panel offers no choice, and the host degrades automatically. Kept in the payload
180
- * (rather than omitted) so an older record's `alias`/`ssh2` is rewritten to the preferred path on the first
181
- * save, and so the host keeps the "always explicit" contract it already validates.
182
- */
183
- readonly transport?: ConnectionTransportChoice;
184
- readonly keyPath?: string;
185
- readonly password?: string;
186
- readonly port?: number;
187
- }
188
- /** The identity the host also uses, so removals address the same row the list shows. */
189
- export declare function rootKey(location: RootLocation, path: string, connectionId?: string): string;
190
- /**
191
- * Narrow one GET answer to the panel's model, field by field.
192
- *
193
- * Unknown fields are dropped rather than copied: an answer carrying `keyPath` reaches the DOM only as a
194
- * missing field, never as a value. A malformed answer becomes an empty snapshot with a warning instead of a
195
- * throw — a broken channel must degrade the panel, not break the whole settings page.
196
- *
197
- * @param value - the parsed JSON the route answered with.
198
- * @returns a snapshot that is safe to render.
199
- */
200
- export declare function parseRootsSnapshot(value: unknown): RootsSnapshot;
201
- /** True when a string is an absolute path this machine can open (drive letter, POSIX root, or UNC). */
202
- export declare function isAbsoluteLocalPath(path: string): boolean;
203
- /**
204
- * Refuse a draft before it becomes a request.
205
- *
206
- * @param draft - what the form holds.
207
- * @param existing - roots already shown, to reject a duplicate.
208
- * @returns the wire payload, or the message key to show next to the field.
209
- */
210
- export declare function validateRootDraft(draft: RootDraft, existing?: readonly RootRow[]): Validation<RootDraft>;
211
- /**
212
- * Refuse a connection draft before it becomes a request.
213
- *
214
- * The id rule is the **host's** rule (a user-chosen name, Chinese included), so the panel refuses exactly
215
- * what `path-grammar.ts` would refuse instead of sending a request that comes back 400.
216
- *
217
- * Password auth is supported: the draft carries the password once, the host stores it in `ctx.credentials`,
218
- * and nothing in any answer ever carries it back (FR-2.1.2).
219
- *
220
- * The payload always carries `transport: 'agent'`: the panel no longer offers a choice, and the host downgrades
221
- * automatically when the Tier 2 ladder cannot serve this connection.
222
- *
223
- * @param draft - what the form holds.
224
- * @param existing - connections already shown, to reject a duplicate id.
225
- * @returns the wire payload, or the message key to show next to the field.
226
- */
227
- export declare function validateConnectionDraft(draft: ConnectionDraft, existing?: readonly ConnectionSummary[]): Validation<ConnectionDraft>;
228
- /**
229
- * Refuse an **edit** draft before it becomes a request.
230
- *
231
- * The same rules as {@link validateConnectionDraft} for every field, with the three differences an edit
232
- * needs, and each one exists for a reason:
233
- *
234
- * 1. **A rename is allowed but must not collide with a *different* connection.** Uniqueness is checked
235
- * against the rows except the one being edited - `validateConnectionDraft` cannot express that, and using
236
- * it here would refuse every save that kept the id.
237
- * 2. **An empty password means "do not touch the stored secret", not "refuse".** No answer ever carries a
238
- * secret back, so the form's password box is always empty on render; requiring it would make it
239
- * impossible to change a host without retyping the password, and treating empty as "clear" would delete
240
- * the password on every unrelated edit. The field is therefore **omitted** from the payload unless the
241
- * user typed one, which is exactly how the host reads it.
242
- * 3. **`keyPath` follows the same rule as the password: blank means "keep the stored one".** The panel
243
- * cannot prefill it, because the host's answer deliberately never carries a `keyPath` (it names a real
244
- * file). Refusing a blank one would therefore make it impossible to edit a key connection's host without
245
- * retyping its private-key path, and *filling* it with a guess would silently repoint the connection.
246
- * So it is omitted unless the user typed one - the host's merge keeps what is stored.
247
- *
248
- * @param draft - what the form holds.
249
- * @param originalId - the id of the connection being edited (the lookup key the host needs).
250
- * @param existing - connections already shown, to reject a colliding rename.
251
- * @returns the wire payload, or the message key to show next to the field.
252
- */
253
- export declare function validateConnectionEdit(draft: ConnectionDraft, originalId: string, existing?: readonly ConnectionSummary[]): Validation<Record<string, unknown>>;
254
- /** One row as the component renders it. */
255
- export interface RootRowView {
256
- readonly key: string;
257
- readonly location: RootLocation;
258
- readonly title: string;
259
- readonly subtitle: string;
260
- readonly badgeKey: string;
261
- readonly removable: boolean;
262
- }
263
- /** The panel's complete render input. */
264
- export interface RootsView {
265
- readonly rows: readonly RootRowView[];
266
- readonly connections: readonly ConnectionSummary[];
267
- /** Set when the host did not answer, so the panel can say why instead of looking empty. */
268
- readonly notice?: MessageKey;
269
- /** False while a request is in flight, so the form cannot double-submit. */
270
- readonly canEdit: boolean;
271
- }
272
- /**
273
- * Turn a snapshot (or the failure to get one) into render input.
274
- *
275
- * @param input - the last answer, or the last error, plus whether a request is running.
276
- * @returns rows, connections, notice and the edit gate.
277
- */
278
- export declare function buildRootsView(input: {
279
- readonly snapshot?: RootsSnapshot;
280
- readonly error?: string;
281
- readonly pending?: boolean;
282
- }): RootsView;
283
- /** The calls the panel makes. */
284
- export interface RootsApi {
285
- load(signal?: AbortSignal): Promise<ApiResult<RootsSnapshot>>;
286
- send(body: Record<string, unknown>): Promise<ApiResult<RootsSnapshot>>;
287
- /**
288
- * A POST whose answer is **not** a snapshot.
289
- *
290
- * The import actions answer a candidate list and an import report, neither of which is a roots snapshot. It
291
- * is a separate method rather than a looser return type on {@link RootsApi.send} so that every existing call
292
- * keeps the snapshot it already depends on: widening `send` would make `state` optional for all of them.
293
- */
294
- sendRaw(body: Record<string, unknown>): Promise<ApiResult<Record<string, unknown>>>;
295
- }
296
- /** One importable `~/.ssh/config` alias, as the import dialog renders it. */
297
- export interface SshConfigCandidate {
298
- /** The literal alias; also the `ssh` target. Shown verbatim so the user recognises their own config. */
299
- readonly alias: string;
300
- /** Resolved host name. */
301
- readonly host: string;
302
- readonly user?: string;
303
- readonly port?: number;
304
- /** Whether the block named an identity file. The path itself never reaches the client. */
305
- readonly hasIdentityFile: boolean;
306
- readonly hasProxyJump: boolean;
307
- /** The id the import would create. Editable in spirit: the user sees it before committing. */
308
- readonly suggestedId: string;
309
- }
310
- /** One alias the host refused, with the machine-readable reason. */
311
- export interface SshConfigSkipped {
312
- readonly pattern: string;
313
- readonly reason: string;
314
- readonly line: number;
315
- }
316
- /** What `list-ssh-config` answers. */
317
- export interface SshConfigListing {
318
- readonly candidates: readonly SshConfigCandidate[];
319
- readonly skipped: readonly SshConfigSkipped[];
320
- }
321
- /**
322
- * Narrow the host's `list-ssh-config` answer.
323
- *
324
- * Defensive in the same way {@link parseRootsSnapshot} is: the client never trusts the wire's shape, and a row
325
- * without a usable alias or id is **dropped** rather than rendered as a checkbox that would import nothing.
326
- *
327
- * @param value - the parsed JSON body.
328
- * @returns the listing, with unusable rows removed.
329
- */
330
- export declare function parseSshConfigListing(value: unknown): SshConfigListing;
331
- /**
332
- * The human-readable reason one alias was refused.
333
- *
334
- * The mapping lives here rather than in the view so the reasons are testable without rendering, and an unknown
335
- * reason from a newer host still produces a sentence instead of an empty cell.
336
- *
337
- * @param reason - the machine-readable reason.
338
- * @returns a message key.
339
- */
340
- export declare function sshConfigSkipKey(reason: string): string;
341
- /** A candidate's one-line summary: `user@host:port`, with the flags that matter for the import. */
342
- export declare function sshConfigCandidateSummary(candidate: SshConfigCandidate): string;
343
- /**
344
- * The reason a failed response reports, when the host did not name one.
345
- *
346
- * The path is part of the text on purpose: `http-404` alone cannot distinguish "this route is not
347
- * registered" (the host half never claimed it — that was a real defect) from "the request never reached the
348
- * host" (the shell's proxy or the composition is at fault). Both are shown verbatim in the panel's notice.
349
- *
350
- * @param status - the HTTP status.
351
- * @param path - the exact URL that was requested.
352
- * @returns the machine-readable reason.
353
- */
354
- export declare function transportFailureReason(status: number, path: string): string;
355
- /**
356
- * Bind the route to the panel's two operations.
357
- *
358
- * @param fetchImpl - `window.fetch` in the app, a stub in tests.
359
- * @param path - override for tests; defaults to {@link ROOTS_API_PATH}.
360
- * @returns the calls the component uses.
361
- */
362
- export declare function createRootsApi(fetchImpl: FetchLike, path?: string): RootsApi;
363
- /** The field names the panel must never send, asserted by tests rather than trusted to reviewers. */
364
- export declare function isForbiddenField(name: string): boolean;
@@ -1,35 +0,0 @@
1
- /**
2
- * 传输面板:进度列表、**长轮询驱动**与文案。
3
- *
4
- * 从 `roots-panel-view.ts` 抽出(审计 0076);task-19 把定时轮询换成「回答一到就再问一次」的
5
- * 长轮询循环,循环本身在 `transfer-progress.ts`(可注入时钟,因此可在离线测试里驱动)。
6
- *
7
- * 容器只做三件事:建 io、把 controller 挂到 React 生命周期、把状态与回调交给纯模板。
8
- *
9
- * @module dsh-over-ssh/client/transfer-panel-view
10
- */
11
- import { type ReactNode } from 'react';
12
- import type { RootsPanelCopy, RootsPanelState } from './roots-panel-state.ts';
13
- /**
14
- * The panel, driven by {@link createTransferProgress}.
15
- *
16
- * `state` is accepted but not read: the panel's own snapshot is what it renders, and the roots state it used to
17
- * receive was never read either (the prop is kept so the section's call site does not have to change).
18
- *
19
- * @param props - the dictionary (the section's `state` is unused).
20
- * @returns the transfer section.
21
- */
22
- export declare function TransferProgressSection({ copy }: {
23
- readonly copy: RootsPanelCopy;
24
- readonly state?: RootsPanelState;
25
- }): ReactNode;
26
- /**
27
- * The tree's strings, read out of the panel's dictionary.
28
- *
29
- * Kept as an explicit projection rather than passing the dictionary itself: the tree's copy type is its own
30
- * contract, so a key it needs and the dictionary does not have is a compile error here, not a blank label at
31
- * runtime.
32
- *
33
- * @param copy - the panel's dictionary.
34
- * @returns the tree's strings.
35
- */
@@ -1,241 +0,0 @@
1
- /**
2
- * The transfer progress panel (FR-2.6.4): rate, bytes done, time left, and the failure list.
3
- *
4
- * The client **polls** rather than subscribes: this plugin's client half talks to the host over one HTTP route,
5
- * so "what is transferring" is a question it asks. Polling only while the panel is mounted is what keeps that
6
- * from being a permanent request loop.
7
- *
8
- * Two rules it inherits from the rest of this half:
9
- *
10
- * - the answer is narrowed field by field (the wire is not trusted), and
11
- * - **nothing is inferred**: an unknown status is `running`, an unknown direction is `upload`, and a missing
12
- * `remainingMs` renders as "unknown" rather than as a guess.
13
- *
14
- * @module dsh-over-ssh/client/transfer-panel
15
- */
16
- import { type ReactNode } from 'react';
17
- import { type MessageKey } from '../core/message.ts';
18
- import { type ApiResult, type FetchLike } from './roots-panel.ts';
19
- /** One tracked transfer, as the panel renders it. */
20
- export interface TransferRow {
21
- readonly id: string;
22
- readonly direction: 'upload' | 'download';
23
- readonly connectionId: string;
24
- readonly remotePath: string;
25
- readonly localPath: string;
26
- readonly status: 'running' | 'done' | 'failed' | 'cancelled';
27
- readonly transferredBytes: number;
28
- readonly totalBytes: number;
29
- readonly percent: number;
30
- readonly bytesPerSecond: number;
31
- readonly elapsedMs: number;
32
- readonly remainingMs: number | null;
33
- readonly error?: string;
34
- readonly errorCode?: string;
35
- }
36
- /** How one snapshot may be asked for. */
37
- export interface TransferListOptions {
38
- /**
39
- * Host-side long-poll budget: the host parks the request until the table changes or this many milliseconds pass.
40
- *
41
- * Omitted/0 answers immediately (the pre-task-19 behaviour) — which is what a **user refresh** wants, because the
42
- * user is waiting for an answer now, not for a change.
43
- */
44
- readonly waitMs?: number;
45
- /** Abort the request (panel unmount, or a user refresh superseding a parked one). */
46
- readonly signal?: AbortSignal;
47
- }
48
- /** The calls the panel makes. Injected, so the model is testable with no server. */
49
- export interface TransferPanelIo {
50
- list(options?: TransferListOptions): Promise<ApiResult<readonly TransferRow[]>>;
51
- clear(): Promise<ApiResult<number>>;
52
- }
53
- /**
54
- * Narrow one `list-transfers` answer.
55
- *
56
- * @param value - the parsed body.
57
- * @returns the rows, with unusable entries dropped.
58
- */
59
- export declare function parseTransferRows(value: unknown): readonly TransferRow[];
60
- /**
61
- * The rows still worth showing: everything running, plus the most recent finished ones.
62
- *
63
- * A cap on finished rows because the requirement's "failure list" is a list of *recent* problems; the host keeps
64
- * the full history, and the panel's job is to be readable.
65
- *
66
- * @param rows - every row the host reported.
67
- * @param finishedLimit - how many finished rows to keep.
68
- * @returns the rows to render, running first.
69
- */
70
- export declare function visibleTransfers(rows: readonly TransferRow[], finishedLimit?: number): readonly TransferRow[];
71
- /**
72
- * One row's one-line summary.
73
- *
74
- * @param row - the transfer.
75
- * @param format - the byte/duration formatters plus the localized direction label (injected so this stays free of
76
- * the domain layer).
77
- * @param copy - the sentence templates; see {@link TransferPanelCopy.summaryRunning}.
78
- * @returns e.g. `上传 40.0%(4.0 KiB/10.0 KiB,2.0 KiB/s,剩余约 3.0s)` in Chinese, and the English template in `en`.
79
- */
80
- export declare function transferSummary(row: TransferRow, format: {
81
- readonly bytes: (value: number) => string;
82
- readonly duration: (value: number) => string;
83
- readonly label: string;
84
- }, copy: Pick<TransferPanelCopy, 'summaryRunning' | 'summaryDone' | 'remaining' | 'remainingUnknown'>): string;
85
- /**
86
- * The accessible name of one transfer's progress bar.
87
- *
88
- * Separate from {@link transferSummary} on purpose. The summary is the row's *visible* line, and it
89
- * deliberately leaves the path out because the path is rendered next to it; a `<progress>` element has no
90
- * other text of its own, so a screen reader user needs the path **inside** the bar's name. Every bar used to
91
- * carry `copy.title` ("传输进度"), which meant N bars with N identical names and no way to tell which transfer
92
- * was which.
93
- *
94
- * The target falls back to the local path when the remote one is empty, so a row can never end up with a name
95
- * that is only a percentage.
96
- *
97
- * @param row - the transfer.
98
- * @param format - the label plus the byte/duration formatters.
99
- * @returns e.g. `上传 40.0%(400B/1000B,200B/s,剩余约 3000ms) — /srv/a.bin`.
100
- */
101
- export declare function transferProgressLabel(row: TransferRow, format: {
102
- readonly bytes: (value: number) => string;
103
- readonly duration: (value: number) => string;
104
- readonly label: string;
105
- }, copy: Pick<TransferPanelCopy, 'summaryRunning' | 'summaryDone' | 'remaining' | 'remainingUnknown'>): string;
106
- /** Everything the panel renders from. */
107
- export interface TransferPanelState {
108
- readonly rows: readonly TransferRow[];
109
- /**
110
- * Why the last list read failed, **as a key the view resolves**.
111
- *
112
- * It used to be the raw transport string, so the failure notice printed `http-500 @ /api/over-ssh/roots`
113
- * or `TypeError` — machine text, in both locales, naming an internal URL (audit 2026-11-01b ui P2-5).
114
- */
115
- readonly error?: MessageKey;
116
- readonly loading: boolean;
117
- /** True while the user's 「清除」 request is in flight, so the two buttons can be disabled. */
118
- readonly clearing: boolean;
119
- /** Why the last 「清除」 failed, as a key. Kept apart from {@link TransferPanelState.error}: one is a read, the
120
- * other a command, and they need different sentences. */
121
- readonly clearError?: MessageKey;
122
- }
123
- /**
124
- * Turn one transport-level failure string into a sentence key.
125
- *
126
- * The three cases are the three things the panel can honestly say (audit 2026-11-01b ui P2-5):
127
- *
128
- * - a status the route answered → 「…(HTTP {status})」, the number is the useful part;
129
- * - a transport spelling (`network`, `TypeError`, `AbortError`, `http-500 @ /api/…`) → carrying no information at
130
- * all, so the panel says the host did not answer instead of printing the spelling;
131
- * - anything else is the **host's own** explanation (this route answers prose for a refused capability), and that
132
- * is the one case worth quoting.
133
- *
134
- * @param reason - the failure string the io produced.
135
- * @returns the dictionary key plus its parameters.
136
- */
137
- export declare function transferFailureKey(reason: string): MessageKey;
138
- /** @returns the starting state. */
139
- export declare function initialTransferPanelState(): TransferPanelState;
140
- /** A poll started. */
141
- export declare function transferPanelLoading(state: TransferPanelState): TransferPanelState;
142
- /** A poll answered. */
143
- export declare function transferPanelLoaded(state: TransferPanelState, rows: readonly TransferRow[]): TransferPanelState;
144
- /** A poll failed; the **last known rows stay** (a blip must not blank the panel). */
145
- export declare function transferPanelFailed(state: TransferPanelState, error: string): TransferPanelState;
146
- /** The user pressed 「清除」: the request is in flight and the previous clear failure is superseded. */
147
- export declare function transferPanelClearing(state: TransferPanelState): TransferPanelState;
148
- /** 「清除」 succeeded: the next poll shows the empty table. */
149
- export declare function transferPanelCleared(state: TransferPanelState): TransferPanelState;
150
- /**
151
- * 「清除」 failed.
152
- *
153
- * It used to be dropped on the floor (`void io.clear().then(...)` had no failure branch), so the button did
154
- * nothing at all on a refusal and the user could click it again and again (audit 2026-11-01b ui P2-5).
155
- */
156
- export declare function transferPanelClearFailed(state: TransferPanelState, error: string): TransferPanelState;
157
- /**
158
- * The state a poll starts from.
159
- *
160
- * `showLoading` separates the user's own action from the timer, and that distinction is the whole point: the
161
- * view renders a **visible** "reading…" notice for `loading` (`TransferPanelSection`), so a background poll
162
- * that raised it made the panel alternate between "reading…" and "nothing is transferring" once a second while
163
- * nothing was happening (audit 0057). Only opening the panel or pressing refresh asks to be seen doing it.
164
- *
165
- * A named function rather than an `if` inside the component because this repository's tests render no hooks: a
166
- * branch buried in a `useEffect` cannot be executed by a test, so the decision lives out here where it can be
167
- * (removing that branch used to leave every test green).
168
- *
169
- * @param state - the current panel state.
170
- * @param showLoading - `true` when the user asked for this poll.
171
- * @returns the state to render while the request is in flight.
172
- */
173
- export declare function transferPanelPollStarted(state: TransferPanelState, showLoading: boolean): TransferPanelState;
174
- /**
175
- * Bind the panel to the route.
176
- *
177
- * @param fetchImpl - `window.fetch` in the app, a stub in tests.
178
- * @param path - override for tests.
179
- * @returns the two calls the panel makes.
180
- */
181
- export declare function createTransferPanelIo(fetchImpl: FetchLike, path?: string): TransferPanelIo;
182
- /** The strings the panel renders. */
183
- export interface TransferPanelCopy {
184
- readonly title: string;
185
- /** The panel's description line (it existed as a locale key and was never rendered). */
186
- readonly description: string;
187
- readonly empty: string;
188
- readonly loading: string;
189
- readonly refresh: string;
190
- readonly clear: string;
191
- readonly failedTitle: string;
192
- readonly cancelled: string;
193
- readonly done: string;
194
- readonly uploadLabel: string;
195
- readonly downloadLabel: string;
196
- /**
197
- * The one-line summary templates, with `{label} {percent} {done} {total} {rate} {elapsed} {remaining}`.
198
- *
199
- * Templates rather than a sentence spelled here: this module is a pure model, and the sentence it used to
200
- * build carried Chinese full-width punctuation (`%(…,…)`) into the English UI. The dictionaries own the
201
- * sentence now; the numbers stay here.
202
- */
203
- readonly summaryRunning: string;
204
- readonly summaryDone: string;
205
- /** `剩余约 {duration}` */
206
- readonly remaining: string;
207
- /** `剩余时间未知` */
208
- readonly remainingUnknown: string;
209
- /** What a duration formatter prints when it was handed a value it cannot render. */
210
- readonly durationUnknown: string;
211
- /** Resolve one deferred failure message against the active dictionary. */
212
- readonly resolve: (message: MessageKey) => string;
213
- /** One failed transfer's line: `{path}{sep}{reason}` — the punctuation belongs to the locale. */
214
- readonly failureLine: string;
215
- /** The badge text when the host gave no `errorCode` (it used to be the English literal `error`). */
216
- readonly errorCodeUnknown: string;
217
- }
218
- /** What the panel's controls call. */
219
- export interface TransferPanelActions {
220
- readonly onRefresh: () => void;
221
- readonly onClear: () => void;
222
- }
223
- /**
224
- * The panel's markup.
225
- *
226
- * A `<progress>` element for the bar rather than a styled `div`: it is the platform's own primitive, it is
227
- * announced correctly by a screen reader, and it needs no CSS to be a progress bar at all (which matters here,
228
- * because this plugin's stylesheet is injected and can be missing in a composition that never loaded it).
229
- *
230
- * @param props - state, copy, formatters and callbacks.
231
- * @returns the panel section.
232
- */
233
- export declare function TransferPanelSection(props: {
234
- readonly copy: TransferPanelCopy;
235
- readonly state: TransferPanelState;
236
- readonly actions: TransferPanelActions;
237
- readonly format: {
238
- readonly bytes: (value: number) => string;
239
- readonly duration: (value: number) => string;
240
- };
241
- }): ReactNode;