Fix terminal renderer remount loading

This commit is contained in:
Mohamed Boudra
2026-05-03 20:38:12 +07:00
parent ade05607d2
commit b7f9092e93
4 changed files with 171 additions and 2 deletions
@@ -0,0 +1,96 @@
import { describe, expect, it } from "vitest";
import {
applyTerminalRendererReadyChange,
shouldReplayTerminalSnapshotForRenderer,
shouldShowTerminalLoadingOverlay,
} from "./terminal-renderer-readiness";
describe("terminal-renderer-readiness", () => {
it("preserves the attach loader even after the renderer is ready", () => {
expect(
shouldShowTerminalLoadingOverlay({
isWorkspaceFocused: true,
hasStreamError: false,
isAttaching: true,
rendererReadyStreamKey: "scope:terminal-1",
terminalStreamKey: "scope:terminal-1",
}),
).toBe(true);
});
it("keeps the loader visible until the current renderer is ready", () => {
expect(
shouldShowTerminalLoadingOverlay({
isWorkspaceFocused: true,
hasStreamError: false,
isAttaching: false,
rendererReadyStreamKey: null,
terminalStreamKey: "scope:terminal-1",
}),
).toBe(true);
});
it("hides the loader only after attach completes and the current renderer is ready", () => {
expect(
shouldShowTerminalLoadingOverlay({
isWorkspaceFocused: true,
hasStreamError: false,
isAttaching: false,
rendererReadyStreamKey: "scope:terminal-1",
terminalStreamKey: "scope:terminal-1",
}),
).toBe(false);
});
it("does not cover stream errors", () => {
expect(
shouldShowTerminalLoadingOverlay({
isWorkspaceFocused: true,
hasStreamError: true,
isAttaching: true,
rendererReadyStreamKey: null,
terminalStreamKey: "scope:terminal-1",
}),
).toBe(false);
});
it("ignores stale unready events from an old renderer", () => {
const current = applyTerminalRendererReadyChange("scope:terminal-2", {
streamKey: "scope:terminal-1",
isReady: false,
});
expect(current).toBe("scope:terminal-2");
});
it("clears readiness when the current renderer unmounts", () => {
const current = applyTerminalRendererReadyChange("scope:terminal-1", {
streamKey: "scope:terminal-1",
isReady: false,
});
expect(current).toBeNull();
});
it("replays snapshots only for ready events from the current renderer", () => {
expect(
shouldReplayTerminalSnapshotForRenderer({
change: { streamKey: "scope:terminal-1", isReady: true },
terminalStreamKey: "scope:terminal-1",
}),
).toBe(true);
expect(
shouldReplayTerminalSnapshotForRenderer({
change: { streamKey: "scope:terminal-1", isReady: false },
terminalStreamKey: "scope:terminal-1",
}),
).toBe(false);
expect(
shouldReplayTerminalSnapshotForRenderer({
change: { streamKey: "scope:terminal-1", isReady: true },
terminalStreamKey: "scope:terminal-2",
}),
).toBe(false);
});
});
@@ -0,0 +1,36 @@
export interface TerminalRendererReadyChange {
streamKey: string;
isReady: boolean;
}
export function applyTerminalRendererReadyChange(
currentReadyStreamKey: string | null,
change: TerminalRendererReadyChange,
): string | null {
if (change.isReady) {
return change.streamKey;
}
return currentReadyStreamKey === change.streamKey ? null : currentReadyStreamKey;
}
export function shouldReplayTerminalSnapshotForRenderer(input: {
change: TerminalRendererReadyChange;
terminalStreamKey: string;
}): boolean {
return input.change.isReady && input.change.streamKey === input.terminalStreamKey;
}
export function shouldShowTerminalLoadingOverlay(input: {
isWorkspaceFocused: boolean;
hasStreamError: boolean;
isAttaching: boolean;
rendererReadyStreamKey: string | null;
terminalStreamKey: string;
}): boolean {
return (
input.isWorkspaceFocused &&
!input.hasStreamError &&
(input.isAttaching || input.rendererReadyStreamKey !== input.terminalStreamKey)
);
}