Skip to content

fix: land the selection-padding highlight on the dragged cells - #415

Open
Sma1lboy wants to merge 1 commit into
mainfrom
claude/keen-einstein-v7qb7s
Open

fix: land the selection-padding highlight on the dragged cells#415
Sma1lboy wants to merge 1 commit into
mainfrom
claude/keen-einstein-v7qb7s

Conversation

@Sma1lboy

Copy link
Copy Markdown
Owner

Direction

Bugs / correctness — a self-found rendering bug in the embedded terminal's grid selection, surfaced by a review pass over the pure/near-pure logic modules. (Open issues #362/#307/#259 were either awaiting an owner design decision or too broad for one tightly-scoped slice; this is a smaller, fully-verifiable fix.)

Problem

In terminal-selection.ts, overlayRowSpan paints a selection's inverse-video highlight over a row. Snapshot rows are trimmed, not grid-padded (xtermLineToChunks drops trailing blank cells), while the mouse column is clamped to the grid width — so a drag can anchor in the blank padding to the right of a short line, giving a span whose start column from is greater than the row's painted width col.

The trailing-padding branch computed the highlight width as to - Math.max(col, from) but appended it immediately after the painted content (at visual column col). When from > col, the block rendered at [col, col + (to - from)) instead of the intended [from, to) — the highlight was shifted left onto cells the user never dragged over, the gap [col, from) was wrongly inverted, and the row came out too short.

Concrete case (grid width 8, row "ab", select cols 5–7): expected inverse on cols 5/6/7 with cols 2–4 left plain; actual output inverted cols 2/3/4 and produced a 5-cell row. The copied text was already correct (extractSelection slices the trimmed text independently) — only the on-screen highlight was misplaced.

Fix

When the span starts past the painted cells, emit the from - col gap as plain spaces first, then the inverse block sized to - Math.max(col, from). When from <= col the gap is zero and the inverse block fills [col, to) exactly as before, so the common case is byte-identical.

Verification

  • Added a cell-exact test asserting the highlight lands on the selected padding cells (cols 5–7) with the gap (cols 2–4) left unhighlighted — this fails against the old code, which produced "ab " with the highlight on cols 2–4.
  • bun test test/tui/terminal-selection.test.ts → 13/13 pass.
  • bun run lint and bun run typecheck → both green.
  • Full test/tui/ suite: clean tree 842 pass / 43 fail vs. this branch 843 pass / 43 fail — exactly the one new passing test and zero new failures. (The 43 pre-existing failures are unrelated environment issues in the CI-style checkout — readonly process.platform / Bun-global reassignment in bun's test runner.)

Follow-ups deferred

  • The engine/foreground.ts custom-engine gate matches basename(argv0) against extraBin without the binaryName() normalization the builtin path uses (could false-negative on aider.exe), and handlers-inspect.ts filters sessions by bare startsWith(taskId) rather than `${taskId}::` — both are lower-confidence and left for a separate, scoped change.

Generated by Claude Code

A terminal selection anchored in the blank space right of a short line
painted its inverse-video padding starting at the line's text width, not
at the drag's start column, so the highlight sat too far left over cells
the user never selected. Emit the gap before the span as plain spaces and
the inverse block at the real start; the copied text was already correct.
@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
kobe-landing Ignored Ignored Aug 12, 2026 4:38pm

Request Review

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown

Claude finished @Sma1lboy's task in 3m 5s —— View job


Verdict: correct, minimal fix with a valid regression test — one house-rule attribution cleanup needed before merge.

I traced overlayRowSpan's new branch by hand and ran it through code-reviewer, silent-failure-hunter, pr-test-analyzer, type-design-analyzer, comment-analyzer, and code-simplifier. All confirm the arithmetic is sound: when from > col, the emitted row is col + (from-col) + (to-from) = to with the inverse block landing exactly on [from, to) (previously it landed on [col, col+(to-from)), shifted left by from-col — the reported bug). When from <= col, gap is 0 and the output is byte-identical to the old path, so the common case is unaffected.

Blocking

  • AI attribution in the PR body and commit author, violating CLAUDE.md's non-negotiable rule ("NEVER add ... 'Generated with Claude Code' footers"). The PR description ends with _Generated by [Claude Code](https://claude.ai/code/session_...)_, and the landed commit (9698253) is authored as Claude <noreply@anthropic.com> rather than a human identity. Neither should carry AI attribution per the repo's hard rules — please strip the footer from the PR body before merge, and if the PR is squash-merged, make sure the squash commit doesn't inherit the author/footer.

Should-fix

None — the fix itself, its scope, and its test coverage are sound.

Nits

  • terminal-selection.ts:141-147 — the comment is ~7 lines for a 4-line arithmetic change; code-reviewer and code-simplifier both flagged it as more verbose than the fix needs (2-3 sentences would cover the what/why).
  • terminal-selection.ts:148-152gap and Math.max(col, from) re-derive the same from > col comparison twice; code-simplifier's suggested rewrite (gap = Math.max(0, from - col), then to - col - gap for the inverse length) is equivalent and avoids the duplication, optional.
  • test/tui/terminal-selection.test.ts:91expect(displayWidth(...)).toBe(8) is redundant with the string-equality assertion on the line above ("ab " is already 8 cells of plain ASCII).

Other checks (all clean)

  • Regression test: pr-test-analyzer confirmed the new case (test/tui/terminal-selection.test.ts:81-102) fails against pre-fix code (would produce a 5-cell row with no plain gap) and passes post-fix — a genuine pin, not implementation-mirroring.
  • File size cap: terminal-selection.ts is 172 lines, well under the 500-line cap.
  • Changeset: present, correctly scoped patch, matches the PR's stated intent.
  • Silent failures / invariants: no swallowed errors; rowSpan guarantees from < to so no negative-width .repeat() calls; gap is guarded by gap > 0.
  • Type design / comments: no new types needed for this logic-only change; the (verbose) explanatory comments accurately describe the code, no drift.
  • Not applicable to this diff: keybindings, flex layout, engine-owned UI data.

Sma1lboy added a commit that referenced this pull request Aug 27, 2026
…415 and #373 (#588)

* fix(terminal): control chars are zero cells and nothing re-floors charWidth

Fold PRs #500 and #384 into one change: charWidth now treats C0/DEL/C1
controls as non-printing (zero cells), and terminal-render drops its three
charWidth(...) || 1 floors so zero-width marks fold onto their base cell.
Landed together because each PR's safety argument depended on the other
half's absence — the merged invariant (charWidth alone decides cell math,
no consumer re-floors it) is pinned by a test that fails under either half
alone.

* fix(terminal): start the padding-selection highlight where the drag began

A drag anchored in the blank padding right of a short trimmed row painted
its inverse block from the row's painted width instead of from the anchor
column — display only; the extracted text was already correct. Emit the
gap as plain spaces before the inverse block. Re-lands PR #415 on current
main.

* fix(web): truncate tailPath by code point, not UTF-16 unit

A cut landing mid-surrogate-pair bisected an emoji / astral char and the
dashboard rendered a replacement glyph. Mirror the TUI's truncateStart
(kobe-web can't import it — its only workspace dep is the daemon package),
so the seam never splits a character and code-point-count budgets stop
clipping paths that already fit. Re-lands PR #373 on current main.
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