fix(dashboard): card timer shows total execution = timed events + workflow runtime

The task card timer chip previously fell back through several metrics
(timed duration → workflow runtime → wallclock), so cards showed only a
subset of execution time. For FN-2714 this rendered <1m on the card while
the stats tab reported >2m of workflow runtime.

The chip now reports the sum of [timing]-tagged log events and workflow
step runtime (matching the new "Total execution time" metric in the stats
panel), with live elapsed for in-progress workflow steps. When neither
metric is recorded, the chip is hidden rather than falling back to
wallclock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Fusion
2026-04-27 14:23:54 -07:00
committed by gsxdsm
parent 3e43477dc8
commit 9eef425525
9 changed files with 921 additions and 412 deletions

View File

@@ -402,8 +402,17 @@ 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();
}
@@ -442,6 +451,7 @@ 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();
}
@@ -637,6 +647,16 @@ 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,