Repository navigation
[Bug] Progress bar visibly flickers in Ptyxis 50.1 but not in Zed terminal #613
Description
Activity
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 😬
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 direct target writes: 15 writes containing erase-to-end: 4 erase writes with no replacement text: 4 first erase write: "ESC [ J" Batched adapter target writes: 6 writes containing erase-to-end: 4 erase writes with no replacement text: 0 first erase write: "ESC [ 1 G ESC [ J ◐ ━━━… Translating [0s]"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:
import { Writable } from 'node:stream' function createFrameOutput(target) { let pending = '' let scheduled = false function flush() { scheduled = false if (!pending) return const frame = pending pending = '' target.write(frame) } const output = Object.assign( new Writable({ write(chunk, _encoding, done) { pending += chunk.toString() if (!scheduled) { scheduled = true queueMicrotask(flush) } done() }, }), { isTTY: target.isTTY, columns: target.columns }, ) return { output, flush } }
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?
@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 :)
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.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsNeeds triage
Environment
v24.13.0@clack/prompts1.8.150.14.22.5(compiled against4.22.4)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.1Save the following as
repro.mjs:Run in Ptyxis with a terminal at least 80 columns wide, without piping or redirecting output:
Compare the same commands in Zed's integrated terminal.
--stderrExpected behavior
The progress animation updates without a visibly blank interval between frames.