tmux-ide 2.9.0-beta.21 → 2.9.0-beta.26
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/bin/cli.js +46791 -43589
- package/package.json +2 -2
- package/packages/contracts/src/__tests__/daemon-wire.test.ts +2 -2
- package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +35 -0
- package/packages/contracts/src/__tests__/issue-error.test.ts +1 -1
- package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +3 -1
- package/packages/contracts/src/__tests__/pane-stream.test.ts +17 -5
- package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +166 -0
- package/packages/contracts/src/__tests__/window-links.test.ts +243 -0
- package/packages/contracts/src/actions-contract.ts +12 -0
- package/packages/contracts/src/daemon-resource-request.test.ts +1 -1
- package/packages/contracts/src/daemon-wire.ts +10 -5
- package/packages/contracts/src/fleet-client-state.ts +3 -0
- package/packages/contracts/src/fleet-lifecycle.ts +5 -0
- package/packages/contracts/src/index.ts +2 -0
- package/packages/contracts/src/interaction-receipts.ts +39 -0
- package/packages/contracts/src/issue-error.ts +1 -1
- package/packages/contracts/src/multiplexer-verbs.ts +25 -1
- package/packages/contracts/src/pane-stream.ts +51 -8
- package/packages/contracts/src/tmux-server-scope.ts +172 -0
- package/packages/contracts/src/window-links.ts +76 -0
- package/packages/contracts/src/workspace-multiplexer.ts +54 -0
- package/packages/core/src/interaction-receipts.ts +4 -0
- package/packages/daemon/dist/command-center/actions/command-definitions.js +6 -0
- package/packages/daemon/dist/command-center/actions/handlers/workspace-multiplexer.js +2 -0
- package/packages/daemon/dist/command-center/actions/registry.js +13 -1
- package/packages/daemon/dist/command-center/discovery.js +2 -9
- package/packages/daemon/dist/command-center/server.js +21 -1
- package/packages/daemon/dist/command-center/tmux-server-native-backing.js +37 -0
- package/packages/daemon/dist/command-center/tmux-servers.js +351 -0
- package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +64 -8
- package/packages/daemon/dist/lib/canonical-daemon.js +5 -2
- package/packages/daemon/dist/lib/daemon-embed.js +198 -30
- package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +147 -0
- package/packages/daemon/dist/lib/fleet-lifecycle-authority.js +31 -11
- package/packages/daemon/dist/lib/headless-daemon.js +10 -1
- package/packages/daemon/dist/lib/runtime-namespace.js +17 -3
- package/packages/daemon/dist/lib/tmux-client-execution.js +59 -0
- package/packages/daemon/dist/lib/tmux-named-socket-fence.js +4 -0
- package/packages/daemon/dist/lib/tmux-server-generation-runner.js +62 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +285 -0
- package/packages/daemon/dist/lib/tmux-server-owners.js +192 -0
- package/packages/daemon/dist/lib/tmux-server-proof.js +82 -0
- package/packages/daemon/dist/lib/tmux-server-registration.js +85 -0
- package/packages/daemon/dist/lib/tmux-server-session-create.js +43 -0
- package/packages/daemon/dist/lib/tmux-server-session-open.js +101 -0
- package/packages/daemon/dist/lib/tmux-servers-cli.js +134 -0
- package/packages/daemon/dist/lib/tmux-session-mutation-fence.js +55 -0
- package/packages/daemon/dist/lib/tmux-window-link-guard.js +88 -0
- package/packages/daemon/dist/lib/workspace-config-loader.js +3 -0
- package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +82 -1
- package/packages/daemon/dist/lib/workspace-pane-creation.js +13 -57
- package/packages/daemon/dist/lib/workspace-promotion.js +91 -12
- package/packages/daemon/dist/lib/workspace-registry.js +4 -0
- package/packages/daemon/dist/server/pane-stream-upgrade.js +20 -6
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +61 -17
- package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +9 -1
- package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +11 -2
- package/packages/daemon/dist/terminal/mirror/control-channel.js +65 -6
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +44 -0
- package/packages/daemon/dist/terminal/mirror/session-channel.js +210 -50
- package/packages/daemon/dist/terminal/mirror/window-link-authority.js +160 -0
- package/packages/daemon/dist/terminal/pane-stream/lease-manager.js +3 -3
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +40 -6
- package/packages/daemon/dist/terminal/protocol/live-session-identity.js +9 -0
- package/packages/daemon/dist/terminal/protocol/native-backing-client.js +2 -1
- package/packages/daemon/dist/terminal/session-runtime/interaction-receipt-facts.js +9 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +6 -0
- package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +1 -1
- package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-resource-changes.js +1 -0
- package/packages/daemon/dist/tui/mirror/application-shell-daemon-connection.js +7 -2
- package/packages/daemon/dist/tui/mirror/application-shell-server-connection.js +114 -0
- package/packages/daemon/dist/tui/mirror/menu-model.js +1 -1
- package/packages/daemon/dist/tui/mirror/open-tui-verified-routing.js +34 -4
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +29 -8
- package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-integration.jsx +119 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-owner.js +132 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +21 -9
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-transport.js +63 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +8 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents.js +3 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog-owner.js +11 -5
- package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog.js +140 -75
- package/packages/daemon/dist/tui/mirror/runtime/application-home-experience.js +40 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +132 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +9 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-catalog.js +12 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +211 -31
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +91 -37
- package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +20 -7
- package/packages/daemon/dist/tui/mirror/runtime/application-root-configuration.js +9 -3
- package/packages/daemon/dist/tui/mirror/runtime/application-route-connection.js +95 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-shell-home.jsx +6 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +158 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace-policy.js +43 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +78 -25
- package/packages/daemon/dist/tui/mirror/runtime/application-theme-repaint.js +9 -0
- package/packages/daemon/dist/tui/mirror/runtime/fleet-lifecycle-client.js +44 -1
- package/packages/daemon/dist/tui/mirror/runtime/guided-tour-coach.jsx +54 -0
- package/packages/daemon/dist/tui/mirror/runtime/guided-tour-storage.js +67 -0
- package/packages/daemon/dist/tui/mirror/runtime/guided-tour.js +113 -0
- package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/resize-terminal-pane.js +4 -0
- package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +2 -1
- package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +12 -4
- package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +6 -3
- package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +11 -2
- package/packages/daemon/dist/tui/mirror/workspace/terminal-window-strip.jsx +2 -1
- package/packages/daemon/src/command-center/actions/command-definitions.ts +6 -0
- package/packages/daemon/src/command-center/actions/errors.ts +4 -0
- package/packages/daemon/src/command-center/actions/handlers/workspace-multiplexer.ts +2 -0
- package/packages/daemon/src/command-center/actions/registry.ts +14 -0
- package/packages/daemon/src/command-center/discovery.ts +2 -10
- package/packages/daemon/src/command-center/server.ts +29 -1
- package/packages/daemon/src/command-center/tmux-server-native-backing.ts +49 -0
- package/packages/daemon/src/command-center/tmux-servers.ts +479 -0
- package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +111 -4
- package/packages/daemon/src/lib/canonical-daemon.ts +15 -3
- package/packages/daemon/src/lib/daemon-embed.ts +241 -28
- package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +190 -0
- package/packages/daemon/src/lib/fleet-lifecycle-authority.ts +33 -9
- package/packages/daemon/src/lib/headless-daemon.ts +18 -1
- package/packages/daemon/src/lib/runtime-namespace.ts +24 -3
- package/packages/daemon/src/lib/tmux-client-execution.ts +56 -0
- package/packages/daemon/src/lib/tmux-named-socket-fence.ts +5 -0
- package/packages/daemon/src/lib/tmux-server-generation-runner.ts +81 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +336 -0
- package/packages/daemon/src/lib/tmux-server-owners.ts +228 -0
- package/packages/daemon/src/lib/tmux-server-proof.ts +95 -0
- package/packages/daemon/src/lib/tmux-server-registration.ts +105 -0
- package/packages/daemon/src/lib/tmux-server-session-create.ts +52 -0
- package/packages/daemon/src/lib/tmux-server-session-open.ts +106 -0
- package/packages/daemon/src/lib/tmux-servers-cli.ts +186 -0
- package/packages/daemon/src/lib/tmux-session-mutation-fence.ts +62 -0
- package/packages/daemon/src/lib/tmux-window-link-guard.ts +112 -0
- package/packages/daemon/src/lib/workspace-config-loader.ts +3 -0
- package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +121 -1
- package/packages/daemon/src/lib/workspace-pane-creation.ts +16 -55
- package/packages/daemon/src/lib/workspace-promotion.ts +98 -12
- package/packages/daemon/src/lib/workspace-registry.ts +5 -0
- package/packages/daemon/src/server/pane-stream-upgrade.ts +25 -5
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +70 -16
- package/packages/daemon/src/terminal/mirror/control-channel.ts +64 -7
- package/packages/daemon/src/terminal/mirror/events.ts +2 -0
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +56 -0
- package/packages/daemon/src/terminal/mirror/session-channel.ts +245 -50
- package/packages/daemon/src/terminal/mirror/window-link-authority.ts +203 -0
- package/packages/daemon/src/terminal/pane-stream/lease-manager.ts +3 -3
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +50 -6
- package/packages/daemon/src/terminal/protocol/live-session-identity.ts +14 -0
- package/packages/daemon/src/terminal/protocol/native-backing-client.ts +4 -1
- package/packages/daemon/src/terminal/session-runtime/interaction-receipt-facts.ts +9 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +9 -0
- package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +2 -2
- package/packages/daemon/src/terminal/session-runtime/semantic-mutation-resource-changes.ts +1 -0
- package/packages/daemon/src/tui/mirror/application-shell-daemon-connection.ts +28 -2
- package/packages/daemon/src/tui/mirror/application-shell-server-connection.ts +154 -0
- package/packages/daemon/src/tui/mirror/menu-model.ts +1 -1
- package/packages/daemon/src/tui/mirror/open-tui-verified-routing.ts +41 -3
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +47 -6
- package/packages/daemon/src/tui/mirror/runtime/application-entry.ts +2 -1
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions.tsx +10 -4
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-tabs.ts +2 -0
- package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-integration.tsx +199 -0
- package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-owner.ts +162 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +36 -10
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-transport.ts +69 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +12 -2
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents.ts +13 -2
- package/packages/daemon/src/tui/mirror/runtime/application-home-catalog-owner.ts +13 -3
- package/packages/daemon/src/tui/mirror/runtime/application-home-catalog.ts +172 -100
- package/packages/daemon/src/tui/mirror/runtime/application-home-experience.ts +59 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +175 -0
- package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +9 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-catalog.ts +19 -7
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +285 -36
- package/packages/daemon/src/tui/mirror/runtime/application-machine-overlays.tsx +1 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +124 -40
- package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +3 -0
- package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +18 -6
- package/packages/daemon/src/tui/mirror/runtime/application-root-configuration.ts +12 -3
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +40 -40
- package/packages/daemon/src/tui/mirror/runtime/application-route-connection.ts +93 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +20 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +10 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +177 -2
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-policy.ts +62 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +87 -30
- package/packages/daemon/src/tui/mirror/runtime/application-theme-repaint.ts +19 -0
- package/packages/daemon/src/tui/mirror/runtime/fleet-lifecycle-client.ts +55 -0
- package/packages/daemon/src/tui/mirror/runtime/guided-tour-coach.tsx +118 -0
- package/packages/daemon/src/tui/mirror/runtime/guided-tour-storage.ts +72 -0
- package/packages/daemon/src/tui/mirror/runtime/guided-tour.ts +147 -0
- package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +1 -1
- package/packages/daemon/src/tui/mirror/runtime/resize-terminal-pane.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +11 -4
- package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +15 -4
- package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +13 -2
- package/packages/daemon/src/tui/mirror/workspace/terminal-window-strip.tsx +5 -1
- package/packages/daemon-client/package.json +3 -1
- package/packages/daemon-client/src/index.ts +2 -0
- package/packages/daemon-client/src/pane-stream-client.test.ts +108 -38
- package/packages/daemon-client/src/pane-stream-client.ts +26 -0
- package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +114 -0
- package/packages/daemon-client/src/scoped-tmux-server-transport.ts +385 -0
- package/packages/daemon-client/src/tmux-server-client.test.ts +279 -0
- package/packages/daemon-client/src/tmux-server-client.ts +287 -0
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
listTmuxServers,
|
|
3
|
+
createTmuxServerClient,
|
|
4
|
+
} from "@tmux-ide/daemon-client/tmux-server-client";
|
|
5
|
+
import { canonicalDaemonUrl } from "../../../lib/canonical-daemon.ts";
|
|
6
|
+
import type { TmuxServerScope } from "@tmux-ide/contracts";
|
|
1
7
|
import { fetchCanonicalLiveWorkspaceRouting } from "../canonical-workspace-routing.ts";
|
|
2
8
|
import type { ApplicationMachineAuthorityHandle } from "./application-machine-authority.ts";
|
|
3
9
|
import { prepareOpenTuiApplicationShellConnection } from "../application-shell-daemon-connection.ts";
|
|
@@ -8,14 +14,100 @@ export function applicationRouteConnection(
|
|
|
8
14
|
handle: ApplicationMachineAuthorityHandle,
|
|
9
15
|
expectedLiveSessionId?: string,
|
|
10
16
|
onDiagnostic?: (phase: string, details: Readonly<Record<string, unknown>>) => void,
|
|
17
|
+
server?: TmuxServerScope,
|
|
11
18
|
) {
|
|
19
|
+
let rootInstanceId = handle.read()?.instanceId;
|
|
20
|
+
let selectedServer = server;
|
|
21
|
+
let refresh: { root: string; promise: Promise<TmuxServerScope> } | null = null;
|
|
22
|
+
const refreshAfterDaemonReplacement = async (nextRoot: string): Promise<TmuxServerScope> => {
|
|
23
|
+
if (!selectedServer) throw new Error("Select the server session again");
|
|
24
|
+
if (rootInstanceId === nextRoot) return selectedServer;
|
|
25
|
+
if (!expectedLiveSessionId) throw new Error("Select the server session again");
|
|
26
|
+
if (refresh?.root === nextRoot) return refresh.promise;
|
|
27
|
+
const stableServerId = selectedServer.serverId;
|
|
28
|
+
const promise = (async () => {
|
|
29
|
+
const daemon = handle.read();
|
|
30
|
+
const epoch = handle.endpoint()?.epoch;
|
|
31
|
+
if (!daemon?.authToken || daemon.instanceId !== nextRoot)
|
|
32
|
+
throw new Error("Daemon changed during reconnect");
|
|
33
|
+
const options = {
|
|
34
|
+
baseUrl: canonicalDaemonUrl("http", daemon.bindHostname, daemon.port),
|
|
35
|
+
ownerToken: daemon.authToken,
|
|
36
|
+
hostClientId: `opentui-reconnect:${process.pid}`,
|
|
37
|
+
origin: "tmux-ide://opentui",
|
|
38
|
+
};
|
|
39
|
+
const catalog = await listTmuxServers(options);
|
|
40
|
+
const descriptor = catalog.servers.find((candidate) => candidate.serverId === stableServerId);
|
|
41
|
+
if (!descriptor || descriptor.state !== "online")
|
|
42
|
+
throw new Error("Selected tmux server is unavailable");
|
|
43
|
+
const scope = { serverId: descriptor.serverId, generation: descriptor.generation };
|
|
44
|
+
const client = createTmuxServerClient(options, scope);
|
|
45
|
+
try {
|
|
46
|
+
const sessions = await client.sessions();
|
|
47
|
+
if (!sessions.sessions.some((session) => session.liveSessionId === expectedLiveSessionId))
|
|
48
|
+
throw new Error("Selected tmux session was replaced");
|
|
49
|
+
} finally {
|
|
50
|
+
client.dispose();
|
|
51
|
+
}
|
|
52
|
+
if (handle.read()?.instanceId !== nextRoot || handle.endpoint()?.epoch !== epoch)
|
|
53
|
+
throw new Error("Daemon changed during reconnect");
|
|
54
|
+
rootInstanceId = nextRoot;
|
|
55
|
+
selectedServer = scope;
|
|
56
|
+
return scope;
|
|
57
|
+
})();
|
|
58
|
+
refresh = { root: nextRoot, promise };
|
|
59
|
+
try {
|
|
60
|
+
return await promise;
|
|
61
|
+
} finally {
|
|
62
|
+
if (refresh?.promise === promise) refresh = null;
|
|
63
|
+
}
|
|
64
|
+
};
|
|
12
65
|
return {
|
|
13
66
|
readDaemon: handle.read,
|
|
14
|
-
observeCanonicalGeneration:
|
|
67
|
+
observeCanonicalGeneration: server
|
|
68
|
+
? async (listener: (generation: string | null) => void) => {
|
|
69
|
+
let stopped = false;
|
|
70
|
+
let revision = 0;
|
|
71
|
+
const stop = await handle.observe((generation) => {
|
|
72
|
+
const token = ++revision;
|
|
73
|
+
if (!generation) {
|
|
74
|
+
listener(null);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (generation === rootInstanceId) {
|
|
78
|
+
listener(selectedServer!.generation);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
// Retire old input before reading the replacement daemon's registration.
|
|
82
|
+
listener(null);
|
|
83
|
+
void refreshAfterDaemonReplacement(generation).then(
|
|
84
|
+
(scope) => {
|
|
85
|
+
if (!stopped && token === revision && handle.read()?.instanceId === generation)
|
|
86
|
+
listener(scope.generation);
|
|
87
|
+
},
|
|
88
|
+
() => {
|
|
89
|
+
if (!stopped && token === revision) listener(null);
|
|
90
|
+
},
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
return () => {
|
|
94
|
+
stopped = true;
|
|
95
|
+
revision++;
|
|
96
|
+
stop();
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
: handle.observe,
|
|
15
100
|
resolveConnection: async (sessionName: string) => {
|
|
101
|
+
if (selectedServer) {
|
|
102
|
+
const currentRoot = handle.read()?.instanceId;
|
|
103
|
+
if (!currentRoot) return null;
|
|
104
|
+
await refreshAfterDaemonReplacement(currentRoot);
|
|
105
|
+
}
|
|
16
106
|
const connection = await prepareOpenTuiApplicationShellConnection(sessionName, {
|
|
17
107
|
...(onDiagnostic ? { onDiagnostic } : {}),
|
|
108
|
+
...(selectedServer ? { server: selectedServer, expectedLiveSessionId } : {}),
|
|
18
109
|
readCanonicalDaemonInfo: handle.read,
|
|
110
|
+
readDaemonEndpoint: handle.endpoint,
|
|
19
111
|
isCanonicalDaemonAlive: handle.isAlive,
|
|
20
112
|
ensureSessionWorkspace: (name) =>
|
|
21
113
|
ensureOpenTuiSessionWorkspaceResult(name, {
|
|
@@ -71,6 +71,8 @@ export interface ApplicationCatalogShellProps {
|
|
|
71
71
|
) => void;
|
|
72
72
|
readonly onCreateSession?: () => void;
|
|
73
73
|
readonly onCycleTheme?: () => void;
|
|
74
|
+
readonly onOpenTutorial?: () => void;
|
|
75
|
+
readonly tutorialLabel?: string;
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
const CATALOG_VIEWS: readonly ShellChromeView[] = [
|
|
@@ -462,6 +464,8 @@ export function ApplicationCatalogShell(props: ApplicationCatalogShellProps): JS
|
|
|
462
464
|
onOpenTerminals={() => props.onOpenSurface("terminals", "mouse")}
|
|
463
465
|
onOpenCommands={() => props.onSetPaletteOpen(true, "mouse")}
|
|
464
466
|
onCycleTheme={props.onCycleTheme}
|
|
467
|
+
onOpenTutorial={props.onOpenTutorial}
|
|
468
|
+
tutorialLabel={props.tutorialLabel}
|
|
465
469
|
/>
|
|
466
470
|
</Show>
|
|
467
471
|
</box>
|
|
@@ -35,7 +35,12 @@ export interface ApplicationHomeSurfaceProps {
|
|
|
35
35
|
readonly theme: SemanticThemeSnapshot;
|
|
36
36
|
readonly onOpenTerminals: () => void;
|
|
37
37
|
readonly onOpenCommands: () => void;
|
|
38
|
+
readonly onOpenTutorial?: () => void;
|
|
39
|
+
readonly tutorialLabel?: string;
|
|
38
40
|
readonly onCycleTheme?: () => void;
|
|
41
|
+
readonly agentFilterLabel?: string;
|
|
42
|
+
readonly onCycleAgentMachine?: () => void;
|
|
43
|
+
readonly onToggleAgentAttention?: () => void;
|
|
39
44
|
readonly agentRoster?: HomeAgentSnapshot;
|
|
40
45
|
readonly recentPaneActivity?: readonly InteractionReceipt[];
|
|
41
46
|
readonly agentSelection?: HomeAgentSelectionSnapshot;
|
|
@@ -74,10 +79,11 @@ export function ApplicationHomeSurface(props: ApplicationHomeSurfaceProps): JSX.
|
|
|
74
79
|
naturalButtonWidth("Open terminals", "F2") +
|
|
75
80
|
naturalButtonWidth("Commands", "F5") +
|
|
76
81
|
(props.onCycleTheme ? naturalButtonWidth(themeLabel()) + 2 : 0) +
|
|
82
|
+
(props.onOpenTutorial ? naturalButtonWidth(props.tutorialLabel ?? "Learn tmux-ide") + 2 : 0) +
|
|
77
83
|
2;
|
|
78
84
|
const reservedRows = () =>
|
|
79
85
|
(spacious() ? 4 : 2) +
|
|
80
|
-
(actionsInRow() ? 1 : props.onCycleTheme ?
|
|
86
|
+
(actionsInRow() ? 1 : 2 + (props.onCycleTheme ? 1 : 0) + (props.onOpenTutorial ? 1 : 0)) +
|
|
81
87
|
(spacious() ? 1 : 0) +
|
|
82
88
|
(props.note ? (spacious() ? 2 : 1) : 0);
|
|
83
89
|
const recentActivity = () =>
|
|
@@ -156,6 +162,9 @@ export function ApplicationHomeSurface(props: ApplicationHomeSurfaceProps): JSX.
|
|
|
156
162
|
>
|
|
157
163
|
{(snapshot) => (
|
|
158
164
|
<HomeAgentRoster
|
|
165
|
+
filterLabel={props.agentFilterLabel}
|
|
166
|
+
onCycleMachine={props.onCycleAgentMachine}
|
|
167
|
+
onToggleAttention={props.onToggleAgentAttention}
|
|
159
168
|
theme={props.theme}
|
|
160
169
|
width={bodyWidth()}
|
|
161
170
|
height={rosterHeight()}
|
|
@@ -220,6 +229,16 @@ export function ApplicationHomeSurface(props: ApplicationHomeSurfaceProps): JSX.
|
|
|
220
229
|
width={buttonWidth("Commands", "F5")}
|
|
221
230
|
onPress={props.onOpenCommands}
|
|
222
231
|
/>
|
|
232
|
+
<Show when={props.onOpenTutorial}>
|
|
233
|
+
{(open) => (
|
|
234
|
+
<TuiButton
|
|
235
|
+
theme={props.theme}
|
|
236
|
+
label={props.tutorialLabel ?? "Learn tmux-ide"}
|
|
237
|
+
width={buttonWidth(props.tutorialLabel ?? "Learn tmux-ide")}
|
|
238
|
+
onPress={open()}
|
|
239
|
+
/>
|
|
240
|
+
)}
|
|
241
|
+
</Show>
|
|
223
242
|
<Show when={props.onCycleTheme}>
|
|
224
243
|
{(onCycleTheme) => (
|
|
225
244
|
<TuiButton
|
|
@@ -117,12 +117,16 @@ export interface ApplicationShellViewProps {
|
|
|
117
117
|
readonly onCreateWindow?: () => void;
|
|
118
118
|
readonly onCreateSession?: () => void;
|
|
119
119
|
readonly onCycleTheme?: () => void;
|
|
120
|
+
readonly onOpenTutorial?: () => void;
|
|
121
|
+
readonly tutorialLabel?: string;
|
|
120
122
|
readonly onBeginPaneRename?: (paneId: string, currentName: string) => void;
|
|
121
123
|
readonly onCancelPaneRename?: () => void;
|
|
122
124
|
readonly onSubmitPaneRename?: () => void;
|
|
123
125
|
readonly onDismissNotification?: () => void;
|
|
124
126
|
readonly paletteCloseArmed?: Accessor<boolean>;
|
|
125
127
|
readonly onSelectPane: TerminalWorkspaceProps["onSelectPane"];
|
|
128
|
+
readonly onSelectWindowLink?: TerminalWorkspaceProps["onSelectWindowLink"];
|
|
129
|
+
readonly onUnlinkWindowLink?: TerminalWorkspaceProps["onUnlinkWindowLink"];
|
|
126
130
|
readonly onResizePreview: TerminalWorkspaceProps["onResizePreview"];
|
|
127
131
|
readonly onResizePane: TerminalWorkspaceProps["onResizePane"];
|
|
128
132
|
readonly onCancelResize?: TerminalWorkspaceProps["onCancelResize"];
|
|
@@ -268,6 +272,8 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
|
|
|
268
272
|
onPaletteActivate={props.onPaletteActivate}
|
|
269
273
|
onCreateSession={props.onCreateSession}
|
|
270
274
|
onCycleTheme={props.onCycleTheme}
|
|
275
|
+
onOpenTutorial={props.onOpenTutorial}
|
|
276
|
+
tutorialLabel={props.tutorialLabel}
|
|
271
277
|
/>
|
|
272
278
|
}
|
|
273
279
|
>
|
|
@@ -454,6 +460,8 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
|
|
|
454
460
|
onOpenTerminals={() => props.onOpenSurface("terminals", "mouse")}
|
|
455
461
|
onOpenCommands={() => props.onSetPaletteOpen(true, "mouse")}
|
|
456
462
|
onCycleTheme={props.onCycleTheme}
|
|
463
|
+
onOpenTutorial={props.onOpenTutorial}
|
|
464
|
+
tutorialLabel={props.tutorialLabel}
|
|
457
465
|
/>
|
|
458
466
|
</Show>
|
|
459
467
|
<box
|
|
@@ -509,6 +517,8 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
|
|
|
509
517
|
agentIndicators={agentIndicators}
|
|
510
518
|
paneInteractions={props.paneInteractions}
|
|
511
519
|
onSelectPane={props.onSelectPane}
|
|
520
|
+
onSelectWindowLink={props.onSelectWindowLink}
|
|
521
|
+
onUnlinkWindowLink={props.onUnlinkWindowLink}
|
|
512
522
|
onCreateWindow={props.onCreateWindow}
|
|
513
523
|
onPaneContextAction={(paneId, action, currentName) => {
|
|
514
524
|
if (action === "rename-pane")
|
package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { WorkspaceWindowLinkSelectResultSchemaZ } from "@tmux-ide/contracts";
|
|
2
|
+
import { windowLinkForPane, windowLinkTarget } from "./application-terminal-workspace-policy.ts";
|
|
1
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
4
|
import type { Accessor, Setter } from "solid-js";
|
|
3
|
-
import type { SessionRuntimeTerminalInput } from "@tmux-ide/contracts";
|
|
5
|
+
import type { WindowLinkTarget, SessionRuntimeTerminalInput } from "@tmux-ide/contracts";
|
|
4
6
|
import {
|
|
5
7
|
currentTuiPerformanceEventSink,
|
|
6
8
|
type TuiTerminalInputOrigin,
|
|
@@ -79,6 +81,8 @@ export interface ApplicationTerminalInteractionController {
|
|
|
79
81
|
adoptGeneration(snapshot: OpenTuiGenerationHostSnapshot | null): void;
|
|
80
82
|
adoptLayout(snapshot: OpenTuiWorkspaceLayoutSnapshot): void;
|
|
81
83
|
selectPane(paneId: string): void;
|
|
84
|
+
selectWindowLink(target: WindowLinkTarget): Promise<void>;
|
|
85
|
+
unlinkWindowLink(target: WindowLinkTarget): Promise<string>;
|
|
82
86
|
sendInput(
|
|
83
87
|
input: SessionRuntimeTerminalInput,
|
|
84
88
|
parserOrigin?: Omit<
|
|
@@ -628,6 +632,41 @@ export function createApplicationTerminalInteractionController(
|
|
|
628
632
|
}
|
|
629
633
|
};
|
|
630
634
|
|
|
635
|
+
let linkSelection: {
|
|
636
|
+
target: WindowLinkTarget;
|
|
637
|
+
paneId: string;
|
|
638
|
+
layout: OpenTuiWorkspaceLayoutSnapshot;
|
|
639
|
+
received: boolean;
|
|
640
|
+
resolve: (selected: boolean) => void;
|
|
641
|
+
timer: ReturnType<typeof setTimeout>;
|
|
642
|
+
} | null = null;
|
|
643
|
+
const finishLinkSelection = (selected: boolean) => {
|
|
644
|
+
const pending = linkSelection;
|
|
645
|
+
if (!pending) return;
|
|
646
|
+
linkSelection = null;
|
|
647
|
+
clearTimeout(pending.timer);
|
|
648
|
+
pending.resolve(selected);
|
|
649
|
+
};
|
|
650
|
+
const reconcileLinkSelection = (snapshot: OpenTuiWorkspaceLayoutSnapshot) => {
|
|
651
|
+
const pending = linkSelection;
|
|
652
|
+
if (!pending) return;
|
|
653
|
+
pending.layout = snapshot;
|
|
654
|
+
const observed = windowLinkTarget(snapshot, pending.target.linkId);
|
|
655
|
+
if (
|
|
656
|
+
!observed ||
|
|
657
|
+
observed.linkRevision !== pending.target.linkRevision ||
|
|
658
|
+
observed.liveSessionId !== pending.target.liveSessionId ||
|
|
659
|
+
observed.expectedSemanticWindowId !== pending.target.expectedSemanticWindowId
|
|
660
|
+
) {
|
|
661
|
+
finishLinkSelection(false);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (!pending.received || snapshot.windowLinks?.activeLinkId !== pending.target.linkId) return;
|
|
665
|
+
finishLinkSelection(
|
|
666
|
+
snapshot.current !== null && paneForWindow(snapshot.current) === pending.paneId,
|
|
667
|
+
);
|
|
668
|
+
};
|
|
669
|
+
|
|
631
670
|
const paneInput = new TerminalPaneInputRouter<{
|
|
632
671
|
readonly input: SessionRuntimeTerminalInput;
|
|
633
672
|
readonly parserOrigin?: Omit<
|
|
@@ -636,6 +675,7 @@ export function createApplicationTerminalInteractionController(
|
|
|
636
675
|
>;
|
|
637
676
|
}>({
|
|
638
677
|
select: async (paneId) => {
|
|
678
|
+
finishLinkSelection(false);
|
|
639
679
|
const expected = liveSelectionTarget();
|
|
640
680
|
const selecting = pendingWindowSwitch;
|
|
641
681
|
const operationId = selecting?.traceId;
|
|
@@ -649,6 +689,7 @@ export function createApplicationTerminalInteractionController(
|
|
|
649
689
|
(failure) => {
|
|
650
690
|
selectionFailure.current = failure;
|
|
651
691
|
},
|
|
692
|
+
windowLinkForPane(options.layout(), paneId) ?? undefined,
|
|
652
693
|
)
|
|
653
694
|
: null;
|
|
654
695
|
if (selecting && pendingWindowSwitch === selecting) {
|
|
@@ -833,6 +874,7 @@ export function createApplicationTerminalInteractionController(
|
|
|
833
874
|
next.fastLane !== inputAuthorityIdentity.fastLane ||
|
|
834
875
|
next.adapter !== inputAuthorityIdentity.adapter);
|
|
835
876
|
if (next === null || replaced) {
|
|
877
|
+
finishLinkSelection(false);
|
|
836
878
|
paneInput.invalidateSelection();
|
|
837
879
|
pendingWindowSwitch = null;
|
|
838
880
|
pendingWindowRename = null;
|
|
@@ -846,6 +888,7 @@ export function createApplicationTerminalInteractionController(
|
|
|
846
888
|
inputAuthorityIdentity = next;
|
|
847
889
|
},
|
|
848
890
|
adoptLayout(snapshot) {
|
|
891
|
+
reconcileLinkSelection(snapshot);
|
|
849
892
|
paneInput.adoptCanonicalPane(snapshot.current ? paneForWindow(snapshot.current) : null);
|
|
850
893
|
const currentWindow = snapshot.current?.semanticWindowId ?? snapshot.current?.windowName;
|
|
851
894
|
const currentPane = snapshot.current ? paneForWindow(snapshot.current) : null;
|
|
@@ -933,7 +976,130 @@ export function createApplicationTerminalInteractionController(
|
|
|
933
976
|
maybeRequestWindowSwitchFrame();
|
|
934
977
|
maybeRequestPaneResizeFrame();
|
|
935
978
|
},
|
|
936
|
-
selectPane: (paneId) =>
|
|
979
|
+
selectPane: (paneId) => {
|
|
980
|
+
finishLinkSelection(false);
|
|
981
|
+
paneInput.selectPane(paneId);
|
|
982
|
+
},
|
|
983
|
+
async unlinkWindowLink(target) {
|
|
984
|
+
const expected = liveSelectionTarget();
|
|
985
|
+
const observed = windowLinkTarget(options.layout(), target.linkId);
|
|
986
|
+
if (
|
|
987
|
+
!expected ||
|
|
988
|
+
!observed ||
|
|
989
|
+
observed.liveSessionId !== target.liveSessionId ||
|
|
990
|
+
observed.linkRevision !== target.linkRevision ||
|
|
991
|
+
observed.expectedSemanticWindowId !== target.expectedSemanticWindowId
|
|
992
|
+
)
|
|
993
|
+
return "Window link changed; reopen its menu";
|
|
994
|
+
try {
|
|
995
|
+
if (
|
|
996
|
+
!expected.client.ownsRuntimeAuthority?.("input") &&
|
|
997
|
+
!(await expected.client.requestAuthority("input"))
|
|
998
|
+
)
|
|
999
|
+
return "Window unlink requires input control";
|
|
1000
|
+
const current = liveSelectionTarget();
|
|
1001
|
+
if (
|
|
1002
|
+
current?.client !== expected.client ||
|
|
1003
|
+
current.daemonGeneration !== expected.daemonGeneration
|
|
1004
|
+
)
|
|
1005
|
+
return "Session changed; reopen the window menu";
|
|
1006
|
+
await expected.client.dispatch({
|
|
1007
|
+
kind: "semantic-intent",
|
|
1008
|
+
intent: {
|
|
1009
|
+
verb: "workspace.window.link.unlink",
|
|
1010
|
+
workspaceName: expected.workspaceName,
|
|
1011
|
+
target,
|
|
1012
|
+
},
|
|
1013
|
+
});
|
|
1014
|
+
return "Window link removed";
|
|
1015
|
+
} catch {
|
|
1016
|
+
return "Window link could not be removed; it may be the last link or have changed";
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
async selectWindowLink(target) {
|
|
1020
|
+
const expected = liveSelectionTarget();
|
|
1021
|
+
const snapshot = options.layout();
|
|
1022
|
+
const observed = windowLinkTarget(snapshot, target.linkId);
|
|
1023
|
+
const backing = snapshot.windows.find(
|
|
1024
|
+
(window) => window.semanticWindowId === target.expectedSemanticWindowId,
|
|
1025
|
+
);
|
|
1026
|
+
const paneId = backing && paneForWindow(backing);
|
|
1027
|
+
if (
|
|
1028
|
+
!expected ||
|
|
1029
|
+
!paneId ||
|
|
1030
|
+
!observed ||
|
|
1031
|
+
observed.liveSessionId !== target.liveSessionId ||
|
|
1032
|
+
observed.linkRevision !== target.linkRevision ||
|
|
1033
|
+
observed.expectedSemanticWindowId !== target.expectedSemanticWindowId
|
|
1034
|
+
)
|
|
1035
|
+
return;
|
|
1036
|
+
finishLinkSelection(false);
|
|
1037
|
+
let resolve!: (selected: boolean) => void;
|
|
1038
|
+
const settled = new Promise<boolean>((done) => {
|
|
1039
|
+
resolve = done;
|
|
1040
|
+
});
|
|
1041
|
+
const pending = {
|
|
1042
|
+
target,
|
|
1043
|
+
paneId,
|
|
1044
|
+
layout: snapshot,
|
|
1045
|
+
received: false,
|
|
1046
|
+
resolve,
|
|
1047
|
+
timer: setTimeout(() => {
|
|
1048
|
+
if (linkSelection === pending) finishLinkSelection(false);
|
|
1049
|
+
}, 5_000),
|
|
1050
|
+
};
|
|
1051
|
+
pending.timer.unref?.();
|
|
1052
|
+
linkSelection = pending;
|
|
1053
|
+
// Register input ordering synchronously, before authority or command awaits.
|
|
1054
|
+
paneInput.selectPane(paneId, { presentOptimistically: false, selection: () => settled });
|
|
1055
|
+
try {
|
|
1056
|
+
if (
|
|
1057
|
+
!expected.client.ownsRuntimeAuthority?.("input") &&
|
|
1058
|
+
!(await expected.client.requestAuthority("input"))
|
|
1059
|
+
) {
|
|
1060
|
+
if (linkSelection === pending) finishLinkSelection(false);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
const current = liveSelectionTarget();
|
|
1064
|
+
if (
|
|
1065
|
+
linkSelection !== pending ||
|
|
1066
|
+
current?.client !== expected.client ||
|
|
1067
|
+
current.daemonGeneration !== expected.daemonGeneration
|
|
1068
|
+
) {
|
|
1069
|
+
if (linkSelection === pending) finishLinkSelection(false);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const wrapper = (await expected.client.dispatch({
|
|
1073
|
+
kind: "semantic-intent",
|
|
1074
|
+
intent: {
|
|
1075
|
+
verb: "workspace.window.link.select",
|
|
1076
|
+
workspaceName: expected.workspaceName,
|
|
1077
|
+
target,
|
|
1078
|
+
},
|
|
1079
|
+
})) as { kind?: string; operationId?: string; result?: unknown };
|
|
1080
|
+
const result = WorkspaceWindowLinkSelectResultSchemaZ.safeParse(wrapper.result);
|
|
1081
|
+
if (linkSelection !== pending) return;
|
|
1082
|
+
if (
|
|
1083
|
+
wrapper.kind !== "semantic-intent" ||
|
|
1084
|
+
!result.success ||
|
|
1085
|
+
result.data.operationId !== wrapper.operationId ||
|
|
1086
|
+
result.data.daemonInstanceId !== expected.daemonGeneration ||
|
|
1087
|
+
result.data.workspaceName !== expected.workspaceName ||
|
|
1088
|
+
result.data.target.linkId !== target.linkId ||
|
|
1089
|
+
result.data.target.liveSessionId !== target.liveSessionId ||
|
|
1090
|
+
result.data.target.linkRevision !== target.linkRevision ||
|
|
1091
|
+
result.data.target.expectedSemanticWindowId !== target.expectedSemanticWindowId
|
|
1092
|
+
) {
|
|
1093
|
+
finishLinkSelection(false);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
pending.received = true;
|
|
1097
|
+
reconcileLinkSelection(pending.layout);
|
|
1098
|
+
await settled;
|
|
1099
|
+
} catch {
|
|
1100
|
+
if (linkSelection === pending) finishLinkSelection(false);
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
937
1103
|
sendInput: async (input, parserOrigin) => {
|
|
938
1104
|
await paneInput.sendInput({ input, parserOrigin });
|
|
939
1105
|
},
|
|
@@ -1152,6 +1318,15 @@ export function createApplicationTerminalInteractionController(
|
|
|
1152
1318
|
if (next) paneInput.selectPane(next);
|
|
1153
1319
|
},
|
|
1154
1320
|
cycleWindow() {
|
|
1321
|
+
const topology = options.layout().windowLinks;
|
|
1322
|
+
if (topology) {
|
|
1323
|
+
if (topology.links.length < 2) return;
|
|
1324
|
+
const current = topology.links.findIndex((link) => link.linkId === topology.activeLinkId);
|
|
1325
|
+
const next = topology.links[(current + 1) % topology.links.length]!;
|
|
1326
|
+
const target = windowLinkTarget(options.layout(), next.linkId);
|
|
1327
|
+
if (target) void this.selectWindowLink(target);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1155
1330
|
const windows = options.layout().windows;
|
|
1156
1331
|
if (windows.length < 2) return;
|
|
1157
1332
|
const current = windows.findIndex((window) => window.currentWindow);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WindowLinkTarget } from "@tmux-ide/contracts";
|
|
1
2
|
import type { AgentActivity } from "@tmux-ide/contracts";
|
|
2
3
|
|
|
3
4
|
import type { OpenTuiWorkspaceLayoutSnapshot } from "../open-tui-workspace-runtime-port.ts";
|
|
@@ -309,3 +310,64 @@ export function terminalPaneResizePreview(
|
|
|
309
310
|
}),
|
|
310
311
|
});
|
|
311
312
|
}
|
|
313
|
+
|
|
314
|
+
/** Resolve only live opaque observations; native indexes are display metadata. */
|
|
315
|
+
export function windowLinkTarget(
|
|
316
|
+
snapshot: OpenTuiWorkspaceLayoutSnapshot,
|
|
317
|
+
linkId: string,
|
|
318
|
+
): WindowLinkTarget | null {
|
|
319
|
+
const topology = snapshot.windowLinks;
|
|
320
|
+
const link = topology?.links.find((candidate) => candidate.linkId === linkId);
|
|
321
|
+
if (!topology || !link) return null;
|
|
322
|
+
return {
|
|
323
|
+
liveSessionId: topology.liveSessionId,
|
|
324
|
+
linkRevision: topology.linkRevision,
|
|
325
|
+
linkId: link.linkId,
|
|
326
|
+
expectedSemanticWindowId: link.semanticWindowId,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Pane navigation within the active link must not silently choose a sibling link. */
|
|
331
|
+
export function windowLinkForPane(
|
|
332
|
+
snapshot: OpenTuiWorkspaceLayoutSnapshot,
|
|
333
|
+
paneId: string,
|
|
334
|
+
): WindowLinkTarget | null {
|
|
335
|
+
const backing = snapshot.windows.find((window) =>
|
|
336
|
+
window.panes.some((pane) => pane.pane === paneId),
|
|
337
|
+
);
|
|
338
|
+
const topology = snapshot.windowLinks;
|
|
339
|
+
if (!backing || !topology) return null;
|
|
340
|
+
const links = topology.links.filter((link) => link.semanticWindowId === backing.semanticWindowId);
|
|
341
|
+
const link =
|
|
342
|
+
links.find((candidate) => candidate.linkId === topology.activeLinkId) ??
|
|
343
|
+
(links.length === 1 ? links[0] : undefined);
|
|
344
|
+
return link ? windowLinkTarget(snapshot, link.linkId) : null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Root composition for backing zoom and link actions, all restoring host focus. */
|
|
348
|
+
export function terminalWindowActionCallbacks(
|
|
349
|
+
controller: Pick<
|
|
350
|
+
import("./application-terminal-interaction-controller.ts").ApplicationTerminalInteractionController,
|
|
351
|
+
"selectWindowLink" | "unlinkWindowLink" | "zoomPane" | "selectPane"
|
|
352
|
+
>,
|
|
353
|
+
notify: (message: string) => void,
|
|
354
|
+
recover: import("./application-host-focus-presentation.ts").ApplicationHostFocusRecovery,
|
|
355
|
+
cancelNavigation: () => void,
|
|
356
|
+
) {
|
|
357
|
+
return {
|
|
358
|
+
onSelectPane: recover((paneId: string) => {
|
|
359
|
+
cancelNavigation();
|
|
360
|
+
controller.selectPane(paneId);
|
|
361
|
+
}),
|
|
362
|
+
onZoomPane: recover((paneId?: string) => {
|
|
363
|
+
void controller.zoomPane(paneId).then(notify);
|
|
364
|
+
}),
|
|
365
|
+
onSelectWindowLink: recover((target: WindowLinkTarget) => {
|
|
366
|
+
cancelNavigation();
|
|
367
|
+
void controller.selectWindowLink(target);
|
|
368
|
+
}),
|
|
369
|
+
onUnlinkWindowLink: recover((target: WindowLinkTarget) => {
|
|
370
|
+
void controller.unlinkWindowLink(target).then(notify);
|
|
371
|
+
}),
|
|
372
|
+
};
|
|
373
|
+
}
|