Skip to content

[Bug]: Terminal cursor leaves top and bottom edges visible during blink off phase #11402

Description

@nbrosei

Before submitting

  • I searched existing issues and did not find an exact duplicate.
  • I included enough detail to investigate the problem.

Area

apps/desktop (integrated terminal rendered by apps/web)

Steps to reproduce

  1. Open the integrated terminal with a blinking cursor.
  2. Leave the shell idle at a prompt.
  3. Observe the cursor during the blink's off phase.

The reporter's screenshot shows a light theme and an idle % shell prompt after git pull completed. The command is context, not an established prerequisite.

Expected behavior

The cursor disappears completely during the off phase, leaving the terminal background and underlying text intact.

Actual behavior

Two faint horizontal lines remain at the top and bottom of the cursor cell when the cursor is off. The screenshot supplied by the reporter clearly shows these lines in the otherwise blank cell immediately after the prompt.

Impact

Cosmetic issue.

Version or commit

T3 Code desktop 0.0.40 (09e8de9c655a).

Environment

T3 Code desktop on macOS, light theme. Exact macOS version, display scale, and zoom level were not supplied.

Investigation status

The artifact is visible in the reporter's screenshot; the blinking behavior is reporter-observed. No independent interactive reproduction or root cause has been established.

Relevant rendering code is apps/web/src/terminal/ghostty/renderer.ts, with cursor repaint coverage in renderer.test.ts. Existing coverage uses a mocked canvas and does not establish that no residual pixels remain after an on/off cycle on a real canvas.

Searched open and closed issues for cursor blink, terminal border, and cursor terminal. #1939 concerns cursor positioning, and #7644 concerns stale/garbled terminal output; neither specifically describes residual top/bottom cursor edges during blinking.

Workaround

None verified.

Activity

  1. nbrosei commented on Sep 12, 2026

    @nbrosei
    Author
    Image
  2. juliusmarminge commented on Sep 12, 2026

    @juliusmarminge
    Member

    Triage

    Confirmed as a cosmetic renderer bug in the shared Ghostty canvas terminal (web + desktop). Not a duplicate of #1939 (cursor position) or #7644 (stale/garbled output).

    The screenshot shows two faint horizontal lines in the blank cell after an idle % prompt during the blink off phase (light theme). That matches the focused default block cursor (cursorStyle === 1), not the unfocused hollow strokeRect (unfocused cursors do not blink).

    Likely cause

    renderGhosttySnapshot in apps/web/src/terminal/ghostty/renderer.ts already repaints the cursor row on blink-off and skips the overlay (renderer.test.ts only asserts fillText on a mock context). The row is cleared with the same fillRect geometry as the block cursor, using source-over.

    Cell height is integer, but row top is originY + row * height. Once scrollback exists, terminalContentOriginY in apps/web/src/terminal/ghostty/surface.ts adds leftover viewport slack from clientHeight, which is often fractional (the reporter’s screenshot has output above the prompt, so this path is active). Non-integer devicePixelRatio / zoom does the same.

    The cursor’s top/bottom antialiased edges are the row’s top/bottom edges. A later background fillRect blends over those pixels instead of replacing them, leaving a faint cursor-colored fringe. Left/right cursor edges sit inside the row rect and get fully overwritten — which is why only the top and bottom remain.

    Blinking itself was restored in #5314; this looks like the off-phase erase gap that followed.

    Mobile’s native Ghostty surface does not use this canvas path.

    Suggested fix

    In renderGhosttySnapshot, snap row/cursor Y to the device pixel grid, or clear the dirty row with a 1 device-pixel vertical inflate / globalCompositeOperation = "copy" so the off phase replaces the previous cursor fringe. Cover it with an offscreen-canvas test at a fractional originY: paint cursor on, then off, and assert the cursor cell’s first and last pixel rows match the background.

    No further reporter info required to implement. macOS version / display scale / zoom would only help confirm DPR, not block the fix.

  3. added
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions