Skip to content

fix(prompt): stop a stale render sticking on the prompt - #94

Merged
plttn merged 3 commits into
mainfrom
push-zrzvkskuysnr
Jul 25, 2026
Merged

fix(prompt): stop a stale render sticking on the prompt#94
plttn merged 3 commits into
mainfrom
push-zrzvkskuysnr

Conversation

@plttn

@plttn plttn commented Jul 25, 2026

Copy link
Copy Markdown
Owner

The async prompt still showed stale content now and then, once right after a full-screen program exited. Two separate causes, both in functions/fish_prompt.fish.

A fresh render could be destroyed, not just dropped. Every background job renamed its output over one shared file. When a slow render outlived a faster one dispatched after it, the slow write landed last and overwrote the fresh content. The generation stamp added in #91 then refused to apply what it read, so the prompt kept the content it already had until the next command. The stamp stopped stale content being shown, but not fresh content being overwritten, so it replaces that mechanism rather than building on it.

Each job now renders into <tmpfile>.<pid>.part and renames that to <tmpfile>.<pid>, and the signal handler reads only the newest job's file. A job that finishes late writes where nothing reads it, so it can neither be applied nor destroy a newer result. The generation counter is gone.

The repaint flag could leak into the next prompt. _tide_repaint tells the next render "this repaint is mine, do not start another render". Both handlers that set it can fire while a command is still running: a render landing, or a terminal resize. The flag then survived into the next prompt, whose render consumed it and started nothing, so the prompt drawn after the command showed the state from before it ran and stayed that way until the next command. A full-screen program is a good way to hit this, since it runs long and resizes the terminal.

Prompt cycles are now counted with fish_postexec and the cycle is stamped into the flag. A render still skips its dispatch right after a repaint it asked for itself, so no render loop, and repaints within a cycle still start a render, which is what keeps the prompt tracking $fish_bind_mode. A flag left over from an earlier cycle no longer matches.

Third commit: the transient render (--final-rendering) prints the prompt character and nothing else, so it never reads the rendered prompt. It no longer starts a background render — that was a fork spent on output that gets thrown away, and one more render in flight while the command ran.

Testing

Four new littlecheck assertions, all of which fail on main and pass here:

  • a superseded job publishing late must not hide or destroy the newest job's result
  • after a cycle boundary, the prompt must show the new command's status, for a flag left by a render landing and by a resize — on main both print the previous command's status, which is the bug
  • the transient render must not dispatch a job

Plus one guard for a bug found while writing this: deleting a superseded job's scratch file without killing that job first makes its mv fail and print to the terminal from a background process. Twenty overlapping dispatches must leave stderr empty.

Found by driving a real interactive fish 4.8.1 in a pty and logging every prompt render, dispatch and signal handler run. mise run lint and mise run test pass at each of the three commits.

Co-authored-by: Claude Opus 5 noreply@anthropic.com

plttn and others added 3 commits July 24, 2026 20:21
Every background render renamed its output over one shared file. When a
slow render outlived a faster one dispatched after it, the slow write
landed last and overwrote the fresh content. The generation stamp then
refused to apply what it read, so the prompt kept the content it already
had until the next command. The stamp stopped stale content being shown,
but not fresh content being overwritten.

Each job now renders into <tmpfile>.<pid>.part and renames that to
<tmpfile>.<pid>, and the signal handler reads only the newest job's
file. A job that finishes late writes where nothing reads it, so it can
neither be applied nor destroy a newer result, and the generation counter
has nothing left to do. Deleting the previous job's files moves into the
next job, off the interactive path.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
_tide_repaint tells the next prompt render "this repaint is mine, do not
start another render". Both handlers that set it can fire while a command
is still running: a background render landing, or a terminal resize. The
flag then survived into the next prompt, whose render consumed it and
started nothing, so the prompt drawn after the command showed the state
from before it ran and stayed that way until the next command. A
full-screen program is a good way to hit this, since it runs long and
resizes the terminal.

Count prompt cycles with fish_postexec and stamp the cycle into the flag.
A render still skips its dispatch right after a repaint it asked for
itself, so no render loop, and repaints within a cycle still start a
render as they did before, which is what keeps the prompt tracking
$fish_bind_mode. A flag left over from an earlier cycle no longer
matches, so the new prompt renders.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The transient render prints the prompt character and nothing else, so it
never reads the rendered prompt. Starting a background render for it spent
a fork on output that gets thrown away, and left one more render in flight
while the command ran. The prompt drawn after the command starts its own
render, so nothing is lost by skipping this one.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@plttn
plttn merged commit 936b533 into main Jul 25, 2026
7 checks passed
@plttn
plttn deleted the push-zrzvkskuysnr branch July 25, 2026 04:37
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.

1 participant