fix(FN-2722): simplify dashboard TUI header and panel switching behavior
- Remove stdout header overlay patching and related frame recovery hooks from the dashboard TUI controller - Keep panel switching logic lightweight by relying on normal state updates without forced recover cycles - Adjust status footer and utility/system panel rendering to maintain a single-line, truncation-friendly layout - Add explicit number-key panel mapping while preserving existing tab/cycle panel order
This commit is contained in:
@@ -96,9 +96,6 @@ export class DashboardTUI {
|
||||
} & Record<string, unknown> | null = null;
|
||||
// Resize listener attached at start(), detached at stop().
|
||||
private resizeListener: (() => void) | null = null;
|
||||
// Original process.stdout.write before we patch it for the header overlay.
|
||||
// Stored so stop() can restore it.
|
||||
private originalStdoutWrite: typeof process.stdout.write | null = null;
|
||||
// Debounce timer for resize handling — coalesces tmux/ssh resize bursts.
|
||||
private resizeDebounceTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
// Last observed terminal dims, used by the dim-poll fallback to detect
|
||||
@@ -429,17 +426,8 @@ export class DashboardTUI {
|
||||
// ── State helpers called from Ink App ────────────────────────────────────
|
||||
|
||||
setActiveSection(section: SectionId): void {
|
||||
const changed = this.activeSection !== section;
|
||||
this.activeSection = section;
|
||||
this.showHelp = false;
|
||||
if (changed) {
|
||||
// Brute-force: wipe alt-screen + reset Ink's log-update tracking before
|
||||
// rendering the new section. Some sections (Logs, Utilities) appear to
|
||||
// leave residual state in tmux that scrolls the header off the top on
|
||||
// the first render after switching. A full recover here is overkill on
|
||||
// every switch but eliminates the artifact.
|
||||
this.recoverFrameNow();
|
||||
}
|
||||
this.notify();
|
||||
}
|
||||
|
||||
@@ -478,7 +466,6 @@ export class DashboardTUI {
|
||||
const idx = SECTION_ORDER.indexOf(this.activeSection);
|
||||
this.activeSection = SECTION_ORDER[(idx + direction + SECTION_ORDER.length) % SECTION_ORDER.length];
|
||||
this.showHelp = false;
|
||||
this.recoverFrameNow();
|
||||
this.notify();
|
||||
}
|
||||
|
||||
@@ -563,7 +550,7 @@ export class DashboardTUI {
|
||||
// that only exercise pure logic).
|
||||
const { render } = await import("ink");
|
||||
const { createElement } = await import("react");
|
||||
const { DashboardApp, buildHeaderAnsiLine } = await import("./app.js");
|
||||
const { DashboardApp } = await import("./app.js");
|
||||
|
||||
// Enter the terminal's alternate-screen buffer before mounting Ink so
|
||||
// the TUI gets a dedicated fullscreen surface that doesn't share
|
||||
@@ -577,45 +564,6 @@ export class DashboardTUI {
|
||||
process.stdout.write("\x1b[?1049h\x1b[H");
|
||||
}
|
||||
|
||||
// Header overlay failsafe — paint the header at terminal row 1 after
|
||||
// every Ink frame write. The Ink/Yoga layout is correct in synthetic
|
||||
// tests, but in real tmux at narrow widths the header still goes
|
||||
// missing — likely log-update line-tracking drift over many state
|
||||
// updates. The overlay is a guaranteed-paint-on-top write that's
|
||||
// independent of Ink's output, with cursor save/restore so log-update
|
||||
// tracking is preserved.
|
||||
if (process.stdout?.isTTY && typeof process.stdout.write === "function") {
|
||||
const original = process.stdout.write.bind(process.stdout) as
|
||||
((...args: unknown[]) => boolean);
|
||||
this.originalStdoutWrite = process.stdout.write;
|
||||
let inOverlay = false;
|
||||
const overlay = (text: string): void => {
|
||||
if (inOverlay) return;
|
||||
if (typeof text !== "string" || text.length < 200) return;
|
||||
const snapshot = this.getSnapshot();
|
||||
// Skip overlay during splash/loading (before systemInfo arrives) so
|
||||
// the splash screen has clean unobstructed real estate.
|
||||
if (!snapshot.systemInfo) return;
|
||||
const cols = process.stdout.columns ?? 0;
|
||||
if (cols <= 0) return;
|
||||
try {
|
||||
inOverlay = true;
|
||||
const headerLine = buildHeaderAnsiLine(snapshot, cols);
|
||||
// \x1b7 = DECSC (save cursor + attrs), \x1b[1;1H = move to terminal
|
||||
// row 1 col 1, header, \x1b8 = DECRC (restore cursor + attrs).
|
||||
original(`\x1b7\x1b[1;1H${headerLine}\x1b8`);
|
||||
} finally {
|
||||
inOverlay = false;
|
||||
}
|
||||
};
|
||||
const patched = (...args: unknown[]): boolean => {
|
||||
const result = original(...args);
|
||||
overlay(args[0] as string);
|
||||
return result;
|
||||
};
|
||||
(process.stdout as unknown as { write: unknown }).write = patched;
|
||||
}
|
||||
|
||||
this.inkInstance = render(
|
||||
createElement(DashboardApp, { controller: this }),
|
||||
);
|
||||
@@ -713,16 +661,6 @@ export class DashboardTUI {
|
||||
// visible at the bottom. \x1b[2J\x1b[H wipes the buffer first.
|
||||
// Order: wipe → reset Ink's tracking → record dims → notify so React
|
||||
// reads fresh dims and rerenders cleanly.
|
||||
// Public version: callable from state-changing methods (e.g. switching
|
||||
// panels). Reads current dims from process.stdout and runs the same
|
||||
// wipe + ink-clear sequence as the resize-driven path.
|
||||
recoverFrameNow(): void {
|
||||
const cols = process.stdout?.columns ?? 0;
|
||||
const rows = process.stdout?.rows ?? 0;
|
||||
if (cols <= 0 || rows <= 0) return;
|
||||
this.recoverFrame(cols, rows);
|
||||
}
|
||||
|
||||
private recoverFrame(cols: number, rows: number): void {
|
||||
tuiDebug("recoverFrame", {
|
||||
cols,
|
||||
@@ -774,12 +712,6 @@ export class DashboardTUI {
|
||||
this.inkInstance.unmount();
|
||||
this.inkInstance = null;
|
||||
}
|
||||
// Restore original stdout.write (undo the header-overlay patch).
|
||||
if (this.originalStdoutWrite) {
|
||||
(process.stdout as unknown as { write: typeof process.stdout.write }).write =
|
||||
this.originalStdoutWrite;
|
||||
this.originalStdoutWrite = null;
|
||||
}
|
||||
// Leave the alt-screen buffer last so the user's shell scrollback
|
||||
// is restored cleanly. \x1b[?1049l = leave alt-screen.
|
||||
if (process.stdout?.isTTY && typeof process.stdout.write === "function") {
|
||||
|
||||
Reference in New Issue
Block a user