mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-24 16:01:42 +00:00
fix: move pane zoom into attach cmd chain for reliable initial zoom
- Pass initialPane to attach() so zoom runs inside the shell command chain where tmux is guaranteed to exist (no external race) - Send initialPane in the first resize WS message (atomic, single msg) - Remove pendingPane from WsData (dead code from iterative patching) - Fix unzoom: use shell conditional instead of broken tmux if-shell Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -73,7 +73,6 @@ function errorResponse(message: string, status = 500): Response {
|
||||
interface WsData {
|
||||
worktree: string;
|
||||
attached: boolean;
|
||||
pendingPane: number | null;
|
||||
}
|
||||
|
||||
function makeCallbacks(ws: { send: (data: string) => void; readyState: number }) {
|
||||
@@ -101,7 +100,7 @@ Bun.serve<WsData>({
|
||||
const wsMatch = url.pathname.match(/^\/ws\/(.+)$/);
|
||||
if (wsMatch) {
|
||||
const worktree = decodeURIComponent(wsMatch[1]);
|
||||
const upgraded = server.upgrade(req, { data: { worktree, attached: false, pendingPane: null } });
|
||||
const upgraded = server.upgrade(req, { data: { worktree, attached: false } });
|
||||
if (upgraded) return undefined as unknown as Response;
|
||||
return new Response("WebSocket upgrade failed", { status: 400 });
|
||||
}
|
||||
@@ -128,16 +127,9 @@ Bun.serve<WsData>({
|
||||
write(worktree, msg.data);
|
||||
break;
|
||||
case "selectPane":
|
||||
if (typeof msg.pane === "number") {
|
||||
if (ws.data.attached) {
|
||||
// Attach already completed — apply directly
|
||||
console.log(`[ws:${ts()}] selectPane direct pane=${msg.pane} worktree=${worktree}`);
|
||||
selectPane(worktree, msg.pane);
|
||||
} else {
|
||||
// Attach still in progress — queue for post-attach
|
||||
ws.data.pendingPane = msg.pane;
|
||||
console.log(`[ws:${ts()}] selectPane queued pane=${msg.pane} worktree=${worktree}`);
|
||||
}
|
||||
if (ws.data.attached && typeof msg.pane === "number") {
|
||||
console.log(`[ws:${ts()}] selectPane pane=${msg.pane} worktree=${worktree}`);
|
||||
selectPane(worktree, msg.pane);
|
||||
}
|
||||
break;
|
||||
case "resize":
|
||||
@@ -146,7 +138,11 @@ Bun.serve<WsData>({
|
||||
ws.data.attached = true;
|
||||
console.log(`[ws:${ts()}] first resize (attaching) worktree=${worktree} cols=${msg.cols} rows=${msg.rows}`);
|
||||
try {
|
||||
await attach(worktree, msg.cols, msg.rows);
|
||||
const initialPane = typeof msg.initialPane === "number" ? msg.initialPane : undefined;
|
||||
if (initialPane !== undefined) {
|
||||
console.log(`[ws:${ts()}] initialPane=${initialPane} worktree=${worktree}`);
|
||||
}
|
||||
await attach(worktree, msg.cols, msg.rows, initialPane);
|
||||
const { onData, onExit } = makeCallbacks(ws);
|
||||
setCallbacks(worktree, onData, onExit);
|
||||
const scrollback = getScrollback(worktree);
|
||||
@@ -154,12 +150,6 @@ Bun.serve<WsData>({
|
||||
if (scrollback) {
|
||||
ws.send(JSON.stringify({ type: "scrollback", data: scrollback }));
|
||||
}
|
||||
// Apply any pane zoom queued while attach was in progress
|
||||
if (ws.data.pendingPane !== null) {
|
||||
console.log(`[ws:${ts()}] applying pendingPane=${ws.data.pendingPane} worktree=${worktree}`);
|
||||
selectPane(worktree, ws.data.pendingPane);
|
||||
ws.data.pendingPane = null;
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
const errMsg = err instanceof Error ? err.message : String(err);
|
||||
console.log(`[ws:${ts()}] attach failed worktree=${worktree}: ${errMsg}`);
|
||||
|
||||
@@ -51,7 +51,8 @@ function killTmuxSession(name: string): void {
|
||||
export async function attach(
|
||||
worktreeName: string,
|
||||
cols: number,
|
||||
rows: number
|
||||
rows: number,
|
||||
initialPane?: number
|
||||
): Promise<string> {
|
||||
console.log(`[term:${ts()}] attach(${worktreeName}) cols=${cols} rows=${rows} existing=${sessions.has(worktreeName)}`);
|
||||
if (sessions.has(worktreeName)) {
|
||||
@@ -68,14 +69,17 @@ export async function attach(
|
||||
// Kill stale session with same name if it exists (leftover from previous server run)
|
||||
killTmuxSession(gName);
|
||||
|
||||
const paneTarget = `${gName}:${windowTarget}.${initialPane ?? 0}`;
|
||||
const cmd = [
|
||||
`tmux new-session -d -s "${gName}" -t "${tmuxSession}"`,
|
||||
`tmux set-option -t "${gName}" mouse on`,
|
||||
`tmux set-option -t "${gName}" set-clipboard on`,
|
||||
`tmux select-window -t "${gName}:${windowTarget}"`,
|
||||
// Unzoom if a previous session left a pane zoomed (zoom state is shared)
|
||||
`tmux if-shell -t "${gName}:${windowTarget}" "#{window_zoomed_flag}" "resize-pane -Z -t ${gName}:${windowTarget}"`,
|
||||
`tmux select-pane -t "${gName}:${windowTarget}.0"`,
|
||||
// Unzoom if a previous session left a pane zoomed (zoom state is shared across grouped sessions)
|
||||
`if [ "$(tmux display-message -t '${gName}:${windowTarget}' -p '#{window_zoomed_flag}')" = "1" ]; then tmux resize-pane -Z -t '${gName}:${windowTarget}'; fi`,
|
||||
`tmux select-pane -t "${paneTarget}"`,
|
||||
// On mobile, zoom the selected pane to fill the window
|
||||
...(initialPane !== undefined ? [`tmux resize-pane -Z -t "${paneTarget}"`] : []),
|
||||
`stty rows ${rows} cols ${cols}`,
|
||||
`exec tmux attach-session -t "${gName}"`,
|
||||
].join(" && ");
|
||||
|
||||
@@ -107,10 +107,11 @@
|
||||
|
||||
ws.onopen = () => {
|
||||
fitAddon.fit();
|
||||
ws.send(JSON.stringify({ type: "resize", cols: term.cols, rows: term.rows }));
|
||||
const msg: Record<string, unknown> = { type: "resize", cols: term.cols, rows: term.rows };
|
||||
if (isMobile && initialPane !== undefined) {
|
||||
sendSelectPane(initialPane);
|
||||
msg.initialPane = initialPane;
|
||||
}
|
||||
ws.send(JSON.stringify(msg));
|
||||
};
|
||||
|
||||
ws.onclose = () => {
|
||||
|
||||
Reference in New Issue
Block a user