Skip to content

Stabilize TUI refresh, scrolling, responsive layout, and dashboard data density - #863

Open
ihearttokyo wants to merge 5 commits into
getagentseal:mainfrom
ihearttokyo:codex/fix-tui-refresh-layout
Open

Stabilize TUI refresh, scrolling, responsive layout, and dashboard data density#863
ihearttokyo wants to merge 5 commits into
getagentseal:mainfrom
ihearttokyo:codex/fix-tui-refresh-layout

Conversation

@ihearttokyo

@ihearttokyo ihearttokyo commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Keep the active TUI view and scroll position stable during background refresh, clamp enabled refresh to 60 seconds, and retain the existing heavy-period refresh gate.
  • Make the complete dashboard scrollable and reflow the eight panels in a stable 1/2/3-column order, including 3/3/2 wide rows and a data-aware 256-column cap.
  • Keep every metric heading and value visible before shortening labels, stage project-path ellipsis, and size Daily Activity to the relevant neighboring panels.

Maintainer review reconciliation

The prior custom synchronized-update write has been removed. src/ink-win.ts is restored to upstream, Ink exclusively owns terminal synchronization, and CodeBurn's prepended resize handler only captures the new width and rerenders React. This eliminates the reviewed ConPTY escape path rather than maintaining another platform-specific protocol.

The reconciliation also restores the existing policy that Today, 7 Days, and concrete-day views may auto-refresh while 30 Days, Month, All, and Lifetime remain static. All three CLI help surfaces now state the 60-second minimum and the 0 disable value.

Testing

  • I have tested this locally against both real data and unit tests
  • npm test passes — the supported root-only run has 2,481 passing, 3 failing, and 5 skipped tests; all three failures reproduce unchanged on upstream 2c3319b. The separately configured desktop suite passes 462/462.
  • npm run build succeeds

Additional gates:

  • Focused refresh/resize/layout/help matrix: 59/59
  • Relevant dashboard/model/overview/help matrix: 72/72
  • Dashboard suite: 56/56
  • CLI and desktop TypeScript checks: passed
  • Desktop production build: passed
  • Deterministic widths 60–300: 241 frames
  • Native Ghostty shrink sweep: 105 settled captures from 146 down to 42 columns, all populated
  • Native Ghostty matrix: 40 captures across two font zoom levels, multiple shapes, scrolled state, and Optimize
  • Windows: mocked filter/source verification passed; AppX CI is the package-level Windows gate because no physical Windows host was available

Full methodology and evidence are recorded in SUBMISSION.md.

Reviewer focus

Please focus on the interaction among shared metric sizing, adaptive Daily Activity page size, and scroll state. Background refresh must preserve view and position. Supported widths must retain every metric, settled resize must preserve panel order and content, and navigation must use the page size displayed on screen.

@ihearttokyo

ihearttokyo commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Reconciled submission — 47968c1a2241

This branch is now rebased on current upstream main (2c3319b) and reconciles every substantive dashboard change with the maintainer review.

What is preserved

  • Background refresh keeps the active view, visible frame, and scroll position. Enabled refresh is clamped to one minute; --refresh 0 stays static.
  • The full application scrolls by line, page, and boundary, and position survives refresh and resize.
  • Panels preserve source order through one column at 89 characters or fewer, two from 90–134, and three from 135 upward. Wide rows use 3/3/2 and grow symmetrically to the lesser of 256 characters or useful source width.
  • Bars stay left. Metric cells keep full headings and values with one separating character. Tok/s and unavailable metrics remain present, costs remain full when space permits, and session is spelled out.
  • Project paths shorten parent folder first, date year second, and project title last.
  • Daily Activity uses 10 rows in one column, MAX(10, By Project) in two, and MAX(10, By Project, By Activity) in three. Rendering and all navigation share that size.

Maintainer review resolved

The application-owned synchronized-update sequence is gone. src/ink-win.ts matches upstream; CodeBurn emits no manual BSU/ESU or clear-and-home write. Ink alone owns synchronization, while the prepended CodeBurn resize listener captures the new column count before rerender. This removes the reviewed Windows ConPTY hazard rather than extending a custom escape protocol.

The existing heavy-period policy is restored: Today, 7 Days, and concrete-day views may auto-refresh; 30 Days, Month, All, and Lifetime remain static. CLI help is truthful on all three relevant commands.

Verification

  • 59/59 focused refresh, resize, layout, scrolling, metric, and help tests
  • 72/72 relevant dashboard, model, overview, and help tests
  • 56/56 dashboard tests
  • 462/462 desktop tests
  • TypeScript and all production builds green
  • Root tests/: 2,481 passed, 3 failed, 5 skipped; the same three failures reproduce at pristine upstream 2c3319b
  • 241 deterministic width frames from 60–300
  • 40 native Ghostty captures across two zoom levels and multiple shapes
  • 105 settled one-column-at-a-time shrink captures from 146–42, all populated
  • Live End-scroll remained at the bottom through 89→100 columns; Optimize remained mounted through 100→89

The complete contract, deliberate non-changes, and evidence are in SUBMISSION.md.

@ihearttokyo ihearttokyo changed the title Stabilize TUI refresh and responsive layout Stabilize TUI refresh, scrolling, and responsive layout Jul 31, 2026
@ihearttokyo

ihearttokyo commented Jul 31, 2026

Copy link
Copy Markdown
Contributor Author

Final validation update — 47968c1a2241

TDDRGR and the requested post-implementation bug-fix rounds are complete.

  1. The adaptive-row regression began red at 10 dates in a two-column 14-project fixture, then turned green through one shared page-size calculation.
  2. The maintainer reconciliation began red on application-owned synchronized writes, four heavy-period schedules, and three stale CLI help strings; the focused matrix is now 59/59.
  3. Correctness review is clean. Ponytail review: Lean already. Ship.
  4. A measured incrementalRendering experiment did not improve resize behavior and was removed, leaving the smaller Ink-owned design.
  5. The canonical desktop suite passes 462/462. The supported root tests/ run has 2,481 passing, 3 failing, and 5 skipped tests; the three failures reproduce identically on unmodified upstream 2c3319b.
  6. Native Ghostty validation used only window-bounded screencapture -l: 40 zoom/shape/state views, 105 settled one-column shrink captures, and final scroll/Optimize breakpoint paths. No settled frame was blank.
  7. No physical Windows machine was available. Mocked win32 filtering passed locally; the AppX GitHub job is the remaining package-level Windows evidence.

The branch is ready for a fresh maintainer review after CI completes.

@ihearttokyo ihearttokyo changed the title Stabilize TUI refresh, scrolling, and responsive layout Stabilize TUI refresh, scrolling, responsive layout, and dashboard data density Aug 2, 2026

@ozymandiashh ozymandiashh left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this. The scrolling, refresh-race and adaptive-layout work is careful, and I traced a fair bit of it by hand: the generation-counter and pending-ref coordination in reloadData, the viewRef stale-closure avoidance, the shared getDailyActivityPageSize / getProjectBreakdownRowLimit calculation that fixes the drift you describe, and the shortProject truncation staging. All of it holds up, and the test coverage for the stated behaviour is genuinely strong.

One thing should block, though, because it is invisible to CI and to this PR's own tests by construction.

The resize handler reintroduces the Windows/ConPTY hang from #195

src/dashboard.tsx:1504

process.stdout.write('\u001B[?2026h\u001B[2J\u001B[H')

That writes Begin-Synchronized-Update directly to stdout. The repo already has a guard for exactly this sequence, added by c511627 to close #195, whose commit message states that ConPTY "does not implement this protocol and buffers indefinitely, causing the dashboard to hang with no output":

// src/ink-win.ts:11
if (chunk === BSU || chunk === ESU) return true

The filter is an exact string comparison. The write above is BSU concatenated with clear-screen and cursor-home, so it is a different string, chunk === BSU is false, and the raw BSU passes straight through to the terminal on Windows. Every resize would then trigger the failure mode that guard exists to prevent.

There is also no matching End-Synchronized-Update anywhere in the diff. I grepped the branch: \u001B[?2026l appears zero times. The region is left open and only closes because Ink's next internal frame happens to emit its own correctly paired bsu/esu.

Neither the Windows package-build check nor tests/dashboard.test.ts can catch this. The tests drive PassThrough streams and assert on stripAnsied substrings, so they cannot observe ConPTY buffering or cursor desync.

Ink 7 already listens for resize on stdout and performs a layout recalc plus a full unthrottled re-render (ink.js:261-272), wrapped in its own correct bsu/esu pairing, so the manual write may simply be unnecessary. If a hard clear really is needed, gating it behind process.platform !== 'win32' would at least match the reasoning already encoded in ink-win.ts.

Smaller things, none blocking

  1. The isHeavyPeriod auto-refresh gate is gone. The previous code did if (!dayDate && isHeavyPeriod(period)) return, skipping auto-refresh on 30days/month/all/lifetime. src/dashboard.tsx:1270-1276 no longer has it, so background reparses now fire on heavy tabs. getDashboardScanRange bounds the scan when scrollableHistory is set, so this may well be fine, but it is a behaviour change the description does not mention and it would be good to hear it was deliberate.

  2. Aggregation runs inside render. dailyHistoryPageSize, dailyHistoryRowCount and modelCount are computed in the InteractiveDashboard body (src/dashboard.tsx:1132-1144) rather than in useMemo, so they recompute on every paging keystroke. Separately aggregateModelTotals(projects) runs twice per data-changing render, once inside the memoized getDashboardMaxWidth and again unmemoized in ModelBreakdown.

  3. ScrollableViewport's useLayoutEffect has no dependency array (src/dashboard.tsx:1062-1067), so measureElement runs after every render including every scroll tick, even though content height does not change while scrolling.

  4. StatusBar scrolls away. It is the last child of content, which is entirely inside ScrollableViewport (:1431-1439), so the keybinding hints disappear when you scroll down on a tall dashboard. Pinning it outside the scroll region would keep them visible.

  5. --refresh help text does not mention the new 60s floor. src/main.ts:767,1197,1215 still say "Auto-refresh interval in seconds (0 to disable)", so --refresh 10 silently becomes 60 with only the README explaining why.

  6. src/compare.tsx still caps at 2 columns (:214-215,257), so pressing c on a 135+ column terminal visibly narrows from the new 3-column layout. Pre-existing duplication rather than something this PR introduced, but the gap is wider now.

Everything else checked out on my side: tsc --noEmit is clean, and tests/dashboard.test.ts passes in full. The other failures I saw in full-suite runs (cli-emitters, cli-status-menubar, cache-refresh-lock) reproduce on unmodified main in my environment and are not attributable to this branch.

Happy to re-review once the resize write is sorted.

@ozymandiashh

Copy link
Copy Markdown
Collaborator

The branch allows maintainer edits, so I pushed 7716f95 with the targeted fix for the one blocking finding in my review, rather than leaving it to stall. @ihearttokyo it stays your PR, amend freely.

The commit keeps the resize reset but emits the synchronized-update escapes as standalone writes, sourced from ink-win.ts (now exported): standalone chunks are exactly what the ConPTY filter's chunk === BSU comparison can swallow, so Windows never sees the raw sequence that caused the #195 hang, while terminals that implement mode 2026 get a properly opened and closed update around the clear instead of an unterminated one left to their timeout.

Behavior by platform: Windows gets clear+home only (BSU/ESU filtered), everything else gets BSU, clear+home, ESU. The rest of the resize path is untouched.

All 48 dashboard tests pass locally on the branch, tsc --noEmit clean.

Since the fix is my own code I'm not converting my review into an approval; the remaining call is a maintainer's. Everything else in the PR I had already traced and found solid (generation-counter reload coordination, viewport preservation, the shared row-limit calculation, truncation staging), so from my side this is now unblocked.

@ozymandiashh ozymandiashh reopened this Aug 3, 2026
@ozymandiashh

Copy link
Copy Markdown
Collaborator

Status for whoever picks this up (@iamtoruk): the branch now carries the ConPTY fix (7716f95) and I verified locally what CI would check and more: tsc --noEmit clean, all 48 dashboard tests green, no trailer, diff limited to src/dashboard.tsx + src/ink-win.ts.

Two things need a call I should not make alone:

  1. Actions anomaly: since that push, GitHub has created zero check suites for this PR head, and a close/reopen did not retrigger anything (other PRs run fine; @avs-io this may be worth a look at the org/Actions level, the head commit is maintainer-pushed to the contributor fork).
  2. Sequencing vs tui: Workflow panel, rotating coaching notes, titles in the context browser #786: both PRs rewrite the same DashboardContent/InteractiveDashboard region; whichever merges second pays a real rebase (details in my tui: Workflow panel, rotating coaching notes, titles in the context browser #786 review). My suggestion: land this one first since it is a from-scratch layout model and tui: Workflow panel, rotating coaching notes, titles in the context browser #786's Workflow panel is easier to re-place as a ninth panel inside it than the reverse, but that is your wave to order.

From my side the PR is technically unblocked.

@ozymandiashh
ozymandiashh dismissed their stale review August 3, 2026 23:48

Blocking finding fixed by 7716f95 (standalone BSU/ESU writes); conflict with main resolved in 4df066d, 50/50 dashboard tests green. Leaving the approve/merge decision to iamtoruk given the #786 sequencing question.

ihearttokyo and others added 5 commits August 3, 2026 23:49
Keep background refreshes from blanking or replacing the active Optimize view, and enforce a one-minute minimum refresh interval.\n\nRework the dashboard into a stable 3/2/1-column flow with left-aligned bars, justified metric columns, readable project headings, and a ten-row Daily Activity viewport.\n\nSynchronize resize state before Ink paints so breakpoint transitions do not leave stale frames, preserve content beyond 256 terminal columns, and cap the dashboard at the current data's renderable width. Add focused regression coverage and a submission statement documenting live Ghostty validation.
Pin the interactive dashboard to a terminal-sized viewport and add line,
page, home, and end navigation without sacrificing the alternate-screen
resize protections. Preserve the viewport offset across background refreshes
and ordinary rerenders while resetting cleanly for a new view or period.

Give daily-history paging its own Space binding, render full model costs
whenever the panel can hold them, and spell out the project session heading.
Extend the responsive dashboard regressions across one-, two-, and
three-column viewports and update the submission evidence.
Keep every dashboard metric visible with intrinsic column widths and one
cell of separation, allowing bars and project labels to yield space before
headings or values disappear. Shorten project paths in meaningful stages so
the project title remains recognizable for as long as possible.

Derive Daily Activity's page size from the sibling panels in the active
responsive row: ten dates in one column, the visible project count in two,
and the greater project or activity count in three. Reuse that calculation
for rendering, cursor bounds, paging, and status text so the viewport cannot
drift from its navigation contract.

Cover the behavior with live Ink regressions, path-shortening contracts, the
70-test dashboard/model/overview matrix, and the rebuilt submission record.
The resize reset wrote Begin-Synchronized-Update concatenated with
clear+home in a single chunk. ink-win's ConPTY filter compares chunks
exactly, so the raw BSU passed through on Windows and would reintroduce
the getagentseal#195 hang; the update was also never ended, leaving terminals that do
implement 2026 to rely on their timeout. BSU/ESU now live in ink-win.ts
and are written standalone: swallowed by the Windows filter, honored
elsewhere, and properly closed around the clear.
Remove application-owned synchronized terminal writes so Ink remains the
single resize synchronization owner and the Windows ConPTY filter retains its
upstream contract.

Restore the aggregate-period refresh gate, document the one-minute floor in
all CLI help surfaces, and lock both policies with focused regressions. Rebuild
the submission record around the rebased branch, extensive shrink-heavy
Ghostty evidence, upstream-baseline failures, and the maintainer review.
@ihearttokyo
ihearttokyo force-pushed the codex/fix-tui-refresh-layout branch from 4df066d to 47968c1 Compare August 4, 2026 05:28
@ihearttokyo

Copy link
Copy Markdown
Contributor Author

@ozymandiashh @iamtoruk — thank you for tracing the ConPTY path and for the targeted maintainer fix. I reconciled that review by removing CodeBurn's custom synchronized-update writes entirely: src/ink-win.ts is back to upstream, and Ink is again the sole synchronization owner. The prepended resize handler now only captures width and rerenders.

I also restored the heavy-period refresh gate, corrected the CLI help, reran the full focused and broader matrices, and completed a shrink-heavy native Ghostty sweep. The refreshed head is 47968c1a2241; CI has been retriggered. When it is green, would you please take another look?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants