fix(FN-2657): stabilize TUI logs panel width

- Set explicit column widths in the dashboard TUI logs panel to prevent width jitter during log updates
- Add a regression test covering logs panel width stability across rerenders
- Keep existing layout behavior while eliminating shifting in the logs view
This commit is contained in:
Fusion
2026-04-27 00:47:47 -07:00
committed by gsxdsm
parent f0e800598c
commit 0bd30f7b58
2 changed files with 59 additions and 2 deletions

View File

@@ -163,6 +163,15 @@ async function waitForFrameContains(lastFrame: () => string | undefined, text: s
throw new Error(`Timed out waiting for frame to include: ${text}`);
}
function findTokenPosition(frame: string, token: string): { row: number; col: number } {
const lines = frame.split("\n");
const row = lines.findIndex((line) => line.includes(token));
expect(row).toBeGreaterThanOrEqual(0);
const col = lines[row].indexOf(token);
expect(col).toBeGreaterThanOrEqual(0);
return { row, col };
}
describe("DashboardApp smoke", () => {
it("renders the splash logo and tagline before systemInfo arrives", () => {
const controller = newController();
@@ -611,6 +620,54 @@ describe("LogsPanel indicator", () => {
});
});
describe("StatusModeGrid layout stability", () => {
it("keeps System and Logs panel anchors stable as log content length changes", async () => {
const controller = newController();
controller.setSystemInfo(makeSystemInfo());
controller.setActiveSection("logs");
const rendered = render(renderDashboardAppNode(controller));
setTerminalSize(rendered, 120, 28);
rendered.rerender(renderDashboardAppNode(controller));
controller.log("short msg", "app");
controller.log("small", "worker");
controller.log("ok", "db");
rendered.rerender(renderDashboardAppNode(controller));
await new Promise((r) => setTimeout(r, 10));
const shortFrame = rendered.lastFrame() ?? "";
const shortSystem = findTokenPosition(shortFrame, "System");
const shortLogs = findTokenPosition(shortFrame, "Logs (3/1000)");
const shortStatus = findTokenPosition(shortFrame, "http://localhost:4040");
controller.log(
"this is a deliberately long log message that should force truncation or wrapping and previously nudged grid widths",
"very-long-prefix-value",
);
controller.log(
"another long log payload with changing text widths to emulate timer and event updates in real sessions",
"background-scheduler",
);
controller.log(
"final long line for width stability regression coverage across re-renders",
"super-verbose-component-prefix",
);
rendered.rerender(renderDashboardAppNode(controller));
await new Promise((r) => setTimeout(r, 10));
const longFrame = rendered.lastFrame() ?? "";
const longSystem = findTokenPosition(longFrame, "System");
const longLogs = findTokenPosition(longFrame, "Logs (6/1000)");
const longStatus = findTokenPosition(longFrame, "http://localhost:4040");
expect(longSystem).toEqual(shortSystem);
expect(longLogs).toEqual(shortLogs);
expect(longStatus).toEqual(shortStatus);
rendered.unmount();
});
});
describe("LogsPanel narrow formatting", () => {
it("shows a compact index instead of full timestamp in narrow terminals", async () => {
const controller = newController();

View File

@@ -760,11 +760,11 @@ function StatusModeGrid({
</Box>
<Box flexDirection="row" flexGrow={1} overflow="hidden">
<Box flexDirection="column" flexGrow={5} overflow="hidden">
<Box flexDirection="column" width="45%" flexGrow={0} flexShrink={1} overflow="hidden">
<SystemPanel state={state} isFocused={focused === "system"} />
<StatsPanel state={state} isFocused={focused === "stats"} />
</Box>
<Box flexDirection="column" flexGrow={6} overflow="hidden">
<Box flexDirection="column" width="55%" flexGrow={0} flexShrink={1} overflow="hidden">
<LogsPanel
state={state}
isFocused={focused === "logs"}