fix(desktop): wait for terminal frame before splash (#4781)

## Summary

- keep the first-open Buzz Term splash pending until the active PTY
delivers its first frame
- retrigger the splash effect when that readiness gate changes
- cover the real bootstrap path so startup latency cannot consume the
animation invisibly

## Verification

- Wes manually verified the first-open animation in the worktree
- `pnpm --dir desktop typecheck`
- `pnpm --dir desktop test` — 4,195 passed
- `pnpm exec biome check src/features/terminal/TerminalBootstrap.tsx
src/features/terminal/TerminalSubstrate.tsx
src/features/terminal/TerminalBootstrap.test.mjs`
- pre-push hooks — branch skew, desktop check, and 4,195 desktop tests
passed

The repository-wide `pnpm --dir desktop check` still reports
pre-existing diagnostics in `personaCatalogRelay.test.mjs` and
`terminal.css`; the three changed files pass Biome directly.

Signed-off-by: Wes <wesbillman@users.noreply.github.com>
Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz>
This commit is contained in:
Wes
2026-08-04 13:50:20 -07:00
committed by GitHub
co-authored by Carl
parent 8b8d86c5d2
commit 65f7a10035
3 changed files with 54 additions and 3 deletions
@@ -234,6 +234,56 @@ test("mounted bootstrap passes GUI context and ACKs only after consuming a frame
);
view.unmount();
});
test("first-open splash waits for the first terminal frame", async () => {
const { createElement } = await import("react");
const { act, render, waitFor } = await import("@testing-library/react");
const { ThemeProvider } = await import("@/shared/theme/ThemeProvider");
const { TerminalBootstrap } = await import("./TerminalBootstrap.tsx");
const view = render(
createElement(
ThemeProvider,
null,
createElement(TerminalBootstrap, {
channelId: "channel-1",
channelName: "general",
npub: "npub1owner",
relayUrl: "wss://relay.example",
threadId: null,
}),
),
);
await waitFor(() =>
assert.ok(calls.some(({ command }) => command === "terminal_attach")),
);
assert.equal(
view.container.querySelector(".buzz-terminal-welcome"),
null,
"the splash must not be consumed while the first PTY frame is pending",
);
await act(async () => {
emit({
type: "frame",
payload: {
bracketedPaste: false,
cursor: { column: 0, line: 0, visible: true },
focusReporting: false,
full: true,
rows: [],
sequence: 1,
subscriptionId: "subscription-1",
viewport: { columns: 100, generation: 0, screenLines: 24 },
},
});
});
await waitFor(() =>
assert.ok(view.container.querySelector(".buzz-terminal-welcome")),
);
view.unmount();
});
test("resize during in-flight catch-up keeps the newest viewport ready", async () => {
const { createElement } = await import("react");
const { act, render, waitFor } = await import("@testing-library/react");
@@ -371,7 +371,7 @@ export function TerminalBootstrap({
focusReportingEnabled={active?.frame?.focusReporting ?? false}
frame={active?.frame}
viewportReportingEnabled={viewportReportingEnabled}
showSplash={splashPending}
showSplash={splashPending && Boolean(active?.frame)}
onSplashStarted={handleSplashStarted}
sessionFrames={channelSessions.flatMap((session) =>
session.frame ? [{ sessionId: session.key, frame: session.frame }] : [],
@@ -345,8 +345,9 @@ export function TerminalSubstrate({
setWelcomeVisible(false);
return;
}
if (!viewportReportingEnabled || !banner || !beginSplash()) return;
}, [banner, viewportReportingEnabled, visible]);
if (!showSplash || !viewportReportingEnabled || !banner || !beginSplash())
return;
}, [banner, showSplash, viewportReportingEnabled, visible]);
React.useEffect(() => {
if (!welcomeVisible) return;