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
package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js
CHANGED
|
@@ -54,6 +54,29 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
54
54
|
let previousCurrentWindow = null;
|
|
55
55
|
let pendingResizeGuide = null;
|
|
56
56
|
let pendingPaneResize = null;
|
|
57
|
+
let paneResizeFrame = null;
|
|
58
|
+
let resizePaneIdentity = null;
|
|
59
|
+
const captureResizePaneIdentity = (semanticPaneId) => {
|
|
60
|
+
const identity = options.generation()?.adapter?.paneCanonicalIdentity(semanticPaneId);
|
|
61
|
+
resizePaneIdentity = identity
|
|
62
|
+
? {
|
|
63
|
+
semanticPaneId,
|
|
64
|
+
incarnation: identity.incarnation,
|
|
65
|
+
generation: identity.generation,
|
|
66
|
+
sourceEpoch: identity.sourceEpoch,
|
|
67
|
+
}
|
|
68
|
+
: null;
|
|
69
|
+
};
|
|
70
|
+
const resizePaneIdentityCurrent = () => {
|
|
71
|
+
if (!resizePaneIdentity)
|
|
72
|
+
return false;
|
|
73
|
+
const current = options
|
|
74
|
+
.generation()
|
|
75
|
+
?.adapter?.paneCanonicalIdentity(resizePaneIdentity.semanticPaneId);
|
|
76
|
+
return (current?.incarnation === resizePaneIdentity.incarnation &&
|
|
77
|
+
current?.generation === resizePaneIdentity.generation &&
|
|
78
|
+
current?.sourceEpoch === resizePaneIdentity.sourceEpoch);
|
|
79
|
+
};
|
|
57
80
|
let lastResizeGuidePresentationDigest = null;
|
|
58
81
|
let lastWindowPresentationDigest = null;
|
|
59
82
|
let diagnosticWindowFrame = null;
|
|
@@ -144,22 +167,35 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
144
167
|
};
|
|
145
168
|
const maybeRequestPaneResizeFrame = () => {
|
|
146
169
|
const pending = pendingPaneResize;
|
|
147
|
-
if (!pending ||
|
|
148
|
-
pending.frameRequested ||
|
|
149
|
-
pending.receiptCells === null ||
|
|
150
|
-
pending.layoutCells !== pending.receiptCells)
|
|
170
|
+
if (!pending || pending.receiptCells === null || pending.layoutCells !== pending.receiptCells)
|
|
151
171
|
return;
|
|
152
|
-
if (!
|
|
172
|
+
if (!resizePaneIdentityCurrent()) {
|
|
153
173
|
pendingPaneResize = null;
|
|
174
|
+
resizeTransaction.retire();
|
|
154
175
|
return;
|
|
155
176
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
177
|
+
// Detach before settlement: it can synchronously dispatch the latest target.
|
|
178
|
+
pendingPaneResize = null;
|
|
179
|
+
if (options.diagnosticsEnabled && pending.receiptOutcome !== "unchanged") {
|
|
180
|
+
if (paneResizeFrame)
|
|
181
|
+
diagnose("pane-resize-frame-superseded", { operationId: paneResizeFrame.operationId });
|
|
182
|
+
paneResizeFrame = pending;
|
|
183
|
+
pending.frameRequested = true;
|
|
184
|
+
try {
|
|
185
|
+
options.requestRender?.();
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
paneResizeFrame = null;
|
|
189
|
+
}
|
|
162
190
|
}
|
|
191
|
+
resizeTransaction.observeLayout({
|
|
192
|
+
operationId: pending.operationId,
|
|
193
|
+
authorityGeneration: pending.daemonGeneration,
|
|
194
|
+
workspaceName: pending.workspaceName,
|
|
195
|
+
semanticPaneId: pending.semanticPaneId,
|
|
196
|
+
axis: pending.axis,
|
|
197
|
+
cells: pending.receiptCells,
|
|
198
|
+
});
|
|
163
199
|
};
|
|
164
200
|
const dispatchPaneResize = (preview, source, operationId, beforeCells) => {
|
|
165
201
|
const expected = liveResizeTarget();
|
|
@@ -205,6 +241,7 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
205
241
|
layoutCells: null,
|
|
206
242
|
frameRequested: false,
|
|
207
243
|
};
|
|
244
|
+
diagnose("pane-resize-dispatch", resizeDiagnosticDetails(pendingPaneResize));
|
|
208
245
|
const failure = { current: null };
|
|
209
246
|
void resizeTerminalPane(expected, liveResizeTarget, {
|
|
210
247
|
operationId,
|
|
@@ -238,32 +275,15 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
238
275
|
...resizeDiagnosticDetails(pending),
|
|
239
276
|
verb: "workspace.pane.resize",
|
|
240
277
|
});
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
workspaceName: pending.workspaceName,
|
|
247
|
-
semanticPaneId: pending.semanticPaneId,
|
|
248
|
-
axis: pending.axis,
|
|
249
|
-
cells: receipt.cells,
|
|
250
|
-
});
|
|
278
|
+
// A clamp/no-op still needs current canonical truth, not the guide.
|
|
279
|
+
const observed = paneCells(options.layout(), pending.semanticPaneId, pending.axis);
|
|
280
|
+
if (observed === receipt.cells)
|
|
281
|
+
pending.layoutCells = observed;
|
|
282
|
+
if (receipt.outcome === "unchanged")
|
|
251
283
|
diagnoseCritical(`pane-resize:${operationId}:unchanged`, "pane-resize-unchanged", {
|
|
252
284
|
...resizeDiagnosticDetails(pending),
|
|
253
285
|
changed: false,
|
|
254
286
|
});
|
|
255
|
-
pendingPaneResize = null;
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
if (pending.layoutCells === receipt.cells)
|
|
259
|
-
resizeTransaction.observeLayout({
|
|
260
|
-
operationId,
|
|
261
|
-
authorityGeneration: pending.daemonGeneration,
|
|
262
|
-
workspaceName: pending.workspaceName,
|
|
263
|
-
semanticPaneId: pending.semanticPaneId,
|
|
264
|
-
axis: pending.axis,
|
|
265
|
-
cells: receipt.cells,
|
|
266
|
-
});
|
|
267
287
|
maybeRequestPaneResizeFrame();
|
|
268
288
|
})
|
|
269
289
|
.catch(() => {
|
|
@@ -288,8 +308,15 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
288
308
|
submit: ({ operationId, intent }) => {
|
|
289
309
|
const state = resizeTransaction.state();
|
|
290
310
|
const context = resizeCommitContext;
|
|
291
|
-
|
|
292
|
-
|
|
311
|
+
const live = liveResizeTarget();
|
|
312
|
+
if (state.phase !== "pending" ||
|
|
313
|
+
state.operationId !== operationId ||
|
|
314
|
+
!context ||
|
|
315
|
+
!resizePaneIdentityCurrent() ||
|
|
316
|
+
live?.daemonGeneration !== state.authorityGeneration ||
|
|
317
|
+
live.workspaceName !== state.workspaceName ||
|
|
318
|
+
!options.layout().current?.panes.some((pane) => pane.pane === intent.semanticPaneId))
|
|
319
|
+
throw new Error("resize target retired");
|
|
293
320
|
dispatchPaneResize({
|
|
294
321
|
semanticPaneId: intent.semanticPaneId,
|
|
295
322
|
axis: intent.axis,
|
|
@@ -299,6 +326,8 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
299
326
|
}, context.source, operationId, state.canonicalCells);
|
|
300
327
|
},
|
|
301
328
|
onState: (state) => {
|
|
329
|
+
if (state.phase === "idle" && state.outcome?.kind === "settled")
|
|
330
|
+
resizeCommitContext = null;
|
|
302
331
|
if (state.phase === "idle" && state.outcome?.kind === "reverted") {
|
|
303
332
|
diagnose("pane-resize-transaction-reverted", {
|
|
304
333
|
operationId: state.outcome.operationId,
|
|
@@ -516,6 +545,7 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
516
545
|
pendingWindowRename = null;
|
|
517
546
|
diagnosticWindowFrame = null;
|
|
518
547
|
pendingPaneResize = null;
|
|
548
|
+
paneResizeFrame = null;
|
|
519
549
|
pendingResizeGuide = null;
|
|
520
550
|
resizeCommitContext = null;
|
|
521
551
|
resizeTransaction.retire();
|
|
@@ -586,20 +616,20 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
586
616
|
});
|
|
587
617
|
}
|
|
588
618
|
}
|
|
619
|
+
const resizeState = resizeTransaction.state();
|
|
620
|
+
if (resizeState.phase !== "idle" &&
|
|
621
|
+
!snapshot.current?.panes.some((pane) => pane.pane === resizeState.semanticPaneId)) {
|
|
622
|
+
resizeTransaction.retire();
|
|
623
|
+
pendingPaneResize = null;
|
|
624
|
+
pendingResizeGuide = null;
|
|
625
|
+
}
|
|
589
626
|
if (pendingPaneResize) {
|
|
590
627
|
const cells = paneCells(snapshot, pendingPaneResize.semanticPaneId, pendingPaneResize.axis);
|
|
591
|
-
if (cells !== null &&
|
|
628
|
+
if (cells !== null &&
|
|
629
|
+
cells !== pendingPaneResize.layoutCells &&
|
|
630
|
+
cells !== pendingPaneResize.beforeCells) {
|
|
592
631
|
pendingPaneResize.layoutCells = cells;
|
|
593
632
|
diagnose("pane-resize-layout", resizeDiagnosticDetails(pendingPaneResize));
|
|
594
|
-
if (pendingPaneResize.receiptCells === cells)
|
|
595
|
-
resizeTransaction.observeLayout({
|
|
596
|
-
operationId: pendingPaneResize.operationId,
|
|
597
|
-
authorityGeneration: pendingPaneResize.daemonGeneration,
|
|
598
|
-
workspaceName: pendingPaneResize.workspaceName,
|
|
599
|
-
semanticPaneId: pendingPaneResize.semanticPaneId,
|
|
600
|
-
axis: pendingPaneResize.axis,
|
|
601
|
-
cells,
|
|
602
|
-
});
|
|
603
633
|
}
|
|
604
634
|
}
|
|
605
635
|
maybeRequestWindowSwitchFrame();
|
|
@@ -639,7 +669,9 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
639
669
|
const live = liveResizeTarget();
|
|
640
670
|
const canonicalCells = paneCells(options.layout(), preview.semanticPaneId, preview.axis);
|
|
641
671
|
const state = resizeTransaction.state();
|
|
642
|
-
if (live && canonicalCells !== null && state.phase === "idle")
|
|
672
|
+
if (!resizeCommitContext && live && canonicalCells !== null && state.phase === "idle") {
|
|
673
|
+
resizeCommitContext = { source: "pointer", pointerIngress: preview.pointerIngress ?? null };
|
|
674
|
+
captureResizePaneIdentity(preview.semanticPaneId);
|
|
643
675
|
resizeTransaction.begin({
|
|
644
676
|
authorityGeneration: live.daemonGeneration,
|
|
645
677
|
workspaceName: live.workspaceName,
|
|
@@ -647,6 +679,9 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
647
679
|
axis: preview.axis,
|
|
648
680
|
canonicalCells,
|
|
649
681
|
});
|
|
682
|
+
}
|
|
683
|
+
if (resizeCommitContext?.source === "pointer")
|
|
684
|
+
resizeCommitContext = { source: "pointer", pointerIngress: preview.pointerIngress ?? null };
|
|
650
685
|
resizeTransaction.move(preview.cells);
|
|
651
686
|
if (!options.diagnosticsEnabled)
|
|
652
687
|
return;
|
|
@@ -691,6 +726,7 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
691
726
|
startedAtMicros,
|
|
692
727
|
preview,
|
|
693
728
|
guideDigest,
|
|
729
|
+
workspaceName: active.connection.workspaceName,
|
|
694
730
|
daemonGeneration: active.daemonGeneration,
|
|
695
731
|
clientGeneration: clientGeneration,
|
|
696
732
|
rendererEpoch: active.rendererEpoch,
|
|
@@ -722,17 +758,42 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
722
758
|
resizePane(preview) {
|
|
723
759
|
pendingResizeGuide = null;
|
|
724
760
|
lastResizeGuidePresentationDigest = null;
|
|
761
|
+
if (!resizeCommitContext || resizeCommitContext.source !== "pointer")
|
|
762
|
+
return;
|
|
763
|
+
resizeCommitContext = { source: "pointer", pointerIngress: preview.pointerIngress ?? null };
|
|
725
764
|
resizeTransaction.move(preview.cells);
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
765
|
+
resizeTransaction.release();
|
|
766
|
+
if (options.diagnosticsEnabled)
|
|
767
|
+
try {
|
|
768
|
+
const active = options.generation();
|
|
769
|
+
const identity = active?.adapter?.paneCanonicalIdentity(preview.semanticPaneId);
|
|
770
|
+
diagnose("pane-resize-release", {
|
|
771
|
+
semanticPaneId: preview.semanticPaneId,
|
|
772
|
+
axis: preview.axis,
|
|
773
|
+
requestedCells: preview.cells,
|
|
774
|
+
pointerIngress: preview.pointerIngress ?? null,
|
|
775
|
+
transactionPhase: resizeTransaction.state().phase,
|
|
776
|
+
workspaceName: active?.connection?.workspaceName,
|
|
777
|
+
daemonGeneration: active?.daemonGeneration,
|
|
778
|
+
clientGeneration: active?.client?.getSnapshot().generation,
|
|
779
|
+
rendererEpoch: active?.rendererEpoch,
|
|
780
|
+
sourceEpoch: identity?.sourceEpoch,
|
|
781
|
+
generation: identity?.generation,
|
|
782
|
+
incarnation: identity?.incarnation,
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
catch {
|
|
786
|
+
/* Diagnostics never own gesture completion. */
|
|
787
|
+
}
|
|
788
|
+
// Retain context while a coalesced final target awaits dispatch.
|
|
789
|
+
if (resizeTransaction.state().phase === "idle")
|
|
790
|
+
resizeCommitContext = null;
|
|
791
|
+
},
|
|
792
|
+
cancelPaneResize() {
|
|
793
|
+
resizeTransaction.cancelDrag();
|
|
794
|
+
pendingResizeGuide = null;
|
|
795
|
+
if (resizeTransaction.state().phase === "idle")
|
|
734
796
|
resizeCommitContext = null;
|
|
735
|
-
}
|
|
736
797
|
},
|
|
737
798
|
keyboardResize(axis, direction) {
|
|
738
799
|
const paneId = options.focusedPane?.() ?? null;
|
|
@@ -752,13 +813,15 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
752
813
|
canonicalCells: beforeCells,
|
|
753
814
|
}))
|
|
754
815
|
return;
|
|
755
|
-
resizeTransaction.move(Math.max(1, beforeCells + direction));
|
|
756
816
|
resizeCommitContext = { source: "keyboard", pointerIngress: null };
|
|
817
|
+
captureResizePaneIdentity(paneId);
|
|
818
|
+
resizeTransaction.move(Math.max(1, beforeCells + direction));
|
|
757
819
|
try {
|
|
758
820
|
resizeTransaction.release();
|
|
759
821
|
}
|
|
760
822
|
finally {
|
|
761
|
-
|
|
823
|
+
if (resizeTransaction.state().phase === "idle")
|
|
824
|
+
resizeCommitContext = null;
|
|
762
825
|
}
|
|
763
826
|
},
|
|
764
827
|
routeWorkspaceKey(event) {
|
|
@@ -1235,12 +1298,14 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
1235
1298
|
pendingResizeGuide = null;
|
|
1236
1299
|
const settledAtMicros = diagnosticNowMicros();
|
|
1237
1300
|
let identityExact;
|
|
1301
|
+
let canonicalAfter = null;
|
|
1238
1302
|
try {
|
|
1239
1303
|
const active = options.generation();
|
|
1240
1304
|
const identity = active?.adapter?.paneCanonicalIdentity(settled.preview.semanticPaneId);
|
|
1241
1305
|
const clientGeneration = active?.client?.getSnapshot().generation;
|
|
1242
1306
|
const layoutWindow = options.layout().current;
|
|
1243
1307
|
const layoutPane = layoutWindow?.panes.find(({ pane }) => pane === settled.preview.semanticPaneId);
|
|
1308
|
+
canonicalAfter = identity ?? null;
|
|
1244
1309
|
identityExact =
|
|
1245
1310
|
active?.status === "live" &&
|
|
1246
1311
|
active.daemonGeneration === settled.daemonGeneration &&
|
|
@@ -1250,14 +1315,11 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
1250
1315
|
identity?.sourceEpoch === settled.sourceEpoch &&
|
|
1251
1316
|
identity.generation === settled.generation &&
|
|
1252
1317
|
identity.incarnation === settled.incarnation &&
|
|
1253
|
-
identity.revision
|
|
1254
|
-
|
|
1255
|
-
identity.cols === settled.cols &&
|
|
1256
|
-
identity.rows === settled.rows &&
|
|
1257
|
-
layoutPane?.width === settled.cols &&
|
|
1318
|
+
identity.revision >= settled.revision &&
|
|
1319
|
+
layoutPane?.width === identity.cols &&
|
|
1258
1320
|
layoutWindow !== undefined &&
|
|
1259
1321
|
layoutWindow !== null &&
|
|
1260
|
-
nativePaneResizeCells(layoutPane, "rows", layoutWindow.paneBorderStatus, layoutWindow.rows) ===
|
|
1322
|
+
nativePaneResizeCells(layoutPane, "rows", layoutWindow.paneBorderStatus, layoutWindow.rows) === identity.rows;
|
|
1261
1323
|
}
|
|
1262
1324
|
catch {
|
|
1263
1325
|
identityExact = false;
|
|
@@ -1277,6 +1339,7 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
1277
1339
|
settled.presentationBeforeDigest.length === 64 &&
|
|
1278
1340
|
presentationDigest !== settled.presentationBeforeDigest,
|
|
1279
1341
|
identityExact,
|
|
1342
|
+
canonicalAfter,
|
|
1280
1343
|
durationMicros: settledAtMicros === null ? null : settledAtMicros - settled.startedAtMicros,
|
|
1281
1344
|
};
|
|
1282
1345
|
diagnoseCritical(`resize-guide:${settled.traceId}:settled`, "resize-guide-settled", {
|
|
@@ -1294,10 +1357,9 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
1294
1357
|
writerHealth,
|
|
1295
1358
|
});
|
|
1296
1359
|
}
|
|
1297
|
-
const pending =
|
|
1360
|
+
const pending = paneResizeFrame;
|
|
1298
1361
|
if (!pending?.frameRequested)
|
|
1299
1362
|
return;
|
|
1300
|
-
pendingPaneResize = null;
|
|
1301
1363
|
let canonicalAfter = null;
|
|
1302
1364
|
let identityLineageExact = false;
|
|
1303
1365
|
try {
|
|
@@ -1315,14 +1377,23 @@ export function createApplicationTerminalInteractionController(options) {
|
|
|
1315
1377
|
identity.sourceEpoch === pending.sourceEpoch &&
|
|
1316
1378
|
identity.generation === pending.generation &&
|
|
1317
1379
|
identity.incarnation === pending.incarnation &&
|
|
1318
|
-
identity.revision >= pending.revision
|
|
1319
|
-
|
|
1380
|
+
identity.revision >= pending.revision;
|
|
1381
|
+
// Layout publication can precede the canonical terminal snapshot. Keep
|
|
1382
|
+
// the bounded diagnostic slot until a coherent frame consumes both.
|
|
1383
|
+
// A newer operation explicitly supersedes it; diagnostics never hold
|
|
1384
|
+
// the resize transaction or request a polling render loop.
|
|
1385
|
+
if (identityLineageExact &&
|
|
1386
|
+
(identity.cols !== paneCells(options.layout(), pending.semanticPaneId, "cols") ||
|
|
1387
|
+
identity.rows !== paneCells(options.layout(), pending.semanticPaneId, "rows") ||
|
|
1388
|
+
layoutCells !== pending.layoutCells))
|
|
1389
|
+
return;
|
|
1320
1390
|
}
|
|
1321
1391
|
}
|
|
1322
1392
|
catch {
|
|
1323
1393
|
canonicalAfter = null;
|
|
1324
1394
|
identityLineageExact = false;
|
|
1325
1395
|
}
|
|
1396
|
+
paneResizeFrame = null;
|
|
1326
1397
|
const presentationDigest = resizePresentationDigest(null);
|
|
1327
1398
|
const details = {
|
|
1328
1399
|
...resizeDiagnosticDetails(pending),
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseTerminalHostColor, terminalHostMode } from "../../../lib/terminal-host-color.js";
|
|
1
2
|
const PALETTE_SIZE = 16;
|
|
2
3
|
const QUERY_TIMEOUT_MS = 1_000;
|
|
3
4
|
const FOLLOW_UP_DELAYS_MS = [250, 1_000];
|
|
@@ -6,7 +7,7 @@ function normalizeColor(value) {
|
|
|
6
7
|
if (typeof value !== "string")
|
|
7
8
|
return null;
|
|
8
9
|
const normalized = value.trim().toLowerCase();
|
|
9
|
-
return normalized
|
|
10
|
+
return parseTerminalHostColor(normalized) ? normalized : null;
|
|
10
11
|
}
|
|
11
12
|
function normalizePalette(values) {
|
|
12
13
|
const palette = Array(PALETTE_SIZE).fill(null);
|
|
@@ -31,19 +32,6 @@ function immutableCapabilities(value) {
|
|
|
31
32
|
return null;
|
|
32
33
|
return immutableValue(value);
|
|
33
34
|
}
|
|
34
|
-
function detectedMode(background, fallback) {
|
|
35
|
-
if (!background)
|
|
36
|
-
return fallback;
|
|
37
|
-
const match = /^#([\da-f]{3}|[\da-f]{6})$/iu.exec(background);
|
|
38
|
-
if (!match)
|
|
39
|
-
return fallback;
|
|
40
|
-
const hex = match[1];
|
|
41
|
-
const expanded = hex.length === 3 ? `${hex[0]}${hex[0]}${hex[1]}${hex[1]}${hex[2]}${hex[2]}` : hex;
|
|
42
|
-
const red = Number.parseInt(expanded.slice(0, 2), 16) / 255;
|
|
43
|
-
const green = Number.parseInt(expanded.slice(2, 4), 16) / 255;
|
|
44
|
-
const blue = Number.parseInt(expanded.slice(4, 6), 16) / 255;
|
|
45
|
-
return 0.299 * red + 0.587 * green + 0.114 * blue > 0.5 ? "light" : "dark";
|
|
46
|
-
}
|
|
47
35
|
function snapshotSignature(input) {
|
|
48
36
|
return JSON.stringify(input);
|
|
49
37
|
}
|
|
@@ -81,7 +69,7 @@ function availableSnapshot(result, fallbackMode, capabilities) {
|
|
|
81
69
|
tekBackground: normalizeColor(result.tekBackground),
|
|
82
70
|
highlightBackground: normalizeColor(result.highlightBackground),
|
|
83
71
|
highlightForeground: normalizeColor(result.highlightForeground),
|
|
84
|
-
detectedMode:
|
|
72
|
+
detectedMode: terminalHostMode(defaultBackground) ?? fallbackMode,
|
|
85
73
|
capabilities,
|
|
86
74
|
});
|
|
87
75
|
}
|
|
@@ -103,6 +91,9 @@ export function createApplicationTerminalPaletteOwner(renderer, options = {}) {
|
|
|
103
91
|
let lastValid = null;
|
|
104
92
|
let active = null;
|
|
105
93
|
let queued = false;
|
|
94
|
+
let modeHint = null;
|
|
95
|
+
let modeGeneration = 0;
|
|
96
|
+
let awaitingModePalette = false;
|
|
106
97
|
let disposed = false;
|
|
107
98
|
const publish = (next) => {
|
|
108
99
|
if (disposed || next.signature === snapshot.signature)
|
|
@@ -119,24 +110,31 @@ export function createApplicationTerminalPaletteOwner(renderer, options = {}) {
|
|
|
119
110
|
return active;
|
|
120
111
|
}
|
|
121
112
|
renderer.clearPaletteCache();
|
|
113
|
+
const requestedModeGeneration = modeGeneration;
|
|
114
|
+
const guardModeForQuery = awaitingModePalette;
|
|
122
115
|
const generation = Promise.resolve()
|
|
123
116
|
.then(() => renderer.getPalette({ size: PALETTE_SIZE, timeout: queryTimeoutMs }))
|
|
124
117
|
.then((result) => {
|
|
125
|
-
if (disposed)
|
|
118
|
+
if (disposed || requestedModeGeneration !== modeGeneration)
|
|
119
|
+
return;
|
|
120
|
+
const reportedMode = terminalHostMode(result.defaultBackground);
|
|
121
|
+
// A terminal can still answer with its previous palette while switching.
|
|
122
|
+
// Queries issued during the transition wait for a matching reply.
|
|
123
|
+
if (guardModeForQuery && modeHint && reportedMode && reportedMode !== modeHint)
|
|
126
124
|
return;
|
|
127
125
|
if (!hasReportedColor(result)) {
|
|
128
126
|
if (!lastValid)
|
|
129
|
-
publish(fallbackSnapshot("unavailable", renderer.themeMode ?? "dark", capabilities));
|
|
127
|
+
publish(fallbackSnapshot("unavailable", modeHint ?? renderer.themeMode ?? "dark", capabilities));
|
|
130
128
|
return;
|
|
131
129
|
}
|
|
132
|
-
const next = availableSnapshot(result, renderer.themeMode ?? "dark", capabilities);
|
|
130
|
+
const next = availableSnapshot(result, modeHint ?? renderer.themeMode ?? "dark", capabilities);
|
|
133
131
|
lastValid = next;
|
|
134
132
|
publish(next);
|
|
135
133
|
})
|
|
136
134
|
.catch(() => {
|
|
137
|
-
if (disposed || lastValid)
|
|
135
|
+
if (disposed || requestedModeGeneration !== modeGeneration || lastValid)
|
|
138
136
|
return;
|
|
139
|
-
publish(fallbackSnapshot("unavailable", renderer.themeMode ?? "dark", capabilities));
|
|
137
|
+
publish(fallbackSnapshot("unavailable", modeHint ?? renderer.themeMode ?? "dark", capabilities));
|
|
140
138
|
})
|
|
141
139
|
.finally(() => {
|
|
142
140
|
if (active !== generation)
|
|
@@ -152,20 +150,39 @@ export function createApplicationTerminalPaletteOwner(renderer, options = {}) {
|
|
|
152
150
|
};
|
|
153
151
|
const scheduleFollowUps = () => {
|
|
154
152
|
if (disposed)
|
|
155
|
-
return;
|
|
156
|
-
|
|
153
|
+
return Promise.resolve();
|
|
154
|
+
const first = refresh();
|
|
155
|
+
// A burst owns only one bounded pair of retries, never one pair per event.
|
|
156
|
+
for (const timer of timers)
|
|
157
|
+
cancelTimeout(timer);
|
|
158
|
+
timers.clear();
|
|
157
159
|
for (const delay of FOLLOW_UP_DELAYS_MS) {
|
|
158
160
|
const timer = scheduleTimeout(() => {
|
|
159
161
|
timers.delete(timer);
|
|
162
|
+
// After the bounded transition window, a fresh measured OSC 11 color
|
|
163
|
+
// wins even when a custom theme's luminance disagrees with its hint.
|
|
164
|
+
if (delay === FOLLOW_UP_DELAYS_MS[FOLLOW_UP_DELAYS_MS.length - 1])
|
|
165
|
+
awaitingModePalette = false;
|
|
160
166
|
if (!disposed)
|
|
161
167
|
void refresh();
|
|
162
168
|
}, delay);
|
|
163
169
|
timers.add(timer);
|
|
164
170
|
}
|
|
171
|
+
return first;
|
|
165
172
|
};
|
|
166
|
-
const onThemeMode = () => {
|
|
173
|
+
const onThemeMode = (mode) => {
|
|
174
|
+
const changed = modeHint !== mode;
|
|
175
|
+
if (changed)
|
|
176
|
+
modeGeneration += 1;
|
|
177
|
+
modeHint = mode;
|
|
178
|
+
if (changed && snapshot.detectedMode !== mode) {
|
|
179
|
+
awaitingModePalette = true;
|
|
180
|
+
// Track hints even while locked, so unlocking cannot resurrect old colors.
|
|
181
|
+
lastValid = null;
|
|
182
|
+
publish(fallbackSnapshot("unavailable", mode, capabilities));
|
|
183
|
+
}
|
|
167
184
|
if (isThemeModeUnlocked())
|
|
168
|
-
scheduleFollowUps();
|
|
185
|
+
void scheduleFollowUps();
|
|
169
186
|
};
|
|
170
187
|
const onCapabilities = (next) => {
|
|
171
188
|
capabilities = immutableCapabilities(next);
|
|
@@ -176,13 +193,12 @@ export function createApplicationTerminalPaletteOwner(renderer, options = {}) {
|
|
|
176
193
|
publish(lastValid);
|
|
177
194
|
return;
|
|
178
195
|
}
|
|
179
|
-
publish(fallbackSnapshot(snapshot.availability === "pending" ? "pending" : "unavailable", renderer.themeMode ?? "dark", capabilities));
|
|
196
|
+
publish(fallbackSnapshot(snapshot.availability === "pending" ? "pending" : "unavailable", modeHint ?? renderer.themeMode ?? "dark", capabilities));
|
|
180
197
|
};
|
|
181
198
|
const onThemeNotification = (sequence) => {
|
|
182
199
|
if (sequence !== "\x1b[?997;1n" && sequence !== "\x1b[?997;2n")
|
|
183
200
|
return false;
|
|
184
|
-
|
|
185
|
-
scheduleFollowUps();
|
|
201
|
+
onThemeMode(sequence === "\x1b[?997;1n" ? "dark" : "light");
|
|
186
202
|
return false;
|
|
187
203
|
};
|
|
188
204
|
renderer.on("theme_mode", onThemeMode);
|
|
@@ -198,7 +214,7 @@ export function createApplicationTerminalPaletteOwner(renderer, options = {}) {
|
|
|
198
214
|
listeners.add(listener);
|
|
199
215
|
return () => listeners.delete(listener);
|
|
200
216
|
},
|
|
201
|
-
refresh,
|
|
217
|
+
refresh: scheduleFollowUps,
|
|
202
218
|
dispose() {
|
|
203
219
|
if (disposed)
|
|
204
220
|
return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* @jsxImportSource @opentui/solid */
|
|
2
2
|
import { clampTerminalViewportOrigin, terminalLiveViewportOrigin, reflowTerminalPosition, } from "../terminal-viewport.js";
|
|
3
|
-
import { For, Show, createMemo, createRenderEffect, createSignal, onCleanup, untrack, } from "solid-js";
|
|
3
|
+
import { For, Show, createMemo, createEffect, createRenderEffect, createSignal, onCleanup, untrack, } from "solid-js";
|
|
4
4
|
import { PaneScopedTerminalSurface } from "./pane-scoped-terminal-surface.jsx";
|
|
5
5
|
import { nativePaneGeometries, projectOpenTuiPaneFrames, } from "./terminal-layout-projection.js";
|
|
6
6
|
import { TerminalWindowStrip, } from "../workspace/terminal-window-strip.jsx";
|
|
@@ -228,6 +228,28 @@ export function ApplicationTerminalWorkspace(props) {
|
|
|
228
228
|
};
|
|
229
229
|
let forwardedPointer = null;
|
|
230
230
|
let drag = null;
|
|
231
|
+
const cancelResize = () => {
|
|
232
|
+
if (!drag)
|
|
233
|
+
return;
|
|
234
|
+
drag = null;
|
|
235
|
+
setResizePreview(null);
|
|
236
|
+
setHoveredSeparator(null);
|
|
237
|
+
props.onCancelResize?.();
|
|
238
|
+
};
|
|
239
|
+
createEffect(() => {
|
|
240
|
+
const current = layout().current;
|
|
241
|
+
const epoch = props.rendererEpoch;
|
|
242
|
+
const adapter = props.adapter;
|
|
243
|
+
const interactive = props.interactive;
|
|
244
|
+
if (drag &&
|
|
245
|
+
(interactive === false ||
|
|
246
|
+
epoch !== drag.rendererEpoch ||
|
|
247
|
+
adapter !== drag.adapter ||
|
|
248
|
+
(current?.semanticWindowId ?? current?.windowName) !== drag.windowId ||
|
|
249
|
+
!current?.panes.some((pane) => pane.pane === drag?.preview.semanticPaneId)))
|
|
250
|
+
cancelResize();
|
|
251
|
+
});
|
|
252
|
+
onCleanup(cancelResize);
|
|
231
253
|
const terminalPoint = (event) => ({
|
|
232
254
|
x: event.x - (props.originX ?? 0),
|
|
233
255
|
y: event.y - (props.originY ?? 0) - topOffset(),
|
|
@@ -688,7 +710,14 @@ export function ApplicationTerminalWorkspace(props) {
|
|
|
688
710
|
});
|
|
689
711
|
return true;
|
|
690
712
|
};
|
|
691
|
-
props.onSelectionKeyOwner?.(
|
|
713
|
+
props.onSelectionKeyOwner?.((name, event) => {
|
|
714
|
+
if (drag && name === "escape") {
|
|
715
|
+
cancelResize();
|
|
716
|
+
return true;
|
|
717
|
+
}
|
|
718
|
+
return handlePaneMenuKey(name, event);
|
|
719
|
+
}, () => props.interactive !== false &&
|
|
720
|
+
(drag !== null || paneMenu.ownsInput() || keyboardCopy() !== null), () => {
|
|
692
721
|
// Called only after global shortcuts, copy, and local navigation decline
|
|
693
722
|
// the event, immediately before terminal key or paste delivery.
|
|
694
723
|
const paneId = retainedSelectionPane() ?? props.focusedPane;
|
|
@@ -702,6 +731,8 @@ export function ApplicationTerminalWorkspace(props) {
|
|
|
702
731
|
props.onSelectionCopyOwner?.(null);
|
|
703
732
|
props.onSelectionKeyOwner?.(null);
|
|
704
733
|
});
|
|
734
|
+
// Register this router only as onMouse. OpenTUI invokes both the general
|
|
735
|
+
// listener and the typed listener on a target, even after stopPropagation.
|
|
705
736
|
const routePointer = (event) => {
|
|
706
737
|
if (event.type === "down")
|
|
707
738
|
wheelGesture.reset();
|
|
@@ -775,15 +806,13 @@ export function ApplicationTerminalWorkspace(props) {
|
|
|
775
806
|
}
|
|
776
807
|
if (isRelease) {
|
|
777
808
|
const completed = drag.preview;
|
|
778
|
-
const changed = completed.cells !== drag.separator.initialCells;
|
|
779
809
|
drag = null;
|
|
780
810
|
setResizePreview(null);
|
|
781
811
|
setHoveredSeparator(null);
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
})));
|
|
812
|
+
props.onResizePane?.(globalPreview(Object.freeze({
|
|
813
|
+
...completed,
|
|
814
|
+
...(ingress ? { pointerIngress: ingress } : {}),
|
|
815
|
+
})));
|
|
787
816
|
}
|
|
788
817
|
}
|
|
789
818
|
return;
|
|
@@ -918,7 +947,16 @@ export function ApplicationTerminalWorkspace(props) {
|
|
|
918
947
|
const ingress = resizeIngress();
|
|
919
948
|
const origin = separator.axis === "x" ? point.x : point.y;
|
|
920
949
|
const preview = terminalPaneResizePreview(separator, origin, origin);
|
|
921
|
-
drag = {
|
|
950
|
+
drag = {
|
|
951
|
+
separator,
|
|
952
|
+
origin,
|
|
953
|
+
preview,
|
|
954
|
+
gestureId: ingress?.gestureId ?? null,
|
|
955
|
+
rendererEpoch: props.rendererEpoch,
|
|
956
|
+
adapter: props.adapter,
|
|
957
|
+
windowId: layout().current?.semanticWindowId ?? layout().current?.windowName,
|
|
958
|
+
};
|
|
959
|
+
props.onResizePreview?.(globalPreview(preview));
|
|
922
960
|
setHoveredSeparator(null);
|
|
923
961
|
setResizePreview(preview);
|
|
924
962
|
return;
|
|
@@ -1136,7 +1174,7 @@ export function ApplicationTerminalWorkspace(props) {
|
|
|
1136
1174
|
return Object.freeze(cells);
|
|
1137
1175
|
});
|
|
1138
1176
|
return (<>
|
|
1139
|
-
<box position="absolute" left={0} top={topOffset()} width={props.width} height={props.height} onMouse={routePointer}
|
|
1177
|
+
<box position="absolute" left={0} top={topOffset()} width={props.width} height={props.height} onMouse={routePointer}/>
|
|
1140
1178
|
<box position="absolute" left={0} top={topOffset() - 1} width={props.width} height={1} backgroundColor={props.theme.roles.surfaces.panel} flexDirection="row" onMouse={routePointer}>
|
|
1141
1179
|
<Show when={layout().windows.length > 0} fallback={<text fg={props.theme.roles.text.muted}> no terminal windows </text>}>
|
|
1142
1180
|
<TerminalWindowStrip theme={props.theme} width={props.width} tabs={terminalWindowTabs} hoveredIndex={null} onActivate={(index) => {
|
|
@@ -1200,7 +1238,7 @@ export function ApplicationTerminalWorkspace(props) {
|
|
|
1200
1238
|
}}
|
|
1201
1239
|
</For>
|
|
1202
1240
|
<For each={terminalPaneSeparators(visibleFrames(), layout().current?.paneBorderStatus ?? "off")}>
|
|
1203
|
-
{(separator) => (<box position="absolute" left={separator.axis === "x" ? separator.position : separator.start} top={(separator.axis === "x" ? separator.start : separator.position) + topOffset()} width={1} height={separator.axis === "x" ? Math.max(1, separator.end - separator.start) : 1} backgroundColor={props.theme.colors.accentMuted} onMouse={routePointer}
|
|
1241
|
+
{(separator) => (<box position="absolute" left={separator.axis === "x" ? separator.position : separator.start} top={(separator.axis === "x" ? separator.start : separator.position) + topOffset()} width={1} height={separator.axis === "x" ? Math.max(1, separator.end - separator.start) : 1} backgroundColor={props.theme.colors.accentMuted} onMouse={routePointer}>
|
|
1204
1242
|
<Show when={separator.axis === "y"}>
|
|
1205
1243
|
<text fg={props.theme.roles.text.primary}>↕</text>
|
|
1206
1244
|
</Show>
|