Skip to content

fix(tui): overlays as absolute popups, no painter ghosting - #78

Merged
Exelord merged 1 commit into
mainfrom
claude/tui-fix-overlays-and-init
May 13, 2026
Merged

fix(tui): overlays as absolute popups, no painter ghosting#78
Exelord merged 1 commit into
mainfrom
claude/tui-fix-overlays-and-init

Conversation

@Exelord

@Exelord Exelord commented May 13, 2026

Copy link
Copy Markdown
Member

Summary

You said the TUI was "all broken." It actually mounted and ran, but two
real rendering bugs made it look like trash:

  1. Overlays weren't overlays. Help / TaskDetail / AutoExit
    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. — the
    overlay's text was painted over the unchanged StatusBar hint.
  2. OpenTUI's painter ghosts on shrink. Multi-<text> rows (<text> "foo"/> <text "bar"/>) leave stale cells when the right-hand text
    shrinks. So "1/1 25% parallel" → "0/1 25% parallel" produced
    125% paralel artifacts.

Fixes

Overlays now actually overlay

Studied Turbo's crates/turborepo-ui/src/tui/popup.rs for the
pattern: compute a centered Rect, render Clear + popup widget.
OpenTUI equivalent: position="absolute" + explicit top/left/width/height

  • opaque backgroundColor.
  • App.tsx root gains position="relative" so absolute children
    anchor to it.
  • Help, TaskDetail, AutoExit all use position="absolute",
    centered on screen, with fixed (Help / AutoExit) or
    bounded-flexible (TaskDetail) dimensions.

No more painter ghosting

Header, StatusBar, ProgressBar, and each StatsPanel row are
now a single <text> element pre-padded to the full width. Every
cell 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 runStart fires, body now shows "Waiting for tasks…" instead
of empty bordered "Tasks" / "Log" frames. Header shows run … ellipsis
instead of run --------.

Visual check (PTY 140×40)

After the run completes, the final frame now reads:

 vx 0.0.0  run X2AWP45F  tasks build  pkgs 1  ✓1  ▶0  ⏳0  parallel 0%  local cache only
┌─Tasks──────────────┐┌─Log─────────────────────────────────────┐
│✓ a#build 317ms     ││$ echo build-a && sleep 0.3              │
│                    ││build-a                                  │
└────────────────────┘│                                         │
┌─Stats──────────────┐│                                         │
│ throughput   0/s   ││         ┌─Run complete─────────────┐    │
│ parallel %   0%    ││         │  Closing in 3s…          │    │
│ remote ops   0/s   ││         │  Press any key to stay…  │    │
└────────────────────┘│         │  q quits immediately.    │    │
                      │         └──────────────────────────┘    │
                      └─────────────────────────────────────────┘
 ████████████████████████████████████████  1/1  100% parallel
 [1] Overview  j/k select · / filter · ? help · 1-5 switch · q quit

Test plan

  • bun src/bin.ts run format clean
  • bun src/bin.ts run lint 0/0
  • bun src/bin.ts run test — 506/506 pass
  • Manual PTY render verified (above)

Caveat

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

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
@Exelord
Exelord merged commit b99fa20 into main May 13, 2026
1 check passed
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