dsh-over-ssh 0.1.0 → 0.1.1

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 (247) hide show
  1. package/README.md +10 -10
  2. package/agents/agent-manifest.json +1 -1
  3. package/lib/client.js +363 -314
  4. package/lib/file-references.js +80 -35
  5. package/lib/fs.js +158 -75
  6. package/lib/index.js +4103 -3397
  7. package/lib/terminal-wrapper.js +9 -4
  8. package/lib/types/src/config.d.ts +80 -9
  9. package/lib/types/src/file-references.d.ts +0 -1
  10. package/lib/types/src/host/app/backend.d.ts +9 -1
  11. package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +0 -1
  12. package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +0 -1
  13. package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +12 -1
  14. package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +0 -1
  15. package/lib/types/src/host/app/transport/transport-selector.d.ts +0 -1
  16. package/lib/types/src/host/app/transport/transport-service.d.ts +0 -1
  17. package/lib/types/src/host/domain/capability.d.ts +0 -1
  18. package/lib/types/src/host/domain/errors.d.ts +0 -1
  19. package/lib/types/src/host/domain/roots/connection.d.ts +0 -1
  20. package/lib/types/src/host/domain/roots/root.d.ts +24 -9
  21. package/lib/types/src/host/domain/roots/session-roots.d.ts +0 -1
  22. package/lib/types/src/host/domain/roots/ssh-config.d.ts +0 -1
  23. package/lib/types/src/host/domain/session/session-admin.d.ts +0 -1
  24. package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +0 -1
  25. package/lib/types/src/host/domain/transfer/transfer.d.ts +0 -1
  26. package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +0 -1
  27. package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +0 -1
  28. package/lib/types/src/host/infra/transport/transport-base.d.ts +0 -1
  29. package/lib/types/src/host/infra/transport/transport.d.ts +16 -2
  30. package/lib/types/src/host/integration/agent/agent-preflight.d.ts +0 -1
  31. package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +0 -1
  32. package/lib/types/src/host/integration/fs/fs-provider.d.ts +0 -2
  33. package/lib/types/src/host/integration/transfer/transfer-service.d.ts +0 -1
  34. package/lib/types/src/index.d.ts +23 -1
  35. package/lib/types/src/local-bridge-refs.d.ts +0 -1
  36. package/lib/types/src/local-bridge.d.ts +0 -1
  37. package/lib/types/src/shared/root-keys.d.ts +120 -0
  38. package/lib/types/src/wiring/helpers.d.ts +0 -1
  39. package/package.json +5 -3
  40. package/lib/types/src/client/controls.d.ts +0 -131
  41. package/lib/types/src/client/core/card-focus-trap.d.ts +0 -45
  42. package/lib/types/src/client/core/controls.d.ts +0 -164
  43. package/lib/types/src/client/core/latest.d.ts +0 -48
  44. package/lib/types/src/client/core/locale.d.ts +0 -38
  45. package/lib/types/src/client/core/message.d.ts +0 -37
  46. package/lib/types/src/client/core/panel-model.d.ts +0 -134
  47. package/lib/types/src/client/core/portal.d.ts +0 -266
  48. package/lib/types/src/client/core/remote.d.ts +0 -84
  49. package/lib/types/src/client/core/styles.d.ts +0 -458
  50. package/lib/types/src/client/file-tree-io.d.ts +0 -19
  51. package/lib/types/src/client/file-tree.d.ts +0 -271
  52. package/lib/types/src/client/flows/directory-browser.d.ts +0 -587
  53. package/lib/types/src/client/flows/session-directory.d.ts +0 -415
  54. package/lib/types/src/client/flows/workspace-directory-flow.d.ts +0 -480
  55. package/lib/types/src/client/locale.d.ts +0 -31
  56. package/lib/types/src/client/panel-model.d.ts +0 -37
  57. package/lib/types/src/client/panel.d.ts +0 -77
  58. package/lib/types/src/client/roots/connection-test.d.ts +0 -69
  59. package/lib/types/src/client/roots/roots-panel-state.d.ts +0 -307
  60. package/lib/types/src/client/roots/roots-panel-view.d.ts +0 -280
  61. package/lib/types/src/client/roots/roots-panel.d.ts +0 -364
  62. package/lib/types/src/client/roots/transfer-panel-view.d.ts +0 -35
  63. package/lib/types/src/client/roots/transfer-panel.d.ts +0 -241
  64. package/lib/types/src/client/roots/transfer-progress.d.ts +0 -67
  65. package/lib/types/src/client/roots-panel-view.d.ts +0 -236
  66. package/lib/types/src/client/roots-panel.d.ts +0 -347
  67. package/lib/types/src/client/session/session-panel-view.d.ts +0 -336
  68. package/lib/types/src/client/session/session-panel.d.ts +0 -322
  69. package/lib/types/src/client/session/session-remote-mark.d.ts +0 -110
  70. package/lib/types/src/client/session/session-row-actions.d.ts +0 -398
  71. package/lib/types/src/client/session-panel.d.ts +0 -152
  72. package/lib/types/src/client/styles.d.ts +0 -252
  73. package/lib/types/src/client/workspace-directory-flow.d.ts +0 -342
  74. package/lib/types/src/fs.d.ts +0 -46
  75. package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +0 -18
  76. package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +0 -38
  77. package/lib/types/src/host/app/recycle-bin-contract.d.ts +0 -215
  78. package/lib/types/src/host/app/recycle-bin-move.d.ts +0 -18
  79. package/lib/types/src/host/app/recycle-bin-service.d.ts +0 -45
  80. package/lib/types/src/host/app/recycle-bin-world.d.ts +0 -35
  81. package/lib/types/src/host/app/roots/roots-api-connections.d.ts +0 -49
  82. package/lib/types/src/host/app/roots/roots-api-contract.d.ts +0 -369
  83. package/lib/types/src/host/app/roots/roots-api-directories.d.ts +0 -13
  84. package/lib/types/src/host/app/roots/roots-api-http.d.ts +0 -77
  85. package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +0 -130
  86. package/lib/types/src/host/app/roots/roots-api-projections.d.ts +0 -102
  87. package/lib/types/src/host/app/roots/roots-api-roots.d.ts +0 -13
  88. package/lib/types/src/host/app/roots/roots-api-state.d.ts +0 -22
  89. package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +0 -26
  90. package/lib/types/src/host/app/roots/roots-api.d.ts +0 -55
  91. package/lib/types/src/host/app/roots/user-roots.d.ts +0 -552
  92. package/lib/types/src/host/app/roots-api-connections.d.ts +0 -49
  93. package/lib/types/src/host/app/roots-api-contract.d.ts +0 -339
  94. package/lib/types/src/host/app/roots-api-directories.d.ts +0 -13
  95. package/lib/types/src/host/app/roots-api-http.d.ts +0 -77
  96. package/lib/types/src/host/app/roots-api-projections.d.ts +0 -102
  97. package/lib/types/src/host/app/roots-api-roots.d.ts +0 -13
  98. package/lib/types/src/host/app/roots-api-state.d.ts +0 -22
  99. package/lib/types/src/host/app/roots-api-support.d.ts +0 -182
  100. package/lib/types/src/host/app/roots-api-transfers.d.ts +0 -13
  101. package/lib/types/src/host/app/roots-api.d.ts +0 -55
  102. package/lib/types/src/host/app/routed-fs.d.ts +0 -239
  103. package/lib/types/src/host/app/serialize.d.ts +0 -37
  104. package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +0 -36
  105. package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +0 -34
  106. package/lib/types/src/host/app/session-admin-context.d.ts +0 -36
  107. package/lib/types/src/host/app/session-admin-contract.d.ts +0 -214
  108. package/lib/types/src/host/app/session-admin-delete.d.ts +0 -34
  109. package/lib/types/src/host/app/session-admin-service.d.ts +0 -38
  110. package/lib/types/src/host/app/takeover/takeover-plan.d.ts +0 -227
  111. package/lib/types/src/host/app/takeover/takeover-rows.d.ts +0 -55
  112. package/lib/types/src/host/app/transport-selector.d.ts +0 -98
  113. package/lib/types/src/host/app/transport-service.d.ts +0 -75
  114. package/lib/types/src/host/app/user-roots.d.ts +0 -461
  115. package/lib/types/src/host/domain/bin-location.d.ts +0 -79
  116. package/lib/types/src/host/domain/connection.d.ts +0 -95
  117. package/lib/types/src/host/domain/glob-pattern.d.ts +0 -62
  118. package/lib/types/src/host/domain/literal-text.d.ts +0 -63
  119. package/lib/types/src/host/domain/path-grammar.d.ts +0 -84
  120. package/lib/types/src/host/domain/recycle-bin.d.ts +0 -337
  121. package/lib/types/src/host/domain/root.d.ts +0 -74
  122. package/lib/types/src/host/domain/roots/bin-location.d.ts +0 -91
  123. package/lib/types/src/host/domain/roots/route-table.d.ts +0 -102
  124. package/lib/types/src/host/domain/route-table.d.ts +0 -80
  125. package/lib/types/src/host/domain/session/terminal-target.d.ts +0 -136
  126. package/lib/types/src/host/domain/session-admin.d.ts +0 -330
  127. package/lib/types/src/host/domain/shell.d.ts +0 -47
  128. package/lib/types/src/host/domain/ssh-config.d.ts +0 -73
  129. package/lib/types/src/host/domain/terminal-target.d.ts +0 -132
  130. package/lib/types/src/host/domain/tool-requests.d.ts +0 -369
  131. package/lib/types/src/host/domain/transfer.d.ts +0 -260
  132. package/lib/types/src/host/infra/backend/local-backend.d.ts +0 -84
  133. package/lib/types/src/host/infra/backend/local-version.d.ts +0 -43
  134. package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +0 -89
  135. package/lib/types/src/host/infra/backend/remote-backend.d.ts +0 -239
  136. package/lib/types/src/host/infra/cache/lru.d.ts +0 -52
  137. package/lib/types/src/host/infra/cache/versioned.d.ts +0 -77
  138. package/lib/types/src/host/infra/temp-name.d.ts +0 -19
  139. package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +0 -132
  140. package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +0 -140
  141. package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +0 -41
  142. package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +0 -197
  143. package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +0 -564
  144. package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +0 -418
  145. package/lib/types/src/host/infra/transport/agent-installer.d.ts +0 -121
  146. package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +0 -123
  147. package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +0 -131
  148. package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +0 -41
  149. package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +0 -197
  150. package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +0 -509
  151. package/lib/types/src/host/infra/transport/agent-transport.d.ts +0 -353
  152. package/lib/types/src/host/infra/transport/alias-transport.d.ts +0 -206
  153. package/lib/types/src/host/infra/transport/askpass-fs.d.ts +0 -20
  154. package/lib/types/src/host/infra/transport/askpass.d.ts +0 -133
  155. package/lib/types/src/host/infra/transport/command-pool.d.ts +0 -61
  156. package/lib/types/src/host/infra/transport/fs-commands.d.ts +0 -140
  157. package/lib/types/src/host/infra/transport/git-status-route.d.ts +0 -63
  158. package/lib/types/src/host/infra/transport/git-status.d.ts +0 -101
  159. package/lib/types/src/host/infra/transport/node-spawn.d.ts +0 -26
  160. package/lib/types/src/host/infra/transport/session-window.d.ts +0 -51
  161. package/lib/types/src/host/infra/transport/shell-quote.d.ts +0 -59
  162. package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +0 -206
  163. package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +0 -20
  164. package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +0 -133
  165. package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +0 -62
  166. package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +0 -43
  167. package/lib/types/src/host/infra/transport/ssh-argv.d.ts +0 -43
  168. package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +0 -302
  169. package/lib/types/src/host/integration/agent/agent-install-io.d.ts +0 -33
  170. package/lib/types/src/host/integration/agent/askpass-registry.d.ts +0 -66
  171. package/lib/types/src/host/integration/agent-install-io.d.ts +0 -33
  172. package/lib/types/src/host/integration/agent-preflight.d.ts +0 -56
  173. package/lib/types/src/host/integration/anchor-directory.d.ts +0 -56
  174. package/lib/types/src/host/integration/askpass-registry.d.ts +0 -59
  175. package/lib/types/src/host/integration/bin-root-resolver.d.ts +0 -81
  176. package/lib/types/src/host/integration/connection-test.d.ts +0 -73
  177. package/lib/types/src/host/integration/file-reference-provider.d.ts +0 -115
  178. package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +0 -21
  179. package/lib/types/src/host/integration/fs/platform-errors.d.ts +0 -94
  180. package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +0 -369
  181. package/lib/types/src/host/integration/fs/remote-index.d.ts +0 -177
  182. package/lib/types/src/host/integration/fs-provider.d.ts +0 -226
  183. package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +0 -38
  184. package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +0 -67
  185. package/lib/types/src/host/integration/lsp/lsp-session.d.ts +0 -130
  186. package/lib/types/src/host/integration/management-tools.d.ts +0 -64
  187. package/lib/types/src/host/integration/recycle-bin-service.d.ts +0 -247
  188. package/lib/types/src/host/integration/remote/connection-test.d.ts +0 -73
  189. package/lib/types/src/host/integration/remote/remote-face.d.ts +0 -127
  190. package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +0 -108
  191. package/lib/types/src/host/integration/remote-face.d.ts +0 -127
  192. package/lib/types/src/host/integration/remote-file-tools.d.ts +0 -241
  193. package/lib/types/src/host/integration/remote-home.d.ts +0 -69
  194. package/lib/types/src/host/integration/remote-index.d.ts +0 -165
  195. package/lib/types/src/host/integration/remote-job-tools.d.ts +0 -295
  196. package/lib/types/src/host/integration/remote-tool-backend.d.ts +0 -97
  197. package/lib/types/src/host/integration/remote-tools.d.ts +0 -206
  198. package/lib/types/src/host/integration/root-registry.d.ts +0 -115
  199. package/lib/types/src/host/integration/roots/anchor-directory.d.ts +0 -56
  200. package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +0 -81
  201. package/lib/types/src/host/integration/roots/remote-home.d.ts +0 -69
  202. package/lib/types/src/host/integration/roots/root-registry.d.ts +0 -144
  203. package/lib/types/src/host/integration/roots/roots-wiring.d.ts +0 -219
  204. package/lib/types/src/host/integration/roots/session-root-index.d.ts +0 -52
  205. package/lib/types/src/host/integration/roots-api.d.ts +0 -407
  206. package/lib/types/src/host/integration/roots-wiring.d.ts +0 -211
  207. package/lib/types/src/host/integration/session/session-api.d.ts +0 -88
  208. package/lib/types/src/host/integration/session/session-bin-lock.d.ts +0 -80
  209. package/lib/types/src/host/integration/session/session-header.d.ts +0 -151
  210. package/lib/types/src/host/integration/session/session-labels.d.ts +0 -56
  211. package/lib/types/src/host/integration/session/session-route-wiring.d.ts +0 -50
  212. package/lib/types/src/host/integration/session-admin-service.d.ts +0 -236
  213. package/lib/types/src/host/integration/session-api.d.ts +0 -81
  214. package/lib/types/src/host/integration/session-bin-lock.d.ts +0 -80
  215. package/lib/types/src/host/integration/session-header.d.ts +0 -118
  216. package/lib/types/src/host/integration/session-labels.d.ts +0 -56
  217. package/lib/types/src/host/integration/session-route-wiring.d.ts +0 -50
  218. package/lib/types/src/host/integration/session-scope.d.ts +0 -113
  219. package/lib/types/src/host/integration/session-tools.d.ts +0 -44
  220. package/lib/types/src/host/integration/strict-arguments.d.ts +0 -79
  221. package/lib/types/src/host/integration/takeover/takeover-api.d.ts +0 -62
  222. package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +0 -60
  223. package/lib/types/src/host/integration/takeover/takeover-service.d.ts +0 -167
  224. package/lib/types/src/host/integration/terminal-manifest.d.ts +0 -94
  225. package/lib/types/src/host/integration/tools/management-tools.d.ts +0 -245
  226. package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +0 -316
  227. package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +0 -43
  228. package/lib/types/src/host/integration/tools/remote-tools.d.ts +0 -236
  229. package/lib/types/src/host/integration/tools/session-target.d.ts +0 -52
  230. package/lib/types/src/host/integration/tools/session-tools.d.ts +0 -46
  231. package/lib/types/src/host/integration/tools/strict-arguments.d.ts +0 -79
  232. package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +0 -94
  233. package/lib/types/src/host/integration/transfer/remote-copy.d.ts +0 -66
  234. package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +0 -48
  235. package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +0 -106
  236. package/lib/types/src/host/integration/transfer-adapters.d.ts +0 -44
  237. package/lib/types/src/host/integration/transfer-service.d.ts +0 -164
  238. package/lib/types/src/host/integration/transfer-tracker.d.ts +0 -106
  239. package/lib/types/src/host/integration/user-roots.d.ts +0 -461
  240. package/lib/types/src/wiring/connections.d.ts +0 -189
  241. package/lib/types/src/wiring/fs.d.ts +0 -13
  242. package/lib/types/src/wiring/install-memo.d.ts +0 -32
  243. package/lib/types/src/wiring/legacy-dirs.d.ts +0 -62
  244. package/lib/types/src/wiring/package-paths.d.ts +0 -35
  245. package/lib/types/src/wiring/roots.d.ts +0 -21
  246. package/lib/types/src/wiring/state-dir.d.ts +0 -29
  247. package/lib/types/src/wiring/tools.d.ts +0 -35
package/lib/client.js CHANGED
@@ -510,7 +510,7 @@ var OVER_SSH_DICTS = {
510
510
  };
511
511
 
512
512
  // src/client/panel.ts
513
- var import_react7 = require("react");
513
+ var import_react6 = require("react");
514
514
 
515
515
  // src/client/core/controls.ts
516
516
  var import_react = require("react");
@@ -1356,29 +1356,7 @@ function Checkbox(props) {
1356
1356
  }
1357
1357
 
1358
1358
  // src/client/roots/roots-panel-view.ts
1359
- var import_react6 = require("react");
1360
-
1361
- // src/shared/format.ts
1362
- var BYTE_UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
1363
- function formatBytes(bytes) {
1364
- if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
1365
- let value = bytes;
1366
- let unit = 0;
1367
- while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
1368
- value /= 1024;
1369
- unit += 1;
1370
- }
1371
- const label = BYTE_UNITS[unit] ?? "B";
1372
- return unit === 0 ? `${Math.round(value)} ${label}` : `${value.toFixed(1)} ${label}`;
1373
- }
1374
- function formatDuration(ms) {
1375
- if (!Number.isFinite(ms) || ms < 0) return "\u672A\u77E5";
1376
- if (ms < 1e3) return `${Math.round(ms)}ms`;
1377
- const seconds = ms / 1e3;
1378
- if (seconds < 60) return `${seconds.toFixed(1)}s`;
1379
- const minutes = Math.floor(seconds / 60);
1380
- return `${minutes}m${Math.round(seconds - minutes * 60)}s`;
1381
- }
1359
+ var import_react5 = require("react");
1382
1360
 
1383
1361
  // src/client/core/card-focus-trap.ts
1384
1362
  var import_react2 = require("react");
@@ -1526,8 +1504,67 @@ function useCardTabTrap(open, container) {
1526
1504
  }, [open, container]);
1527
1505
  }
1528
1506
 
1529
- // src/client/roots/roots-panel.ts
1507
+ // src/shared/routes.ts
1530
1508
  var ROOTS_API_PATH = "/api/over-ssh/roots";
1509
+ var SESSION_API_PATH = "/api/over-ssh/sessions";
1510
+
1511
+ // src/shared/root-keys.ts
1512
+ function isAbsoluteLocalPath(path) {
1513
+ return /^[A-Za-z]:[\\/]/.test(path) || path.startsWith("/") || path.startsWith("\\\\");
1514
+ }
1515
+ function normalizeLocalPath(path, options = {}) {
1516
+ const caseInsensitive = options.caseInsensitive ?? false;
1517
+ const unified = path.replace(/\\/g, "/");
1518
+ const unc = unified.startsWith("//");
1519
+ const posixAbsolute = unified.startsWith("/");
1520
+ const driveMatch = /^([A-Za-z]):/.exec(unified);
1521
+ const driveLetter = driveMatch?.[1];
1522
+ const drive = driveLetter === void 0 ? "" : `${driveLetter.toUpperCase()}:`;
1523
+ const rest = unc ? unified.slice(2) : drive === "" ? unified : unified.slice(2);
1524
+ const segments = [];
1525
+ for (const segment of rest.split("/")) {
1526
+ if (segment === "" || segment === ".") continue;
1527
+ if (segment === "..") {
1528
+ segments.pop();
1529
+ continue;
1530
+ }
1531
+ segments.push(segment);
1532
+ }
1533
+ const body = segments.join("/");
1534
+ let normalized;
1535
+ if (unc) normalized = `//${body}`;
1536
+ else if (drive !== "") normalized = body === "" ? `${drive}/` : `${drive}/${body}`;
1537
+ else if (posixAbsolute) normalized = body === "" ? "/" : `/${body}`;
1538
+ else normalized = body === "" ? "." : body;
1539
+ return caseInsensitive ? normalized.toLowerCase() : normalized;
1540
+ }
1541
+ function normalizePosixPath(path) {
1542
+ if (!path.startsWith("/")) return void 0;
1543
+ const segments = [];
1544
+ for (const segment of path.split("/")) {
1545
+ if (segment === "" || segment === ".") continue;
1546
+ if (segment === "..") {
1547
+ segments.pop();
1548
+ continue;
1549
+ }
1550
+ segments.push(segment);
1551
+ }
1552
+ return segments.length === 0 ? "/" : `/${segments.join("/")}`;
1553
+ }
1554
+ function posixDirname(normalized) {
1555
+ const index = normalized.lastIndexOf("/");
1556
+ return index <= 0 ? "/" : normalized.slice(0, index);
1557
+ }
1558
+ function rootKeySpelling(location, path, connectionId, options = {}) {
1559
+ if (location === "remote") return `remote:${connectionId ?? ""}:${normalizePosixPath(path) ?? path}`;
1560
+ return `local:${normalizeLocalPath(path, options)}`;
1561
+ }
1562
+ function rootKeyIdentity(key) {
1563
+ const lowered = key.toLowerCase();
1564
+ return lowered.startsWith("local:") ? lowered : key;
1565
+ }
1566
+
1567
+ // src/client/roots/roots-panel.ts
1531
1568
  function readString(source, key) {
1532
1569
  const value = source[key];
1533
1570
  return typeof value === "string" && value !== "" ? value : void 0;
@@ -1549,11 +1586,7 @@ function readBoolean(source, key) {
1549
1586
  return typeof value === "boolean" ? value : void 0;
1550
1587
  }
1551
1588
  function rootKey(location, path, connectionId) {
1552
- return location === "remote" ? `remote:${connectionId ?? ""}:${path}` : `local:${path}`;
1553
- }
1554
- function rootKeyIdentity(key) {
1555
- const lowered = key.toLowerCase();
1556
- return lowered.startsWith("local:") ? lowered : key;
1589
+ return rootKeySpelling(location, path, connectionId);
1557
1590
  }
1558
1591
  function toRootRow(value, source, inUserFile) {
1559
1592
  if (typeof value !== "object" || value === null) return void 0;
@@ -1654,9 +1687,6 @@ function parseRootsSnapshot(value) {
1654
1687
  const anchorRoot = readString(payload, "anchorRoot");
1655
1688
  return { roots, connections, warnings, ...anchorRoot === void 0 ? {} : { anchorRoot } };
1656
1689
  }
1657
- function isAbsoluteLocalPath(path) {
1658
- return /^[A-Za-z]:[\\/]/.test(path) || path.startsWith("/") || path.startsWith("\\\\");
1659
- }
1660
1690
  function isAbsoluteRemotePath(path) {
1661
1691
  if (!path.startsWith("/")) return false;
1662
1692
  return !path.split("/").includes("..");
@@ -1671,7 +1701,8 @@ function validateRootDraft(draft, existing = []) {
1671
1701
  return { ok: false, message: { key: "rootsErrorLocalPath" } };
1672
1702
  }
1673
1703
  const key = rootKey(draft.location, path, draft.location === "remote" ? draft.connectionId : void 0);
1674
- if (existing.some((row) => row.key === key)) return { ok: false, message: { key: "rootsErrorDuplicate" } };
1704
+ const identity = rootKeyIdentity(key);
1705
+ if (existing.some((row) => rootKeyIdentity(row.key) === identity)) return { ok: false, message: { key: "rootsErrorDuplicate" } };
1675
1706
  const label = draft.label?.trim();
1676
1707
  return {
1677
1708
  ok: true,
@@ -1878,7 +1909,6 @@ function resolveMessage(copy, message) {
1878
1909
  }
1879
1910
 
1880
1911
  // src/client/roots/roots-panel-state.ts
1881
- var import_react3 = require("react");
1882
1912
  function emptyRootsPanelEditDrafts() {
1883
1913
  return { id: "", host: "", user: "", authKind: "key", keyPath: "", password: "", port: "" };
1884
1914
  }
@@ -2051,10 +2081,32 @@ function resolveMessage2(copy, message) {
2051
2081
  }
2052
2082
 
2053
2083
  // src/client/roots/transfer-panel-view.ts
2054
- var import_react5 = require("react");
2084
+ var import_react4 = require("react");
2085
+
2086
+ // src/shared/format.ts
2087
+ var BYTE_UNITS = ["B", "KiB", "MiB", "GiB", "TiB", "PiB"];
2088
+ function formatBytes(bytes) {
2089
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
2090
+ let value = bytes;
2091
+ let unit = 0;
2092
+ while (value >= 1024 && unit < BYTE_UNITS.length - 1) {
2093
+ value /= 1024;
2094
+ unit += 1;
2095
+ }
2096
+ const label = BYTE_UNITS[unit] ?? "B";
2097
+ return unit === 0 ? `${Math.round(value)} ${label}` : `${value.toFixed(1)} ${label}`;
2098
+ }
2099
+ function formatDuration(ms) {
2100
+ if (!Number.isFinite(ms) || ms < 0) return "\u672A\u77E5";
2101
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
2102
+ const seconds = ms / 1e3;
2103
+ if (seconds < 60) return `${seconds.toFixed(1)}s`;
2104
+ const minutes = Math.floor(seconds / 60);
2105
+ return `${minutes}m${Math.round(seconds - minutes * 60)}s`;
2106
+ }
2055
2107
 
2056
2108
  // src/client/roots/transfer-panel.ts
2057
- var import_react4 = require("react");
2109
+ var import_react3 = require("react");
2058
2110
  function parseTransferRows(value) {
2059
2111
  const document2 = typeof value === "object" && value !== null ? value : {};
2060
2112
  const rows = [];
@@ -2206,11 +2258,11 @@ function TransferPanelSection(props) {
2206
2258
  const { copy, state, actions, format } = props;
2207
2259
  const rows = visibleTransfers(state.rows);
2208
2260
  const failures = rows.filter((row) => row.status === "failed");
2209
- return (0, import_react4.createElement)(
2261
+ return (0, import_react3.createElement)(
2210
2262
  "section",
2211
2263
  { className: CLASS.stack, "aria-label": copy.title },
2212
- (0, import_react4.createElement)("h3", { className: CLASS.sectionTitle }, copy.title),
2213
- (0, import_react4.createElement)("p", { className: CLASS.description }, copy.description),
2264
+ (0, import_react3.createElement)("h3", { className: CLASS.sectionTitle }, copy.title),
2265
+ (0, import_react3.createElement)("p", { className: CLASS.description }, copy.description),
2214
2266
  state.error === void 0 ? null : Notice({ role: "alert", tone: "error", children: copy.resolve(state.error) }),
2215
2267
  state.clearError === void 0 ? null : Notice({ role: "alert", tone: "error", children: copy.resolve(state.clearError) }),
2216
2268
  state.loading ? Notice({ role: "status", tone: "info", children: copy.loading }) : null,
@@ -2221,17 +2273,17 @@ function TransferPanelSection(props) {
2221
2273
  * *and* the empty line tells the user both "we are reading" and "there is nothing" at once. `loading`
2222
2274
  * therefore suppresses the empty state.
2223
2275
  */
2224
- rows.length > 0 ? (0, import_react4.createElement)(
2276
+ rows.length > 0 ? (0, import_react3.createElement)(
2225
2277
  "ul",
2226
2278
  { className: CLASS.list },
2227
2279
  ...rows.map((row) => {
2228
2280
  const label = row.direction === "download" ? copy.downloadLabel : copy.uploadLabel;
2229
2281
  const summary = transferSummary(row, { bytes: format.bytes, duration: format.duration, label }, copy);
2230
- return (0, import_react4.createElement)(
2282
+ return (0, import_react3.createElement)(
2231
2283
  "li",
2232
2284
  { key: row.id, className: CLASS.row },
2233
- (0, import_react4.createElement)("span", { className: CLASS.rowTitle }, summary),
2234
- (0, import_react4.createElement)("span", { className: CLASS.rowMeta }, `${row.connectionId}:${row.remotePath}`),
2285
+ (0, import_react3.createElement)("span", { className: CLASS.rowTitle }, summary),
2286
+ (0, import_react3.createElement)("span", { className: CLASS.rowMeta }, `${row.connectionId}:${row.remotePath}`),
2235
2287
  /**
2236
2288
  * A native `<progress>` — the platform's own primitive, announced by a screen reader, and a
2237
2289
  * progress bar even if this stylesheet never loads — with its own class for geometry and accent.
@@ -2239,7 +2291,7 @@ function TransferPanelSection(props) {
2239
2291
  * The `aria-label` names **this** transfer (see {@link transferProgressLabel}): every row used to
2240
2292
  * carry `copy.title`, so a screen reader read N identical bars with no way to tell them apart.
2241
2293
  */
2242
- (0, import_react4.createElement)("progress", {
2294
+ (0, import_react3.createElement)("progress", {
2243
2295
  className: CLASS.progress,
2244
2296
  value: Math.round(row.percent * 10),
2245
2297
  max: 1e3,
@@ -2249,29 +2301,29 @@ function TransferPanelSection(props) {
2249
2301
  copy
2250
2302
  )
2251
2303
  }),
2252
- row.status === "running" ? null : (0, import_react4.createElement)(
2304
+ row.status === "running" ? null : (0, import_react3.createElement)(
2253
2305
  "code",
2254
2306
  { className: classes(CLASS.badge, row.status === "failed" ? CLASS.badgeError : row.status === "done" ? CLASS.badgeSuccess : void 0) },
2255
2307
  row.status === "done" ? copy.done : row.status === "cancelled" ? copy.cancelled : copy.failedTitle
2256
2308
  )
2257
2309
  );
2258
2310
  })
2259
- ) : state.loading ? null : (0, import_react4.createElement)("p", { className: CLASS.empty }, copy.empty),
2260
- failures.length === 0 ? null : (0, import_react4.createElement)(
2311
+ ) : state.loading ? null : (0, import_react3.createElement)("p", { className: CLASS.empty }, copy.empty),
2312
+ failures.length === 0 ? null : (0, import_react3.createElement)(
2261
2313
  "div",
2262
2314
  { className: CLASS.block },
2263
- (0, import_react4.createElement)("p", { className: CLASS.hint }, copy.failedTitle),
2264
- (0, import_react4.createElement)(
2315
+ (0, import_react3.createElement)("p", { className: CLASS.hint }, copy.failedTitle),
2316
+ (0, import_react3.createElement)(
2265
2317
  "ul",
2266
2318
  { className: CLASS.list },
2267
2319
  ...failures.map(
2268
- (row) => (0, import_react4.createElement)(
2320
+ (row) => (0, import_react3.createElement)(
2269
2321
  "li",
2270
2322
  { key: `${row.id}-error`, className: CLASS.row },
2271
- (0, import_react4.createElement)("code", { className: classes(CLASS.badge, CLASS.badgeError) }, row.errorCode ?? copy.errorCodeUnknown),
2323
+ (0, import_react3.createElement)("code", { className: classes(CLASS.badge, CLASS.badgeError) }, row.errorCode ?? copy.errorCodeUnknown),
2272
2324
  // The separator is the dictionary's business: a hard-coded full-width colon put CJK punctuation
2273
2325
  // into the English UI (audit 2026-11-01b ui P3-3).
2274
- (0, import_react4.createElement)(
2326
+ (0, import_react3.createElement)(
2275
2327
  "span",
2276
2328
  { className: CLASS.rowMeta },
2277
2329
  resolveTemplate(copy.failureLine, { path: row.remotePath, reason: row.error ?? "" })
@@ -2280,7 +2332,7 @@ function TransferPanelSection(props) {
2280
2332
  )
2281
2333
  )
2282
2334
  ),
2283
- (0, import_react4.createElement)(
2335
+ (0, import_react3.createElement)(
2284
2336
  "div",
2285
2337
  { className: CLASS.form },
2286
2338
  Button({ variant: "secondary", size: "compact", disabled: state.clearing, onClick: () => actions.onRefresh(), children: copy.refresh }),
@@ -2400,14 +2452,14 @@ function createTransferProgress(options) {
2400
2452
 
2401
2453
  // src/client/roots/transfer-panel-view.ts
2402
2454
  function TransferProgressSection({ copy }) {
2403
- const io = (0, import_react5.useMemo)(() => createTransferPanelIo((input, init) => globalThis.fetch(input, init)), []);
2404
- const progress = (0, import_react5.useMemo)(() => createTransferProgress({ io }), [io]);
2405
- const panel = (0, import_react5.useSyncExternalStore)(progress.subscribe, progress.getState);
2406
- (0, import_react5.useEffect)(() => {
2455
+ const io = (0, import_react4.useMemo)(() => createTransferPanelIo((input, init) => globalThis.fetch(input, init)), []);
2456
+ const progress = (0, import_react4.useMemo)(() => createTransferProgress({ io }), [io]);
2457
+ const panel = (0, import_react4.useSyncExternalStore)(progress.subscribe, progress.getState);
2458
+ (0, import_react4.useEffect)(() => {
2407
2459
  progress.start();
2408
2460
  return () => progress.dispose();
2409
2461
  }, [progress]);
2410
- (0, import_react5.useEffect)(injectControlStylesInBrowser, []);
2462
+ (0, import_react4.useEffect)(injectControlStylesInBrowser, []);
2411
2463
  const panelCopy = transferPanelCopy(copy);
2412
2464
  return TransferPanelSection({
2413
2465
  copy: panelCopy,
@@ -2708,15 +2760,15 @@ function renderFormSuccess(copy, state, scope) {
2708
2760
  return Notice({ role: "status", tone: "info", children: resolveMessage2(copy, saved.message) });
2709
2761
  }
2710
2762
  function renderRow(row, copy, editable, onRemove) {
2711
- return (0, import_react6.createElement)(
2763
+ return (0, import_react5.createElement)(
2712
2764
  "li",
2713
2765
  { key: row.key, className: CLASS.row },
2714
- (0, import_react6.createElement)("span", { className: CLASS.rowTitle }, row.title),
2715
- (0, import_react6.createElement)("code", { className: CLASS.badge }, resolveMessage2(copy, { key: row.badgeKey })),
2716
- (0, import_react6.createElement)("span", { className: CLASS.rowMeta }, row.subtitle),
2766
+ (0, import_react5.createElement)("span", { className: CLASS.rowTitle }, row.title),
2767
+ (0, import_react5.createElement)("code", { className: CLASS.badge }, resolveMessage2(copy, { key: row.badgeKey })),
2768
+ (0, import_react5.createElement)("span", { className: CLASS.rowMeta }, row.subtitle),
2717
2769
  // The one and only source of "may this be deleted": the view model's `removable`. A per-row destructive
2718
2770
  // action is the `ghost` variant, so the row stays quiet until the operator means it.
2719
- row.removable ? (0, import_react6.createElement)(
2771
+ row.removable ? (0, import_react5.createElement)(
2720
2772
  "span",
2721
2773
  { className: CLASS.rowActions },
2722
2774
  Button({ variant: "ghost", size: "compact", disabled: !editable, onClick: () => onRemove(row.key), children: copy.rootsRemove })
@@ -2733,7 +2785,7 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2733
2785
  const candidates = listing?.candidates ?? [];
2734
2786
  const showCandidates = candidates.length > 0;
2735
2787
  const showEmpty = listing !== void 0 && candidates.length === 0 && !busy && state.importError === void 0;
2736
- return (0, import_react6.createElement)(
2788
+ return (0, import_react5.createElement)(
2737
2789
  "section",
2738
2790
  {
2739
2791
  className: CLASS.block,
@@ -2750,8 +2802,8 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2750
2802
  actions.onImportClose();
2751
2803
  }
2752
2804
  },
2753
- (0, import_react6.createElement)("h4", { id: `${idPrefix}-import-title`, className: CLASS.blockTitle }, copy.rootsImportTitle),
2754
- (0, import_react6.createElement)("p", { className: CLASS.description }, copy.rootsImportDescription),
2805
+ (0, import_react5.createElement)("h4", { id: `${idPrefix}-import-title`, className: CLASS.blockTitle }, copy.rootsImportTitle),
2806
+ (0, import_react5.createElement)("p", { className: CLASS.description }, copy.rootsImportDescription),
2755
2807
  state.importError === void 0 ? null : Notice({ role: "alert", tone: "error", children: resolveMessage2(copy, { key: "rootsImportFailed", params: { message: state.importError } }) }),
2756
2808
  // A clean import says what it imported; a **partial** one says both numbers and then lists what was refused.
2757
2809
  //
@@ -2762,7 +2814,7 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2762
2814
  report === void 0 ? null : report.refused.length === 0 ? Notice({
2763
2815
  role: "status",
2764
2816
  children: resolveMessage2(copy, { key: "rootsImportDone", params: { count: report.imported.length, ids: report.imported.join(", ") } })
2765
- }) : (0, import_react6.createElement)(
2817
+ }) : (0, import_react5.createElement)(
2766
2818
  "div",
2767
2819
  { className: CLASS.block },
2768
2820
  Notice({
@@ -2772,15 +2824,15 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2772
2824
  params: { imported: report.imported.length, refused: report.refused.length }
2773
2825
  })
2774
2826
  }),
2775
- (0, import_react6.createElement)("p", { className: CLASS.hint }, copy.rootsImportRefusedTitle),
2776
- (0, import_react6.createElement)(
2827
+ (0, import_react5.createElement)("p", { className: CLASS.hint }, copy.rootsImportRefusedTitle),
2828
+ (0, import_react5.createElement)(
2777
2829
  "ul",
2778
2830
  { className: CLASS.list },
2779
2831
  ...report.refused.map(
2780
- (entry) => (0, import_react6.createElement)(
2832
+ (entry) => (0, import_react5.createElement)(
2781
2833
  "li",
2782
2834
  { key: entry.alias, className: CLASS.row },
2783
- (0, import_react6.createElement)(
2835
+ (0, import_react5.createElement)(
2784
2836
  "span",
2785
2837
  { className: CLASS.rowMeta },
2786
2838
  resolveMessage2(copy, { key: "rootsImportRefusedLine", params: { alias: entry.alias, reason: entry.reason } })
@@ -2792,11 +2844,11 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2792
2844
  busy ? Notice({ role: "status", tone: "info", children: copy.rootsImportBusy }) : null,
2793
2845
  // The candidate list. A checkbox per alias, with the id it will become spelled out. While a read is in flight
2794
2846
  // (or after it failed) the notice above is the whole story — see `showEmpty`.
2795
- showCandidates ? (0, import_react6.createElement)(
2847
+ showCandidates ? (0, import_react5.createElement)(
2796
2848
  "ul",
2797
2849
  { className: CLASS.list },
2798
2850
  ...candidates.map(
2799
- (candidate) => (0, import_react6.createElement)(
2851
+ (candidate) => (0, import_react5.createElement)(
2800
2852
  "li",
2801
2853
  { key: candidate.alias, className: CLASS.row },
2802
2854
  Checkbox({
@@ -2806,38 +2858,38 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2806
2858
  disabled: !editable || busy,
2807
2859
  onChange: () => actions.onImportToggle(candidate.alias)
2808
2860
  }),
2809
- (0, import_react6.createElement)(
2861
+ (0, import_react5.createElement)(
2810
2862
  "label",
2811
2863
  { htmlFor: `${idPrefix}-import-${candidate.alias}`, className: CLASS.rowTitle },
2812
2864
  candidate.alias
2813
2865
  ),
2814
- (0, import_react6.createElement)("span", { className: CLASS.rowMeta }, sshConfigCandidateSummary(candidate)),
2815
- (0, import_react6.createElement)("code", { className: CLASS.badge }, candidate.suggestedId),
2866
+ (0, import_react5.createElement)("span", { className: CLASS.rowMeta }, sshConfigCandidateSummary(candidate)),
2867
+ (0, import_react5.createElement)("code", { className: CLASS.badge }, candidate.suggestedId),
2816
2868
  // Flags, not values: the presence of a key or a jump is what changes the resulting connection.
2817
- candidate.hasIdentityFile ? (0, import_react6.createElement)("code", { className: CLASS.badge }, copy.rootsImportHasKey) : null,
2818
- candidate.hasProxyJump ? (0, import_react6.createElement)("code", { className: CLASS.badge }, copy.rootsImportHasJump) : null
2869
+ candidate.hasIdentityFile ? (0, import_react5.createElement)("code", { className: CLASS.badge }, copy.rootsImportHasKey) : null,
2870
+ candidate.hasProxyJump ? (0, import_react5.createElement)("code", { className: CLASS.badge }, copy.rootsImportHasJump) : null
2819
2871
  )
2820
2872
  )
2821
- ) : showEmpty ? (0, import_react6.createElement)("p", { className: CLASS.empty }, copy.rootsImportEmpty) : null,
2822
- (0, import_react6.createElement)("p", { className: CLASS.hint }, copy.rootsImportIdHint),
2873
+ ) : showEmpty ? (0, import_react5.createElement)("p", { className: CLASS.empty }, copy.rootsImportEmpty) : null,
2874
+ (0, import_react5.createElement)("p", { className: CLASS.hint }, copy.rootsImportIdHint),
2823
2875
  // The refusals, with the reason for each.
2824
- listing !== void 0 && listing.skipped.length > 0 ? (0, import_react6.createElement)(
2876
+ listing !== void 0 && listing.skipped.length > 0 ? (0, import_react5.createElement)(
2825
2877
  "div",
2826
2878
  { className: CLASS.block },
2827
- (0, import_react6.createElement)(
2879
+ (0, import_react5.createElement)(
2828
2880
  "p",
2829
2881
  { className: CLASS.hint },
2830
2882
  resolveMessage2(copy, { key: "rootsImportSkippedTitle", params: { count: listing.skipped.length } })
2831
2883
  ),
2832
- (0, import_react6.createElement)(
2884
+ (0, import_react5.createElement)(
2833
2885
  "ul",
2834
2886
  { className: CLASS.list },
2835
2887
  ...listing.skipped.map(
2836
- (entry) => (0, import_react6.createElement)(
2888
+ (entry) => (0, import_react5.createElement)(
2837
2889
  "li",
2838
2890
  { key: `${entry.pattern}-${entry.reason}`, className: CLASS.row },
2839
- (0, import_react6.createElement)("code", { className: CLASS.badge }, entry.pattern),
2840
- (0, import_react6.createElement)(
2891
+ (0, import_react5.createElement)("code", { className: CLASS.badge }, entry.pattern),
2892
+ (0, import_react5.createElement)(
2841
2893
  "span",
2842
2894
  { className: CLASS.rowMeta },
2843
2895
  resolveMessage2(copy, { key: sshConfigSkipKey(entry.reason) })
@@ -2846,7 +2898,7 @@ function SshConfigImportDialog({ copy, state, actions, idPrefix = DEFAULT_ROOTS_
2846
2898
  )
2847
2899
  )
2848
2900
  ) : null,
2849
- (0, import_react6.createElement)(
2901
+ (0, import_react5.createElement)(
2850
2902
  "div",
2851
2903
  { className: CLASS.form },
2852
2904
  Button({ variant: "secondary", size: "compact", disabled: !editable || busy, onClick: () => actions.onImportAll(), children: copy.rootsImportSelectAll }),
@@ -2869,7 +2921,7 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
2869
2921
  const editable = canEdit(state);
2870
2922
  const drafts = state.editDrafts;
2871
2923
  const renamed = drafts.id.trim() !== editingId;
2872
- return (0, import_react6.createElement)(
2924
+ return (0, import_react5.createElement)(
2873
2925
  "form",
2874
2926
  {
2875
2927
  onSubmit: (event) => {
@@ -2895,20 +2947,20 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
2895
2947
  actions.onCancelEdit();
2896
2948
  }
2897
2949
  },
2898
- (0, import_react6.createElement)(
2950
+ (0, import_react5.createElement)(
2899
2951
  "div",
2900
2952
  { className: CLASS.dialogHeader },
2901
- (0, import_react6.createElement)("h3", { className: CLASS.sectionTitle }, `${copy.rootsEditTitle}: ${editingId}`)
2953
+ (0, import_react5.createElement)("h3", { className: CLASS.sectionTitle }, `${copy.rootsEditTitle}: ${editingId}`)
2902
2954
  ),
2903
- (0, import_react6.createElement)(
2955
+ (0, import_react5.createElement)(
2904
2956
  "div",
2905
2957
  { className: CLASS.dialogBody },
2906
- (0, import_react6.createElement)(
2958
+ (0, import_react5.createElement)(
2907
2959
  "div",
2908
2960
  { className: CLASS.form },
2909
2961
  // The id is the rename field and carries the note below it, so it spans the whole row instead of
2910
2962
  // pairing with a neighbour that would then sit next to a paragraph.
2911
- (0, import_react6.createElement)(
2963
+ (0, import_react5.createElement)(
2912
2964
  "div",
2913
2965
  { className: CLASS.fieldSpan },
2914
2966
  Field({
@@ -2925,7 +2977,7 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
2925
2977
  ),
2926
2978
  // Only when it is actually being renamed: the note is about a change the user is making, and a
2927
2979
  // permanent paragraph would be noise on every edit.
2928
- renamed ? (0, import_react6.createElement)(
2980
+ renamed ? (0, import_react5.createElement)(
2929
2981
  "p",
2930
2982
  { className: classes(CLASS.hint, CLASS.fieldSpan) },
2931
2983
  resolveMessage2(copy, { key: "rootsEditRenameHint", params: { id: editingId } })
@@ -2995,7 +3047,7 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
2995
3047
  onChange: (event) => actions.onEditDraftChange({ password: fieldValue(event) })
2996
3048
  })
2997
3049
  }) : null,
2998
- drafts.authKind === "password" ? (0, import_react6.createElement)("p", { className: classes(CLASS.hint, CLASS.fieldSpan) }, copy.rootsEditPasswordHint) : null,
3050
+ drafts.authKind === "password" ? (0, import_react5.createElement)("p", { className: classes(CLASS.hint, CLASS.fieldSpan) }, copy.rootsEditPasswordHint) : null,
2999
3051
  Field({
3000
3052
  htmlFor: `${idPrefix}-edit-port`,
3001
3053
  label: copy.rootsPortLabel,
@@ -3012,7 +3064,7 @@ function ConnectionEditForm({ copy, state, actions, idPrefix = DEFAULT_ROOTS_ID_
3012
3064
  ),
3013
3065
  // The action row is a **sibling of the grid**, not a cell inside it: as a grid item it occupied a single
3014
3066
  // column, which is why the two buttons came out small and jammed under the first field (audit 0090).
3015
- (0, import_react6.createElement)(
3067
+ (0, import_react5.createElement)(
3016
3068
  "div",
3017
3069
  { className: CLASS.actions },
3018
3070
  Button({ variant: "primary", type: "submit", disabled: !editable, children: copy.rootsEditSave }),
@@ -3037,11 +3089,11 @@ function RootsPanelSection({
3037
3089
  const editable = view.canEdit;
3038
3090
  const { drafts } = state;
3039
3091
  const notice = resolveMessage2(copy, view.notice);
3040
- return (0, import_react6.createElement)(
3092
+ return (0, import_react5.createElement)(
3041
3093
  "section",
3042
3094
  { "aria-label": copy.rootsTitle, className: CLASS.stack },
3043
- (0, import_react6.createElement)("h3", { className: CLASS.sectionTitle }, copy.rootsTitle),
3044
- (0, import_react6.createElement)("p", { className: CLASS.description }, copy.rootsDescription),
3095
+ (0, import_react5.createElement)("h3", { className: CLASS.sectionTitle }, copy.rootsTitle),
3096
+ (0, import_react5.createElement)("p", { className: CLASS.description }, copy.rootsDescription),
3045
3097
  // "No host data" / "the host reported warnings": a block, never bare text.
3046
3098
  notice === "" ? null : Notice({ role: "note", children: notice }),
3047
3099
  state.pending ? Notice({ role: "status", tone: "info", children: copy.rootsPending }) : null,
@@ -3051,7 +3103,7 @@ function RootsPanelSection({
3051
3103
  // collapsed diagnostic block at the bottom of this section (see below): it is still the only place a
3052
3104
  // root can be removed from, so it is demoted, never dropped.
3053
3105
  // -- add a connection ----------------------------------------------------
3054
- (0, import_react6.createElement)(
3106
+ (0, import_react5.createElement)(
3055
3107
  "form",
3056
3108
  {
3057
3109
  onSubmit: (event) => {
@@ -3060,8 +3112,8 @@ function RootsPanelSection({
3060
3112
  },
3061
3113
  className: CLASS.block
3062
3114
  },
3063
- (0, import_react6.createElement)("h4", { className: CLASS.blockTitle }, copy.rootsAddConnection),
3064
- (0, import_react6.createElement)(
3115
+ (0, import_react5.createElement)("h4", { className: CLASS.blockTitle }, copy.rootsAddConnection),
3116
+ (0, import_react5.createElement)(
3065
3117
  "div",
3066
3118
  { className: CLASS.form },
3067
3119
  Field({
@@ -3161,7 +3213,7 @@ function RootsPanelSection({
3161
3213
  // -- import from ~/.ssh/config (FR-2.1.4) --------------------------------
3162
3214
  // The button sits next to the add form because it is an alternative way to *create* a connection, and it
3163
3215
  // is disabled for the same reason every other control is (`canEdit`).
3164
- (0, import_react6.createElement)(
3216
+ (0, import_react5.createElement)(
3165
3217
  "div",
3166
3218
  { className: CLASS.block },
3167
3219
  Button({ variant: "secondary", disabled: !editable, onClick: () => actions.onImportOpen(), children: copy.rootsImportOpen })
@@ -3172,7 +3224,7 @@ function RootsPanelSection({
3172
3224
  // artifact: it duplicated the agent's own access, wrote to the remote machine with no audit trail, and
3173
3225
  // competed with the conversation as a second source of truth. The sentence says where the capability
3174
3226
  // actually lives; the tools behind it (`@` a path, or asking for a listing) are the agent's.
3175
- (0, import_react6.createElement)("p", { className: CLASS.description }, copy.remoteFilesHint),
3227
+ (0, import_react5.createElement)("p", { className: CLASS.description }, copy.remoteFilesHint),
3176
3228
  // -- transfer progress (FR-2.6.4) ----------------------------------------
3177
3229
  transfers ?? null,
3178
3230
  // -- edit one saved connection (a dialog, not a block in this page) -------
@@ -3185,27 +3237,27 @@ function RootsPanelSection({
3185
3237
  // be removed from: dropping the rows would remove the only cleanup entry point, not just declutter.
3186
3238
  //
3187
3239
  // No `open` prop on purpose: the native default is collapsed, which is exactly the demotion asked for.
3188
- (0, import_react6.createElement)(
3240
+ (0, import_react5.createElement)(
3189
3241
  // The card surface is an **outer div**, not the `<details>` element: `.dsh-rw-block` is `display: flex`, and
3190
3242
  // that changes the disclosure's box model (the marker/`open` rendering is a UA-internal slot) — audit
3191
3243
  // 2026-11-01b ui P3-11. The disclosure keeps its native display and only the card around it is a flex column.
3192
3244
  "div",
3193
3245
  { className: CLASS.block },
3194
- (0, import_react6.createElement)(
3246
+ (0, import_react5.createElement)(
3195
3247
  "details",
3196
3248
  void 0,
3197
- (0, import_react6.createElement)("summary", { className: CLASS.blockTitle }, copy.rootsAdvancedSummary),
3198
- (0, import_react6.createElement)("p", { className: CLASS.description }, copy.rootsAdvancedNote),
3199
- view.rows.length === 0 ? (0, import_react6.createElement)("p", { className: CLASS.empty }, copy.rootsEmpty) : (0, import_react6.createElement)("ul", { className: CLASS.list }, ...view.rows.map((row) => renderRow(row, copy, editable, actions.onRemove)))
3249
+ (0, import_react5.createElement)("summary", { className: CLASS.blockTitle }, copy.rootsAdvancedSummary),
3250
+ (0, import_react5.createElement)("p", { className: CLASS.description }, copy.rootsAdvancedNote),
3251
+ view.rows.length === 0 ? (0, import_react5.createElement)("p", { className: CLASS.empty }, copy.rootsEmpty) : (0, import_react5.createElement)("ul", { className: CLASS.list }, ...view.rows.map((row) => renderRow(row, copy, editable, actions.onRemove)))
3200
3252
  )
3201
3253
  )
3202
3254
  );
3203
3255
  }
3204
3256
  function RootsPanelContainer({ copy, controller }) {
3205
- const state = (0, import_react6.useSyncExternalStore)(controller.subscribe, controller.getState);
3206
- const uid = (0, import_react6.useId)();
3257
+ const state = (0, import_react5.useSyncExternalStore)(controller.subscribe, controller.getState);
3258
+ const uid = (0, import_react5.useId)();
3207
3259
  const idPrefix = `dsh-rw-${uid.replace(/[^A-Za-z0-9_-]/g, "")}`;
3208
- const [portal] = (0, import_react6.useState)(
3260
+ const [portal] = (0, import_react5.useState)(
3209
3261
  () => createCardPortal({
3210
3262
  document: browserPortalDocument(),
3211
3263
  className: ROOTS_EDIT_PORTAL_CLASS,
@@ -3213,16 +3265,16 @@ function RootsPanelContainer({ copy, controller }) {
3213
3265
  render: renderInPortal
3214
3266
  })
3215
3267
  );
3216
- (0, import_react6.useEffect)(() => {
3268
+ (0, import_react5.useEffect)(() => {
3217
3269
  controller.start();
3218
3270
  }, [controller]);
3219
- (0, import_react6.useEffect)(injectControlStylesInBrowser, []);
3220
- (0, import_react6.useEffect)(() => {
3271
+ (0, import_react5.useEffect)(injectControlStylesInBrowser, []);
3272
+ (0, import_react5.useEffect)(() => {
3221
3273
  controller.surfaceAttached();
3222
3274
  return controller.surfaceDetached;
3223
3275
  }, [controller]);
3224
3276
  useCardTabTrap(state.editingId !== void 0, portal.container);
3225
- (0, import_react6.useLayoutEffect)(() => {
3277
+ (0, import_react5.useLayoutEffect)(() => {
3226
3278
  if (state.editingId === void 0) return;
3227
3279
  portal.mount();
3228
3280
  portal.focusCard();
@@ -3230,14 +3282,14 @@ function RootsPanelContainer({ copy, controller }) {
3230
3282
  portal.unmount();
3231
3283
  };
3232
3284
  }, [portal, state.editingId]);
3233
- const sectionActions = (0, import_react6.useMemo)(() => rootsPanelSectionActions(controller), [controller]);
3285
+ const sectionActions = (0, import_react5.useMemo)(() => rootsPanelSectionActions(controller), [controller]);
3234
3286
  return RootsPanelSection({
3235
3287
  copy,
3236
3288
  state,
3237
3289
  idPrefix,
3238
3290
  // No `actions`: the transfer panel owns its own polling and its own buttons (the prop that used to be passed
3239
3291
  // here was typed `unknown` and never read — audit 2026-10-31 client.md §2.4).
3240
- transfers: (0, import_react6.createElement)(TransferProgressSection, { copy, state }),
3292
+ transfers: (0, import_react5.createElement)(TransferProgressSection, { copy, state }),
3241
3293
  // The panel needs the connection list, which this section already read; it does not re-fetch it.
3242
3294
  actions: sectionActions,
3243
3295
  portal
@@ -3247,19 +3299,19 @@ function RootsPanelContainer({ copy, controller }) {
3247
3299
  // src/client/panel.ts
3248
3300
  function renderConnectionRow(row, actions) {
3249
3301
  const { onTest, onEdit, onRemove, disabled } = actions;
3250
- return (0, import_react7.createElement)(
3302
+ return (0, import_react6.createElement)(
3251
3303
  "li",
3252
3304
  { key: row.id, className: CLASS.row },
3253
- (0, import_react7.createElement)("code", { className: CLASS.badge }, row.transportLabel ?? row.transport),
3254
- (0, import_react7.createElement)("span", { className: CLASS.rowTitle }, `${row.id} \xB7 ${row.host}`),
3255
- row.degraded ? (0, import_react7.createElement)("span", { className: CLASS.warnGlyph, "aria-hidden": true }, "(!)") : null,
3256
- row.note === "" ? null : (0, import_react7.createElement)("span", { className: CLASS.rowNote }, row.note),
3305
+ (0, import_react6.createElement)("code", { className: CLASS.badge }, row.transportLabel ?? row.transport),
3306
+ (0, import_react6.createElement)("span", { className: CLASS.rowTitle }, `${row.id} \xB7 ${row.host}`),
3307
+ row.degraded ? (0, import_react6.createElement)("span", { className: CLASS.warnGlyph, "aria-hidden": true }, "(!)") : null,
3308
+ row.note === "" ? null : (0, import_react6.createElement)("span", { className: CLASS.rowNote }, row.note),
3257
3309
  // The probe's result sits beside the row it belongs to: "is this server reachable right now" is a fact
3258
3310
  // about that connection, not a page-level notice.
3259
- row.test === void 0 || row.test.detail === "" ? null : (0, import_react7.createElement)("span", { className: CLASS.description }, row.test.detail),
3311
+ row.test === void 0 || row.test.detail === "" ? null : (0, import_react6.createElement)("span", { className: CLASS.description }, row.test.detail),
3260
3312
  // Everything after this point is an action, so it is pushed to the far edge as one group: a row whose
3261
3313
  // buttons drifted apart as the note grew would be unreadable.
3262
- (0, import_react7.createElement)(
3314
+ (0, import_react6.createElement)(
3263
3315
  "span",
3264
3316
  { className: CLASS.rowActions },
3265
3317
  row.test === void 0 || onTest === void 0 ? null : Button({
@@ -3276,12 +3328,12 @@ function renderConnectionRow(row, actions) {
3276
3328
  );
3277
3329
  }
3278
3330
  function renderRemoveConfirmation(copy, row, remoteRoots, disabled, onCancel, onAccept) {
3279
- return (0, import_react7.createElement)(
3331
+ return (0, import_react6.createElement)(
3280
3332
  "div",
3281
3333
  { role: "alertdialog", "aria-label": copy.title, className: CLASS.block },
3282
- (0, import_react7.createElement)("h4", { className: CLASS.blockTitle }, copy.title),
3283
- (0, import_react7.createElement)("p", { className: CLASS.description }, copy.body(row, remoteRoots)),
3284
- (0, import_react7.createElement)(
3334
+ (0, import_react6.createElement)("h4", { className: CLASS.blockTitle }, copy.title),
3335
+ (0, import_react6.createElement)("p", { className: CLASS.description }, copy.body(row, remoteRoots)),
3336
+ (0, import_react6.createElement)(
3285
3337
  "div",
3286
3338
  { className: CLASS.actions },
3287
3339
  Button({ variant: "primary", size: "compact", disabled, onClick: onAccept, children: copy.acceptLabel }),
@@ -3291,29 +3343,29 @@ function renderRemoveConfirmation(copy, row, remoteRoots, disabled, onCancel, on
3291
3343
  }
3292
3344
  function SshDevPanel({ model, close, roots, onTestConnection, rowState }) {
3293
3345
  const confirming = rowState?.confirmingId === void 0 ? void 0 : model.connections.find((row) => row.id === rowState.confirmingId);
3294
- return (0, import_react7.createElement)(
3346
+ return (0, import_react6.createElement)(
3295
3347
  "section",
3296
3348
  { "aria-label": model.title, className: CLASS.stack },
3297
- (0, import_react7.createElement)(
3349
+ (0, import_react6.createElement)(
3298
3350
  "header",
3299
3351
  { className: CLASS.header },
3300
- (0, import_react7.createElement)("h2", { className: CLASS.sectionTitle }, model.title),
3352
+ (0, import_react6.createElement)("h2", { className: CLASS.sectionTitle }, model.title),
3301
3353
  Button({ variant: "secondary", size: "compact", onClick: close, children: model.closeLabel })
3302
3354
  ),
3303
- (0, import_react7.createElement)("p", { className: CLASS.description }, model.description),
3355
+ (0, import_react6.createElement)("p", { className: CLASS.description }, model.description),
3304
3356
  // The page-level notice belongs **above** the content it explains: it used to be rendered after the whole
3305
3357
  // editor (several screens of forms), so "this page currently shows static copy only" sat at the very bottom,
3306
3358
  // below everything it was about (audit 2026-11-01b ui P2-7).
3307
3359
  model.notice === "" ? null : Notice({ role: "note", children: model.notice }),
3308
- (0, import_react7.createElement)(
3360
+ (0, import_react6.createElement)(
3309
3361
  "div",
3310
3362
  { className: CLASS.block },
3311
- (0, import_react7.createElement)("h3", { className: CLASS.blockTitle }, model.connectionsHeading),
3363
+ (0, import_react6.createElement)("h3", { className: CLASS.blockTitle }, model.connectionsHeading),
3312
3364
  // The list has three states, and they used to be two: rows, or nothing at all. An answered-but-empty
3313
3365
  // list now says so (the block used to vanish, leaving the page silent about whether there were no
3314
3366
  // connections or the section had simply not rendered), and a channel that has not answered keeps the
3315
3367
  // sentence at the top instead of claiming "none yet" at the bottom (audit 2026-11-01b ui P2-7).
3316
- model.connections.length > 0 ? (0, import_react7.createElement)(
3368
+ model.connections.length > 0 ? (0, import_react6.createElement)(
3317
3369
  "ul",
3318
3370
  { className: CLASS.list },
3319
3371
  ...model.connections.map(
@@ -3322,7 +3374,7 @@ function SshDevPanel({ model, close, roots, onTestConnection, rowState }) {
3322
3374
  ...rowState === void 0 ? {} : { onEdit: rowState.onEdit, onRemove: rowState.onRemove, disabled: rowState.disabled }
3323
3375
  })
3324
3376
  )
3325
- ) : model.notice === "" ? (0, import_react7.createElement)("p", { className: CLASS.empty }, model.connectionsEmpty) : null
3377
+ ) : model.notice === "" ? (0, import_react6.createElement)("p", { className: CLASS.empty }, model.connectionsEmpty) : null
3326
3378
  ),
3327
3379
  // The confirmation sits directly under the row list it belongs to, above everything else: it is a
3328
3380
  // question about a row the user just clicked, and it must not be pushed off-screen by the editor below.
@@ -3336,7 +3388,7 @@ function SshDevPanel({ model, close, roots, onTestConnection, rowState }) {
3336
3388
  ),
3337
3389
  // The read-only explanation stays above the editor: it answers "which transport is in force", which the
3338
3390
  // editor's forms do not touch (a new connection only becomes live after a reload).
3339
- roots === void 0 ? null : (0, import_react7.createElement)(RootsPanelContainer, { copy: roots.copy, controller: roots.controller })
3391
+ roots === void 0 ? null : (0, import_react6.createElement)(RootsPanelContainer, { copy: roots.copy, controller: roots.controller })
3340
3392
  );
3341
3393
  }
3342
3394
 
@@ -3440,14 +3492,13 @@ async function openConnectionChannel(ctx, observer) {
3440
3492
  }
3441
3493
 
3442
3494
  // src/client/roots/connection-test.ts
3443
- var DEFAULT_PATH = "/api/over-ssh/roots";
3444
3495
  var CONNECTION_TEST_TIMEOUT_MS = 15e3;
3445
3496
  function isTimeout(error) {
3446
3497
  const name = typeof error === "object" && error !== null ? error.name : void 0;
3447
3498
  return name === "TimeoutError" || name === "AbortError";
3448
3499
  }
3449
3500
  function createConnectionTester(options) {
3450
- const path = options.path ?? DEFAULT_PATH;
3501
+ const path = options.path ?? ROOTS_API_PATH;
3451
3502
  const timeoutMs = options.timeoutMs ?? CONNECTION_TEST_TIMEOUT_MS;
3452
3503
  const listeners = /* @__PURE__ */ new Set();
3453
3504
  let state = /* @__PURE__ */ new Map();
@@ -3530,7 +3581,7 @@ function createConnectionTester(options) {
3530
3581
  }
3531
3582
 
3532
3583
  // src/client/session/session-panel-view.ts
3533
- var import_react8 = require("react");
3584
+ var import_react7 = require("react");
3534
3585
 
3535
3586
  // src/client/core/latest.ts
3536
3587
  function createLatestOnly() {
@@ -3547,7 +3598,6 @@ function createLatestOnly() {
3547
3598
  }
3548
3599
 
3549
3600
  // src/client/session/session-panel.ts
3550
- var SESSION_API_PATH = "/api/over-ssh/sessions";
3551
3601
  function toSessionRowView(raw) {
3552
3602
  const id = raw["id"];
3553
3603
  if (typeof id !== "string" || id === "") return void 0;
@@ -3792,7 +3842,7 @@ function sessionRowTemplate(props) {
3792
3842
  const { row, copy, checked, onToggle, pickIdPrefix } = props;
3793
3843
  const badge = badgeFor(row, copy);
3794
3844
  const stamp = formatStamp(row.updatedAtMs);
3795
- return (0, import_react8.createElement)(
3845
+ return (0, import_react7.createElement)(
3796
3846
  "li",
3797
3847
  { key: row.id, className: CLASS.sessionRow },
3798
3848
  /**
@@ -3800,7 +3850,7 @@ function sessionRowTemplate(props) {
3800
3850
  * Before this the box stood alone (only `aria-label` on the input): clicking the session
3801
3851
  * title did nothing, and a 16px square was the entire target for a two-line row.
3802
3852
  */
3803
- (0, import_react8.createElement)(
3853
+ (0, import_react7.createElement)(
3804
3854
  "label",
3805
3855
  { className: CLASS.checkboxLabel, htmlFor: `${pickIdPrefix}${row.id}` },
3806
3856
  Checkbox({
@@ -3810,44 +3860,44 @@ function sessionRowTemplate(props) {
3810
3860
  onChange: () => onToggle(row.id),
3811
3861
  ariaLabel: row.id
3812
3862
  }),
3813
- (0, import_react8.createElement)(
3863
+ (0, import_react7.createElement)(
3814
3864
  "div",
3815
3865
  { className: CLASS.sessionBody },
3816
- (0, import_react8.createElement)(
3866
+ (0, import_react7.createElement)(
3817
3867
  "div",
3818
3868
  { className: CLASS.sessionHead },
3819
- (0, import_react8.createElement)("span", { className: CLASS.rowTitle, title: row.id }, rowLabel(row, copy)),
3820
- badge === void 0 ? null : (0, import_react8.createElement)(
3869
+ (0, import_react7.createElement)("span", { className: CLASS.rowTitle, title: row.id }, rowLabel(row, copy)),
3870
+ badge === void 0 ? null : (0, import_react7.createElement)(
3821
3871
  "span",
3822
3872
  { className: classes(CLASS.badge, badge.success ? CLASS.badgeSuccess : void 0) },
3823
3873
  badge.text
3824
3874
  )
3825
3875
  ),
3826
- (0, import_react8.createElement)(
3876
+ (0, import_react7.createElement)(
3827
3877
  "div",
3828
3878
  { className: CLASS.sessionMeta },
3829
- (0, import_react8.createElement)("code", { className: CLASS.idChip, title: row.id }, shortId(row.id)),
3830
- stamp === "" ? null : (0, import_react8.createElement)("span", void 0, stamp),
3831
- (0, import_react8.createElement)("span", void 0, formatBytes(row.bytes))
3879
+ (0, import_react7.createElement)("code", { className: CLASS.idChip, title: row.id }, shortId(row.id)),
3880
+ stamp === "" ? null : (0, import_react7.createElement)("span", void 0, stamp),
3881
+ (0, import_react7.createElement)("span", void 0, formatBytes(row.bytes))
3832
3882
  )
3833
3883
  )
3834
3884
  )
3835
3885
  );
3836
3886
  }
3837
- var SessionRow = (0, import_react8.memo)(function SessionRow2(props) {
3887
+ var SessionRow = (0, import_react7.memo)(function SessionRow2(props) {
3838
3888
  return sessionRowTemplate(props);
3839
3889
  });
3840
3890
  function renderBinRow(props) {
3841
3891
  const { row, copy, busy } = props;
3842
- return (0, import_react8.createElement)(
3892
+ return (0, import_react7.createElement)(
3843
3893
  "li",
3844
3894
  { key: row.entryId },
3845
- (0, import_react8.createElement)(
3895
+ (0, import_react7.createElement)(
3846
3896
  "div",
3847
3897
  { className: CLASS.row },
3848
- (0, import_react8.createElement)("code", { className: CLASS.badge }, row.sessionId),
3849
- (0, import_react8.createElement)("span", { className: CLASS.rowMeta }, formatBytes(row.bytes)),
3850
- (0, import_react8.createElement)(
3898
+ (0, import_react7.createElement)("code", { className: CLASS.badge }, row.sessionId),
3899
+ (0, import_react7.createElement)("span", { className: CLASS.rowMeta }, formatBytes(row.bytes)),
3900
+ (0, import_react7.createElement)(
3851
3901
  "span",
3852
3902
  { className: CLASS.rowActions },
3853
3903
  Button({
@@ -3872,14 +3922,14 @@ function renderWorkspaceGroup(props) {
3872
3922
  const { group, copy, selected, busy } = props;
3873
3923
  const remove = workspaceDeleteAction(group);
3874
3924
  const selectedSet = new Set(selected);
3875
- return (0, import_react8.createElement)(
3925
+ return (0, import_react7.createElement)(
3876
3926
  "section",
3877
3927
  { key: group.key, className: CLASS.group },
3878
- (0, import_react8.createElement)(
3928
+ (0, import_react7.createElement)(
3879
3929
  "div",
3880
3930
  { className: CLASS.groupHead },
3881
- (0, import_react8.createElement)("span", { className: CLASS.rowTitle }, group.label === "" ? copy.workspaceUnknown : group.label),
3882
- (0, import_react8.createElement)("span", { className: CLASS.badge }, String(group.sessions.length)),
3931
+ (0, import_react7.createElement)("span", { className: CLASS.rowTitle }, group.label === "" ? copy.workspaceUnknown : group.label),
3932
+ (0, import_react7.createElement)("span", { className: CLASS.badge }, String(group.sessions.length)),
3883
3933
  // A workspace with no directory (the platform's no-cwd bucket) has nothing to scope a delete to, so it
3884
3934
  // gets no button rather than a button that guesses.
3885
3935
  remove === void 0 ? null : Button({
@@ -3890,12 +3940,12 @@ function renderWorkspaceGroup(props) {
3890
3940
  children: copy.deleteWorkspace
3891
3941
  })
3892
3942
  ),
3893
- group.cwd === void 0 ? null : (0, import_react8.createElement)("p", { className: CLASS.hint, title: group.cwd }, group.cwd),
3894
- (0, import_react8.createElement)(
3943
+ group.cwd === void 0 ? null : (0, import_react7.createElement)("p", { className: CLASS.hint, title: group.cwd }, group.cwd),
3944
+ (0, import_react7.createElement)(
3895
3945
  "ul",
3896
3946
  { className: CLASS.list },
3897
3947
  ...group.sessions.map(
3898
- (row) => (0, import_react8.createElement)(SessionRow, {
3948
+ (row) => (0, import_react7.createElement)(SessionRow, {
3899
3949
  // The key belongs to the element the group creates: the boundary is per row, so React has to match "this
3900
3950
  // is the same row as last render" before it compares props.
3901
3951
  key: row.id,
@@ -3922,24 +3972,24 @@ function sessionPanelStatus(phase, copy) {
3922
3972
  function sessionInventoryEmpty(phase, copy) {
3923
3973
  if (phase.kind !== "ready") return null;
3924
3974
  if (phase.snapshot.sessions.length > 0) return null;
3925
- return (0, import_react8.createElement)("p", { className: CLASS.empty }, copy.sessionsEmpty);
3975
+ return (0, import_react7.createElement)("p", { className: CLASS.empty }, copy.sessionsEmpty);
3926
3976
  }
3927
3977
  function SessionPanel({ copy, io, close }) {
3928
- const [phase, setPhase] = (0, import_react8.useState)({ kind: "loading" });
3929
- const [selected, setSelected] = (0, import_react8.useState)([]);
3930
- const [outcome, setOutcome] = (0, import_react8.useState)(void 0);
3931
- const [busy, setBusy] = (0, import_react8.useState)(false);
3932
- const [pending, setPending] = (0, import_react8.useState)(void 0);
3978
+ const [phase, setPhase] = (0, import_react7.useState)({ kind: "loading" });
3979
+ const [selected, setSelected] = (0, import_react7.useState)([]);
3980
+ const [outcome, setOutcome] = (0, import_react7.useState)(void 0);
3981
+ const [busy, setBusy] = (0, import_react7.useState)(false);
3982
+ const [pending, setPending] = (0, import_react7.useState)(void 0);
3933
3983
  const snapshot = phase.kind === "ready" ? phase.snapshot : EMPTY_SNAPSHOT;
3934
- const [reads] = (0, import_react8.useState)(() => createLatestOnly());
3935
- const alive = (0, import_react8.useRef)(true);
3936
- (0, import_react8.useEffect)(() => {
3984
+ const [reads] = (0, import_react7.useState)(() => createLatestOnly());
3985
+ const alive = (0, import_react7.useRef)(true);
3986
+ (0, import_react7.useEffect)(() => {
3937
3987
  alive.current = true;
3938
3988
  return () => {
3939
3989
  alive.current = false;
3940
3990
  };
3941
3991
  }, []);
3942
- const refresh = (0, import_react8.useCallback)(async () => {
3992
+ const refresh = (0, import_react7.useCallback)(async () => {
3943
3993
  const mine = reads.begin();
3944
3994
  setBusy(true);
3945
3995
  try {
@@ -3950,10 +4000,10 @@ function SessionPanel({ copy, io, close }) {
3950
4000
  if (alive.current && reads.isCurrent(mine)) setBusy(false);
3951
4001
  }
3952
4002
  }, [io, reads]);
3953
- (0, import_react8.useEffect)(() => {
4003
+ (0, import_react7.useEffect)(() => {
3954
4004
  void refresh();
3955
4005
  }, [refresh]);
3956
- const run = (0, import_react8.useCallback)(
4006
+ const run = (0, import_react7.useCallback)(
3957
4007
  async (action) => {
3958
4008
  const mine = reads.begin();
3959
4009
  setBusy(true);
@@ -3972,14 +4022,14 @@ function SessionPanel({ copy, io, close }) {
3972
4022
  },
3973
4023
  [io, reads]
3974
4024
  );
3975
- const visible = (0, import_react8.useMemo)(() => visibleSessions(snapshot.sessions), [snapshot.sessions]);
4025
+ const visible = (0, import_react7.useMemo)(() => visibleSessions(snapshot.sessions), [snapshot.sessions]);
3976
4026
  const hidden = hiddenSubsessionCount(snapshot.sessions);
3977
- const deletable = (0, import_react8.useMemo)(
4027
+ const deletable = (0, import_react7.useMemo)(
3978
4028
  () => visible.filter((row) => row.deletable && selected.includes(row.id)),
3979
4029
  [visible, selected]
3980
4030
  );
3981
- const groups = (0, import_react8.useMemo)(() => groupByWorkspace(visible), [visible]);
3982
- const toggle = (0, import_react8.useCallback)((id) => {
4031
+ const groups = (0, import_react7.useMemo)(() => groupByWorkspace(visible), [visible]);
4032
+ const toggle = (0, import_react7.useCallback)((id) => {
3983
4033
  setSelected((current) => current.includes(id) ? current.filter((value) => value !== id) : [...current, id]);
3984
4034
  }, []);
3985
4035
  const acceptPending = () => {
@@ -3993,9 +4043,9 @@ function SessionPanel({ copy, io, close }) {
3993
4043
  });
3994
4044
  };
3995
4045
  const cancelPending = () => setPending(void 0);
3996
- const confirmTitleId = (0, import_react8.useId)();
3997
- const pickIdPrefix = `session-pick-${(0, import_react8.useId)().replace(/[^A-Za-z0-9_-]/g, "")}-`;
3998
- const [portal] = (0, import_react8.useState)(
4046
+ const confirmTitleId = (0, import_react7.useId)();
4047
+ const pickIdPrefix = `session-pick-${(0, import_react7.useId)().replace(/[^A-Za-z0-9_-]/g, "")}-`;
4048
+ const [portal] = (0, import_react7.useState)(
3999
4049
  () => createCardPortal({
4000
4050
  document: browserPortalDocument(),
4001
4051
  className: CONFIRM_PORTAL_CLASS,
@@ -4003,10 +4053,10 @@ function SessionPanel({ copy, io, close }) {
4003
4053
  render: renderInPortal
4004
4054
  })
4005
4055
  );
4006
- const focusOrigin = (0, import_react8.useRef)(void 0);
4056
+ const focusOrigin = (0, import_react7.useRef)(void 0);
4007
4057
  const confirmationOpen = pending !== void 0;
4008
- (0, import_react8.useEffect)(injectControlStylesInBrowser, []);
4009
- (0, import_react8.useLayoutEffect)(() => {
4058
+ (0, import_react7.useEffect)(injectControlStylesInBrowser, []);
4059
+ (0, import_react7.useLayoutEffect)(() => {
4010
4060
  if (!confirmationOpen) return;
4011
4061
  focusOrigin.current = captureFocusOrigin(browserPortalDocument());
4012
4062
  portal.mount();
@@ -4029,21 +4079,21 @@ function SessionPanel({ copy, io, close }) {
4029
4079
  );
4030
4080
  return portal.container === void 0 ? card : portal.render(card, portal.container);
4031
4081
  };
4032
- return (0, import_react8.createElement)(
4082
+ return (0, import_react7.createElement)(
4033
4083
  "section",
4034
4084
  { "aria-label": copy.title, className: CLASS.stack },
4035
- (0, import_react8.createElement)(
4085
+ (0, import_react7.createElement)(
4036
4086
  "header",
4037
4087
  { className: CLASS.header },
4038
- (0, import_react8.createElement)("h2", { className: CLASS.sectionTitle }, copy.title),
4039
- (0, import_react8.createElement)(
4088
+ (0, import_react7.createElement)("h2", { className: CLASS.sectionTitle }, copy.title),
4089
+ (0, import_react7.createElement)(
4040
4090
  "div",
4041
4091
  { className: CLASS.headerActions },
4042
4092
  Button({ variant: "secondary", size: "compact", onClick: () => void refresh(), children: copy.refresh }),
4043
4093
  Button({ variant: "secondary", size: "compact", onClick: close, children: copy.close })
4044
4094
  )
4045
4095
  ),
4046
- (0, import_react8.createElement)("p", { className: CLASS.description }, copy.description),
4096
+ (0, import_react7.createElement)("p", { className: CLASS.description }, copy.description),
4047
4097
  sessionPanelStatus(phase, copy),
4048
4098
  outcome === void 0 ? null : Notice({
4049
4099
  role: outcome.kind === "ok" ? "status" : "alert",
@@ -4051,12 +4101,12 @@ function SessionPanel({ copy, io, close }) {
4051
4101
  children: copy.resolve(outcome.message)
4052
4102
  }),
4053
4103
  // --- the inventory ---------------------------------------------------------------------------
4054
- (0, import_react8.createElement)("h3", { className: CLASS.blockTitle }, copy.sessionsHeading),
4104
+ (0, import_react7.createElement)("h3", { className: CLASS.blockTitle }, copy.sessionsHeading),
4055
4105
  // No totals from an empty snapshot: 「0 个会话…共 0 B」 next to 「正在读取会话…」 was half of the first-paint lie.
4056
- phase.kind === "ready" ? (0, import_react8.createElement)("p", { className: CLASS.description }, copy.totals(snapshot)) : null,
4057
- hidden === 0 ? null : (0, import_react8.createElement)("p", { className: CLASS.hint }, copy.subsessionsHidden(hidden)),
4106
+ phase.kind === "ready" ? (0, import_react7.createElement)("p", { className: CLASS.description }, copy.totals(snapshot)) : null,
4107
+ hidden === 0 ? null : (0, import_react7.createElement)("p", { className: CLASS.hint }, copy.subsessionsHidden(hidden)),
4058
4108
  sessionInventoryEmpty(phase, copy),
4059
- snapshot.sessions.length === 0 ? null : (0, import_react8.createElement)(
4109
+ snapshot.sessions.length === 0 ? null : (0, import_react7.createElement)(
4060
4110
  "div",
4061
4111
  { className: CLASS.block },
4062
4112
  /**
@@ -4065,7 +4115,7 @@ function SessionPanel({ copy, io, close }) {
4065
4115
  * to scroll back up for every action. `position: sticky` keeps it inside the panel's own scroll box
4066
4116
  * (the card is the scrolling ancestor, not the window), so it follows the list without covering it.
4067
4117
  */
4068
- (0, import_react8.createElement)(
4118
+ (0, import_react7.createElement)(
4069
4119
  "div",
4070
4120
  { className: CLASS.actionBar },
4071
4121
  Button({
@@ -4090,7 +4140,7 @@ function SessionPanel({ copy, io, close }) {
4090
4140
  children: copy.deletePermanent
4091
4141
  })
4092
4142
  ),
4093
- (0, import_react8.createElement)(
4143
+ (0, import_react7.createElement)(
4094
4144
  "div",
4095
4145
  { className: CLASS.list },
4096
4146
  ...groups.map(
@@ -4107,13 +4157,13 @@ function SessionPanel({ copy, io, close }) {
4107
4157
  )
4108
4158
  ),
4109
4159
  // --- the bin --------------------------------------------------------------------------------
4110
- (0, import_react8.createElement)("h3", { className: CLASS.blockTitle }, copy.binHeading),
4160
+ (0, import_react7.createElement)("h3", { className: CLASS.blockTitle }, copy.binHeading),
4111
4161
  // Same rule as the inventory: 「回收站为空」 is a claim about an **answer**. While nothing has been read (or the
4112
4162
  // read failed) the status line above says what is happening (audit 2026-11-01b ui P1-3).
4113
- phase.kind !== "ready" ? null : snapshot.bin.length === 0 ? (0, import_react8.createElement)("p", { className: CLASS.empty }, copy.binEmpty) : (0, import_react8.createElement)(
4163
+ phase.kind !== "ready" ? null : snapshot.bin.length === 0 ? (0, import_react7.createElement)("p", { className: CLASS.empty }, copy.binEmpty) : (0, import_react7.createElement)(
4114
4164
  "div",
4115
4165
  { className: CLASS.block },
4116
- (0, import_react8.createElement)(
4166
+ (0, import_react7.createElement)(
4117
4167
  "ul",
4118
4168
  { className: CLASS.list },
4119
4169
  ...snapshot.bin.map(
@@ -4139,7 +4189,7 @@ function SessionPanel({ copy, io, close }) {
4139
4189
  );
4140
4190
  }
4141
4191
  function renderConfirmationDialog(pending, copy, handlers, titleId, loadedIdle = 0) {
4142
- return (0, import_react8.createElement)(
4192
+ return (0, import_react7.createElement)(
4143
4193
  "div",
4144
4194
  {
4145
4195
  className: CLASS.dialog,
@@ -4152,9 +4202,9 @@ function renderConfirmationDialog(pending, copy, handlers, titleId, loadedIdle =
4152
4202
  handlers.cancel();
4153
4203
  }
4154
4204
  },
4155
- (0, import_react8.createElement)("h3", { id: titleId, className: CLASS.dialogHeader }, copy.confirmTitle),
4156
- (0, import_react8.createElement)("p", { className: CLASS.dialogBody }, confirmationBody(pending, copy, loadedIdle)),
4157
- (0, import_react8.createElement)(
4205
+ (0, import_react7.createElement)("h3", { id: titleId, className: CLASS.dialogHeader }, copy.confirmTitle),
4206
+ (0, import_react7.createElement)("p", { className: CLASS.dialogBody }, confirmationBody(pending, copy, loadedIdle)),
4207
+ (0, import_react7.createElement)(
4158
4208
  "div",
4159
4209
  { className: CLASS.actions },
4160
4210
  Button({ variant: "primary", size: "compact", onClick: handlers.accept, children: copy.confirmAccept }),
@@ -4180,7 +4230,7 @@ function loadedIdleIds(pending, rows) {
4180
4230
  }
4181
4231
 
4182
4232
  // src/client/session/session-row-actions.ts
4183
- var import_react9 = require("react");
4233
+ var import_react8 = require("react");
4184
4234
  var SESSION_MENU_SLOT = "sidebar.workspaces.session.menu.item";
4185
4235
  var SESSION_ROW_RECYCLE_ID = "over-ssh-session-recycle";
4186
4236
  var SESSION_ROW_RECYCLE_ORDER = 500;
@@ -4296,7 +4346,7 @@ function sessionRowMenuEntry(kind) {
4296
4346
  return kind === "recycle" ? { itemId: SESSION_ROW_RECYCLE_ID, danger: false } : { itemId: SESSION_ROW_DELETE_ID, danger: true };
4297
4347
  }
4298
4348
  function SessionRowMenuItem(props) {
4299
- return (0, import_react9.createElement)(
4349
+ return (0, import_react8.createElement)(
4300
4350
  "button",
4301
4351
  {
4302
4352
  type: "button",
@@ -4364,7 +4414,7 @@ function SessionRowActionDialog({
4364
4414
  onClose,
4365
4415
  titleId = SESSION_ROW_ACTIONS_TITLE_ID
4366
4416
  }) {
4367
- return (0, import_react9.createElement)(
4417
+ return (0, import_react8.createElement)(
4368
4418
  "div",
4369
4419
  {
4370
4420
  // The shared confirmation shell: the same surface and radius as the settings panel's confirmation, instead
@@ -4379,8 +4429,8 @@ function SessionRowActionDialog({
4379
4429
  onClose();
4380
4430
  }
4381
4431
  },
4382
- (0, import_react9.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, view.title),
4383
- view.body === "" ? null : (0, import_react9.createElement)("p", { className: CLASS.description }, view.body),
4432
+ (0, import_react8.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, view.title),
4433
+ view.body === "" ? null : (0, import_react8.createElement)("p", { className: CLASS.description }, view.body),
4384
4434
  view.warning === "" ? null : Notice({ role: "note", tone: "warn", children: view.warning }),
4385
4435
  view.blocked === "" ? null : Notice({ role: "alert", tone: "error", children: view.blocked }),
4386
4436
  view.outcome === "" ? null : Notice({
@@ -4388,7 +4438,7 @@ function SessionRowActionDialog({
4388
4438
  tone: view.outcomeTone,
4389
4439
  children: view.outcome
4390
4440
  }),
4391
- (0, import_react9.createElement)(
4441
+ (0, import_react8.createElement)(
4392
4442
  "div",
4393
4443
  { className: CLASS.actions },
4394
4444
  view.buttons === "confirm" ? Button({
@@ -4404,11 +4454,11 @@ function SessionRowActionDialog({
4404
4454
  }
4405
4455
  function SessionRowActionsOverlay(props) {
4406
4456
  const { copy, store } = props;
4407
- const state = (0, import_react9.useSyncExternalStore)(store.subscribe, store.getState);
4457
+ const state = (0, import_react8.useSyncExternalStore)(store.subscribe, store.getState);
4408
4458
  const view = describeSessionRowAction(state, copy);
4409
4459
  const open = view !== void 0;
4410
- const titleId = (0, import_react9.useId)();
4411
- const [portal] = (0, import_react9.useState)(
4460
+ const titleId = (0, import_react8.useId)();
4461
+ const [portal] = (0, import_react8.useState)(
4412
4462
  () => createCardPortal({
4413
4463
  document: browserPortalDocument(),
4414
4464
  className: CONFIRM_PORTAL_CLASS,
@@ -4416,18 +4466,18 @@ function SessionRowActionsOverlay(props) {
4416
4466
  render: renderInPortal
4417
4467
  })
4418
4468
  );
4419
- const focusOrigin = (0, import_react9.useRef)(void 0);
4420
- (0, import_react9.useEffect)(injectControlStylesInBrowser, []);
4469
+ const focusOrigin = (0, import_react8.useRef)(void 0);
4470
+ (0, import_react8.useEffect)(injectControlStylesInBrowser, []);
4421
4471
  const queryKey = actionQueryKey(state);
4422
- (0, import_react9.useEffect)(() => {
4472
+ (0, import_react8.useEffect)(() => {
4423
4473
  if (queryKey === "closed") return;
4424
4474
  void store.load();
4425
4475
  }, [store, queryKey]);
4426
- (0, import_react9.useEffect)(() => {
4476
+ (0, import_react8.useEffect)(() => {
4427
4477
  store.activate();
4428
4478
  return () => store.dispose();
4429
4479
  }, [store]);
4430
- (0, import_react9.useLayoutEffect)(() => {
4480
+ (0, import_react8.useLayoutEffect)(() => {
4431
4481
  if (!open) return;
4432
4482
  focusOrigin.current = captureFocusOrigin(browserPortalDocument());
4433
4483
  portal.mount();
@@ -4450,7 +4500,7 @@ function SessionRowActionsOverlay(props) {
4450
4500
  }
4451
4501
 
4452
4502
  // src/client/session/session-remote-mark.ts
4453
- var import_react10 = require("react");
4503
+ var import_react9 = require("react");
4454
4504
  var REMOTE_MARK_SLOT = "sidebar.session.row.leading";
4455
4505
  var REMOTE_MARK_ID = "over-ssh-remote-mark";
4456
4506
  var REMOTE_MARK_ORDER = 5;
@@ -4477,7 +4527,7 @@ function RemoteSessionMark(props) {
4477
4527
  if (sessionId === void 0) return null;
4478
4528
  if (props.isRemoteSession?.(sessionId) !== true) return null;
4479
4529
  const label = props.label ?? "";
4480
- return (0, import_react10.createElement)(
4530
+ return (0, import_react9.createElement)(
4481
4531
  "span",
4482
4532
  {
4483
4533
  className: CLASS.remoteMark,
@@ -4488,25 +4538,25 @@ function RemoteSessionMark(props) {
4488
4538
  // marks without depending on the icon's geometry.
4489
4539
  "data-dsh-rw-remote-mark": sessionId
4490
4540
  },
4491
- (0, import_react10.createElement)(
4541
+ (0, import_react9.createElement)(
4492
4542
  "svg",
4493
4543
  { viewBox: "0 0 16 16", width: "12", height: "12", "aria-hidden": "true", focusable: "false" },
4494
4544
  // A globe in three strokes: outline, equator, and the two meridian arcs. Same geometry the reference
4495
4545
  // third-party plugin draws, minus its five-arc fidelity; `currentColor` is what keeps it legible in
4496
4546
  // both themes (the reference implementation shipped a hardcoded white stroke and was invisible in the
4497
4547
  // light theme — see its CHANGELOG).
4498
- (0, import_react10.createElement)("circle", { cx: "8", cy: "8", r: "6", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
4499
- (0, import_react10.createElement)("path", { d: "M2 8h12", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
4500
- (0, import_react10.createElement)("path", { d: "M8 2c2.4 2.4 2.4 9.6 0 12M8 2C5.6 4.4 5.6 11.6 8 14", fill: "none", stroke: "currentColor", strokeWidth: "1.2" })
4548
+ (0, import_react9.createElement)("circle", { cx: "8", cy: "8", r: "6", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
4549
+ (0, import_react9.createElement)("path", { d: "M2 8h12", fill: "none", stroke: "currentColor", strokeWidth: "1.5" }),
4550
+ (0, import_react9.createElement)("path", { d: "M8 2c2.4 2.4 2.4 9.6 0 12M8 2C5.6 4.4 5.6 11.6 8 14", fill: "none", stroke: "currentColor", strokeWidth: "1.2" })
4501
4551
  )
4502
4552
  );
4503
4553
  }
4504
4554
 
4505
4555
  // src/client/flows/session-directory.ts
4506
- var import_react12 = require("react");
4556
+ var import_react11 = require("react");
4507
4557
 
4508
4558
  // src/client/flows/directory-browser.ts
4509
- var import_react11 = require("react");
4559
+ var import_react10 = require("react");
4510
4560
  var GIT_STATUS_API_PATH = "/api/over-ssh/git-status";
4511
4561
  var GIT_STATUS_BADGE = {
4512
4562
  modified: "M",
@@ -4721,10 +4771,9 @@ function directoryBrowserReducer(state, action) {
4721
4771
  }
4722
4772
  }
4723
4773
  function parentRemotePath(path) {
4724
- const trimmed = path.replace(/\/+$/, "");
4725
- if (trimmed === "") return void 0;
4726
- const at = trimmed.lastIndexOf("/");
4727
- return at <= 0 ? "/" : trimmed.slice(0, at);
4774
+ const normalized = normalizePosixPath(path);
4775
+ if (normalized === void 0 || normalized === "/") return void 0;
4776
+ return posixDirname(normalized);
4728
4777
  }
4729
4778
  function remoteChildPath(base, name) {
4730
4779
  const trimmed = base.replace(/\/+$/, "");
@@ -4901,7 +4950,7 @@ var DEFAULT_DIRECTORY_BROWSER_ID_PREFIX = "dsh-rw-browse";
4901
4950
  function gitStatusBadgeElement(row, view) {
4902
4951
  const status = gitBadgeForRow(view, row);
4903
4952
  if (status === void 0) return null;
4904
- return (0, import_react11.createElement)("span", { className: CLASS.badge, title: status }, GIT_STATUS_BADGE[status]);
4953
+ return (0, import_react10.createElement)("span", { className: CLASS.badge, title: status }, GIT_STATUS_BADGE[status]);
4905
4954
  }
4906
4955
  function failureText(copy, reason) {
4907
4956
  if (reason === NO_LOCAL_CAPABILITY_REASON) return resolveMessage2(copy, { key: "browseUnavailable" });
@@ -4913,7 +4962,7 @@ function failureText(copy, reason) {
4913
4962
  }
4914
4963
  function connectionListNotice(copy, view, gate, actions) {
4915
4964
  if (view.connectionsError !== void 0) {
4916
- return (0, import_react11.createElement)(
4965
+ return (0, import_react10.createElement)(
4917
4966
  "div",
4918
4967
  { className: CLASS.block },
4919
4968
  Notice({
@@ -4950,10 +4999,10 @@ function DirectoryBrowserSection({
4950
4999
  onClick: () => actions.onModeChange(mode),
4951
5000
  children: label
4952
5001
  });
4953
- return (0, import_react11.createElement)(
5002
+ return (0, import_react10.createElement)(
4954
5003
  "div",
4955
5004
  { className: CLASS.browser, role: "group", "aria-label": copy.browseHeading },
4956
- (0, import_react11.createElement)(
5005
+ (0, import_react10.createElement)(
4957
5006
  "div",
4958
5007
  { className: CLASS.actions },
4959
5008
  fixedMode ? null : modeButton("local", copy.browseLocalMode),
@@ -4965,7 +5014,7 @@ function DirectoryBrowserSection({
4965
5014
  Button({ variant: "secondary", size: "compact", disabled: gate, onClick: actions.onHome, children: copy.browseHome })
4966
5015
  ),
4967
5016
  // -- the remote world needs a connection before anything can be listed -----
4968
- view.needsConnection && view.connections.length > 0 ? (0, import_react11.createElement)(
5017
+ view.needsConnection && view.connections.length > 0 ? (0, import_react10.createElement)(
4969
5018
  "div",
4970
5019
  { className: CLASS.form },
4971
5020
  Field({
@@ -4995,10 +5044,10 @@ function DirectoryBrowserSection({
4995
5044
  // 「浏览…」跟当前位置放在一起,而不是跟模式切换挤在一行:它是**对当前目录**的动作,
4996
5045
  // 放在本机/远端旁边会被读成第三种模式,还会把「主目录」挤到第二行。
4997
5046
  // 远端不画——远端目录在这台机器上没有系统框可选。
4998
- (0, import_react11.createElement)(
5047
+ (0, import_react10.createElement)(
4999
5048
  "div",
5000
5049
  { className: CLASS.actions },
5001
- (0, import_react11.createElement)("p", { className: CLASS.browserPath }, resolveMessage2(copy, { key: "browseCurrent", params: { path: view.path } })),
5050
+ (0, import_react10.createElement)("p", { className: CLASS.browserPath }, resolveMessage2(copy, { key: "browseCurrent", params: { path: view.path } })),
5002
5051
  state.mode !== "local" || actions.onPickNative === void 0 ? null : Button({
5003
5052
  variant: "secondary",
5004
5053
  size: "compact",
@@ -5009,21 +5058,21 @@ function DirectoryBrowserSection({
5009
5058
  ),
5010
5059
  // Branch line: only when the host could name one (detached HEAD / no git / read failure → nothing is shown,
5011
5060
  // never a guessed name). The glyph is language-neutral; the branch name is the host's own string.
5012
- view.gitBranch === void 0 ? null : (0, import_react11.createElement)(
5061
+ view.gitBranch === void 0 ? null : (0, import_react10.createElement)(
5013
5062
  "p",
5014
5063
  { className: CLASS.browserPath, "aria-label": `git branch ${view.gitBranch}` },
5015
5064
  `\u2387 ${view.gitBranch}${view.gitTruncated ? " \xB7 \u2026" : ""}`
5016
5065
  ),
5017
5066
  view.error === void 0 ? null : Notice({ role: "alert", tone: "error", children: failureText(copy, view.error) }),
5018
5067
  view.pending ? Notice({ role: "status", tone: "info", children: copy.browseLoading }) : null,
5019
- view.empty ? (0, import_react11.createElement)("p", { className: CLASS.empty }, copy.browseEmpty) : null,
5020
- (0, import_react11.createElement)(
5068
+ view.empty ? (0, import_react10.createElement)("p", { className: CLASS.empty }, copy.browseEmpty) : null,
5069
+ (0, import_react10.createElement)(
5021
5070
  "ul",
5022
5071
  // `tabIndex: -1` makes the list a programmatic focus target (not a Tab stop): it is where focus goes after a
5023
5072
  // navigation removes the row that had it.
5024
5073
  { className: CLASS.browserList, ref: listRef, tabIndex: -1 },
5025
5074
  ...view.canGoUp ? [
5026
- (0, import_react11.createElement)(
5075
+ (0, import_react10.createElement)(
5027
5076
  "li",
5028
5077
  { key: "..", className: CLASS.browserItem },
5029
5078
  Button({
@@ -5041,7 +5090,7 @@ function DirectoryBrowserSection({
5041
5090
  ] : [],
5042
5091
  ...view.rows.map((row) => {
5043
5092
  const badge = gitStatusBadgeElement(row, view);
5044
- return (0, import_react11.createElement)(
5093
+ return (0, import_react10.createElement)(
5045
5094
  "li",
5046
5095
  { key: row.key, className: CLASS.browserItem },
5047
5096
  row.kind === "directory" ? Button({
@@ -5052,11 +5101,11 @@ function DirectoryBrowserSection({
5052
5101
  disabled: gate,
5053
5102
  onClick: () => actions.onEnter(row.path),
5054
5103
  children: `${row.name}/`
5055
- }) : (0, import_react11.createElement)(
5104
+ }) : (0, import_react10.createElement)(
5056
5105
  "span",
5057
5106
  { className: CLASS.browserQuiet },
5058
5107
  row.name,
5059
- (0, import_react11.createElement)(
5108
+ (0, import_react10.createElement)(
5060
5109
  "span",
5061
5110
  { className: CLASS.badge },
5062
5111
  row.kind === "symlink" ? copy.browseKindSymlink : row.kind === "other" ? copy.browseKindOther : copy.browseKindFile
@@ -5067,7 +5116,7 @@ function DirectoryBrowserSection({
5067
5116
  })
5068
5117
  ),
5069
5118
  view.truncated ? Notice({ role: "note", tone: "info", children: resolveMessage2(copy, { key: "browseTruncated", params: { count: view.rows.length } }) }) : null,
5070
- (0, import_react11.createElement)(
5119
+ (0, import_react10.createElement)(
5071
5120
  "div",
5072
5121
  { className: CLASS.actions },
5073
5122
  Button({ variant: "primary", disabled: gate || !view.canCommit, onClick: actions.onCommit, children: commitLabel, className: CLASS.browserCommit }),
@@ -5077,10 +5126,10 @@ function DirectoryBrowserSection({
5077
5126
  }
5078
5127
  function DirectoryBrowserContainer(props) {
5079
5128
  const { copy, io, mode, seedPath, initialConnectionId, initialConnections, disabled, fixedMode, idPrefix, commitLabel, onCommit, pickNative } = props;
5080
- const copyRef = (0, import_react11.useRef)(copy);
5129
+ const copyRef = (0, import_react10.useRef)(copy);
5081
5130
  copyRef.current = copy;
5082
- const listRef = (0, import_react11.useRef)(null);
5083
- const [controller] = (0, import_react11.useState)(
5131
+ const listRef = (0, import_react10.useRef)(null);
5132
+ const [controller] = (0, import_react10.useState)(
5084
5133
  () => createDirectoryBrowser({
5085
5134
  io,
5086
5135
  copy: () => copyRef.current,
@@ -5089,17 +5138,17 @@ function DirectoryBrowserContainer(props) {
5089
5138
  ...initialConnections === void 0 ? {} : { connections: initialConnections }
5090
5139
  })
5091
5140
  );
5092
- const state = (0, import_react11.useSyncExternalStore)(controller.subscribe, controller.getState);
5093
- (0, import_react11.useEffect)(injectControlStylesInBrowser, []);
5094
- (0, import_react11.useEffect)(() => {
5141
+ const state = (0, import_react10.useSyncExternalStore)(controller.subscribe, controller.getState);
5142
+ (0, import_react10.useEffect)(injectControlStylesInBrowser, []);
5143
+ (0, import_react10.useEffect)(() => {
5095
5144
  controller.activate();
5096
5145
  controller.start(mode === "local" ? seedPath : initialConnectionId === void 0 ? void 0 : "");
5097
5146
  return () => {
5098
5147
  controller.dispose();
5099
5148
  };
5100
5149
  }, [controller]);
5101
- const lastPath = (0, import_react11.useRef)(state.path);
5102
- (0, import_react11.useLayoutEffect)(() => {
5150
+ const lastPath = (0, import_react10.useRef)(state.path);
5151
+ (0, import_react10.useLayoutEffect)(() => {
5103
5152
  if (lastPath.current === state.path) return;
5104
5153
  lastPath.current = state.path;
5105
5154
  listRef.current?.focus();
@@ -5421,7 +5470,7 @@ function SessionDirectorySection({
5421
5470
  titleId
5422
5471
  }) {
5423
5472
  const view = selectSessionDirectoryView(state, copy);
5424
- const card = view.visible === false ? null : (0, import_react12.createElement)(
5473
+ const card = view.visible === false ? null : (0, import_react11.createElement)(
5425
5474
  "div",
5426
5475
  {
5427
5476
  className: CLASS.floating,
@@ -5437,43 +5486,43 @@ function SessionDirectorySection({
5437
5486
  actions.onClose();
5438
5487
  }
5439
5488
  },
5440
- (0, import_react12.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, copy.sessionAddTitle),
5441
- (0, import_react12.createElement)("p", { className: CLASS.hint }, copy.sessionAddHint),
5489
+ (0, import_react11.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, copy.sessionAddTitle),
5490
+ (0, import_react11.createElement)("p", { className: CLASS.hint }, copy.sessionAddHint),
5442
5491
  // Which workspace this lands in, or - when the framework seats could not answer - the fallback the
5443
5492
  // copy states explicitly. Both are one line, so a narrow panel never has to truncate it.
5444
- (0, import_react12.createElement)("p", { className: CLASS.browserPath }, view.workspaceLine),
5493
+ (0, import_react11.createElement)("p", { className: CLASS.browserPath }, view.workspaceLine),
5445
5494
  // The card no longer *is* a browser: it lists what this session has and offers one way in. The picker is a
5446
5495
  // step behind 「浏览…」, which also means the card no longer opens with a full directory listing (that was
5447
5496
  // the complaint: the panel showed the current directory's contents before the user asked for anything).
5448
- view.browsing ? (0, import_react12.createElement)(
5497
+ view.browsing ? (0, import_react11.createElement)(
5449
5498
  "div",
5450
5499
  { className: CLASS.block },
5451
5500
  browser,
5452
- (0, import_react12.createElement)(
5501
+ (0, import_react11.createElement)(
5453
5502
  "div",
5454
5503
  { className: CLASS.actions },
5455
5504
  Button({ variant: "ghost", size: "compact", onClick: actions.onBrowse, children: copy.sessionBrowseBack })
5456
5505
  )
5457
- ) : (0, import_react12.createElement)(
5506
+ ) : (0, import_react11.createElement)(
5458
5507
  "div",
5459
5508
  { className: CLASS.block },
5460
- (0, import_react12.createElement)("h4", { className: CLASS.blockTitle }, copy.sessionRootsTitle),
5461
- (0, import_react12.createElement)("p", { className: CLASS.hint }, copy.sessionRootsHint),
5509
+ (0, import_react11.createElement)("h4", { className: CLASS.blockTitle }, copy.sessionRootsTitle),
5510
+ (0, import_react11.createElement)("p", { className: CLASS.hint }, copy.sessionRootsHint),
5462
5511
  view.roots.length === 0 ? view.rootsReading ? Notice({ role: "status", tone: "info", children: copy.sessionRootsLoading }) : (
5463
5512
  // A failed read is not an empty list: the error notice below is the whole truth there, and
5464
5513
  // 「还没有添加目录」 next to it would contradict it (same rule as the import dialog, ui P2-2/P2-3).
5465
- view.notice === "" ? (0, import_react12.createElement)("p", { className: CLASS.empty }, copy.sessionRootsEmpty) : null
5466
- ) : (0, import_react12.createElement)(
5514
+ view.notice === "" ? (0, import_react11.createElement)("p", { className: CLASS.empty }, copy.sessionRootsEmpty) : null
5515
+ ) : (0, import_react11.createElement)(
5467
5516
  "ul",
5468
5517
  { className: CLASS.list },
5469
5518
  ...view.roots.map(
5470
- (row) => (0, import_react12.createElement)(
5519
+ (row) => (0, import_react11.createElement)(
5471
5520
  "li",
5472
5521
  { key: row.key, className: CLASS.row },
5473
- (0, import_react12.createElement)("span", { className: CLASS.rowTitle }, row.title),
5474
- (0, import_react12.createElement)("code", { className: CLASS.badge }, row.badge),
5475
- row.subtitle === "" ? null : (0, import_react12.createElement)("span", { className: CLASS.rowMeta }, row.subtitle),
5476
- row.removable ? (0, import_react12.createElement)(
5522
+ (0, import_react11.createElement)("span", { className: CLASS.rowTitle }, row.title),
5523
+ (0, import_react11.createElement)("code", { className: CLASS.badge }, row.badge),
5524
+ row.subtitle === "" ? null : (0, import_react11.createElement)("span", { className: CLASS.rowMeta }, row.subtitle),
5525
+ row.removable ? (0, import_react11.createElement)(
5477
5526
  "span",
5478
5527
  { className: CLASS.rowActions },
5479
5528
  Button({
@@ -5487,7 +5536,7 @@ function SessionDirectorySection({
5487
5536
  )
5488
5537
  )
5489
5538
  ),
5490
- (0, import_react12.createElement)(
5539
+ (0, import_react11.createElement)(
5491
5540
  "div",
5492
5541
  { className: CLASS.actions },
5493
5542
  Button({ variant: "secondary", size: "compact", onClick: actions.onBrowse, children: copy.sessionBrowse })
@@ -5496,9 +5545,9 @@ function SessionDirectorySection({
5496
5545
  view.added === void 0 ? null : Notice({ role: "status", tone: "info", children: resolveMessage2(copy, { key: "sessionAddDone", params: { path: view.added } }) }),
5497
5546
  view.pending ? Notice({ role: "status", tone: "info", children: copy.flowPending }) : null,
5498
5547
  view.notice === "" ? null : Notice({ role: "alert", tone: "error", children: view.notice }),
5499
- (0, import_react12.createElement)("div", { className: CLASS.actions }, Button({ variant: "secondary", size: "compact", onClick: actions.onClose, children: copy.close }))
5548
+ (0, import_react11.createElement)("div", { className: CLASS.actions }, Button({ variant: "secondary", size: "compact", onClick: actions.onClose, children: copy.close }))
5500
5549
  );
5501
- return (0, import_react12.createElement)(
5550
+ return (0, import_react11.createElement)(
5502
5551
  "div",
5503
5552
  { className: CLASS.sessionEntry },
5504
5553
  Button({
@@ -5537,13 +5586,13 @@ function rootsQueryKey(state) {
5537
5586
  }
5538
5587
  function SessionDirectoryContainer(props) {
5539
5588
  const { copy, io } = props;
5540
- const copyRef = (0, import_react12.useRef)(copy);
5589
+ const copyRef = (0, import_react11.useRef)(copy);
5541
5590
  copyRef.current = copy;
5542
- const [controller] = (0, import_react12.useState)(() => createSessionDirectory({ io, copy: () => copyRef.current }));
5543
- const state = (0, import_react12.useSyncExternalStore)(controller.subscribe, controller.getState);
5591
+ const [controller] = (0, import_react11.useState)(() => createSessionDirectory({ io, copy: () => copyRef.current }));
5592
+ const state = (0, import_react11.useSyncExternalStore)(controller.subscribe, controller.getState);
5544
5593
  const workspacePath = readWorkspacePath(props);
5545
- const titleId = (0, import_react12.useId)();
5546
- const [portal] = (0, import_react12.useState)(
5594
+ const titleId = (0, import_react11.useId)();
5595
+ const [portal] = (0, import_react11.useState)(
5547
5596
  () => createCardPortal({
5548
5597
  document: browserPortalDocument(),
5549
5598
  className: SESSION_DIRECTORY_PORTAL_CLASS,
@@ -5551,22 +5600,22 @@ function SessionDirectoryContainer(props) {
5551
5600
  render: renderInPortal
5552
5601
  })
5553
5602
  );
5554
- const focusOrigin = (0, import_react12.useRef)(void 0);
5555
- (0, import_react12.useEffect)(injectControlStylesInBrowser, []);
5556
- (0, import_react12.useEffect)(() => controller.setWorkspacePath(workspacePath), [controller, workspacePath]);
5603
+ const focusOrigin = (0, import_react11.useRef)(void 0);
5604
+ (0, import_react11.useEffect)(injectControlStylesInBrowser, []);
5605
+ (0, import_react11.useEffect)(() => controller.setWorkspacePath(workspacePath), [controller, workspacePath]);
5557
5606
  const sessionId = props.sessionId;
5558
- (0, import_react12.useEffect)(() => controller.setSessionId(sessionId), [controller, sessionId]);
5607
+ (0, import_react11.useEffect)(() => controller.setSessionId(sessionId), [controller, sessionId]);
5559
5608
  const queryKey = rootsQueryKey(state);
5560
- (0, import_react12.useEffect)(() => {
5609
+ (0, import_react11.useEffect)(() => {
5561
5610
  if (queryKey === "closed") return;
5562
5611
  void controller.refreshRoots();
5563
5612
  }, [controller, queryKey]);
5564
5613
  useCardTabTrap(state.open, portal.container);
5565
- (0, import_react12.useEffect)(() => {
5614
+ (0, import_react11.useEffect)(() => {
5566
5615
  controller.activate();
5567
5616
  return () => controller.dispose();
5568
5617
  }, [controller]);
5569
- (0, import_react12.useLayoutEffect)(() => {
5618
+ (0, import_react11.useLayoutEffect)(() => {
5570
5619
  if (!state.open) return;
5571
5620
  focusOrigin.current = captureFocusOrigin(browserPortalDocument());
5572
5621
  portal.mount();
@@ -5577,7 +5626,7 @@ function SessionDirectoryContainer(props) {
5577
5626
  focusOrigin.current = void 0;
5578
5627
  };
5579
5628
  }, [portal, state.open]);
5580
- const browser = state.open === false || state.browsing === false ? null : (0, import_react12.createElement)(DirectoryBrowserContainer, {
5629
+ const browser = state.open === false || state.browsing === false ? null : (0, import_react11.createElement)(DirectoryBrowserContainer, {
5581
5630
  // One occurrence per open: a remount is how the browser is reset (its `mode`/seed are mount-time).
5582
5631
  key: "session-browser",
5583
5632
  copy,
@@ -5612,7 +5661,7 @@ function messageOf(error) {
5612
5661
  }
5613
5662
 
5614
5663
  // src/client/flows/workspace-directory-flow.ts
5615
- var import_react13 = require("react");
5664
+ var import_react12 = require("react");
5616
5665
  var WORKSPACE_DIRECTORY_FLOW_SLOTS = ["sidebar.workspaces.directoryFlow", "conversation.hero.workspace.directoryFlow"];
5617
5666
  var WORKSPACE_DIRECTORY_FLOW_PRIORITY = -1;
5618
5667
  var NO_ANCHOR_ERROR = "no-anchor-path";
@@ -5938,7 +5987,7 @@ function WorkspaceDirectoryFlowSection({
5938
5987
  const canSubmitRemote2 = view.canSubmitRemote;
5939
5988
  const connectionFieldId = `${idPrefix}-remote-connection`;
5940
5989
  const pathFieldId = `${idPrefix}-remote-path`;
5941
- const worldSwitch = (0, import_react13.createElement)(
5990
+ const worldSwitch = (0, import_react12.createElement)(
5942
5991
  "div",
5943
5992
  { className: CLASS.actions, role: "group", "aria-label": copy.flowTitle },
5944
5993
  Button({
@@ -5956,7 +6005,7 @@ function WorkspaceDirectoryFlowSection({
5956
6005
  children: copy.flowRemoteHeading
5957
6006
  })
5958
6007
  );
5959
- const card = (0, import_react13.createElement)(
6008
+ const card = (0, import_react12.createElement)(
5960
6009
  "section",
5961
6010
  {
5962
6011
  // The dialog's accessible name is its visible heading: the shell gives the heading a per-mount id, so
@@ -5975,21 +6024,21 @@ function WorkspaceDirectoryFlowSection({
5975
6024
  // wide two-column shape is queried against (see `styles.ts`).
5976
6025
  className: classes(CLASS.stack, CLASS.dialog)
5977
6026
  },
5978
- (0, import_react13.createElement)("div", { className: CLASS.dialogHeader }, (0, import_react13.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, copy.flowTitle)),
5979
- (0, import_react13.createElement)(
6027
+ (0, import_react12.createElement)("div", { className: CLASS.dialogHeader }, (0, import_react12.createElement)("h3", { id: titleId, className: CLASS.sectionTitle }, copy.flowTitle)),
6028
+ (0, import_react12.createElement)(
5980
6029
  "div",
5981
6030
  { className: CLASS.dialogBody },
5982
- (0, import_react13.createElement)("p", { className: CLASS.description }, copy.flowDescription),
6031
+ (0, import_react12.createElement)("p", { className: CLASS.description }, copy.flowDescription),
5983
6032
  notice === "" ? null : Notice({ role: "alert", tone: "error", children: notice }),
5984
6033
  // -- ask first: local or remote; only the selected world's block is rendered below -----------------
5985
6034
  worldSwitch,
5986
6035
  // -- local: the OS folder dialog, opened by one button ------------------------------------------------
5987
6036
  // 「浏览目录」means the platform's native chooser (users can reach any drive there); the embedded browser is
5988
6037
  // the **remote** block's business only. One button, one commit path: the chooser's answer is adopted.
5989
- view.mode === "local" ? (0, import_react13.createElement)(
6038
+ view.mode === "local" ? (0, import_react12.createElement)(
5990
6039
  "div",
5991
6040
  { key: "flow-local", className: CLASS.block },
5992
- (0, import_react13.createElement)("p", { className: CLASS.hint }, copy.flowLocalHint),
6041
+ (0, import_react12.createElement)("p", { className: CLASS.hint }, copy.flowLocalHint),
5993
6042
  Button({
5994
6043
  variant: "primary",
5995
6044
  disabled: !canPickLocal2,
@@ -6000,7 +6049,7 @@ function WorkspaceDirectoryFlowSection({
6000
6049
  view.pending ? Notice({ role: "status", tone: "info", children: copy.flowPending }) : null
6001
6050
  ) : null,
6002
6051
  // -- the remote block: connection + path + one submit; browsing merely fills the path ------------
6003
- view.mode === "remote" ? (0, import_react13.createElement)(
6052
+ view.mode === "remote" ? (0, import_react12.createElement)(
6004
6053
  "form",
6005
6054
  {
6006
6055
  key: "flow-remote",
@@ -6010,10 +6059,10 @@ function WorkspaceDirectoryFlowSection({
6010
6059
  },
6011
6060
  className: CLASS.block
6012
6061
  },
6013
- (0, import_react13.createElement)("p", { className: CLASS.hint }, copy.flowRemoteHint),
6062
+ (0, import_react12.createElement)("p", { className: CLASS.hint }, copy.flowRemoteHint),
6014
6063
  // The connection list's three states are inline, in this block: a failure (with its own retry), a read
6015
6064
  // in progress, and "no connection at all". None of them closes the dialog — the local entry still works.
6016
- view.connectionsError !== void 0 ? (0, import_react13.createElement)(
6065
+ view.connectionsError !== void 0 ? (0, import_react12.createElement)(
6017
6066
  "div",
6018
6067
  { className: CLASS.block },
6019
6068
  Notice({ role: "alert", tone: "error", children: resolveMessage2(copy, { key: "flowUnavailable", params: { reason: view.connectionsError } }) }),
@@ -6022,10 +6071,10 @@ function WorkspaceDirectoryFlowSection({
6022
6071
  // One column by default; two once `@container dsh-rw-flow (min-width: 22rem)` matches. Each field is
6023
6072
  // wrapped so a future sibling (a hint, a row of buttons) stays in the same cell instead of becoming a
6024
6073
  // column of its own.
6025
- (0, import_react13.createElement)(
6074
+ (0, import_react12.createElement)(
6026
6075
  "div",
6027
6076
  { className: CLASS.form },
6028
- (0, import_react13.createElement)(
6077
+ (0, import_react12.createElement)(
6029
6078
  "div",
6030
6079
  { className: CLASS.fieldSpan },
6031
6080
  Field({
@@ -6047,7 +6096,7 @@ function WorkspaceDirectoryFlowSection({
6047
6096
  })
6048
6097
  })
6049
6098
  ),
6050
- (0, import_react13.createElement)(
6099
+ (0, import_react12.createElement)(
6051
6100
  "div",
6052
6101
  { className: CLASS.fieldSpan },
6053
6102
  Field({
@@ -6055,7 +6104,7 @@ function WorkspaceDirectoryFlowSection({
6055
6104
  label: copy.rootsRemotePathLabel,
6056
6105
  // The path and its browse button share one line: typing a path by hand stays possible, and the
6057
6106
  // button that finds it sits exactly where the user looks for it instead of under the form.
6058
- children: (0, import_react13.createElement)(
6107
+ children: (0, import_react12.createElement)(
6059
6108
  "div",
6060
6109
  { className: CLASS.inputRow },
6061
6110
  TextInput({
@@ -6084,7 +6133,7 @@ function WorkspaceDirectoryFlowSection({
6084
6133
  // Browsing the connection's directories: the host answers names and kinds only. Picking one commits
6085
6134
  // through the browser's own "用这个目录" (see browseRemote); the form's "添加远端目录" is hidden while
6086
6135
  // this panel is open, so the dialog never shows two buttons for one action.
6087
- view.browse === "remote" ? (0, import_react13.createElement)(DirectoryBrowserContainer, {
6136
+ view.browse === "remote" ? (0, import_react12.createElement)(DirectoryBrowserContainer, {
6088
6137
  key: "flow-browse-remote",
6089
6138
  copy,
6090
6139
  io: browseIo,
@@ -6112,13 +6161,13 @@ function WorkspaceDirectoryFlowSection({
6112
6161
  }
6113
6162
  function WorkspaceDirectoryFlowContainer(props) {
6114
6163
  const { copy, io, open, busy, onPicked, onCancel, onError } = props;
6115
- const copyRef = (0, import_react13.useRef)(copy);
6164
+ const copyRef = (0, import_react12.useRef)(copy);
6116
6165
  copyRef.current = copy;
6117
- const [controller] = (0, import_react13.useState)(() => createWorkspaceDirectoryFlow({ io, copy: () => copyRef.current }));
6118
- const state = (0, import_react13.useSyncExternalStore)(controller.subscribe, controller.getState);
6119
- const titleId = (0, import_react13.useId)();
6166
+ const [controller] = (0, import_react12.useState)(() => createWorkspaceDirectoryFlow({ io, copy: () => copyRef.current }));
6167
+ const state = (0, import_react12.useSyncExternalStore)(controller.subscribe, controller.getState);
6168
+ const titleId = (0, import_react12.useId)();
6120
6169
  const idPrefix = `dsh-rw-${titleId.replace(/[^A-Za-z0-9_-]/g, "")}`;
6121
- const [portal] = (0, import_react13.useState)(
6170
+ const [portal] = (0, import_react12.useState)(
6122
6171
  () => createCardPortal({
6123
6172
  document: browserPortalDocument(),
6124
6173
  className: WORKSPACE_DIRECTORY_FLOW_PORTAL_CLASS,
@@ -6126,16 +6175,16 @@ function WorkspaceDirectoryFlowContainer(props) {
6126
6175
  render: renderInPortal
6127
6176
  })
6128
6177
  );
6129
- const focusOrigin = (0, import_react13.useRef)(void 0);
6130
- (0, import_react13.useEffect)(() => {
6178
+ const focusOrigin = (0, import_react12.useRef)(void 0);
6179
+ (0, import_react12.useEffect)(() => {
6131
6180
  controller.activate();
6132
6181
  return () => controller.dispose();
6133
6182
  }, [controller]);
6134
- (0, import_react13.useEffect)(injectControlStylesInBrowser, []);
6135
- (0, import_react13.useEffect)(() => {
6183
+ (0, import_react12.useEffect)(injectControlStylesInBrowser, []);
6184
+ (0, import_react12.useEffect)(() => {
6136
6185
  controller.syncOwner({ open, busy, onPicked, onCancel, onError });
6137
6186
  });
6138
- (0, import_react13.useLayoutEffect)(() => {
6187
+ (0, import_react12.useLayoutEffect)(() => {
6139
6188
  if (!state.open || portal.container === void 0) return;
6140
6189
  focusOrigin.current = captureFocusOrigin(browserPortalDocument());
6141
6190
  portal.mount();
@@ -6146,7 +6195,7 @@ function WorkspaceDirectoryFlowContainer(props) {
6146
6195
  focusOrigin.current = void 0;
6147
6196
  };
6148
6197
  }, [portal, state.open]);
6149
- (0, import_react13.useEffect)(() => {
6198
+ (0, import_react12.useEffect)(() => {
6150
6199
  if (!state.open) return;
6151
6200
  const doc = browserPortalDocument();
6152
6201
  if (doc?.addEventListener === void 0) return;