Skip to content

Suppress block cursor during post-input repaint catch-up#3

Open
imakris wants to merge 3 commits into
masterfrom
claude/cursor-render-glitch-0o60un
Open

Suppress block cursor during post-input repaint catch-up#3
imakris wants to merge 3 commits into
masterfrom
claude/cursor-render-glitch-0o60un

Conversation

@imakris

@imakris imakris commented Jun 28, 2026

Copy link
Copy Markdown
Member

Summary

Fixes a visual artifact where the block cursor momentarily inverts a freshly typed character during the intermediate repaint frame after text input acceptance, before the application's cursor movement settles the caret position.

Problem

With remote echo and per-segment snapshot publication, a render snapshot can arrive carrying the just-typed glyph while the application has not yet emitted the cursor movement command that advances the caret past that glyph. When rendering this intermediate snapshot with a block cursor, the cursor sits at or behind the pre-input column and inverts the freshly typed character, creating a brief visual glitch before the next segment corrects it.

Content freshness tokens alone cannot distinguish this intermediate frame from a settled state, since the snapshot is content-fresh but not yet strictly cursor-settled.

Changes

  • Added cursor position tracking: Store the pre-input cursor position (from the pre-echo snapshot) when text input is accepted, along with a flag indicating whether this position is available.

  • Implemented cursor suppression logic: Added detection in should_suppress_cursor_for_render_snapshot() to identify the exact intermediate frame:

    • Forward-advancing printable input is in flight
    • Snapshot is content-fresh but not strictly settled
    • Block cursor is visible and positioned at or before the pre-input column on the same row
    • The pre-input column contains a non-space glyph (confirming the echo has landed)
  • Selective suppression: Only block-shaped cursors are suppressed; bar and underline cursors are unaffected since they do not invert glyphs.

Implementation Details

The suppression is narrowly scoped to a single frame—the settled snapshot one segment later will render the cursor at the advanced position. This avoids cursor blink artifacts while maintaining correct cursor visibility in all other states.

https://claude.ai/code/session_012xHtQ5MMfYzuXZzcjCzMWn

claude added 3 commits June 28, 2026 19:25
The block cursor could momentarily draw at, or behind, the column the
user last saw - inverting the character they just typed - for a single
frame while a TUI (e.g. codex, claude) repaints under a concurrent
animation, then snap to the correct position a frame later.

Root cause: rendering is pure remote echo (no local prediction) and one
render snapshot is published per backend output segment. A snapshot can
therefore capture the child's cursor at an intermediate point of a
repaint - the just-typed glyph has landed but the cursor-settling
sequence has not yet been emitted. The existing input-freshness machinery
gates frames on whether the typed content is present; content freshness
does not imply cursor settledness, so it cannot rule out this frame. The
next segment's snapshot carries the settled cursor and the glitch clears,
which is why it is one frame and worse the more the application repaints.

Fix: add cursor-risk suppression in the post-input catch-up window. When
forward-advancing printable input is in flight and a content-fresh but
not-yet-strictly-settled snapshot shows a block cursor on the pre-input
row at or before the pre-input column with the typed glyph already landed
there, suppress the block cursor for that one frame and drain toward the
settled snapshot. The window is bounded by the strict freshness token, so
the cursor reappears as soon as the caret advances or the frame settles.
Bar and underline cursors are left untouched; only the block shape
inverts the glyph.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xHtQ5MMfYzuXZzcjCzMWn
The "Install package" step ran `cmake --install` on the main build, which
links vnm_msdf_text from source via FetchContent. That configuration is
intentionally non-installable: the MSDF install guard in CMakeLists.txt
fails closed (FATAL_ERROR) so a source-built dependency is never packaged
into a consumer config whose `find_dependency(vnm_msdf_text)` would be
unsatisfiable. The guard has been tripping the install step on every run
since it landed, independent of any product change.

Install and consume a dedicated MSDF-disabled library build instead.
MSDF-off is a first-class, supported package configuration: the renderer
guards the MSDF path behind VNM_TERMINAL_MSDF_TEXT_RENDERER_ENABLED, the
generated config records the flag, and tests/package_smoke already branches
on it (only requiring the vnm_msdf_text dependency when MSDF is enabled).
This validates the real install/export and package-consume mechanics
without the by-design guard. An MSDF-enabled package remains a distribution
build that supplies a packaged vnm_msdf_text via
-DVNM_TERMINAL_MSDF_TEXT_RENDERER_USE_SYSTEM_LIBS=ON.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xHtQ5MMfYzuXZzcjCzMWn
The text-renderer policy, LCD-order, and render-summary helpers
(qsg_atlas_text_renderer_policy_requires_msdf,
qsg_atlas_text_renderer_policy_allows_fallback,
qsg_atlas_effective_text_renderer,
qsg_atlas_lcd_subpixel_order_uses_lcd_sampling, and
qsg_atlas_update_text_renderer_summary) were defined inside the
VNM_TERMINAL_MSDF_TEXT_RENDERER_ENABLED guard but called from unguarded
code and from the non-MSDF (#else) branches. With the MSDF renderer
disabled they were undeclared, so the glyph-only configuration of the
library did not compile. CI never exercised that configuration, so the
breakage stayed dormant.

These helpers only classify policy/subpixel-order enums and fold per-frame
draw counters into the render summary; none reference the MSDF text path.
Close the MSDF guard after the two genuinely MSDF-dependent run-candidate
predicates and leave the MSDF-independent helpers always compiled. No
behavior change for the MSDF-enabled build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012xHtQ5MMfYzuXZzcjCzMWn
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