Skip to content

fix(llm): inline-chat paint CUP-restores cursor to shell row#79

Merged
fentas merged 4 commits into
masterfrom
fix/chat-paint-cursor-restore
May 18, 2026
Merged

fix(llm): inline-chat paint CUP-restores cursor to shell row#79
fentas merged 4 commits into
masterfrom
fix/chat-paint-cursor-restore

Conversation

@fentas
Copy link
Copy Markdown
Owner

@fentas fentas commented May 18, 2026

Summary

Fixes the user-reported "Alt+C → Alt+C → Enter moves the prompt down to where the chat input was" bug. The Alt+C open-paint left the real terminal cursor parked at the chat input row, so bash's echo of any subsequent byte landed AT the input row instead of the shell prompt.

Why the previous DECSC/DECRC pair didn't catch this

  • The open-paint emits DECSC (\x1B[s) at its start.
  • The proxy's applyReserveRows then runs its OWN DECSC/DECRC pair around the row-erase, OVERWRITING the open-time slot.
  • After applyReserveRows, bare DECRC restores to the cursor's position WHEN applyReserveRows ran — wherever the chat panel last left it (the input row), NOT the shell prompt.

PR #78 fixed the close path with CUP to shell_bottom. This PR fixes the missing other half: the open path's CUP-restore.

Approach

  • Runtime.chat_open_cursor_row: snapshot of ctx.cursor_row taken when the toggle OPENS the panel.
  • inlineRestoreRow(rt, total_rows, base_reserve) helper: returns the snapshot, falling back to shell_bottom (last row of the scroll region) when the snapshot is unknown or overshoots the shell area (defensive against cursor_tracker drift / SIGWINCH races).
  • paintInlineChat OPEN + CLOSE paths both end with an explicit CUP \x1B[<row>;1H — survives any DECSC overwrites upstream.

Test plan

  • zig build test -Dtarget=x86_64-linux-gnu — 514/514 pass
  • zig fmt --check src/ build.zig — clean
  • Existing toggle paint test extended to pin the open-paint's CUP-to-row-21 fallback
  • New test: ctx.cursor_row=8 → open + close paint both CUP to row 8 (not the fallback)
  • New test: bogus snapshot row 23 (inside reservation) clamps to 21 (defensive)
  • Manual: Alt+C → Alt+C → Enter — prompt should NOT jump down to the input row
  • Manual: Alt+C while at row 8 → cursor returns to row 8 on close, not row 21

🤖 Generated with Claude Code

The Alt+C open-paint left the real terminal cursor parked at the chat
input row (right after the block-cursor glyph), so bash's echo of any
subsequent byte — typed char, prompt redraw, exec injection — landed
AT the chat input row. Observable as "command shows up in the chat
panel AND in the shell prompt" and "Alt+C, Alt+C, Enter moves the
prompt down to where the chat input was".

Why the previous DECSC/DECRC pair didn't catch this:
- The open-paint emits DECSC (`\x1B[s`) at its start.
- The proxy's `applyReserveRows` then runs its OWN DECSC/DECRC pair
  around the row-erase, OVERWRITING the open-time slot.
- After applyReserveRows, the slot points at wherever the chat panel
  last left the cursor — i.e. the input row — not the shell prompt.

Fix:
- Runtime.chat_open_cursor_row: snapshot of `ctx.cursor_row` taken
  when the toggle opens the panel.
- inlineRestoreRow helper: returns the snapshot or shell_bottom
  (last row of the scroll region) as a clamped fallback.
- paintInlineChat OPEN + CLOSE paths both end with an explicit CUP
  `\x1B[<row>;1H` to the helper's row — survives any DECSC overwrites
  upstream of us.

Tests:
- Existing toggle test now also pins the open-paint's CUP-to-row-21
  fallback (terminal_rows=24, base_reserve=3, no cursor snapshot).
- New test: `ctx.cursor_row=8` → open + close both CUP to row 8.
- New test: bogus snapshot row 23 (inside reservation) clamps to 21.

514/514 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes the "Alt+C → Alt+C → Enter moves the prompt down" bug by ensuring the inline chat paint always ends with an explicit CUP back to the shell prompt row, rather than leaving the real terminal cursor at the chat input row where bare DECRC could be invalidated by the proxy's applyReserveRows DECSC/DECRC pair.

Changes:

  • Add Runtime.chat_open_cursor_row snapshot captured from ctx.cursor_row when Alt+C opens the panel.
  • Introduce inlineRestoreRow helper with defensive clamp to shell_bottom; emit explicit CUP at end of both open and close paint paths.
  • Extend toggle paint test and add two new tests (snapshot-based restore + clamp-on-overshoot).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

… test

- Reframe `chat_open_cursor_row` doc + paint comments as state
  invariants, not historical fix narrative (CLAUDE.md "WHY only,
  never reference the current task/fix/callers").
- Add test pinning that re-open with `ctx.cursor_row = null` falls
  back to shell_bottom (not the stale snapshot from the previous
  open). The open branch already does `orelse 0` — this just locks
  the invariant in.
- 515/515 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

- chat_open_cursor_row doc: dropped misleading "Reset on close" line
  (the field is intentionally preserved through close so the close
  paint can target it). Reframe as "Intentionally preserved through
  close; the next open overwrites unconditionally."
- inlineRestoreRow helper: add a one-line note on why it anchors to
  `total_rows - base_reserve` (stable across panel open/close) — DO
  NOT switch to live_reserve.
- New test: re-open at a DIFFERENT non-null cursor_row overwrites
  the previous snapshot and the paint uses the new row. Symmetric
  to the null-cursor_row test.
- New test: live ctx.cursor_row drift between paints does NOT shift
  the closing CUP — uses std.mem.endsWith to check the final CUP
  precisely (an internal panel CUP can incidentally hit any row
  inside the reservation; only the trailing bytes matter).
- 517/517 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

The field doc + helper doc already explain why DECSC is unreliable
upstream. The paint-site blocks were paraphrasing the same story;
collapse them to one-liners pointing at inlineRestoreRow so the
canonical explanation lives in one place.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@fentas
Copy link
Copy Markdown
Owner Author

fentas commented May 18, 2026

Review loop complete — 3 rounds (Copilot + subagent, parallel).

Round 1 (subagent): comment scrub for CLAUDE.md "WHY only", added re-open-with-null test, deferred SIGWINCH staleness (rare, clamp covers common case). Copilot: 0 comments.
Round 2 (subagent): fixed a docstring lie introduced in round 1 ("Reset on close" — actually preserved through close so close paint can target it), added two more tests (re-open at different non-null row; live cursor drift during open). Copilot: 0 comments.
Round 3 (subagent): verdict ship — no remaining signal. Trimmed paraphrased paint-site comments to point at the canonical helper doc. Copilot: 0 comments.

Stopping condition met (zero new Copilot comments for 3 consecutive rounds, subagent ship verdict). Final state: 517/517 tests pass, 5 new tests pin the snapshot invariants. Manual verification items added to issue #80. Ready for squash-merge.

@fentas fentas merged commit a7fbc74 into master May 18, 2026
3 checks passed
@fentas fentas deleted the fix/chat-paint-cursor-restore branch May 18, 2026 08:19
@github-actions github-actions Bot mentioned this pull request May 19, 2026
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