Skip to content

fix(picker): no layout or palette shift on the first frame - #56

Merged
hammadmajid merged 5 commits into
mainfrom
fix/55-first-frame-flash
Sep 19, 2026
Merged

hammadmajid merged 5 commits into
mainfrom
fix/55-first-frame-flash

Conversation

@hammadmajid

Copy link
Copy Markdown
Contributor

Closes #55.

Launching cdd painted one frame in the light palette and with a one-column status gutter, then repainted it dark with the gutter at its real width — the flash in the screencast, ~50ms long.

Palette. Model.dark started false and tea.BackgroundColorMsg only lands a frame or two in, so a dark terminal saw a washed-out frame, preview border box and all. The Picker now opens dark (as lipgloss assumes when a terminal will not answer) and draws nothing at all until the report lands, with a 50ms deadline of its own so a silent terminal is not left staring at a blank screen.

A middle commit here queried the background synchronously with lipgloss.HasDarkBackground before the program started, which is the tidier fix on paper. Measured against a terminal that answers neither that query nor the device-attributes query alongside it, lipgloss waits out its own two-second timeout and nothing is drawn until it expires, so it is reverted in favour of the Picker's own deadline. The commit and its revert are kept so the trade-off is on the record.

Status column. computeLayout sized the column to whichever statuses had come back, so it grew from one column to six as the git fan-out returned, taking the names, the times and the preview's edge with it. It is now reserved at the width of ✓ ? ↑1↓1 from the first frame and still grows for anything wider.

Verification

Driven under a pty that answers OSC 11 and DA1 like a real terminal, replayed through a terminal emulator frame by frame:

before after
first paint 0.072s 0.096s
light-palette codes on a dark terminal 232 0
distinct screens before settling 9 2
bytes written 17097 6959

Every column sits at the same x from the first paint onwards; only the glyphs fill in. A terminal that answers late (0.4s) and one that never answers both paint at ~0.11s, dark. go test ./... passes; the three new tests fail against the code they fix.

🤖 Generated with Claude Code

hammadmajid and others added 5 commits September 20, 2026 03:45
The Picker drew its first frame with dark false, so a dark terminal saw
one washed-out frame — pale selected row, preview border bright enough to
draw a box that then vanished — before tea.BackgroundColorMsg landed and
repainted it. Dark is the safer assumption, and the one lipgloss makes
when a terminal will not answer.

themeSet went with it: it was written on the report and never read.

Part of #55

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bubble Tea reports the terminal background a frame or two in, so a light
terminal still repainted itself on launch. Query it synchronously on the
screen the Picker is about to draw on: lipgloss sends a device-attributes
query alongside, so a terminal that ignores the background query ends this
one promptly rather than waiting out the timeout. The asynchronous report
stays as the correction for a query that goes unanswered.

ttyProgramOptions became openScreen, which hands back the files as well as
the options, and dropped an error it never returned.

Part of #55

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Row statuses land one at a time with the Picker already on screen, and the
column was sized to whichever of them had arrived: one column wide while
the git fan-out was in flight, six once clusters like "✓ ? ↑1" came back,
taking the names, the times and the preview's edge two columns right with
it. Hold it at "✓ ? ↑1↓1" from the start; glyphs now fill a column that is
already their size, and a wider cluster still grows it.

Part of #55

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured against a terminal that answers neither the background query nor
the device-attributes query alongside it: lipgloss waits out its own
two-second timeout, and the Picker paints nothing at all until it expires.
A two-second blank launch is a worse bug than the frame it was fixing.

The next commit holds the first frame on a deadline of the Picker's own
instead, which is bounded whatever the terminal does.

Part of #55

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The terminal reports its background a frame or two into the Picker's life,
so the frame drawn before it landed was repainted in the other palette
right after: the flash on launch. Draw nothing until the report arrives,
with a 50ms deadline of the Picker's own so a terminal that never answers
gets the dark frame promptly rather than a blank screen.

Measured in a pty that answers, one that answers late and one that stays
silent: the first paint lands at ~0.1s in all three, against ~0.08s and a
full light-palette repaint before.

View tests now settle the palette through sizedModel, and the two tests
that name the deadline message sit in the package.

Closes #55

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hammadmajid
hammadmajid merged commit 7487963 into main Sep 19, 2026
3 checks passed
@hammadmajid
hammadmajid deleted the fix/55-first-frame-flash branch September 19, 2026 22:49
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.

fix(picker): no layout or palette shift on the first frame

1 participant