Skip to content

fix(web): blink the terminal cursor again - #5314

Merged
juliusmarminge merged 3 commits into
pingdotgg:mainfrom
StiensWout:t3code/terminal-cursor-blink
Aug 4, 2026
Merged

fix(web): blink the terminal cursor again#5314
juliusmarminge merged 3 commits into
pingdotgg:mainfrom
StiensWout:t3code/terminal-cursor-blink

Conversation

@StiensWout

@StiensWout StiensWout commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What

The web terminal's cursor stopped blinking when the renderer moved from xterm.js to libghostty-vt. This restores it.

Terminal cursor blinking again

Why it broke

The old renderer was constructed with cursorBlink: true, so every T3 terminal blinked regardless of what the program inside it wanted. libghostty-vt's built-in default is a steady cursor, and the Canvas renderer only toggles the cursor while the terminal reports cursorBlinking — that is, when a program turns blinking on through DECSCUSR (CSI 5/3/1 q) or DEC mode 12. A plain shell prompt does neither, so the cursor sat still.

What changed

  • GhosttyTerminalCore sets GHOSTTY_TERMINAL_OPT_DEFAULT_CURSOR_BLINK when it creates a terminal, so a session starts blinking and returns to blinking on CSI 0 q. Programs still own the cursor: a steady DECSCUSR style or CSI ?12l turns the blink off, and CSI ?12h turns it back on.
  • The option is applied again in resetAndWrite, because RIS restores Ghostty's built-in steady default. That is the path a reattach to an existing session takes, so without it a reconnected terminal came back steady.
  • The blink phase machinery in GhosttyTerminalSurface was already in place (500ms half-cycle, reset to the visible phase on write and focus). It now runs through one predicate, shouldBlinkTerminalCursor, which also holds the cursor steady when the reader prefers reduced motion, next to the existing unfocused and hidden-cursor cases.

Tests

  • runtimeAbi.test.ts exercises the real vendored WASM: the default is steady, option 23 makes it blink, CSI 2 q / CSI ?12l override it, CSI 0 q / CSI ?12h restore it, and RIS drops back to steady (the reason for the second apply).
  • renderer.test.ts covers the off phase repainting the cell without the inverted cursor glyph.
  • surface.test.ts covers the blink predicate.

Verified in a dev environment — the GIF above is the terminal drawer in that build.

Note

apps/mobile's Android and iOS terminal views gate on the same cursorBlinking flag, so they blink only when a program asks. Matching them to this default is a separate change, not included here.


Note

Low Risk
Terminal UI and embedder default cursor option only; no auth, data, or API surface changes beyond local terminal behavior.

Overview
Restores blinking cursor behavior in the Ghostty web terminal after the move from xterm.js, which always blinked, to libghostty-vt whose default is steady.

GhosttyTerminalCore now sets terminal option 23 (embedder default cursor blink) when a terminal is created and again after resetAndWrite, because RIS resets Ghostty’s built-in steady default—important for session reattach. Programs still override via DECSCUSR and DEC mode 12.

Blink scheduling in GhosttyTerminalSurface goes through shouldBlinkTerminalCursor, which also keeps the cursor steady when the surface is unfocused, the cursor is hidden, or prefers-reduced-motion: reduce is set; the surface listens for that media query and resets to the visible phase when it changes.

Tests cover WASM blink semantics, renderer off-phase repaint, and the blink predicate.

Reviewed by Cursor Bugbot for commit 0988f07. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Fix terminal cursor blinking with reduced-motion and focus awareness

  • Enables cursor blinking by default by setting Ghostty option 23 immediately after terminal creation and after any terminal reset.
  • Adds shouldBlinkTerminalCursor to centralize blink logic, requiring the surface to be focused, cursor to be visible and blinking, and prefers-reduced-motion to be false.
  • Watches the prefers-reduced-motion media query at runtime and restarts the blink cycle on change.
  • Replaces the hardcoded 500ms interval with CURSOR_BLINK_INTERVAL_MS and gates blink scheduling through the new blinkEnabled() helper.
  • After resetAndWrite, forces cursorOn=true so the cursor starts visible rather than mid-blink.

Macroscope summarized 0988f07.

The xterm.js renderer ran with `cursorBlink: true`, so a T3 terminal
always had a blinking cursor. libghostty-vt defaults to a steady cursor
and the Canvas renderer only toggles when the terminal itself asks for a
blink through DECSCUSR or DEC mode 12, which a plain shell never does, so
the blink disappeared with the switch.

Set the embedder default (GHOSTTY_TERMINAL_OPT_DEFAULT_CURSOR_BLINK) when
the core starts a terminal and again after RIS, which resets the mode back
to Ghostty's steady built-in. Programs that pick a cursor themselves still
win: a steady DECSCUSR style or DEC mode 12 reset turns the blink off.

The blink phase machinery already existed; it now also holds the cursor
steady for readers who prefer reduced motion, alongside the existing
unfocused and hidden-cursor cases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b5cdcc9e-5e4d-4d7c-ae9e-d774dbd126ce

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 4, 2026
Comment thread apps/web/src/terminal/ghostty/surface.ts
A steady reduced-motion cursor leaves no blink timer running, so reading
the media query at render time never saw the preference being dropped
until unrelated output or a focus change forced a render. Watch the query
and request a render on change, and drop the listener in dispose.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 40684b7. Configure here.

Comment thread apps/web/src/terminal/ghostty/surface.ts
resetAndWrite replays a session buffer the same way write streams output,
so it needs the same blink phase reset: a reattach that lands in the off
phase otherwise shows no cursor for up to half a cycle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@macroscopeapp

macroscopeapp Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Approved 0988f07

Bug fix to restore terminal cursor blinking, with comprehensive test coverage. The author owns these terminal files and the changes are self-contained with clear accessibility considerations (reduced motion support).

You can customize Macroscope's approvability policy. Learn more.

@juliusmarminge
juliusmarminge merged commit 11639bf into pingdotgg:main Aug 4, 2026
17 checks passed
github-actions Bot added a commit to omarcresp/t3code-flake that referenced this pull request Aug 4, 2026
## What's Changed
* fix(web): blink the terminal cursor again by @StiensWout in pingdotgg/t3code#5314
* fix(terminal): protect held Ctrl/Cmd+W close shortcut by @StiensWout in pingdotgg/t3code#5322
* fix(server): strip replayable terminal queries from history by @StiensWout in pingdotgg/t3code#5319
* fix(contracts): decode ServerProviders forward-compatibly by @Brechard in pingdotgg/t3code#5327
* fix(web): simplify chat code blocks by @t3-code[bot] in pingdotgg/t3code#5301
* fix(web): align multiline error alert controls by @t3-code[bot] in pingdotgg/t3code#5304
* Upgrade Effect to beta.103 by @juliusmarminge in pingdotgg/t3code#5331

## New Contributors
* @Brechard made their first contribution in pingdotgg/t3code#5327

**Full Changelog**: pingdotgg/t3code@v0.0.32-nightly.20260804.993...v0.0.32-nightly.20260804.997

Upstream release: https://github.com/pingdotgg/t3code/releases/tag/v0.0.32-nightly.20260804.997
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants