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.
- package/README.md +10 -10
- package/agents/agent-manifest.json +1 -1
- package/lib/client.js +363 -314
- package/lib/file-references.js +80 -35
- package/lib/fs.js +158 -75
- package/lib/index.js +4103 -3397
- package/lib/terminal-wrapper.js +9 -4
- package/lib/types/src/config.d.ts +80 -9
- package/lib/types/src/file-references.d.ts +0 -1
- package/lib/types/src/host/app/backend.d.ts +9 -1
- package/lib/types/src/host/app/recycle-bin/recycle-bin-contract.d.ts +0 -1
- package/lib/types/src/host/app/recycle-bin/recycle-bin-service.d.ts +0 -1
- package/lib/types/src/host/app/session-admin/session-admin-contract.d.ts +12 -1
- package/lib/types/src/host/app/session-admin/session-admin-service.d.ts +0 -1
- package/lib/types/src/host/app/transport/transport-selector.d.ts +0 -1
- package/lib/types/src/host/app/transport/transport-service.d.ts +0 -1
- package/lib/types/src/host/domain/capability.d.ts +0 -1
- package/lib/types/src/host/domain/errors.d.ts +0 -1
- package/lib/types/src/host/domain/roots/connection.d.ts +0 -1
- package/lib/types/src/host/domain/roots/root.d.ts +24 -9
- package/lib/types/src/host/domain/roots/session-roots.d.ts +0 -1
- package/lib/types/src/host/domain/roots/ssh-config.d.ts +0 -1
- package/lib/types/src/host/domain/session/session-admin.d.ts +0 -1
- package/lib/types/src/host/domain/transfer/recycle-bin.d.ts +0 -1
- package/lib/types/src/host/domain/transfer/transfer.d.ts +0 -1
- package/lib/types/src/host/infra/transport/agent/agent-installer.d.ts +0 -1
- package/lib/types/src/host/infra/transport/ssh/ssh2-transport.d.ts +0 -1
- package/lib/types/src/host/infra/transport/transport-base.d.ts +0 -1
- package/lib/types/src/host/infra/transport/transport.d.ts +16 -2
- package/lib/types/src/host/integration/agent/agent-preflight.d.ts +0 -1
- package/lib/types/src/host/integration/fs/file-reference-provider.d.ts +0 -1
- package/lib/types/src/host/integration/fs/fs-provider.d.ts +0 -2
- package/lib/types/src/host/integration/transfer/transfer-service.d.ts +0 -1
- package/lib/types/src/index.d.ts +23 -1
- package/lib/types/src/local-bridge-refs.d.ts +0 -1
- package/lib/types/src/local-bridge.d.ts +0 -1
- package/lib/types/src/shared/root-keys.d.ts +120 -0
- package/lib/types/src/wiring/helpers.d.ts +0 -1
- package/package.json +5 -3
- package/lib/types/src/client/controls.d.ts +0 -131
- package/lib/types/src/client/core/card-focus-trap.d.ts +0 -45
- package/lib/types/src/client/core/controls.d.ts +0 -164
- package/lib/types/src/client/core/latest.d.ts +0 -48
- package/lib/types/src/client/core/locale.d.ts +0 -38
- package/lib/types/src/client/core/message.d.ts +0 -37
- package/lib/types/src/client/core/panel-model.d.ts +0 -134
- package/lib/types/src/client/core/portal.d.ts +0 -266
- package/lib/types/src/client/core/remote.d.ts +0 -84
- package/lib/types/src/client/core/styles.d.ts +0 -458
- package/lib/types/src/client/file-tree-io.d.ts +0 -19
- package/lib/types/src/client/file-tree.d.ts +0 -271
- package/lib/types/src/client/flows/directory-browser.d.ts +0 -587
- package/lib/types/src/client/flows/session-directory.d.ts +0 -415
- package/lib/types/src/client/flows/workspace-directory-flow.d.ts +0 -480
- package/lib/types/src/client/locale.d.ts +0 -31
- package/lib/types/src/client/panel-model.d.ts +0 -37
- package/lib/types/src/client/panel.d.ts +0 -77
- package/lib/types/src/client/roots/connection-test.d.ts +0 -69
- package/lib/types/src/client/roots/roots-panel-state.d.ts +0 -307
- package/lib/types/src/client/roots/roots-panel-view.d.ts +0 -280
- package/lib/types/src/client/roots/roots-panel.d.ts +0 -364
- package/lib/types/src/client/roots/transfer-panel-view.d.ts +0 -35
- package/lib/types/src/client/roots/transfer-panel.d.ts +0 -241
- package/lib/types/src/client/roots/transfer-progress.d.ts +0 -67
- package/lib/types/src/client/roots-panel-view.d.ts +0 -236
- package/lib/types/src/client/roots-panel.d.ts +0 -347
- package/lib/types/src/client/session/session-panel-view.d.ts +0 -336
- package/lib/types/src/client/session/session-panel.d.ts +0 -322
- package/lib/types/src/client/session/session-remote-mark.d.ts +0 -110
- package/lib/types/src/client/session/session-row-actions.d.ts +0 -398
- package/lib/types/src/client/session-panel.d.ts +0 -152
- package/lib/types/src/client/styles.d.ts +0 -252
- package/lib/types/src/client/workspace-directory-flow.d.ts +0 -342
- package/lib/types/src/fs.d.ts +0 -46
- package/lib/types/src/host/app/recycle-bin/recycle-bin-move.d.ts +0 -18
- package/lib/types/src/host/app/recycle-bin/recycle-bin-world.d.ts +0 -38
- package/lib/types/src/host/app/recycle-bin-contract.d.ts +0 -215
- package/lib/types/src/host/app/recycle-bin-move.d.ts +0 -18
- package/lib/types/src/host/app/recycle-bin-service.d.ts +0 -45
- package/lib/types/src/host/app/recycle-bin-world.d.ts +0 -35
- package/lib/types/src/host/app/roots/roots-api-connections.d.ts +0 -49
- package/lib/types/src/host/app/roots/roots-api-contract.d.ts +0 -369
- package/lib/types/src/host/app/roots/roots-api-directories.d.ts +0 -13
- package/lib/types/src/host/app/roots/roots-api-http.d.ts +0 -77
- package/lib/types/src/host/app/roots/roots-api-long-poll.d.ts +0 -130
- package/lib/types/src/host/app/roots/roots-api-projections.d.ts +0 -102
- package/lib/types/src/host/app/roots/roots-api-roots.d.ts +0 -13
- package/lib/types/src/host/app/roots/roots-api-state.d.ts +0 -22
- package/lib/types/src/host/app/roots/roots-api-transfers.d.ts +0 -26
- package/lib/types/src/host/app/roots/roots-api.d.ts +0 -55
- package/lib/types/src/host/app/roots/user-roots.d.ts +0 -552
- package/lib/types/src/host/app/roots-api-connections.d.ts +0 -49
- package/lib/types/src/host/app/roots-api-contract.d.ts +0 -339
- package/lib/types/src/host/app/roots-api-directories.d.ts +0 -13
- package/lib/types/src/host/app/roots-api-http.d.ts +0 -77
- package/lib/types/src/host/app/roots-api-projections.d.ts +0 -102
- package/lib/types/src/host/app/roots-api-roots.d.ts +0 -13
- package/lib/types/src/host/app/roots-api-state.d.ts +0 -22
- package/lib/types/src/host/app/roots-api-support.d.ts +0 -182
- package/lib/types/src/host/app/roots-api-transfers.d.ts +0 -13
- package/lib/types/src/host/app/roots-api.d.ts +0 -55
- package/lib/types/src/host/app/routed-fs.d.ts +0 -239
- package/lib/types/src/host/app/serialize.d.ts +0 -37
- package/lib/types/src/host/app/session-admin/session-admin-context.d.ts +0 -36
- package/lib/types/src/host/app/session-admin/session-admin-delete.d.ts +0 -34
- package/lib/types/src/host/app/session-admin-context.d.ts +0 -36
- package/lib/types/src/host/app/session-admin-contract.d.ts +0 -214
- package/lib/types/src/host/app/session-admin-delete.d.ts +0 -34
- package/lib/types/src/host/app/session-admin-service.d.ts +0 -38
- package/lib/types/src/host/app/takeover/takeover-plan.d.ts +0 -227
- package/lib/types/src/host/app/takeover/takeover-rows.d.ts +0 -55
- package/lib/types/src/host/app/transport-selector.d.ts +0 -98
- package/lib/types/src/host/app/transport-service.d.ts +0 -75
- package/lib/types/src/host/app/user-roots.d.ts +0 -461
- package/lib/types/src/host/domain/bin-location.d.ts +0 -79
- package/lib/types/src/host/domain/connection.d.ts +0 -95
- package/lib/types/src/host/domain/glob-pattern.d.ts +0 -62
- package/lib/types/src/host/domain/literal-text.d.ts +0 -63
- package/lib/types/src/host/domain/path-grammar.d.ts +0 -84
- package/lib/types/src/host/domain/recycle-bin.d.ts +0 -337
- package/lib/types/src/host/domain/root.d.ts +0 -74
- package/lib/types/src/host/domain/roots/bin-location.d.ts +0 -91
- package/lib/types/src/host/domain/roots/route-table.d.ts +0 -102
- package/lib/types/src/host/domain/route-table.d.ts +0 -80
- package/lib/types/src/host/domain/session/terminal-target.d.ts +0 -136
- package/lib/types/src/host/domain/session-admin.d.ts +0 -330
- package/lib/types/src/host/domain/shell.d.ts +0 -47
- package/lib/types/src/host/domain/ssh-config.d.ts +0 -73
- package/lib/types/src/host/domain/terminal-target.d.ts +0 -132
- package/lib/types/src/host/domain/tool-requests.d.ts +0 -369
- package/lib/types/src/host/domain/transfer.d.ts +0 -260
- package/lib/types/src/host/infra/backend/local-backend.d.ts +0 -84
- package/lib/types/src/host/infra/backend/local-version.d.ts +0 -43
- package/lib/types/src/host/infra/backend/platform-local-backend.d.ts +0 -89
- package/lib/types/src/host/infra/backend/remote-backend.d.ts +0 -239
- package/lib/types/src/host/infra/cache/lru.d.ts +0 -52
- package/lib/types/src/host/infra/cache/versioned.d.ts +0 -77
- package/lib/types/src/host/infra/temp-name.d.ts +0 -19
- package/lib/types/src/host/infra/transport/agent/agent-protocol-client.d.ts +0 -132
- package/lib/types/src/host/infra/transport/agent/agent-protocol-codec.d.ts +0 -140
- package/lib/types/src/host/infra/transport/agent/agent-protocol-errors.d.ts +0 -41
- package/lib/types/src/host/infra/transport/agent/agent-protocol-frames.d.ts +0 -197
- package/lib/types/src/host/infra/transport/agent/agent-protocol-types.d.ts +0 -564
- package/lib/types/src/host/infra/transport/agent/agent-transport.d.ts +0 -418
- package/lib/types/src/host/infra/transport/agent-installer.d.ts +0 -121
- package/lib/types/src/host/infra/transport/agent-protocol-client.d.ts +0 -123
- package/lib/types/src/host/infra/transport/agent-protocol-codec.d.ts +0 -131
- package/lib/types/src/host/infra/transport/agent-protocol-errors.d.ts +0 -41
- package/lib/types/src/host/infra/transport/agent-protocol-frames.d.ts +0 -197
- package/lib/types/src/host/infra/transport/agent-protocol-types.d.ts +0 -509
- package/lib/types/src/host/infra/transport/agent-transport.d.ts +0 -353
- package/lib/types/src/host/infra/transport/alias-transport.d.ts +0 -206
- package/lib/types/src/host/infra/transport/askpass-fs.d.ts +0 -20
- package/lib/types/src/host/infra/transport/askpass.d.ts +0 -133
- package/lib/types/src/host/infra/transport/command-pool.d.ts +0 -61
- package/lib/types/src/host/infra/transport/fs-commands.d.ts +0 -140
- package/lib/types/src/host/infra/transport/git-status-route.d.ts +0 -63
- package/lib/types/src/host/infra/transport/git-status.d.ts +0 -101
- package/lib/types/src/host/infra/transport/node-spawn.d.ts +0 -26
- package/lib/types/src/host/infra/transport/session-window.d.ts +0 -51
- package/lib/types/src/host/infra/transport/shell-quote.d.ts +0 -59
- package/lib/types/src/host/infra/transport/ssh/alias-transport.d.ts +0 -206
- package/lib/types/src/host/infra/transport/ssh/askpass-fs.d.ts +0 -20
- package/lib/types/src/host/infra/transport/ssh/askpass.d.ts +0 -133
- package/lib/types/src/host/infra/transport/ssh/shell-quote.d.ts +0 -62
- package/lib/types/src/host/infra/transport/ssh/ssh-argv.d.ts +0 -43
- package/lib/types/src/host/infra/transport/ssh-argv.d.ts +0 -43
- package/lib/types/src/host/infra/transport/ssh2-transport.d.ts +0 -302
- package/lib/types/src/host/integration/agent/agent-install-io.d.ts +0 -33
- package/lib/types/src/host/integration/agent/askpass-registry.d.ts +0 -66
- package/lib/types/src/host/integration/agent-install-io.d.ts +0 -33
- package/lib/types/src/host/integration/agent-preflight.d.ts +0 -56
- package/lib/types/src/host/integration/anchor-directory.d.ts +0 -56
- package/lib/types/src/host/integration/askpass-registry.d.ts +0 -59
- package/lib/types/src/host/integration/bin-root-resolver.d.ts +0 -81
- package/lib/types/src/host/integration/connection-test.d.ts +0 -73
- package/lib/types/src/host/integration/file-reference-provider.d.ts +0 -115
- package/lib/types/src/host/integration/fs/git-status-wiring.d.ts +0 -21
- package/lib/types/src/host/integration/fs/platform-errors.d.ts +0 -94
- package/lib/types/src/host/integration/fs/remote-file-tools.d.ts +0 -369
- package/lib/types/src/host/integration/fs/remote-index.d.ts +0 -177
- package/lib/types/src/host/integration/fs-provider.d.ts +0 -226
- package/lib/types/src/host/integration/lsp/lsp-framing.d.ts +0 -38
- package/lib/types/src/host/integration/lsp/lsp-registry.d.ts +0 -67
- package/lib/types/src/host/integration/lsp/lsp-session.d.ts +0 -130
- package/lib/types/src/host/integration/management-tools.d.ts +0 -64
- package/lib/types/src/host/integration/recycle-bin-service.d.ts +0 -247
- package/lib/types/src/host/integration/remote/connection-test.d.ts +0 -73
- package/lib/types/src/host/integration/remote/remote-face.d.ts +0 -127
- package/lib/types/src/host/integration/remote/remote-tool-backend.d.ts +0 -108
- package/lib/types/src/host/integration/remote-face.d.ts +0 -127
- package/lib/types/src/host/integration/remote-file-tools.d.ts +0 -241
- package/lib/types/src/host/integration/remote-home.d.ts +0 -69
- package/lib/types/src/host/integration/remote-index.d.ts +0 -165
- package/lib/types/src/host/integration/remote-job-tools.d.ts +0 -295
- package/lib/types/src/host/integration/remote-tool-backend.d.ts +0 -97
- package/lib/types/src/host/integration/remote-tools.d.ts +0 -206
- package/lib/types/src/host/integration/root-registry.d.ts +0 -115
- package/lib/types/src/host/integration/roots/anchor-directory.d.ts +0 -56
- package/lib/types/src/host/integration/roots/bin-root-resolver.d.ts +0 -81
- package/lib/types/src/host/integration/roots/remote-home.d.ts +0 -69
- package/lib/types/src/host/integration/roots/root-registry.d.ts +0 -144
- package/lib/types/src/host/integration/roots/roots-wiring.d.ts +0 -219
- package/lib/types/src/host/integration/roots/session-root-index.d.ts +0 -52
- package/lib/types/src/host/integration/roots-api.d.ts +0 -407
- package/lib/types/src/host/integration/roots-wiring.d.ts +0 -211
- package/lib/types/src/host/integration/session/session-api.d.ts +0 -88
- package/lib/types/src/host/integration/session/session-bin-lock.d.ts +0 -80
- package/lib/types/src/host/integration/session/session-header.d.ts +0 -151
- package/lib/types/src/host/integration/session/session-labels.d.ts +0 -56
- package/lib/types/src/host/integration/session/session-route-wiring.d.ts +0 -50
- package/lib/types/src/host/integration/session-admin-service.d.ts +0 -236
- package/lib/types/src/host/integration/session-api.d.ts +0 -81
- package/lib/types/src/host/integration/session-bin-lock.d.ts +0 -80
- package/lib/types/src/host/integration/session-header.d.ts +0 -118
- package/lib/types/src/host/integration/session-labels.d.ts +0 -56
- package/lib/types/src/host/integration/session-route-wiring.d.ts +0 -50
- package/lib/types/src/host/integration/session-scope.d.ts +0 -113
- package/lib/types/src/host/integration/session-tools.d.ts +0 -44
- package/lib/types/src/host/integration/strict-arguments.d.ts +0 -79
- package/lib/types/src/host/integration/takeover/takeover-api.d.ts +0 -62
- package/lib/types/src/host/integration/takeover/takeover-route-wiring.d.ts +0 -60
- package/lib/types/src/host/integration/takeover/takeover-service.d.ts +0 -167
- package/lib/types/src/host/integration/terminal-manifest.d.ts +0 -94
- package/lib/types/src/host/integration/tools/management-tools.d.ts +0 -245
- package/lib/types/src/host/integration/tools/remote-job-tools.d.ts +0 -316
- package/lib/types/src/host/integration/tools/remote-lsp-tools.d.ts +0 -43
- package/lib/types/src/host/integration/tools/remote-tools.d.ts +0 -236
- package/lib/types/src/host/integration/tools/session-target.d.ts +0 -52
- package/lib/types/src/host/integration/tools/session-tools.d.ts +0 -46
- package/lib/types/src/host/integration/tools/strict-arguments.d.ts +0 -79
- package/lib/types/src/host/integration/tools/terminal-manifest.d.ts +0 -94
- package/lib/types/src/host/integration/transfer/remote-copy.d.ts +0 -66
- package/lib/types/src/host/integration/transfer/transfer-adapters.d.ts +0 -48
- package/lib/types/src/host/integration/transfer/transfer-tracker.d.ts +0 -106
- package/lib/types/src/host/integration/transfer-adapters.d.ts +0 -44
- package/lib/types/src/host/integration/transfer-service.d.ts +0 -164
- package/lib/types/src/host/integration/transfer-tracker.d.ts +0 -106
- package/lib/types/src/host/integration/user-roots.d.ts +0 -461
- package/lib/types/src/wiring/connections.d.ts +0 -189
- package/lib/types/src/wiring/fs.d.ts +0 -13
- package/lib/types/src/wiring/install-memo.d.ts +0 -32
- package/lib/types/src/wiring/legacy-dirs.d.ts +0 -62
- package/lib/types/src/wiring/package-paths.d.ts +0 -35
- package/lib/types/src/wiring/roots.d.ts +0 -21
- package/lib/types/src/wiring/state-dir.d.ts +0 -29
- 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
|
|
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
|
|
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/
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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,
|
|
2261
|
+
return (0, import_react3.createElement)(
|
|
2210
2262
|
"section",
|
|
2211
2263
|
{ className: CLASS.stack, "aria-label": copy.title },
|
|
2212
|
-
(0,
|
|
2213
|
-
(0,
|
|
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,
|
|
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,
|
|
2282
|
+
return (0, import_react3.createElement)(
|
|
2231
2283
|
"li",
|
|
2232
2284
|
{ key: row.id, className: CLASS.row },
|
|
2233
|
-
(0,
|
|
2234
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
2260
|
-
failures.length === 0 ? null : (0,
|
|
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,
|
|
2264
|
-
(0,
|
|
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,
|
|
2320
|
+
(row) => (0, import_react3.createElement)(
|
|
2269
2321
|
"li",
|
|
2270
2322
|
{ key: `${row.id}-error`, className: CLASS.row },
|
|
2271
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
2404
|
-
const progress = (0,
|
|
2405
|
-
const panel = (0,
|
|
2406
|
-
(0,
|
|
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,
|
|
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,
|
|
2763
|
+
return (0, import_react5.createElement)(
|
|
2712
2764
|
"li",
|
|
2713
2765
|
{ key: row.key, className: CLASS.row },
|
|
2714
|
-
(0,
|
|
2715
|
-
(0,
|
|
2716
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
2754
|
-
(0,
|
|
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,
|
|
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,
|
|
2776
|
-
(0,
|
|
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,
|
|
2832
|
+
(entry) => (0, import_react5.createElement)(
|
|
2781
2833
|
"li",
|
|
2782
2834
|
{ key: entry.alias, className: CLASS.row },
|
|
2783
|
-
(0,
|
|
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,
|
|
2847
|
+
showCandidates ? (0, import_react5.createElement)(
|
|
2796
2848
|
"ul",
|
|
2797
2849
|
{ className: CLASS.list },
|
|
2798
2850
|
...candidates.map(
|
|
2799
|
-
(candidate) => (0,
|
|
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,
|
|
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,
|
|
2815
|
-
(0,
|
|
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,
|
|
2818
|
-
candidate.hasProxyJump ? (0,
|
|
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,
|
|
2822
|
-
(0,
|
|
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,
|
|
2876
|
+
listing !== void 0 && listing.skipped.length > 0 ? (0, import_react5.createElement)(
|
|
2825
2877
|
"div",
|
|
2826
2878
|
{ className: CLASS.block },
|
|
2827
|
-
(0,
|
|
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,
|
|
2884
|
+
(0, import_react5.createElement)(
|
|
2833
2885
|
"ul",
|
|
2834
2886
|
{ className: CLASS.list },
|
|
2835
2887
|
...listing.skipped.map(
|
|
2836
|
-
(entry) => (0,
|
|
2888
|
+
(entry) => (0, import_react5.createElement)(
|
|
2837
2889
|
"li",
|
|
2838
2890
|
{ key: `${entry.pattern}-${entry.reason}`, className: CLASS.row },
|
|
2839
|
-
(0,
|
|
2840
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
2950
|
+
(0, import_react5.createElement)(
|
|
2899
2951
|
"div",
|
|
2900
2952
|
{ className: CLASS.dialogHeader },
|
|
2901
|
-
(0,
|
|
2953
|
+
(0, import_react5.createElement)("h3", { className: CLASS.sectionTitle }, `${copy.rootsEditTitle}: ${editingId}`)
|
|
2902
2954
|
),
|
|
2903
|
-
(0,
|
|
2955
|
+
(0, import_react5.createElement)(
|
|
2904
2956
|
"div",
|
|
2905
2957
|
{ className: CLASS.dialogBody },
|
|
2906
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
3092
|
+
return (0, import_react5.createElement)(
|
|
3041
3093
|
"section",
|
|
3042
3094
|
{ "aria-label": copy.rootsTitle, className: CLASS.stack },
|
|
3043
|
-
(0,
|
|
3044
|
-
(0,
|
|
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,
|
|
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,
|
|
3064
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
3246
|
+
(0, import_react5.createElement)(
|
|
3195
3247
|
"details",
|
|
3196
3248
|
void 0,
|
|
3197
|
-
(0,
|
|
3198
|
-
(0,
|
|
3199
|
-
view.rows.length === 0 ? (0,
|
|
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,
|
|
3206
|
-
const uid = (0,
|
|
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,
|
|
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,
|
|
3268
|
+
(0, import_react5.useEffect)(() => {
|
|
3217
3269
|
controller.start();
|
|
3218
3270
|
}, [controller]);
|
|
3219
|
-
(0,
|
|
3220
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
3302
|
+
return (0, import_react6.createElement)(
|
|
3251
3303
|
"li",
|
|
3252
3304
|
{ key: row.id, className: CLASS.row },
|
|
3253
|
-
(0,
|
|
3254
|
-
(0,
|
|
3255
|
-
row.degraded ? (0,
|
|
3256
|
-
row.note === "" ? null : (0,
|
|
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,
|
|
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,
|
|
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,
|
|
3331
|
+
return (0, import_react6.createElement)(
|
|
3280
3332
|
"div",
|
|
3281
3333
|
{ role: "alertdialog", "aria-label": copy.title, className: CLASS.block },
|
|
3282
|
-
(0,
|
|
3283
|
-
(0,
|
|
3284
|
-
(0,
|
|
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,
|
|
3346
|
+
return (0, import_react6.createElement)(
|
|
3295
3347
|
"section",
|
|
3296
3348
|
{ "aria-label": model.title, className: CLASS.stack },
|
|
3297
|
-
(0,
|
|
3349
|
+
(0, import_react6.createElement)(
|
|
3298
3350
|
"header",
|
|
3299
3351
|
{ className: CLASS.header },
|
|
3300
|
-
(0,
|
|
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,
|
|
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,
|
|
3360
|
+
(0, import_react6.createElement)(
|
|
3309
3361
|
"div",
|
|
3310
3362
|
{ className: CLASS.block },
|
|
3311
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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 ??
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
3863
|
+
(0, import_react7.createElement)(
|
|
3814
3864
|
"div",
|
|
3815
3865
|
{ className: CLASS.sessionBody },
|
|
3816
|
-
(0,
|
|
3866
|
+
(0, import_react7.createElement)(
|
|
3817
3867
|
"div",
|
|
3818
3868
|
{ className: CLASS.sessionHead },
|
|
3819
|
-
(0,
|
|
3820
|
-
badge === void 0 ? null : (0,
|
|
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,
|
|
3876
|
+
(0, import_react7.createElement)(
|
|
3827
3877
|
"div",
|
|
3828
3878
|
{ className: CLASS.sessionMeta },
|
|
3829
|
-
(0,
|
|
3830
|
-
stamp === "" ? null : (0,
|
|
3831
|
-
(0,
|
|
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,
|
|
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,
|
|
3892
|
+
return (0, import_react7.createElement)(
|
|
3843
3893
|
"li",
|
|
3844
3894
|
{ key: row.entryId },
|
|
3845
|
-
(0,
|
|
3895
|
+
(0, import_react7.createElement)(
|
|
3846
3896
|
"div",
|
|
3847
3897
|
{ className: CLASS.row },
|
|
3848
|
-
(0,
|
|
3849
|
-
(0,
|
|
3850
|
-
(0,
|
|
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,
|
|
3925
|
+
return (0, import_react7.createElement)(
|
|
3876
3926
|
"section",
|
|
3877
3927
|
{ key: group.key, className: CLASS.group },
|
|
3878
|
-
(0,
|
|
3928
|
+
(0, import_react7.createElement)(
|
|
3879
3929
|
"div",
|
|
3880
3930
|
{ className: CLASS.groupHead },
|
|
3881
|
-
(0,
|
|
3882
|
-
(0,
|
|
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,
|
|
3894
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
3929
|
-
const [selected, setSelected] = (0,
|
|
3930
|
-
const [outcome, setOutcome] = (0,
|
|
3931
|
-
const [busy, setBusy] = (0,
|
|
3932
|
-
const [pending, setPending] = (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,
|
|
3935
|
-
const alive = (0,
|
|
3936
|
-
(0,
|
|
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,
|
|
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,
|
|
4003
|
+
(0, import_react7.useEffect)(() => {
|
|
3954
4004
|
void refresh();
|
|
3955
4005
|
}, [refresh]);
|
|
3956
|
-
const run = (0,
|
|
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,
|
|
4025
|
+
const visible = (0, import_react7.useMemo)(() => visibleSessions(snapshot.sessions), [snapshot.sessions]);
|
|
3976
4026
|
const hidden = hiddenSubsessionCount(snapshot.sessions);
|
|
3977
|
-
const deletable = (0,
|
|
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,
|
|
3982
|
-
const toggle = (0,
|
|
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,
|
|
3997
|
-
const pickIdPrefix = `session-pick-${(0,
|
|
3998
|
-
const [portal] = (0,
|
|
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,
|
|
4056
|
+
const focusOrigin = (0, import_react7.useRef)(void 0);
|
|
4007
4057
|
const confirmationOpen = pending !== void 0;
|
|
4008
|
-
(0,
|
|
4009
|
-
(0,
|
|
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,
|
|
4082
|
+
return (0, import_react7.createElement)(
|
|
4033
4083
|
"section",
|
|
4034
4084
|
{ "aria-label": copy.title, className: CLASS.stack },
|
|
4035
|
-
(0,
|
|
4085
|
+
(0, import_react7.createElement)(
|
|
4036
4086
|
"header",
|
|
4037
4087
|
{ className: CLASS.header },
|
|
4038
|
-
(0,
|
|
4039
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
4057
|
-
hidden === 0 ? null : (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
4156
|
-
(0,
|
|
4157
|
-
(0,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
4383
|
-
view.body === "" ? null : (0,
|
|
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,
|
|
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,
|
|
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,
|
|
4411
|
-
const [portal] = (0,
|
|
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,
|
|
4420
|
-
(0,
|
|
4469
|
+
const focusOrigin = (0, import_react8.useRef)(void 0);
|
|
4470
|
+
(0, import_react8.useEffect)(injectControlStylesInBrowser, []);
|
|
4421
4471
|
const queryKey = actionQueryKey(state);
|
|
4422
|
-
(0,
|
|
4472
|
+
(0, import_react8.useEffect)(() => {
|
|
4423
4473
|
if (queryKey === "closed") return;
|
|
4424
4474
|
void store.load();
|
|
4425
4475
|
}, [store, queryKey]);
|
|
4426
|
-
(0,
|
|
4476
|
+
(0, import_react8.useEffect)(() => {
|
|
4427
4477
|
store.activate();
|
|
4428
4478
|
return () => store.dispose();
|
|
4429
4479
|
}, [store]);
|
|
4430
|
-
(0,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
4499
|
-
(0,
|
|
4500
|
-
(0,
|
|
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
|
|
4556
|
+
var import_react11 = require("react");
|
|
4507
4557
|
|
|
4508
4558
|
// src/client/flows/directory-browser.ts
|
|
4509
|
-
var
|
|
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
|
|
4725
|
-
if (
|
|
4726
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
5002
|
+
return (0, import_react10.createElement)(
|
|
4954
5003
|
"div",
|
|
4955
5004
|
{ className: CLASS.browser, role: "group", "aria-label": copy.browseHeading },
|
|
4956
|
-
(0,
|
|
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,
|
|
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,
|
|
5047
|
+
(0, import_react10.createElement)(
|
|
4999
5048
|
"div",
|
|
5000
5049
|
{ className: CLASS.actions },
|
|
5001
|
-
(0,
|
|
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,
|
|
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,
|
|
5020
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
5104
|
+
}) : (0, import_react10.createElement)(
|
|
5056
5105
|
"span",
|
|
5057
5106
|
{ className: CLASS.browserQuiet },
|
|
5058
5107
|
row.name,
|
|
5059
|
-
(0,
|
|
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,
|
|
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,
|
|
5129
|
+
const copyRef = (0, import_react10.useRef)(copy);
|
|
5081
5130
|
copyRef.current = copy;
|
|
5082
|
-
const listRef = (0,
|
|
5083
|
-
const [controller] = (0,
|
|
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,
|
|
5093
|
-
(0,
|
|
5094
|
-
(0,
|
|
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,
|
|
5102
|
-
(0,
|
|
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,
|
|
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,
|
|
5441
|
-
(0,
|
|
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,
|
|
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,
|
|
5497
|
+
view.browsing ? (0, import_react11.createElement)(
|
|
5449
5498
|
"div",
|
|
5450
5499
|
{ className: CLASS.block },
|
|
5451
5500
|
browser,
|
|
5452
|
-
(0,
|
|
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,
|
|
5506
|
+
) : (0, import_react11.createElement)(
|
|
5458
5507
|
"div",
|
|
5459
5508
|
{ className: CLASS.block },
|
|
5460
|
-
(0,
|
|
5461
|
-
(0,
|
|
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,
|
|
5466
|
-
) : (0,
|
|
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,
|
|
5519
|
+
(row) => (0, import_react11.createElement)(
|
|
5471
5520
|
"li",
|
|
5472
5521
|
{ key: row.key, className: CLASS.row },
|
|
5473
|
-
(0,
|
|
5474
|
-
(0,
|
|
5475
|
-
row.subtitle === "" ? null : (0,
|
|
5476
|
-
row.removable ? (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
5589
|
+
const copyRef = (0, import_react11.useRef)(copy);
|
|
5541
5590
|
copyRef.current = copy;
|
|
5542
|
-
const [controller] = (0,
|
|
5543
|
-
const state = (0,
|
|
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,
|
|
5546
|
-
const [portal] = (0,
|
|
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,
|
|
5555
|
-
(0,
|
|
5556
|
-
(0,
|
|
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,
|
|
5607
|
+
(0, import_react11.useEffect)(() => controller.setSessionId(sessionId), [controller, sessionId]);
|
|
5559
5608
|
const queryKey = rootsQueryKey(state);
|
|
5560
|
-
(0,
|
|
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,
|
|
5614
|
+
(0, import_react11.useEffect)(() => {
|
|
5566
5615
|
controller.activate();
|
|
5567
5616
|
return () => controller.dispose();
|
|
5568
5617
|
}, [controller]);
|
|
5569
|
-
(0,
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
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,
|
|
5979
|
-
(0,
|
|
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,
|
|
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,
|
|
6038
|
+
view.mode === "local" ? (0, import_react12.createElement)(
|
|
5990
6039
|
"div",
|
|
5991
6040
|
{ key: "flow-local", className: CLASS.block },
|
|
5992
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
6074
|
+
(0, import_react12.createElement)(
|
|
6026
6075
|
"div",
|
|
6027
6076
|
{ className: CLASS.form },
|
|
6028
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
6164
|
+
const copyRef = (0, import_react12.useRef)(copy);
|
|
6116
6165
|
copyRef.current = copy;
|
|
6117
|
-
const [controller] = (0,
|
|
6118
|
-
const state = (0,
|
|
6119
|
-
const titleId = (0,
|
|
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,
|
|
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,
|
|
6130
|
-
(0,
|
|
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,
|
|
6135
|
-
(0,
|
|
6183
|
+
(0, import_react12.useEffect)(injectControlStylesInBrowser, []);
|
|
6184
|
+
(0, import_react12.useEffect)(() => {
|
|
6136
6185
|
controller.syncOwner({ open, busy, onPicked, onCancel, onError });
|
|
6137
6186
|
});
|
|
6138
|
-
(0,
|
|
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,
|
|
6198
|
+
(0, import_react12.useEffect)(() => {
|
|
6150
6199
|
if (!state.open) return;
|
|
6151
6200
|
const doc = browserPortalDocument();
|
|
6152
6201
|
if (doc?.addEventListener === void 0) return;
|