fix(tui): overlays as absolute popups, no painter ghosting - #78
Merged
Conversation
The user reported the TUI was "all broken". Two real bugs + one layout fix: 1. **Overlays didn't actually overlay.** Help / TaskDetail / AutoExit were rendered as flex siblings of the column root, so they appeared *below* the StatusBar instead of as modal popups. The bottom cells of the underlying view bled through (the visible symptom was "qrquitsnimmediately.y open." — overlay text overlaid on the StatusBar's hint string). Fix: each overlay now uses `position="absolute"` with computed `top` / `left` / `width` / `height`, centered on screen, opaque `backgroundColor`. Matches the lazygit / Turbo / opencode popup pattern. The App's root box gains `position="relative"` so absolute children anchor to it. 2. **OpenTUI painter ghosting on shrink.** Multi-element rows (`<text>` + space + `<text>` + ...) leave stale cells when the right-hand element shrinks — that's how "125% paralel" appeared where "1/1 25% parallel" used to be. Inline padding doesn't help because each `<text>` is its own renderable. Fix: Header, StatusBar, ProgressBar, and StatsPanel rows are now single `<text>` children pre-padded to the full width. The painter writes every cell on every frame; nothing to ghost. 3. **Overview cramped the task list to a single visible row.** TaskList + StatsPanel were both children of a column with no height constraint, so yoga shrank both proportionally. Gave StatsPanel a fixed footer height (6) and let TaskList take the remainder. 4. **Empty state pre-runStart.** Before `runStart` fires, the body used to show empty bordered boxes labelled "Tasks" / "Log". Now shows a "Waiting for tasks…" message until totalNodes > 0. 5. Header shows `run …` (ellipsis) before the first runStart event populates the run id, instead of `run --------`. Studied Turbo's TUI (`crates/turborepo-ui/src/tui`) for the popup pattern — they use `ratatui`'s `Clear` widget + a computed `popup_area` Rect. The OpenTUI equivalent is `position="absolute"` with explicit `backgroundColor`. Tests still 506 / 506. https://claude.ai/code/session_016HXj6HW6bxSn8EYuKcxTD9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
You said the TUI was "all broken." It actually mounted and ran, but two
real rendering bugs made it look like trash:
rendered as the last flex child of the column root, not as modal
popups — so they appeared below the StatusBar and the underlying
view's bottom cells bled through. That's why the "Closing in 3s"
text looked corrupted as
qrquitsnimmediately.y open.— theoverlay's text was painted over the unchanged StatusBar hint.
<text>rows (<text> "foo"/> <text "bar"/>) leave stale cells when the right-hand textshrinks. So "1/1 25% parallel" → "0/1 25% parallel" produced
125% paralelartifacts.Fixes
Overlays now actually overlay
Studied Turbo's
crates/turborepo-ui/src/tui/popup.rsfor thepattern: compute a centered Rect, render
Clear+ popup widget.OpenTUI equivalent:
position="absolute"+ explicittop/left/width/heightbackgroundColor.App.tsxroot gainsposition="relative"so absolute childrenanchor to it.
Help,TaskDetail,AutoExitall useposition="absolute",centered on screen, with fixed (Help / AutoExit) or
bounded-flexible (TaskDetail) dimensions.
No more painter ghosting
Header,StatusBar,ProgressBar, and eachStatsPanelrow arenow a single
<text>element pre-padded to the full width. Everycell repaints every frame; nothing left to ghost.
Layout: Overview no longer collapses the task list
TaskList + StatsPanel were both children of a column with no height
hint, so yoga shrank both proportionally and TaskList showed one row.
Gave StatsPanel a fixed footer height (6); TaskList takes the
remainder.
Loading state pre-runStart
Before
runStartfires, body now shows "Waiting for tasks…" insteadof empty bordered "Tasks" / "Log" frames. Header shows
run …ellipsisinstead of
run --------.Visual check (PTY 140×40)
After the run completes, the final frame now reads:
Test plan
bun src/bin.ts run formatcleanbun src/bin.ts run lint0/0bun src/bin.ts run test— 506/506 passCaveat
The user's "the tui is trash" framing also suggests possible scope
concerns — Turbo's TUI ships only a task list + log + search +
help popup (no 5 views, sparklines, critical-path, etc). If you want
me to reduce scope toward that single-screen pattern, I can do that
in a follow-up — say the word.
https://claude.ai/code/session_016HXj6HW6bxSn8EYuKcxTD9
Generated by Claude Code