[Bug] Progress bar visibly flickers in Ptyxis 50.1 but not in Zed terminal #613
Description
Environment
- OS: Linux
- Node.js:
v24.13.0 - Package:
@clack/prompts - Package version:
1.8.1 - Affected terminal: Ptyxis
50.1 - GTK:
4.22.5(compiled against4.22.4) - VTE:
0.84.1(compiled against0.84.0)
Describe the bug
The entire progress line briefly disappears and reappears during animation in Ptyxis, producing visible rapid flickering/strobing. This is not just the spinner changing frames, repeated output, or the line moving onto another row.
The same standalone script does not visibly flicker in Zed's integrated terminal. Both stdout and stderr exhibit the issue in Ptyxis, while neither visibly flickers in Zed.
To reproduce
This reproduction uses only Clack, with short messages and no network requests during execution.
-
Create a standalone project:
mkdir clack-repro cd clack-repro npm init -y npm install @clack/prompts@1.8.1 -
Save the following as
repro.mjs:import { progress } from '@clack/prompts' const bar = progress({ max: 5, indicator: 'timer', withGuide: false, ...(process.argv.includes('--stderr') ? { output: process.stderr } : {}), }) bar.start('Translating') try { for (let completed = 1; completed <= 5; completed++) { await new Promise((resolve) => setTimeout(resolve, 2000)) bar.advance(1, `Translated ${completed}/5`) } bar.stop('Done') } finally { bar.clear() }
-
Run in Ptyxis with a terminal at least 80 columns wide, without piping or redirecting output:
node repro.mjs node repro.mjs --stderr
-
Compare the same commands in Zed's integrated terminal.
| Terminal | Default stdout | --stderr |
|---|---|---|
| Ptyxis 50.1 | Visible flickering | Visible flickering |
| Zed integrated terminal | No visible flickering | No visible flickering |
Expected behavior
The progress animation updates without a visibly blank interval between frames.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNeeds triage
Activity
43081j commentedon Oct 3, 2026
Very much sounds like a ptyxis problem rather than us.
You could try record it with ansivision. That way you can see the exact frames that rendered. If there are no empty frames (pretty sure there won't be), I'm not sure what we can do 😬
thomasaull commentedon Oct 7, 2026
I repeated the suggested test with
ansivision@0.4.0and@clack/prompts@1.8.1. I captured the exact reproduction through an 80-column pseudo-terminal, which is a software terminal.The original output contained 22,411 bytes and 124 erase-to-end sequences. Ansivision rendered 125 frames and found no empty frames.
I also captured the version with our local batching adapter. It produced the same number of bytes, erase sequences, and frames. The two captures were byte-for-byte identical, and
cmpreported no difference.Ansivision does not model the boundaries between
write()calls. Therefore, it cannot show the short blank state that Ptyxis displays between two calls.A small two-step probe recorded these boundaries:
Clack sends cursor movement, erase, replacement text, and final cursor movement through separate synchronous
write()calls. Ptyxis seems to draw after the erase call and before the replacement-text call.Our adapter collected the synchronous writes during one microtask. A microtask is work that runs after the current code. The adapter then sent each redraw through one
write()call. This change significantly reduced the visible flicker in our Ptyxis test.The local adapter was:
This change seems feasible in Clack core.
render()can assemble each complete update and send it through oneoutput.write(...)call.restoreCursor()must return its cursor sequence instead of writing it directly.The output bytes stay the same, so tests can compare the bytes and the number of writes. A single write is clearer than
cork()anduncork(), which do not guarantee one terminal write.Would this change be acceptable in Clack core?
thomasaull commentedon Oct 7, 2026
@43081j Sorry the previous comment was my agent rambling about it, I personally don’t know much about the internals and the actual issues. It (the agent) is confident the propsed fix might be feasible, but I for myself cannot say anything of value to that.
So feel free to take the suggestion or close the issue :)
dreyfus92 commentedon Oct 9, 2026
hey @thomasaull thanks for reporting this 😄. i agree with james that this looks like ptyxis painting partial output. the sequence we write per frame
ESC[1GthenESC[Jthen the new text, is the same thing ora does, and the batching adapter only reduced the flicker rather than removing it, so the terminal is still the main factor. might be worth reporting to ptyxis/vte, especially if other spinners like ora flicker too.and what your agent suggested feels like a hardening/partial fix. it also targets core
render(), but the progress bar is built on the spinner, which writes on its own, so that change wouldn't touch this flicker. if we harden it'd be in the spinner.