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
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/** Quantized sRGB contrast; Oklab matrices: https://bottosson.github.io/posts/oklab/ . */
|
|
2
|
+
const LINEAR = Float64Array.from({ length: 256 }, (_, n) => {
|
|
3
|
+
const c = n / 255;
|
|
4
|
+
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
5
|
+
});
|
|
6
|
+
const luminance = (rgb) => 0.2126 * LINEAR[(rgb >>> 16) & 255] +
|
|
7
|
+
0.7152 * LINEAR[(rgb >>> 8) & 255] +
|
|
8
|
+
0.0722 * LINEAR[rgb & 255];
|
|
9
|
+
export function packedContrastRatio(a, b) {
|
|
10
|
+
const x = luminance(a) + 0.05, y = luminance(b) + 0.05;
|
|
11
|
+
return Math.max(x, y) / Math.min(x, y);
|
|
12
|
+
}
|
|
13
|
+
function byte(c) {
|
|
14
|
+
return Math.round(Math.max(0, Math.min(1, c <= 0.0031308 ? c * 12.92 : 1.055 * c ** (1 / 2.4) - 0.055)) * 255);
|
|
15
|
+
}
|
|
16
|
+
function candidate(L, a, b) {
|
|
17
|
+
// Reduce chroma along the same hue ray until inside sRGB, then quantize.
|
|
18
|
+
let lo = 0, hi = 1, result = -1;
|
|
19
|
+
for (let i = 0; i < 10; i++) {
|
|
20
|
+
const scale = i === 0 ? 1 : (lo + hi) / 2;
|
|
21
|
+
const l = (L + 0.3963377774 * a * scale + 0.2158037573 * b * scale) ** 3;
|
|
22
|
+
const m = (L - 0.1055613458 * a * scale - 0.0638541728 * b * scale) ** 3;
|
|
23
|
+
const s = (L - 0.0894841775 * a * scale - 1.291485548 * b * scale) ** 3;
|
|
24
|
+
const r = 4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s;
|
|
25
|
+
const g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s;
|
|
26
|
+
const blue = -0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s;
|
|
27
|
+
if (Math.min(r, g, blue) >= 0 && Math.max(r, g, blue) <= 1) {
|
|
28
|
+
result = (byte(r) << 16) | (byte(g) << 8) | byte(blue);
|
|
29
|
+
if (i === 0)
|
|
30
|
+
return result;
|
|
31
|
+
lo = scale;
|
|
32
|
+
}
|
|
33
|
+
else
|
|
34
|
+
hi = scale;
|
|
35
|
+
}
|
|
36
|
+
return result < 0 ? byte(L ** 3) * 0x10101 : result;
|
|
37
|
+
}
|
|
38
|
+
/** Leave passing pairs exact. Search both lightness directions with a verified endpoint fallback. */
|
|
39
|
+
export function correctContrastForeground(fg, bg) {
|
|
40
|
+
if (packedContrastRatio(fg, bg) >= 4.5)
|
|
41
|
+
return fg;
|
|
42
|
+
const r = LINEAR[(fg >>> 16) & 255], g = LINEAR[(fg >>> 8) & 255], b = LINEAR[fg & 255];
|
|
43
|
+
const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
|
|
44
|
+
const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
|
|
45
|
+
const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
|
|
46
|
+
const L = 0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s;
|
|
47
|
+
const a = 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s;
|
|
48
|
+
const bb = 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s;
|
|
49
|
+
let best = 0, distance = Infinity;
|
|
50
|
+
for (const end of [0, 1]) {
|
|
51
|
+
let passing = end, failing = L;
|
|
52
|
+
let rgb = end === 0 ? 0 : 0xffffff;
|
|
53
|
+
if (packedContrastRatio(rgb, bg) < 4.5)
|
|
54
|
+
continue;
|
|
55
|
+
for (let i = 0; i < 12; i++) {
|
|
56
|
+
const mid = (passing + failing) / 2;
|
|
57
|
+
const trial = candidate(mid, a, bb);
|
|
58
|
+
if (packedContrastRatio(trial, bg) >= 4.5) {
|
|
59
|
+
passing = mid;
|
|
60
|
+
rgb = trial;
|
|
61
|
+
}
|
|
62
|
+
else
|
|
63
|
+
failing = mid;
|
|
64
|
+
}
|
|
65
|
+
if (Math.abs(passing - L) < distance) {
|
|
66
|
+
best = rgb;
|
|
67
|
+
distance = Math.abs(passing - L);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return best;
|
|
71
|
+
}
|
|
72
|
+
/** Cheap high-color-churn fallback: linear-sRGB blend to the nearer passing endpoint. */
|
|
73
|
+
export function fastContrastForeground(fg, bg) {
|
|
74
|
+
if (packedContrastRatio(fg, bg) >= 4.5)
|
|
75
|
+
return fg;
|
|
76
|
+
const y = luminance(fg), base = luminance(bg);
|
|
77
|
+
const darkTarget = (base + 0.05) / 4.5 - 0.05;
|
|
78
|
+
const lightTarget = (base + 0.05) * 4.5 - 0.05;
|
|
79
|
+
const darkMix = darkTarget >= 0 ? (y - darkTarget) / y : Infinity;
|
|
80
|
+
const lightMix = lightTarget <= 1 ? (lightTarget - y) / (1 - y) : Infinity;
|
|
81
|
+
const end = darkMix <= lightMix ? 0 : 1;
|
|
82
|
+
// A small quantization margin usually avoids endpoint fallback. Always verify.
|
|
83
|
+
const amount = Math.min(1, Math.max(0, Math.min(darkMix, lightMix)) + 0.012);
|
|
84
|
+
const r = LINEAR[(fg >>> 16) & 255], g = LINEAR[(fg >>> 8) & 255], b = LINEAR[fg & 255];
|
|
85
|
+
const result = (byte(r + (end - r) * amount) << 16) |
|
|
86
|
+
(byte(g + (end - g) * amount) << 8) |
|
|
87
|
+
byte(b + (end - b) * amount);
|
|
88
|
+
return packedContrastRatio(result, bg) >= 4.5 ? result : end === 0 ? 0 : 0xffffff;
|
|
89
|
+
}
|
|
90
|
+
export function createContrastPairCache(limit = 4096) {
|
|
91
|
+
const pairs = new Map();
|
|
92
|
+
let remaining = Infinity;
|
|
93
|
+
return {
|
|
94
|
+
beginFrame() {
|
|
95
|
+
remaining = 128;
|
|
96
|
+
},
|
|
97
|
+
get size() {
|
|
98
|
+
return pairs.size;
|
|
99
|
+
},
|
|
100
|
+
correct(fg, bg) {
|
|
101
|
+
const key = fg * 0x1000000 + bg;
|
|
102
|
+
const cached = pairs.get(key);
|
|
103
|
+
if (cached !== undefined)
|
|
104
|
+
return cached;
|
|
105
|
+
const corrected = packedContrastRatio(fg, bg) >= 4.5
|
|
106
|
+
? fg
|
|
107
|
+
: remaining-- > 0
|
|
108
|
+
? correctContrastForeground(fg, bg)
|
|
109
|
+
: fastContrastForeground(fg, bg);
|
|
110
|
+
if (pairs.size >= limit)
|
|
111
|
+
pairs.clear();
|
|
112
|
+
pairs.set(key, corrected);
|
|
113
|
+
return corrected;
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const read = (c, i) => ((c[i] & 255) << 16) | ((c[i + 1] & 255) << 8) | (c[i + 2] & 255);
|
|
118
|
+
/** Final composed surface only: no canonical state or scheduling; allocation-free cache hits. */
|
|
119
|
+
export function createAutomaticContrastPass() {
|
|
120
|
+
const cache = createContrastPairCache();
|
|
121
|
+
return (buffer) => {
|
|
122
|
+
cache.beginFrame();
|
|
123
|
+
const { char, fg, bg, attributes } = buffer.buffers;
|
|
124
|
+
for (let cell = 0; cell < char.length; cell++) {
|
|
125
|
+
const cp = char[cell], attr = attributes[cell], i = cell * 4;
|
|
126
|
+
// Preserve concealment, whitespace and pixel-art coverage. Grapheme IDs and
|
|
127
|
+
// wide continuations are opaque and retained byte-for-byte.
|
|
128
|
+
if (attr & 64 ||
|
|
129
|
+
cp === 0 ||
|
|
130
|
+
cp === 32 ||
|
|
131
|
+
cp === 160 ||
|
|
132
|
+
(cp >= 0x2580 && cp <= 0x259f) ||
|
|
133
|
+
(cp >= 0x2800 && cp <= 0x28ff))
|
|
134
|
+
continue;
|
|
135
|
+
if ((fg[i + 3] & 255) !== 255 || (bg[i + 3] & 255) !== 255)
|
|
136
|
+
continue;
|
|
137
|
+
const foreground = attr & 32 ? bg : fg;
|
|
138
|
+
const background = attr & 32 ? fg : bg;
|
|
139
|
+
const original = read(foreground, i);
|
|
140
|
+
const base = read(background, i);
|
|
141
|
+
// DIM is emulator-dependent. Materialize a deterministic 50% sRGB blend
|
|
142
|
+
// before correction and clear only the final composed DIM bit.
|
|
143
|
+
let visible = original;
|
|
144
|
+
if (attr & 2) {
|
|
145
|
+
visible =
|
|
146
|
+
(Math.round(((original >>> 16) + (base >>> 16)) / 2) << 16) |
|
|
147
|
+
(Math.round((((original >>> 8) & 255) + ((base >>> 8) & 255)) / 2) << 8) |
|
|
148
|
+
Math.round(((original & 255) + (base & 255)) / 2);
|
|
149
|
+
attributes[cell] = attr & ~2;
|
|
150
|
+
}
|
|
151
|
+
const corrected = cache.correct(visible, base);
|
|
152
|
+
if (corrected === original && !(attr & 2))
|
|
153
|
+
continue;
|
|
154
|
+
// RGB intent is zero. Indexed/default metadata must not override correction.
|
|
155
|
+
foreground[i] = corrected >>> 16;
|
|
156
|
+
foreground[i + 1] = (corrected >>> 8) & 255;
|
|
157
|
+
foreground[i + 2] = corrected & 255;
|
|
158
|
+
foreground[i + 3] = 255;
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
}
|
|
@@ -1124,7 +1124,7 @@ export async function connectOpenTuiWorkspaceRuntimePort(options) {
|
|
|
1124
1124
|
}
|
|
1125
1125
|
}) ?? null;
|
|
1126
1126
|
let pendingInitialFit = null;
|
|
1127
|
-
const fitViewport = (cols, rows) => {
|
|
1127
|
+
const fitViewport = (cols, rows, semanticWindowId) => {
|
|
1128
1128
|
if (closed)
|
|
1129
1129
|
return Promise.resolve("geometry-authority-conflict");
|
|
1130
1130
|
// Keep only the latest requested geometry while hidden seeds arrive. All
|
|
@@ -1133,23 +1133,29 @@ export async function connectOpenTuiWorkspaceRuntimePort(options) {
|
|
|
1133
1133
|
if (pendingInitialFit) {
|
|
1134
1134
|
pendingInitialFit.cols = cols;
|
|
1135
1135
|
pendingInitialFit.rows = rows;
|
|
1136
|
+
pendingInitialFit.semanticWindowId = semanticWindowId;
|
|
1136
1137
|
return pendingInitialFit.result;
|
|
1137
1138
|
}
|
|
1138
1139
|
const request = {
|
|
1139
1140
|
cols,
|
|
1140
1141
|
rows,
|
|
1142
|
+
semanticWindowId,
|
|
1141
1143
|
result: Promise.resolve("geometry-authority-conflict"),
|
|
1142
1144
|
};
|
|
1143
1145
|
pendingInitialFit = request;
|
|
1144
1146
|
request.result = allSeeds.then((ready) => {
|
|
1145
1147
|
pendingInitialFit = null;
|
|
1146
|
-
return ready
|
|
1148
|
+
return ready
|
|
1149
|
+
? fitViewport(request.cols, request.rows, request.semanticWindowId)
|
|
1150
|
+
: "geometry-authority-conflict";
|
|
1147
1151
|
});
|
|
1148
1152
|
return request.result;
|
|
1149
1153
|
}
|
|
1150
1154
|
if ([...endpoints.values()].some((endpoint) => !endpoint.inputReady))
|
|
1151
1155
|
return Promise.resolve("geometry-authority-conflict");
|
|
1152
|
-
return
|
|
1156
|
+
return (semanticWindowId === undefined
|
|
1157
|
+
? opened.fitViewport(cols, rows)
|
|
1158
|
+
: opened.fitViewport(cols, rows, semanticWindowId)).catch((error) => {
|
|
1153
1159
|
if (error instanceof PaneStreamOperationError && error.code === "authority-rejected")
|
|
1154
1160
|
return "geometry-authority-conflict";
|
|
1155
1161
|
throw error;
|
|
@@ -422,11 +422,11 @@ class PaneSurfaceRenderable extends FrameBufferRenderable {
|
|
|
422
422
|
if (v === this._presentationGeneration)
|
|
423
423
|
return;
|
|
424
424
|
this._presentationGeneration = v;
|
|
425
|
-
//
|
|
426
|
-
//
|
|
427
|
-
//
|
|
428
|
-
this.
|
|
429
|
-
this.
|
|
425
|
+
// Composition reuses the retained framebuffer. Geometry reallocation,
|
|
426
|
+
// source replacement and palette changes independently force a full walk.
|
|
427
|
+
// Reclaim the hardware cursor at the new absolute position on visibility.
|
|
428
|
+
this._needsCursorPresentation = true;
|
|
429
|
+
this.requestRender();
|
|
430
430
|
}
|
|
431
431
|
set sourceEpoch(v) {
|
|
432
432
|
if (v === this._sourceEpoch)
|
|
@@ -453,6 +453,8 @@ class PaneSurfaceRenderable extends FrameBufferRenderable {
|
|
|
453
453
|
// Detailed focus correlation never owns renderer replacement.
|
|
454
454
|
}
|
|
455
455
|
this._rendererEpoch = v;
|
|
456
|
+
this._forceFull = true;
|
|
457
|
+
this.invalidate();
|
|
456
458
|
}
|
|
457
459
|
set hostFocusTransitionOwner(v) {
|
|
458
460
|
if (v !== this._hostFocusTransitionOwner)
|
|
@@ -12,6 +12,7 @@ function nonEmpty(value, label) {
|
|
|
12
12
|
}
|
|
13
13
|
function sameTarget(state, observation) {
|
|
14
14
|
return (state.operationId === observation.operationId &&
|
|
15
|
+
state.authorityGeneration === observation.authorityGeneration &&
|
|
15
16
|
state.workspaceName === observation.workspaceName &&
|
|
16
17
|
state.semanticPaneId === observation.semanticPaneId &&
|
|
17
18
|
state.axis === observation.axis);
|
|
@@ -19,9 +20,9 @@ function sameTarget(state, observation) {
|
|
|
19
20
|
/**
|
|
20
21
|
* Renderer-local pane-resize transaction.
|
|
21
22
|
*
|
|
22
|
-
* Pointer motion
|
|
23
|
-
*
|
|
24
|
-
* operation
|
|
23
|
+
* Pointer motion submits the first target immediately, then retains only the
|
|
24
|
+
* latest desired size while receipt and canonical layout settle the pending
|
|
25
|
+
* operation. Release closes the gesture and flushes that latest target. The controller owns no timers, transport, or
|
|
25
26
|
* rendering effects beyond the functions supplied by its caller.
|
|
26
27
|
*/
|
|
27
28
|
export class ResizeTransactionController {
|
|
@@ -35,6 +36,8 @@ export class ResizeTransactionController {
|
|
|
35
36
|
#projection = null;
|
|
36
37
|
#revision = 0;
|
|
37
38
|
#disposed = false;
|
|
39
|
+
#dragOpen = false;
|
|
40
|
+
#submittedCells = null;
|
|
38
41
|
constructor(options) {
|
|
39
42
|
if (!Number.isFinite(options.timeoutMs) || options.timeoutMs <= 0) {
|
|
40
43
|
throw new RangeError("resize transaction timeout must be positive");
|
|
@@ -54,6 +57,8 @@ export class ResizeTransactionController {
|
|
|
54
57
|
const workspaceName = nonEmpty(input.workspaceName, "workspaceName");
|
|
55
58
|
const semanticPaneId = nonEmpty(input.semanticPaneId, "semanticPaneId");
|
|
56
59
|
const canonicalCells = positiveCells(input.canonicalCells, "canonicalCells");
|
|
60
|
+
this.#dragOpen = true;
|
|
61
|
+
this.#submittedCells = null;
|
|
57
62
|
this.#projection = createOptimisticProjection({
|
|
58
63
|
generation: authorityGeneration,
|
|
59
64
|
revision: this.#revision,
|
|
@@ -71,30 +76,36 @@ export class ResizeTransactionController {
|
|
|
71
76
|
});
|
|
72
77
|
return true;
|
|
73
78
|
}
|
|
74
|
-
/**
|
|
79
|
+
/** First/latest coalescing: at most one mutation awaits canonical settlement. */
|
|
75
80
|
move(previewCells) {
|
|
76
|
-
if (this.#state.phase
|
|
81
|
+
if (!this.#dragOpen || this.#state.phase === "idle")
|
|
77
82
|
return false;
|
|
78
83
|
const next = positiveCells(previewCells, "previewCells");
|
|
79
84
|
if (next === this.#state.previewCells)
|
|
80
85
|
return false;
|
|
81
86
|
this.#emit({ ...this.#state, previewCells: next });
|
|
87
|
+
if (this.#state.phase === "dragging")
|
|
88
|
+
this.#submit();
|
|
82
89
|
return true;
|
|
83
90
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Submit once. Duplicate physical release/up/drop events return the existing
|
|
86
|
-
* operation id without authoring another mutation.
|
|
87
|
-
*/
|
|
91
|
+
/** Duplicate releases never author another mutation. */
|
|
88
92
|
release() {
|
|
93
|
+
this.#dragOpen = false;
|
|
89
94
|
if (this.#state.phase === "pending")
|
|
90
95
|
return this.#state.operationId;
|
|
96
|
+
if (this.#state.phase !== "dragging")
|
|
97
|
+
return null;
|
|
98
|
+
const canonicalCells = this.#state.canonicalCells;
|
|
99
|
+
this.#emit({ phase: "idle", canonicalCells, outcome: null });
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
#submit() {
|
|
91
103
|
if (this.#state.phase !== "dragging")
|
|
92
104
|
return null;
|
|
93
105
|
const dragging = this.#state;
|
|
94
|
-
if (dragging.previewCells === dragging.canonicalCells)
|
|
95
|
-
this.#emit({ phase: "idle", canonicalCells: dragging.canonicalCells, outcome: null });
|
|
106
|
+
if (dragging.previewCells === dragging.canonicalCells)
|
|
96
107
|
return null;
|
|
97
|
-
|
|
108
|
+
this.#submittedCells = dragging.previewCells;
|
|
98
109
|
const operationId = nonEmpty(this.#options.operationId(), "operationId");
|
|
99
110
|
const submittedAt = this.#options.now();
|
|
100
111
|
if (!this.#projection)
|
|
@@ -146,10 +157,15 @@ export class ResizeTransactionController {
|
|
|
146
157
|
message: rejection.message,
|
|
147
158
|
});
|
|
148
159
|
}
|
|
149
|
-
/**
|
|
160
|
+
/** Discard queued motion; an already dispatched mutation cannot be undone. */
|
|
150
161
|
cancelDrag() {
|
|
151
|
-
if (this.#state.phase
|
|
162
|
+
if (!this.#dragOpen || this.#state.phase === "idle")
|
|
152
163
|
return false;
|
|
164
|
+
this.#dragOpen = false;
|
|
165
|
+
if (this.#state.phase === "pending") {
|
|
166
|
+
this.#emit({ ...this.#state, previewCells: this.#submittedCells });
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
153
169
|
const canonicalCells = this.#state.canonicalCells;
|
|
154
170
|
this.#emit({ phase: "idle", canonicalCells, outcome: null });
|
|
155
171
|
return true;
|
|
@@ -160,6 +176,8 @@ export class ResizeTransactionController {
|
|
|
160
176
|
}
|
|
161
177
|
/** Retire one runtime lane without permanently disposing the controller. */
|
|
162
178
|
retire() {
|
|
179
|
+
this.#dragOpen = false;
|
|
180
|
+
this.#submittedCells = null;
|
|
163
181
|
this.#clearTimeout();
|
|
164
182
|
this.#projection = null;
|
|
165
183
|
if (this.#state.phase !== "idle" || this.#state.canonicalCells !== null) {
|
|
@@ -172,7 +190,8 @@ export class ResizeTransactionController {
|
|
|
172
190
|
!this.#projection?.pending.some((operation) => operation.operationId === observation.operationId))
|
|
173
191
|
return false;
|
|
174
192
|
const cells = positiveCells(observation.cells, "observed cells");
|
|
175
|
-
const
|
|
193
|
+
const pending = this.#state;
|
|
194
|
+
const operationId = pending.operationId;
|
|
176
195
|
if (!this.#projection)
|
|
177
196
|
return false;
|
|
178
197
|
this.#revision += 1;
|
|
@@ -182,11 +201,19 @@ export class ResizeTransactionController {
|
|
|
182
201
|
value: cells,
|
|
183
202
|
}, { observedOperationIds: [operationId], nowMs: this.#options.now() });
|
|
184
203
|
this.#clearTimeout();
|
|
185
|
-
this.#
|
|
186
|
-
|
|
187
|
-
canonicalCells: cells
|
|
188
|
-
|
|
189
|
-
|
|
204
|
+
const hasLatest = pending.previewCells !== this.#submittedCells && pending.previewCells !== cells;
|
|
205
|
+
if (this.#dragOpen || hasLatest) {
|
|
206
|
+
this.#emit({ ...pending, phase: "dragging", canonicalCells: cells });
|
|
207
|
+
if (hasLatest)
|
|
208
|
+
this.#submit();
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
this.#emit({
|
|
212
|
+
phase: "idle",
|
|
213
|
+
canonicalCells: cells,
|
|
214
|
+
outcome: { kind: "settled", operationId, source, cells },
|
|
215
|
+
});
|
|
216
|
+
}
|
|
190
217
|
return true;
|
|
191
218
|
}
|
|
192
219
|
#revertTimedOut(operationId) {
|
|
@@ -202,6 +229,7 @@ export class ResizeTransactionController {
|
|
|
202
229
|
this.#projection = reconcileOptimisticOperation(this.#projection, operationId, reason.kind === "timed-out" ? "timed-out" : "rejected");
|
|
203
230
|
}
|
|
204
231
|
const canonicalCells = this.#state.canonicalCells;
|
|
232
|
+
this.#dragOpen = false;
|
|
205
233
|
this.#clearTimeout();
|
|
206
234
|
this.#emit({
|
|
207
235
|
phase: "idle",
|
|
@@ -1,14 +1,32 @@
|
|
|
1
|
+
import { createAutomaticContrastPass } from "../automatic-contrast.js";
|
|
1
2
|
import { VISUAL_THEME_PRESETS, findVisualThemePreset } from "@tmux-ide/contracts";
|
|
2
3
|
import { batch, createSignal } from "solid-js";
|
|
3
4
|
import { updateAppConfig } from "../../../lib/app-config.js";
|
|
4
5
|
import { createSemanticThemeStore, createTerminalPaletteProjection, deriveSystemVisualHostDefaults, } from "../theme.js";
|
|
5
6
|
import { createApplicationTransientNoteOwner } from "./application-transient-note-owner.js";
|
|
6
7
|
function hostDefaults(snapshot) {
|
|
7
|
-
|
|
8
|
-
return null;
|
|
9
|
-
return deriveSystemVisualHostDefaults(snapshot);
|
|
8
|
+
return (deriveSystemVisualHostDefaults(snapshot) ?? { appearance: snapshot.detectedMode, overrides: {} });
|
|
10
9
|
}
|
|
11
10
|
export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
11
|
+
const [automaticContrast, setAutomaticContrast] = createSignal(config.theme.automaticContrast ?? true);
|
|
12
|
+
const correct = createAutomaticContrastPass();
|
|
13
|
+
const postProcess = (buffer) => {
|
|
14
|
+
if (automaticContrast())
|
|
15
|
+
correct(buffer);
|
|
16
|
+
};
|
|
17
|
+
renderer.addPostProcessFn?.(postProcess);
|
|
18
|
+
const setContrast = (enabled) => {
|
|
19
|
+
if (enabled === automaticContrast())
|
|
20
|
+
return;
|
|
21
|
+
setAutomaticContrast(enabled);
|
|
22
|
+
renderer.requestRender?.();
|
|
23
|
+
};
|
|
24
|
+
const toggleAutomaticContrast = () => {
|
|
25
|
+
if (pickerOpen()) {
|
|
26
|
+
setContrast(!automaticContrast());
|
|
27
|
+
setPickerError(null);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
12
30
|
const initialHostPalette = terminalPaletteOwner.getSnapshot();
|
|
13
31
|
const store = createSemanticThemeStore(config.theme, {
|
|
14
32
|
rendererMode: renderer.themeMode,
|
|
@@ -65,10 +83,18 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
65
83
|
const apply = (id) => {
|
|
66
84
|
const preset = findVisualThemePreset(id);
|
|
67
85
|
const mode = preset?.appearance ?? id;
|
|
68
|
-
|
|
86
|
+
const wasSystem = theme().setting === "system";
|
|
87
|
+
store.configure({
|
|
88
|
+
...config.theme,
|
|
89
|
+
mode,
|
|
90
|
+
preset: preset?.id,
|
|
91
|
+
});
|
|
92
|
+
if (mode === "system" && !wasSystem)
|
|
93
|
+
void terminalPaletteOwner.refresh();
|
|
69
94
|
setPickerSelection(id);
|
|
70
95
|
};
|
|
71
96
|
let originalSelection = pickerSelection();
|
|
97
|
+
let originalContrast = automaticContrast();
|
|
72
98
|
const preview = (id) => {
|
|
73
99
|
if (!pickerOpen())
|
|
74
100
|
return;
|
|
@@ -79,6 +105,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
79
105
|
const cancelPicker = () => {
|
|
80
106
|
if (!pickerOpen())
|
|
81
107
|
return;
|
|
108
|
+
setContrast(originalContrast);
|
|
82
109
|
apply(originalSelection);
|
|
83
110
|
setPickerOpen(false);
|
|
84
111
|
setPickerError(null);
|
|
@@ -89,6 +116,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
89
116
|
try {
|
|
90
117
|
updateAppConfig({
|
|
91
118
|
theme: {
|
|
119
|
+
automaticContrast: automaticContrast(),
|
|
92
120
|
mode: theme().setting,
|
|
93
121
|
preset: findVisualThemePreset(pickerSelection())?.id ?? "",
|
|
94
122
|
},
|
|
@@ -105,7 +133,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
105
133
|
const order = ["dark", "light", "system"];
|
|
106
134
|
const next = order[(order.indexOf(theme().setting) + 1) % order.length];
|
|
107
135
|
apply(next);
|
|
108
|
-
updateAppConfig({ theme: { mode: next, preset: "" } });
|
|
136
|
+
updateAppConfig({ theme: { mode: next, preset: "", automaticContrast: automaticContrast() } });
|
|
109
137
|
notice.publish(`theme → ${next}`);
|
|
110
138
|
};
|
|
111
139
|
return {
|
|
@@ -116,6 +144,8 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
116
144
|
palette,
|
|
117
145
|
hostPalette,
|
|
118
146
|
setTransientNote: notice.publish,
|
|
147
|
+
automaticContrast,
|
|
148
|
+
toggleAutomaticContrast,
|
|
119
149
|
pickerOpen,
|
|
120
150
|
pickerError,
|
|
121
151
|
pickerQuery,
|
|
@@ -125,6 +155,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
125
155
|
if (pickerOpen())
|
|
126
156
|
return;
|
|
127
157
|
originalSelection = pickerSelection();
|
|
158
|
+
originalContrast = automaticContrast();
|
|
128
159
|
setPickerQuery("");
|
|
129
160
|
setPickerError(null);
|
|
130
161
|
setPickerOpen(true);
|
|
@@ -139,7 +170,9 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
139
170
|
return true;
|
|
140
171
|
const name = event.name.toLowerCase();
|
|
141
172
|
const choices = pickerOptions();
|
|
142
|
-
if (name === "
|
|
173
|
+
if (event.ctrl && name === "a")
|
|
174
|
+
toggleAutomaticContrast();
|
|
175
|
+
else if (name === "escape")
|
|
143
176
|
cancelPicker();
|
|
144
177
|
else if (name === "return" || name === "enter")
|
|
145
178
|
savePicker();
|
|
@@ -166,6 +199,7 @@ export function createAppearanceOwner(config, renderer, terminalPaletteOwner) {
|
|
|
166
199
|
},
|
|
167
200
|
cycleTheme,
|
|
168
201
|
dispose() {
|
|
202
|
+
renderer.removePostProcessFn?.(postProcess);
|
|
169
203
|
stopTheme();
|
|
170
204
|
stopRendererTheme();
|
|
171
205
|
stopHostPalette();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { canonicalDaemonUrl } from "../../../lib/canonical-daemon.js";
|
|
2
2
|
import { stripVTControlCharacters } from "node:util";
|
|
3
3
|
/** One-shot snapshot pinned to this route's exact authority; never the selected-machine singleton. */
|
|
4
|
-
export async function readFleetWindowPreview(handle, liveSessionId, signal, windowId) {
|
|
4
|
+
export async function readFleetWindowPreview(handle, liveSessionId, signal, windowId, paneId) {
|
|
5
5
|
const daemon = handle.read();
|
|
6
6
|
const epoch = handle.endpoint().epoch;
|
|
7
7
|
if (!daemon?.authToken || handle.endpoint().state !== "ready")
|
|
@@ -18,6 +18,7 @@ export async function readFleetWindowPreview(handle, liveSessionId, signal, wind
|
|
|
18
18
|
body: JSON.stringify({
|
|
19
19
|
expectedInstanceId: daemon.instanceId,
|
|
20
20
|
liveSessionId,
|
|
21
|
+
...(paneId ? { paneId } : {}),
|
|
21
22
|
...(windowId ? { windowId } : {}),
|
|
22
23
|
}),
|
|
23
24
|
});
|
|
@@ -50,6 +51,7 @@ export async function readFleetWindowPreview(handle, liveSessionId, signal, wind
|
|
|
50
51
|
body.daemon?.instanceId !== daemon.instanceId ||
|
|
51
52
|
body.daemon?.startedAt !== daemon.startedAt ||
|
|
52
53
|
body.liveSessionId !== liveSessionId ||
|
|
54
|
+
(paneId && body.selectedPaneId !== paneId) ||
|
|
53
55
|
typeof body.text !== "string" ||
|
|
54
56
|
body.text.length > 8192)
|
|
55
57
|
return { status: "unavailable" };
|
|
@@ -87,6 +89,7 @@ export async function readFleetWindowPreview(handle, liveSessionId, signal, wind
|
|
|
87
89
|
status: "ready",
|
|
88
90
|
snapshot: {
|
|
89
91
|
text: clean(body.text),
|
|
92
|
+
...(body.selectedPaneId ? { selectedPaneId: body.selectedPaneId } : {}),
|
|
90
93
|
selectedWindowId,
|
|
91
94
|
windows: windows.map((w) => ({
|
|
92
95
|
id: w.id,
|
|
@@ -68,6 +68,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
68
68
|
}))
|
|
69
69
|
: []),
|
|
70
70
|
]));
|
|
71
|
+
const rowsByKey = createMemo(() => new Map(rows().map((row) => [row.key, row])));
|
|
71
72
|
const index = () => Math.max(0, rows().findIndex((row) => row.key === selectedKey()));
|
|
72
73
|
const controlGroup = () => {
|
|
73
74
|
const group = rows()[index()]?.group;
|
|
@@ -281,49 +282,69 @@ export function ApplicationMachineSidebar(props) {
|
|
|
281
282
|
revealFocusedRow();
|
|
282
283
|
};
|
|
283
284
|
}} height={machineHeight()} width={props.width} scrollX={false} scrollY={true} horizontalScrollbarOptions={{ visible: false }}>
|
|
284
|
-
<For each={rows()}>
|
|
285
|
-
{(
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
285
|
+
<For each={rows().map((row) => row.key)}>
|
|
286
|
+
{(key) => {
|
|
287
|
+
// The semantic key owns the native row; fresh snapshots update its
|
|
288
|
+
// props instead of destroying and recreating identical renderables.
|
|
289
|
+
const current = createMemo((previous) => rowsByKey().get(key) ?? previous, rowsByKey().get(key));
|
|
290
|
+
const row = {
|
|
291
|
+
key,
|
|
292
|
+
get group() {
|
|
293
|
+
return current().group;
|
|
294
|
+
},
|
|
295
|
+
get session() {
|
|
296
|
+
return current().session;
|
|
297
|
+
},
|
|
298
|
+
get agent() {
|
|
299
|
+
return current().agent;
|
|
300
|
+
},
|
|
301
|
+
get agentHeading() {
|
|
302
|
+
return current().agentHeading;
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
return (<box width={Math.max(1, props.width - 1)} height={row.agent ? (row.agentHeading ? 3 : 2) : 1} flexShrink={0} flexDirection="column">
|
|
306
|
+
<Show when={row.agentHeading}>
|
|
307
|
+
<text height={1} fg={props.theme.roles.text.secondary}>
|
|
308
|
+
{" "}
|
|
309
|
+
Agents
|
|
310
|
+
</text>
|
|
311
|
+
</Show>
|
|
312
|
+
<NavigationRow theme={props.theme} id={`machine:${row.key}`} width={Math.max(1, props.width - 1)} labelColor={!row.agent && !row.session ? fleetHostColor(row.group) : undefined} label={row.agent
|
|
313
|
+
? row.agent.name
|
|
314
|
+
: row.session
|
|
315
|
+
? friendlySessionLabel(row.session.name)
|
|
316
|
+
: row.group.label} marker={row.agent
|
|
317
|
+
? active(row)
|
|
318
|
+
? row.agent.attention
|
|
319
|
+
? "›!"
|
|
320
|
+
: "›"
|
|
321
|
+
: row.agent.attention
|
|
322
|
+
? "!"
|
|
323
|
+
: "•"
|
|
324
|
+
: row.session
|
|
325
|
+
? props.model.favorites?.().includes(row.session.id)
|
|
326
|
+
? " ★"
|
|
327
|
+
: active(row)
|
|
328
|
+
? " ›"
|
|
329
|
+
: " "
|
|
330
|
+
: collapsed().has(preferenceKey(row.group))
|
|
331
|
+
? "▸"
|
|
332
|
+
: "▾"} detail={row.agent
|
|
333
|
+
? row.group.state !== "ready" || row.agent.disabled
|
|
318
334
|
? "unavailable"
|
|
319
|
-
:
|
|
320
|
-
: row.
|
|
321
|
-
?
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
335
|
+
: `[${terminalAgentStatusLabel(row.agent.activity)}]`
|
|
336
|
+
: row.session
|
|
337
|
+
? row.group.state !== "ready" || row.session.disabled
|
|
338
|
+
? "unavailable"
|
|
339
|
+
: `${row.session.paneCount}p${row.group.agents?.length ? ` ${activity(row).label}` : ""}`
|
|
340
|
+
: row.group.agents?.length && row.group.state === "ready"
|
|
341
|
+
? activity(row).label
|
|
342
|
+
: `${connectionDetail(row.group)}${row.group.agents?.length ? " ?" : ""}`} selected={Boolean((row.session || row.agent) && active(row))} focused={Boolean(focused() && row.key === selectedKey())} attention={row.agent?.attention ?? activity(row).kind === "attention"} onActivate={(source) => activate(row, source)}/>
|
|
343
|
+
<Show when={row.agent}>
|
|
344
|
+
{(agent) => (<text height={1} fg={props.theme.roles.text.muted} content={clipTerminal(` ${friendlySessionLabel(agent().sessionName)}`, Math.max(1, props.width - 1))}/>)}
|
|
345
|
+
</Show>
|
|
346
|
+
</box>);
|
|
347
|
+
}}
|
|
327
348
|
</For>
|
|
328
349
|
</scrollbox>
|
|
329
350
|
<For each={agentHeight() > 0 ? [true] : []}>
|