tmux-ide 2.9.0-beta.19 → 2.9.0-beta.21
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 +1979 -783
- package/package.json +4 -2
- package/packages/contracts/src/__tests__/daemon-wire.test.ts +32 -0
- package/packages/contracts/src/daemon-events.ts +12 -12
- package/packages/contracts/src/daemon-wire.ts +30 -0
- package/packages/daemon/dist/command-center/agent-status-watch.js +9 -2
- package/packages/daemon/dist/command-center/diagnostics.js +65 -0
- package/packages/daemon/dist/command-center/log-stream.js +9 -0
- package/packages/daemon/dist/command-center/resources/fleet-preview-route.js +22 -6
- package/packages/daemon/dist/command-center/server.js +7 -0
- package/packages/daemon/dist/doctor.js +62 -0
- package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +400 -0
- package/packages/daemon/dist/lib/app-config.js +4 -2
- package/packages/daemon/dist/lib/canonical-daemon.js +1 -0
- package/packages/daemon/dist/lib/daemon-embed.js +24 -0
- package/packages/daemon/dist/lib/daemon-provenance.js +575 -0
- package/packages/daemon/dist/lib/headless-daemon.js +1 -0
- package/packages/daemon/dist/lib/log-sanitize.js +199 -0
- package/packages/daemon/dist/lib/log.js +123 -13
- package/packages/daemon/dist/lib/soak-diagnostics.js +124 -0
- package/packages/daemon/dist/lib/soak-verdict.js +472 -0
- package/packages/daemon/dist/lib/terminal-host-color.js +33 -0
- package/packages/daemon/dist/lib/tmux-external-interaction-observer.js +187 -39
- package/packages/daemon/dist/lib/tmux-interaction-retention.js +21 -0
- package/packages/daemon/dist/lib/workspace-promotion.js +55 -37
- package/packages/daemon/dist/terminal/mirror/session-channel.js +2 -1
- package/packages/daemon/dist/tui/mirror/automatic-contrast.js +161 -0
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +9 -3
- package/packages/daemon/dist/tui/mirror/pane-surface.jsx +7 -5
- package/packages/daemon/dist/tui/mirror/resize-transaction.js +48 -20
- package/packages/daemon/dist/tui/mirror/runtime/application-appearance-owner.js +40 -6
- package/packages/daemon/dist/tui/mirror/runtime/application-fleet-preview.js +4 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +63 -42
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +138 -67
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-palette-owner.js +44 -28
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +49 -11
- package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +140 -2
- package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +3 -1
- package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +2 -1
- package/packages/daemon/dist/tui/mirror/theme.js +4 -31
- package/packages/daemon/dist/tui/mirror/workspace/terminal-pane-header.jsx +15 -8
- package/packages/daemon/dist/tui/team/wait-receipts.js +112 -33
- package/packages/daemon/src/command-center/agent-status-watch.ts +8 -2
- package/packages/daemon/src/command-center/diagnostics.ts +75 -0
- package/packages/daemon/src/command-center/log-stream.ts +8 -0
- package/packages/daemon/src/command-center/resources/fleet-preview-route.ts +27 -5
- package/packages/daemon/src/command-center/server.ts +8 -0
- package/packages/daemon/src/doctor.ts +70 -0
- package/packages/daemon/src/lib/app-config.ts +11 -3
- package/packages/daemon/src/lib/canonical-daemon.ts +1 -0
- package/packages/daemon/src/lib/daemon-embed.ts +30 -0
- package/packages/daemon/src/lib/daemon-provenance.ts +769 -0
- package/packages/daemon/src/lib/fleet-preview-model.ts +1 -0
- package/packages/daemon/src/lib/headless-daemon.ts +1 -0
- package/packages/daemon/src/lib/log-sanitize.ts +248 -0
- package/packages/daemon/src/lib/log.ts +165 -14
- package/packages/daemon/src/lib/soak-diagnostics.ts +183 -0
- package/packages/daemon/src/lib/soak-verdict.ts +785 -0
- package/packages/daemon/src/lib/terminal-host-color.ts +43 -0
- package/packages/daemon/src/lib/tmux-external-interaction-observer.ts +233 -37
- package/packages/daemon/src/lib/tmux-interaction-retention.ts +24 -0
- package/packages/daemon/src/lib/workspace-promotion.ts +74 -45
- package/packages/daemon/src/terminal/mirror/session-channel.ts +2 -1
- package/packages/daemon/src/tui/mirror/automatic-contrast.ts +180 -0
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +26 -5
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +9 -8
- package/packages/daemon/src/tui/mirror/resize-transaction.ts +46 -22
- package/packages/daemon/src/tui/mirror/runtime/application-appearance-owner.ts +45 -6
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-preview.ts +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-switcher.tsx +2 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +99 -75
- package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +13 -3
- package/packages/daemon/src/tui/mirror/runtime/application-reference-sheet.tsx +125 -0
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +2 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-overlays.tsx +199 -157
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +2 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +148 -69
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-palette-owner.ts +55 -28
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +59 -17
- package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +151 -3
- package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +3 -1
- package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +2 -1
- package/packages/daemon/src/tui/mirror/theme.ts +4 -39
- package/packages/daemon/src/tui/mirror/workspace/terminal-pane-header.tsx +21 -8
- package/packages/daemon/src/tui/team/wait-receipts.ts +115 -40
- package/packages/daemon-client/src/terminal-fast-lane.test.ts +18 -0
- package/packages/daemon-client/src/terminal-fast-lane.ts +7 -2
- package/packages/tmux-bridge/src/index.ts +2 -0
- package/packages/tmux-bridge/src/runner.test.ts +41 -0
- package/packages/tmux-bridge/src/runner.ts +41 -2
|
@@ -104,6 +104,7 @@ function sameTarget(
|
|
|
104
104
|
): boolean {
|
|
105
105
|
return (
|
|
106
106
|
state.operationId === observation.operationId &&
|
|
107
|
+
state.authorityGeneration === observation.authorityGeneration &&
|
|
107
108
|
state.workspaceName === observation.workspaceName &&
|
|
108
109
|
state.semanticPaneId === observation.semanticPaneId &&
|
|
109
110
|
state.axis === observation.axis
|
|
@@ -113,9 +114,9 @@ function sameTarget(
|
|
|
113
114
|
/**
|
|
114
115
|
* Renderer-local pane-resize transaction.
|
|
115
116
|
*
|
|
116
|
-
* Pointer motion
|
|
117
|
-
*
|
|
118
|
-
* operation
|
|
117
|
+
* Pointer motion submits the first target immediately, then retains only the
|
|
118
|
+
* latest desired size while receipt and canonical layout settle the pending
|
|
119
|
+
* operation. Release closes the gesture and flushes that latest target. The controller owns no timers, transport, or
|
|
119
120
|
* rendering effects beyond the functions supplied by its caller.
|
|
120
121
|
*/
|
|
121
122
|
export class ResizeTransactionController {
|
|
@@ -129,6 +130,8 @@ export class ResizeTransactionController {
|
|
|
129
130
|
#projection: OptimisticProjectionState<number, number> | null = null;
|
|
130
131
|
#revision = 0;
|
|
131
132
|
#disposed = false;
|
|
133
|
+
#dragOpen = false;
|
|
134
|
+
#submittedCells: number | null = null;
|
|
132
135
|
|
|
133
136
|
constructor(options: ResizeTransactionControllerOptions) {
|
|
134
137
|
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0) {
|
|
@@ -150,6 +153,8 @@ export class ResizeTransactionController {
|
|
|
150
153
|
const workspaceName = nonEmpty(input.workspaceName, "workspaceName");
|
|
151
154
|
const semanticPaneId = nonEmpty(input.semanticPaneId, "semanticPaneId");
|
|
152
155
|
const canonicalCells = positiveCells(input.canonicalCells, "canonicalCells");
|
|
156
|
+
this.#dragOpen = true;
|
|
157
|
+
this.#submittedCells = null;
|
|
153
158
|
this.#projection = createOptimisticProjection<number, number>({
|
|
154
159
|
generation: authorityGeneration,
|
|
155
160
|
revision: this.#revision,
|
|
@@ -168,28 +173,31 @@ export class ResizeTransactionController {
|
|
|
168
173
|
return true;
|
|
169
174
|
}
|
|
170
175
|
|
|
171
|
-
/**
|
|
176
|
+
/** First/latest coalescing: at most one mutation awaits canonical settlement. */
|
|
172
177
|
move(previewCells: number): boolean {
|
|
173
|
-
if (this.#state.phase
|
|
178
|
+
if (!this.#dragOpen || this.#state.phase === "idle") return false;
|
|
174
179
|
const next = positiveCells(previewCells, "previewCells");
|
|
175
180
|
if (next === this.#state.previewCells) return false;
|
|
176
181
|
this.#emit({ ...this.#state, previewCells: next });
|
|
182
|
+
if (this.#state.phase === "dragging") this.#submit();
|
|
177
183
|
return true;
|
|
178
184
|
}
|
|
179
185
|
|
|
180
|
-
/**
|
|
181
|
-
* Submit once. Duplicate physical release/up/drop events return the existing
|
|
182
|
-
* operation id without authoring another mutation.
|
|
183
|
-
*/
|
|
186
|
+
/** Duplicate releases never author another mutation. */
|
|
184
187
|
release(): string | null {
|
|
188
|
+
this.#dragOpen = false;
|
|
185
189
|
if (this.#state.phase === "pending") return this.#state.operationId;
|
|
186
190
|
if (this.#state.phase !== "dragging") return null;
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
191
|
+
const canonicalCells = this.#state.canonicalCells;
|
|
192
|
+
this.#emit({ phase: "idle", canonicalCells, outcome: null });
|
|
193
|
+
return null;
|
|
194
|
+
}
|
|
192
195
|
|
|
196
|
+
#submit(): string | null {
|
|
197
|
+
if (this.#state.phase !== "dragging") return null;
|
|
198
|
+
const dragging = this.#state;
|
|
199
|
+
if (dragging.previewCells === dragging.canonicalCells) return null;
|
|
200
|
+
this.#submittedCells = dragging.previewCells;
|
|
193
201
|
const operationId = nonEmpty(this.#options.operationId(), "operationId");
|
|
194
202
|
const submittedAt = this.#options.now();
|
|
195
203
|
if (!this.#projection) throw new Error("resize projection is unavailable");
|
|
@@ -245,9 +253,14 @@ export class ResizeTransactionController {
|
|
|
245
253
|
});
|
|
246
254
|
}
|
|
247
255
|
|
|
248
|
-
/**
|
|
256
|
+
/** Discard queued motion; an already dispatched mutation cannot be undone. */
|
|
249
257
|
cancelDrag(): boolean {
|
|
250
|
-
if (this.#state.phase
|
|
258
|
+
if (!this.#dragOpen || this.#state.phase === "idle") return false;
|
|
259
|
+
this.#dragOpen = false;
|
|
260
|
+
if (this.#state.phase === "pending") {
|
|
261
|
+
this.#emit({ ...this.#state, previewCells: this.#submittedCells! });
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
251
264
|
const canonicalCells = this.#state.canonicalCells;
|
|
252
265
|
this.#emit({ phase: "idle", canonicalCells, outcome: null });
|
|
253
266
|
return true;
|
|
@@ -260,6 +273,8 @@ export class ResizeTransactionController {
|
|
|
260
273
|
|
|
261
274
|
/** Retire one runtime lane without permanently disposing the controller. */
|
|
262
275
|
retire(): void {
|
|
276
|
+
this.#dragOpen = false;
|
|
277
|
+
this.#submittedCells = null;
|
|
263
278
|
this.#clearTimeout();
|
|
264
279
|
this.#projection = null;
|
|
265
280
|
if (this.#state.phase !== "idle" || this.#state.canonicalCells !== null) {
|
|
@@ -277,7 +292,8 @@ export class ResizeTransactionController {
|
|
|
277
292
|
)
|
|
278
293
|
return false;
|
|
279
294
|
const cells = positiveCells(observation.cells, "observed cells");
|
|
280
|
-
const
|
|
295
|
+
const pending = this.#state;
|
|
296
|
+
const operationId = pending.operationId;
|
|
281
297
|
if (!this.#projection) return false;
|
|
282
298
|
this.#revision += 1;
|
|
283
299
|
this.#projection = replaceCommittedProjection(
|
|
@@ -290,11 +306,18 @@ export class ResizeTransactionController {
|
|
|
290
306
|
{ observedOperationIds: [operationId], nowMs: this.#options.now() },
|
|
291
307
|
);
|
|
292
308
|
this.#clearTimeout();
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
309
|
+
const hasLatest =
|
|
310
|
+
pending.previewCells !== this.#submittedCells && pending.previewCells !== cells;
|
|
311
|
+
if (this.#dragOpen || hasLatest) {
|
|
312
|
+
this.#emit({ ...pending, phase: "dragging", canonicalCells: cells });
|
|
313
|
+
if (hasLatest) this.#submit();
|
|
314
|
+
} else {
|
|
315
|
+
this.#emit({
|
|
316
|
+
phase: "idle",
|
|
317
|
+
canonicalCells: cells,
|
|
318
|
+
outcome: { kind: "settled", operationId, source, cells },
|
|
319
|
+
});
|
|
320
|
+
}
|
|
298
321
|
return true;
|
|
299
322
|
}
|
|
300
323
|
|
|
@@ -321,6 +344,7 @@ export class ResizeTransactionController {
|
|
|
321
344
|
);
|
|
322
345
|
}
|
|
323
346
|
const canonicalCells = this.#state.canonicalCells;
|
|
347
|
+
this.#dragOpen = false;
|
|
324
348
|
this.#clearTimeout();
|
|
325
349
|
this.#emit({
|
|
326
350
|
phase: "idle",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { CliRenderer } from "@opentui/core";
|
|
2
|
+
import { createAutomaticContrastPass } from "../automatic-contrast.ts";
|
|
1
3
|
import { VISUAL_THEME_PRESETS, findVisualThemePreset } from "@tmux-ide/contracts";
|
|
2
4
|
import { batch, createSignal, type Accessor } from "solid-js";
|
|
3
5
|
|
|
@@ -26,6 +28,8 @@ export interface ApplicationAppearanceOwner {
|
|
|
26
28
|
readonly palette: Accessor<TerminalPaletteProjection>;
|
|
27
29
|
readonly hostPalette: Accessor<ApplicationTerminalPaletteSnapshot>;
|
|
28
30
|
readonly setTransientNote: (note: string | null) => void;
|
|
31
|
+
readonly automaticContrast: Accessor<boolean>;
|
|
32
|
+
readonly toggleAutomaticContrast: () => void;
|
|
29
33
|
readonly pickerOpen: Accessor<boolean>;
|
|
30
34
|
readonly pickerError: Accessor<string | null>;
|
|
31
35
|
readonly openPicker: () => void;
|
|
@@ -54,15 +58,36 @@ export interface ApplicationAppearanceSnapshot {
|
|
|
54
58
|
}
|
|
55
59
|
|
|
56
60
|
function hostDefaults(snapshot: ApplicationTerminalPaletteSnapshot) {
|
|
57
|
-
|
|
58
|
-
|
|
61
|
+
return (
|
|
62
|
+
deriveSystemVisualHostDefaults(snapshot) ?? { appearance: snapshot.detectedMode, overrides: {} }
|
|
63
|
+
);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
export function createAppearanceOwner(
|
|
62
67
|
config: AppConfig,
|
|
63
|
-
renderer: ThemeModeSource
|
|
68
|
+
renderer: ThemeModeSource &
|
|
69
|
+
Partial<Pick<CliRenderer, "addPostProcessFn" | "removePostProcessFn" | "requestRender">>,
|
|
64
70
|
terminalPaletteOwner: ApplicationTerminalPaletteOwner,
|
|
65
71
|
): ApplicationAppearanceOwner {
|
|
72
|
+
const [automaticContrast, setAutomaticContrast] = createSignal(
|
|
73
|
+
config.theme.automaticContrast ?? true,
|
|
74
|
+
);
|
|
75
|
+
const correct = createAutomaticContrastPass();
|
|
76
|
+
const postProcess: Parameters<CliRenderer["addPostProcessFn"]>[0] = (buffer) => {
|
|
77
|
+
if (automaticContrast()) correct(buffer);
|
|
78
|
+
};
|
|
79
|
+
renderer.addPostProcessFn?.(postProcess);
|
|
80
|
+
const setContrast = (enabled: boolean) => {
|
|
81
|
+
if (enabled === automaticContrast()) return;
|
|
82
|
+
setAutomaticContrast(enabled);
|
|
83
|
+
renderer.requestRender?.();
|
|
84
|
+
};
|
|
85
|
+
const toggleAutomaticContrast = () => {
|
|
86
|
+
if (pickerOpen()) {
|
|
87
|
+
setContrast(!automaticContrast());
|
|
88
|
+
setPickerError(null);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
66
91
|
const initialHostPalette = terminalPaletteOwner.getSnapshot();
|
|
67
92
|
const store = createSemanticThemeStore(config.theme, {
|
|
68
93
|
rendererMode: renderer.themeMode,
|
|
@@ -128,10 +153,17 @@ export function createAppearanceOwner(
|
|
|
128
153
|
const apply = (id: string) => {
|
|
129
154
|
const preset = findVisualThemePreset(id);
|
|
130
155
|
const mode = preset?.appearance ?? (id as ThemeModeSetting);
|
|
131
|
-
|
|
156
|
+
const wasSystem = theme().setting === "system";
|
|
157
|
+
store.configure({
|
|
158
|
+
...config.theme,
|
|
159
|
+
mode,
|
|
160
|
+
preset: preset?.id,
|
|
161
|
+
});
|
|
162
|
+
if (mode === "system" && !wasSystem) void terminalPaletteOwner.refresh();
|
|
132
163
|
setPickerSelection(id);
|
|
133
164
|
};
|
|
134
165
|
let originalSelection = pickerSelection();
|
|
166
|
+
let originalContrast = automaticContrast();
|
|
135
167
|
const preview = (id: string): void => {
|
|
136
168
|
if (!pickerOpen()) return;
|
|
137
169
|
setPickerError(null);
|
|
@@ -139,6 +171,7 @@ export function createAppearanceOwner(
|
|
|
139
171
|
};
|
|
140
172
|
const cancelPicker = (): void => {
|
|
141
173
|
if (!pickerOpen()) return;
|
|
174
|
+
setContrast(originalContrast);
|
|
142
175
|
apply(originalSelection);
|
|
143
176
|
setPickerOpen(false);
|
|
144
177
|
setPickerError(null);
|
|
@@ -148,6 +181,7 @@ export function createAppearanceOwner(
|
|
|
148
181
|
try {
|
|
149
182
|
updateAppConfig({
|
|
150
183
|
theme: {
|
|
184
|
+
automaticContrast: automaticContrast(),
|
|
151
185
|
mode: theme().setting,
|
|
152
186
|
preset: findVisualThemePreset(pickerSelection())?.id ?? "",
|
|
153
187
|
},
|
|
@@ -165,7 +199,7 @@ export function createAppearanceOwner(
|
|
|
165
199
|
const order: readonly ThemeModeSetting[] = ["dark", "light", "system"];
|
|
166
200
|
const next = order[(order.indexOf(theme().setting) + 1) % order.length]!;
|
|
167
201
|
apply(next);
|
|
168
|
-
updateAppConfig({ theme: { mode: next, preset: "" } });
|
|
202
|
+
updateAppConfig({ theme: { mode: next, preset: "", automaticContrast: automaticContrast() } });
|
|
169
203
|
notice.publish(`theme → ${next}`);
|
|
170
204
|
};
|
|
171
205
|
return {
|
|
@@ -176,6 +210,8 @@ export function createAppearanceOwner(
|
|
|
176
210
|
palette,
|
|
177
211
|
hostPalette,
|
|
178
212
|
setTransientNote: notice.publish,
|
|
213
|
+
automaticContrast,
|
|
214
|
+
toggleAutomaticContrast,
|
|
179
215
|
pickerOpen,
|
|
180
216
|
pickerError,
|
|
181
217
|
pickerQuery,
|
|
@@ -184,6 +220,7 @@ export function createAppearanceOwner(
|
|
|
184
220
|
openPicker() {
|
|
185
221
|
if (pickerOpen()) return;
|
|
186
222
|
originalSelection = pickerSelection();
|
|
223
|
+
originalContrast = automaticContrast();
|
|
187
224
|
setPickerQuery("");
|
|
188
225
|
setPickerError(null);
|
|
189
226
|
setPickerOpen(true);
|
|
@@ -196,7 +233,8 @@ export function createAppearanceOwner(
|
|
|
196
233
|
if (event.eventType === "release" || event.repeated) return true;
|
|
197
234
|
const name = event.name.toLowerCase();
|
|
198
235
|
const choices = pickerOptions();
|
|
199
|
-
if (name === "
|
|
236
|
+
if (event.ctrl && name === "a") toggleAutomaticContrast();
|
|
237
|
+
else if (name === "escape") cancelPicker();
|
|
200
238
|
else if (name === "return" || name === "enter") savePicker();
|
|
201
239
|
else if (name === "up" || name === "down" || name === "tab") {
|
|
202
240
|
if (choices.length) {
|
|
@@ -219,6 +257,7 @@ export function createAppearanceOwner(
|
|
|
219
257
|
},
|
|
220
258
|
cycleTheme,
|
|
221
259
|
dispose() {
|
|
260
|
+
renderer.removePostProcessFn?.(postProcess);
|
|
222
261
|
stopTheme();
|
|
223
262
|
stopRendererTheme();
|
|
224
263
|
stopHostPalette();
|
|
@@ -14,6 +14,7 @@ export async function readFleetWindowPreview(
|
|
|
14
14
|
liveSessionId: string,
|
|
15
15
|
signal: AbortSignal,
|
|
16
16
|
windowId?: string,
|
|
17
|
+
paneId?: string,
|
|
17
18
|
): Promise<FleetPreviewResult> {
|
|
18
19
|
const daemon = handle.read();
|
|
19
20
|
const epoch = handle.endpoint().epoch;
|
|
@@ -32,6 +33,7 @@ export async function readFleetWindowPreview(
|
|
|
32
33
|
body: JSON.stringify({
|
|
33
34
|
expectedInstanceId: daemon.instanceId,
|
|
34
35
|
liveSessionId,
|
|
36
|
+
...(paneId ? { paneId } : {}),
|
|
35
37
|
...(windowId ? { windowId } : {}),
|
|
36
38
|
}),
|
|
37
39
|
},
|
|
@@ -63,6 +65,7 @@ export async function readFleetWindowPreview(
|
|
|
63
65
|
body.daemon?.instanceId !== daemon.instanceId ||
|
|
64
66
|
body.daemon?.startedAt !== daemon.startedAt ||
|
|
65
67
|
body.liveSessionId !== liveSessionId ||
|
|
68
|
+
(paneId && body.selectedPaneId !== paneId) ||
|
|
66
69
|
typeof body.text !== "string" ||
|
|
67
70
|
body.text.length > 8192
|
|
68
71
|
)
|
|
@@ -103,6 +106,7 @@ export async function readFleetWindowPreview(
|
|
|
103
106
|
status: "ready",
|
|
104
107
|
snapshot: {
|
|
105
108
|
text: clean(body.text),
|
|
109
|
+
...(body.selectedPaneId ? { selectedPaneId: body.selectedPaneId } : {}),
|
|
106
110
|
selectedWindowId,
|
|
107
111
|
windows: windows.map((w) => ({
|
|
108
112
|
id: w.id,
|
|
@@ -83,7 +83,8 @@ export function ApplicationFleetSwitcher(props: {
|
|
|
83
83
|
if (!props.open || props.active === false || modal()) return false;
|
|
84
84
|
const key = event.name.toLowerCase();
|
|
85
85
|
// Preview and action components own these chords, without text or terminal leakage.
|
|
86
|
-
if (event.ctrl && ["left", "right", "p", "e", "n", "x", "r"].includes(key))
|
|
86
|
+
if (event.ctrl && ["left", "right", "p", "e", "n", "x", "r", "k", "b"].includes(key))
|
|
87
|
+
return false;
|
|
87
88
|
if (event.ctrl && key === "f") {
|
|
88
89
|
event.preventDefault();
|
|
89
90
|
event.stopPropagation();
|
|
@@ -175,6 +175,7 @@ export function ApplicationMachineSidebar(props: {
|
|
|
175
175
|
: []),
|
|
176
176
|
]),
|
|
177
177
|
);
|
|
178
|
+
const rowsByKey = createMemo(() => new Map(rows().map((row) => [row.key, row])));
|
|
178
179
|
const index = () =>
|
|
179
180
|
Math.max(
|
|
180
181
|
0,
|
|
@@ -437,83 +438,106 @@ export function ApplicationMachineSidebar(props: {
|
|
|
437
438
|
scrollY={true}
|
|
438
439
|
horizontalScrollbarOptions={{ visible: false }}
|
|
439
440
|
>
|
|
440
|
-
<For each={rows()}>
|
|
441
|
-
{(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
441
|
+
<For each={rows().map((row) => row.key)}>
|
|
442
|
+
{(key) => {
|
|
443
|
+
// The semantic key owns the native row; fresh snapshots update its
|
|
444
|
+
// props instead of destroying and recreating identical renderables.
|
|
445
|
+
const current = createMemo<Row>(
|
|
446
|
+
(previous) => rowsByKey().get(key) ?? previous,
|
|
447
|
+
rowsByKey().get(key)!,
|
|
448
|
+
);
|
|
449
|
+
const row: Row = {
|
|
450
|
+
key,
|
|
451
|
+
get group() {
|
|
452
|
+
return current().group;
|
|
453
|
+
},
|
|
454
|
+
get session() {
|
|
455
|
+
return current().session;
|
|
456
|
+
},
|
|
457
|
+
get agent() {
|
|
458
|
+
return current().agent;
|
|
459
|
+
},
|
|
460
|
+
get agentHeading() {
|
|
461
|
+
return current().agentHeading;
|
|
462
|
+
},
|
|
463
|
+
};
|
|
464
|
+
return (
|
|
465
|
+
<box
|
|
457
466
|
width={Math.max(1, props.width - 1)}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
?
|
|
478
|
-
:
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
:
|
|
490
|
-
|
|
491
|
-
|
|
467
|
+
height={row.agent ? (row.agentHeading ? 3 : 2) : 1}
|
|
468
|
+
flexShrink={0}
|
|
469
|
+
flexDirection="column"
|
|
470
|
+
>
|
|
471
|
+
<Show when={row.agentHeading}>
|
|
472
|
+
<text height={1} fg={props.theme.roles.text.secondary}>
|
|
473
|
+
{" "}
|
|
474
|
+
Agents
|
|
475
|
+
</text>
|
|
476
|
+
</Show>
|
|
477
|
+
<NavigationRow
|
|
478
|
+
theme={props.theme}
|
|
479
|
+
id={`machine:${row.key}`}
|
|
480
|
+
width={Math.max(1, props.width - 1)}
|
|
481
|
+
labelColor={!row.agent && !row.session ? fleetHostColor(row.group) : undefined}
|
|
482
|
+
label={
|
|
483
|
+
row.agent
|
|
484
|
+
? row.agent.name
|
|
485
|
+
: row.session
|
|
486
|
+
? friendlySessionLabel(row.session.name)
|
|
487
|
+
: row.group.label
|
|
488
|
+
}
|
|
489
|
+
marker={
|
|
490
|
+
row.agent
|
|
491
|
+
? active(row)
|
|
492
|
+
? row.agent.attention
|
|
493
|
+
? "›!"
|
|
494
|
+
: "›"
|
|
495
|
+
: row.agent.attention
|
|
496
|
+
? "!"
|
|
497
|
+
: "•"
|
|
498
|
+
: row.session
|
|
499
|
+
? props.model.favorites?.().includes(row.session.id)
|
|
500
|
+
? " ★"
|
|
501
|
+
: active(row)
|
|
502
|
+
? " ›"
|
|
503
|
+
: " "
|
|
504
|
+
: collapsed().has(preferenceKey(row.group))
|
|
505
|
+
? "▸"
|
|
506
|
+
: "▾"
|
|
507
|
+
}
|
|
508
|
+
detail={
|
|
509
|
+
row.agent
|
|
510
|
+
? row.group.state !== "ready" || row.agent.disabled
|
|
492
511
|
? "unavailable"
|
|
493
|
-
:
|
|
494
|
-
: row.
|
|
495
|
-
?
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
512
|
+
: `[${terminalAgentStatusLabel(row.agent.activity)}]`
|
|
513
|
+
: row.session
|
|
514
|
+
? row.group.state !== "ready" || row.session.disabled
|
|
515
|
+
? "unavailable"
|
|
516
|
+
: `${row.session.paneCount}p${row.group.agents?.length ? ` ${activity(row).label}` : ""}`
|
|
517
|
+
: row.group.agents?.length && row.group.state === "ready"
|
|
518
|
+
? activity(row).label
|
|
519
|
+
: `${connectionDetail(row.group)}${row.group.agents?.length ? " ?" : ""}`
|
|
520
|
+
}
|
|
521
|
+
selected={Boolean((row.session || row.agent) && active(row))}
|
|
522
|
+
focused={Boolean(focused() && row.key === selectedKey())}
|
|
523
|
+
attention={row.agent?.attention ?? activity(row).kind === "attention"}
|
|
524
|
+
onActivate={(source) => activate(row, source)}
|
|
525
|
+
/>
|
|
526
|
+
<Show when={row.agent}>
|
|
527
|
+
{(agent) => (
|
|
528
|
+
<text
|
|
529
|
+
height={1}
|
|
530
|
+
fg={props.theme.roles.text.muted}
|
|
531
|
+
content={clipTerminal(
|
|
532
|
+
` ${friendlySessionLabel(agent().sessionName)}`,
|
|
533
|
+
Math.max(1, props.width - 1),
|
|
534
|
+
)}
|
|
535
|
+
/>
|
|
536
|
+
)}
|
|
537
|
+
</Show>
|
|
538
|
+
</box>
|
|
539
|
+
);
|
|
540
|
+
}}
|
|
517
541
|
</For>
|
|
518
542
|
</scrollbox>
|
|
519
543
|
<For each={agentHeight() > 0 ? [true] : []}>
|
|
@@ -45,15 +45,24 @@ export function ApplicationPalettePreview(props: {
|
|
|
45
45
|
const owner = createAdaptiveFleetPreviewOwner(setState, { cache: fleetPreviewMemory });
|
|
46
46
|
const stop = applicationMachineAuthorityManager.subscribe(() => setRevision((v) => v + 1));
|
|
47
47
|
const command = () => (typeof props.command === "object" ? props.command : undefined);
|
|
48
|
+
const paneId = () => {
|
|
49
|
+
const c = command();
|
|
50
|
+
return c?.kind === "jump-agent" ? c.paneId : undefined;
|
|
51
|
+
};
|
|
48
52
|
const target = () => command()?.fleet;
|
|
49
53
|
const identity = () =>
|
|
50
|
-
JSON.stringify([
|
|
54
|
+
JSON.stringify([
|
|
55
|
+
target()?.machineId,
|
|
56
|
+
target()?.liveSessionId,
|
|
57
|
+
target()?.daemonInstanceId,
|
|
58
|
+
paneId(),
|
|
59
|
+
]);
|
|
51
60
|
let previousIdentity = "";
|
|
52
61
|
createEffect(() => {
|
|
53
62
|
const next = identity();
|
|
54
63
|
if (next !== previousIdentity) {
|
|
55
64
|
previousIdentity = next;
|
|
56
|
-
setWindowId(rememberedWindows.get(next));
|
|
65
|
+
setWindowId(paneId() ? undefined : rememberedWindows.get(next));
|
|
57
66
|
}
|
|
58
67
|
});
|
|
59
68
|
createEffect(() => {
|
|
@@ -78,6 +87,7 @@ export function ApplicationPalettePreview(props: {
|
|
|
78
87
|
fleet.liveSessionId,
|
|
79
88
|
signal,
|
|
80
89
|
selected,
|
|
90
|
+
selected ? undefined : paneId(),
|
|
81
91
|
);
|
|
82
92
|
// A remembered window may have been closed since the last visit.
|
|
83
93
|
// Fall back to the session's current window instead of retrying it forever.
|
|
@@ -135,7 +145,7 @@ export function ApplicationPalettePreview(props: {
|
|
|
135
145
|
const heading = () => {
|
|
136
146
|
const snapshot = state().snapshot;
|
|
137
147
|
const window = snapshot?.windows.find((w) => w.id === snapshot.selectedWindowId);
|
|
138
|
-
return `${target()?.hostLabel ?? "Session preview"} · ${command()?.sessionName ?? "Select a session"}${window ? ` · ${window.index}: ${window.name}` : ""}`;
|
|
148
|
+
return `${paneId() ? command()?.label + " · " : ""}${target()?.hostLabel ?? "Session preview"} · ${command()?.sessionName ?? "Select a session"}${window ? ` · ${window.index}: ${window.name}` : ""}`;
|
|
139
149
|
};
|
|
140
150
|
const hostColor = () => {
|
|
141
151
|
revision();
|