fix(cli): fix cursor left-move stalling at hard-wrapped line boundary#4852
Conversation
… in text buffer When a single logical line is hard-wrapped across multiple visual lines, pressing left-arrow at the start of a wrapped visual row could land on a position that mapped back to the same logical cursor, making the cursor appear stuck. Detect this case and step one column further left so the visual cursor actually advances.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅ Downgraded from Approve to Comment: CI still running.
The fix correctly handles the hard-wrap boundary stalling case: when moving left from the start of a wrapped visual row lands on the same logical position, the extra decrement ensures the visual cursor progresses. The newVisualCol > 0 guard properly prevents going negative, and the downstream visualToLogicalMap conversion (lines 1450-1461) ensures the logical position stays consistent. Test coverage for the specific scenario is solid.
— qwen3.7-max via Qwen Code /review
|
Thanks for the PR! Template looks good ✓ On direction: this is a straightforward cursor navigation bug fix — pressing left-arrow at a hard-wrapped line boundary causes the visual cursor to stall because the move lands on a position that maps back to the same logical cursor. This is a real, user-facing navigation regression in narrow text inputs (like the ask-user-question prompt). Clearly within scope. Claude Code's CHANGELOG has similar fixes: "Fixed the cursor sticking at the end of the first line when typing a multiline prompt" and "Fixed a stray leading space on wrapped lines when the previous line ended exactly at the terminal width" — so this area is actively on the radar for text editing UX. On approach: the scope is right — 12 lines of production code adding a single conditional branch in the existing left-move case, plus a focused unit test. The fix detects when moving to the previous visual row would land on the same logical position and steps one column further left. Minimal, surgical, no scope creep. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ 方向:这是一个明确的光标导航 bug 修复——在硬换行边界按左箭头会导致视觉光标卡住,因为移动后的位置映射回了相同的逻辑光标位置。这是窄文本输入框(如 ask-user-question 提示)中的真实用户导航回退问题,完全在项目范围内。Claude Code 的 CHANGELOG 也有类似修复:"修复了多行提示输入时在第一行末尾光标卡住的问题"以及"修复了上一行恰好在终端宽度处结束时换行行出现多余前导空格的问题"——说明文本编辑 UX 是活跃关注领域。 方案:范围合理——12 行生产代码,在现有左移分支中添加一个条件判断,加上一个针对性的单元测试。修复检测移到上一视觉行是否会落在相同逻辑位置,如果是则再向左多移一列。最小化、精准、无范围蔓延。 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
DragonnZhang
left a comment
There was a problem hiding this comment.
No issues found. Downgraded from Approve to Comment: self-PR; CI failing: triage. The fix correctly handles the cursor stall at hard-wrapped line boundaries with a minimal conditional branch. The logic properly detects when moving left across a visual row boundary lands on the same logical position and steps one column further. Test coverage is adequate. — claude-sonnet-4-20250514 via Qwen Code /review
|
@DragonnZhang The code looks good — small, clean change with clear logic. 👍 Since this bug is about the cursor visually stalling at a hard-wrapped line boundary, unit tests can verify the logic is correct, but they can't convey the actual user-facing experience. Could you share a before / after screenshot or screen recording? Just run Once we can see the cursor no longer stalls, we'll push this toward merge. 中文@DragonnZhang 代码看了,改动很小很干净,思路也对。 不过这个 bug 本质是光标在视觉上"卡住不动",单测能证明逻辑对了,但没办法让人直观感受到修好了。能不能跑一下 before / after 截个图贴上来?就 看到效果没问题我们就推合并。 |
|
@pomelo-nwu Thanks for your advice. I have added screen recordings of before and after the change. |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI failing: triage. — qwen3.7-max via Qwen Code /review
Local runtime verification report (Linux)Built this branch ( Unit tests
E2E: real keystrokes in the TUISetup: typed a 90-char digit string into the input prompt; at 44 columns it hard-wraps into 3 visual rows of 33 chars (wrap boundaries at logical cols 33 and 66), then pressed ← 60 times, sampling the terminal cursor after each press.
Additional checks on the PR build, same session:
Code-level notesThe fix is tightly scoped: it only adjusts when the landing position maps back to the identical logical cursor (which is precisely the hard-wrap no-op case, since soft wrap consumes characters and shifts the continuation row's start col), in code-point units consistent with the rest of the reducer, with a CI contextAll real checks pass (ubuntu/macos/windows tests, Lint, CodeQL). The failing ConclusionBug reproduced on baseline exactly as described (permanent stall at the hard-wrap boundary in a real terminal), fix verified end-to-end keystroke-by-keystroke, reverse direction and soft-wrap behavior unaffected, unit suite green with the new test pinning the regression. LGTM from the runtime-verification side. |
Address review: the left-arrow stall fix relies on an implicit invariant in calculateVisualCursorFromLayout (a strict `logicalCol < nextStartColInLogical` segment selection) that places a boundary cursor at the start of the next visual row, which is why `case 'right'` has no symmetric stall. Document that coupling next to `case 'right'` so a future change to the segment selection doesn't silently reintroduce a mirror-image right-movement stall. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
DragonnZhang
left a comment
There was a problem hiding this comment.
Incremental review of 34883942c..53504c0a4 (1 new commit):
docs(cli): explain why right-movement needs no hard-wrap stall-fix — Adds an 8-line comment to the case 'right': branch documenting the calculateVisualCursorFromLayout invariant (strict < in segment selection) that prevents a mirror-image stall. This directly addresses @wenshao's suggestion on the previous round. The comment is accurate: I verified the findIndex predicate at ~L1109 uses logicalCol < nextStartColInLogical, so a boundary cursor always resolves to the start of the next visual row, making a right-move fix unnecessary. The caveat about < → <= is a useful guard for future refactors.
No new logic changes. Core fix and test from the previous round remain sound.
— qwen3-coder via Qwen Code /review
Local runtime verification reportVerified this fix three ways: the unit suite on this branch, a counterfactual run of the new test against unfixed 1. Real-terminal A/B trace (the headline check)Typed the same 40-char single-line input (
The stall reported in the PR reproduces exactly on Also empirically confirmed the invariant documented in the new Raw tracesHarness: 2. Unit tests + counterfactual
3. windows-latest CI failure — unrelated, will clear on re-runThe failing job is Review notesThe new branch is tightly scoped: it only fires when the post-move visual position maps back to the same logical Verdict: bug reproduced on |
|
LGTM. The fix correctly detects the hard-wrap boundary stall case where moving the visual cursor up one row maps back to the same logical position, and steps one column further left. The guard conditions ( |
#4852 only changes text-buffer.ts; merging current main brings in the corrected scripts/dev.js (shell:false when local tsx cli.mjs exists) so scripts/tests/dev.test.js passes on Windows.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅ — qwen3.7-max via Qwen Code /review
What this PR does
Fixes a bug in the text buffer where pressing the left-arrow key at the start of a hard-wrapped visual row causes the cursor to appear stuck. This happens when a single logical line (e.g. a long input in the ask-user-question dialog) wraps across multiple visual rows — moving the visual cursor up one row lands on a position that maps back to the same logical cursor, so the cursor doesn't actually advance. The fix detects this case and steps one column further left so the visual cursor progresses correctly.
Before:

After:

Why it's needed
Users editing long text in narrow input fields (such as the ask-user-question prompt) cannot navigate leftward past a hard-wrapped line boundary with the arrow keys, making it feel like the keyboard is unresponsive. This is a basic navigation regression that blocks text editing in constrained viewports.
Reviewer Test Plan
How to verify
npm run devand trigger an ask-user-question prompt (or any text buffer with a narrow viewport).cd packages/cli && npx vitest run src/ui/components/shared/text-buffer.test.ts— all 153 tests should pass, including the new "moves left across a hard-wrapped single-line boundary" test.Evidence (Before & After)
Before: pressing left-arrow at the hard-wrap boundary leaves the cursor visually stuck at the start of the wrapped row.
After: the cursor advances one column further left onto the previous visual row, matching user expectation.
Tested on
Environment (optional)
npm run devwith sandbox disabled. Unit tests via vitest.Risk & Scope
Linked Issues
N/A
🤖 Generated with Qwen Code
中文说明
此 PR 做了什么
修复了文本缓冲区中的一个 bug:当在硬换行(hard-wrapped)的视觉行起始处按左箭头键时,光标会卡住不动。这发生在单条逻辑行(例如 ask-user-question 对话框中的长输入)跨越多行视觉行显示时——将视觉光标上移一行后落到的位置映射回相同的逻辑光标位置,导致光标实际并未前进。修复方案检测到该情况后再向左多移动一列,使视觉光标能正确前进。
为什么需要它
用户在窄输入框(如 ask-user-question 提示框)中编辑长文本时,无法用方向键向左越过硬换行边界,给人一种键盘无响应的感觉。这是一个基本的导航回退问题,会阻塞受限视口中的文本编辑。
审阅者测试计划
如何验证
npm run dev,触发 ask-user-question 提示(或任何窄视口的文本缓冲区)。cd packages/cli && npx vitest run src/ui/components/shared/text-buffer.test.ts— 全部 153 个测试应通过,包括新增的"moves left across a hard-wrapped single-line boundary"测试。证据(修复前 vs 修复后)
修复前:在硬换行边界处按左箭头键,光标在换行行的行首处视觉上卡住。
修复后:光标向前一行再向左多进一列,符合用户预期。
测试环境
环境(可选)
使用
npm run dev并禁用沙箱。单元测试通过 vitest 运行。风险与范围
关联的 Issues
无