When cycling through search results with arrow keys, the caret gets moved #171

Closed
opened 2026-08-26 22:54:54 +00:00 by Grandiras · 2 comments
Owner
No description provided.
Grandiras changed title from When cycling through search results with arrow keys, the carot gets moved to When cycling through search results with arrow keys, the caret gets moved 2026-08-26 23:19:51 +00:00
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/400); the reply will link the full session transcript. <!-- claude-run:comment --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-171/run-400.md) — updating every ~5 minutes while the task runs.
Collaborator

Pushed to claude/issue-171.

What was wrong

The search panel's up/down keys are handled in C# (SearchBox.OnKeyDown), and nothing ever called preventDefault on them. So the browser did what an arrow means in a text field as well as moving the active row: ArrowDown threw the caret to the end of the query, ArrowUp to the start. Look down the results, go back to fix a letter, and the correction lands in the wrong place.

Why the fix is in JS

A Blazor handler can't decide preventDefault per key — @onkeydown:preventDefault is read at render time, so the flag would have to be set before the keystroke it applies to, and it suppresses every key in the field, letters included. So the state stays exactly where it was and only the key's browser meaning moves:

  • search-interop.js is now handed the input element along with the DotNetObjectReference, and calls preventDefault() on ArrowUp/ArrowDown aimed at that field. Blazor's own handler still runs — the caret moves after the event, not during it.
  • It takes the key only while the rendered panel actually holds a row ([role="option"]), which is the C# switch's _hits.Count > 0 read off the markup rather than copied into the browser — the two can't disagree. With no list under the field, the arrows are the caret's again.

Files: wwwroot/js/search-interop.js, Components/Layout/SearchBox.razor, tests/e2e/search.spec.js, docs/search.md.

Verified

Ran the app (dotnet run, Development, local Postgres 15) and drove it with Playwright 1.62.1 — search.spec.js, all 3 tests green. The new assertions in the keyboard block:

  • caret placed mid-query at offset 3 → ArrowDown activates row 2, caret still 3 → ArrowUp activates row 1, caret still 3
  • a query nothing answers → ArrowUp still moves the caret to 0

Both halves fail on the unfixed code, checked by disabling the new branch: Expected: 3, Received: 22 — the caret at the end of the query, which is the bug.

dotnet build clean, dotnet test Ponente.sln 448/448 (includes DocumentationTests).

No changelog entry: the search box itself is still in Changelog.Unreleased, so no member ever saw the broken behaviour — a bullet describing it would be describing something that never shipped.


⏱ 7m 50s · 54 turns · tokens: 82.5k in / 24.5k out (+3645.8k cache-read)
📜 Full session transcript
→ Opened PR: #174

Pushed to `claude/issue-171`. ## What was wrong The search panel's up/down keys are handled in C# (`SearchBox.OnKeyDown`), and nothing ever called `preventDefault` on them. So the browser did what an arrow means in a text field *as well as* moving the active row: `ArrowDown` threw the caret to the end of the query, `ArrowUp` to the start. Look down the results, go back to fix a letter, and the correction lands in the wrong place. ## Why the fix is in JS A Blazor handler can't decide `preventDefault` per key — `@onkeydown:preventDefault` is read at render time, so the flag would have to be set before the keystroke it applies to, and it suppresses *every* key in the field, letters included. So the state stays exactly where it was and only the key's browser meaning moves: - `search-interop.js` is now handed the input element along with the `DotNetObjectReference`, and calls `preventDefault()` on `ArrowUp`/`ArrowDown` aimed at that field. Blazor's own handler still runs — the caret moves after the event, not during it. - It takes the key **only while the rendered panel actually holds a row** (`[role="option"]`), which is the C# switch's `_hits.Count > 0` read off the markup rather than copied into the browser — the two can't disagree. With no list under the field, the arrows are the caret's again. Files: `wwwroot/js/search-interop.js`, `Components/Layout/SearchBox.razor`, `tests/e2e/search.spec.js`, `docs/search.md`. ## Verified Ran the app (`dotnet run`, Development, local Postgres 15) and drove it with Playwright 1.62.1 — `search.spec.js`, all 3 tests green. The new assertions in the keyboard block: - caret placed mid-query at offset 3 → `ArrowDown` activates row 2, caret still 3 → `ArrowUp` activates row 1, caret still 3 - a query nothing answers → `ArrowUp` still moves the caret to 0 Both halves fail on the unfixed code, checked by disabling the new branch: `Expected: 3, Received: 22` — the caret at the end of the query, which is the bug. `dotnet build` clean, `dotnet test Ponente.sln` 448/448 (includes `DocumentationTests`). No changelog entry: the search box itself is still in `Changelog.Unreleased`, so no member ever saw the broken behaviour — a bullet describing it would be describing something that never shipped. --- ⏱ 7m 50s · 54 turns · tokens: 82.5k in / 24.5k out (+3645.8k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-171/run-400.md) → Opened PR: https://git.grandiras.net/Grandiras/Ponente/pulls/174
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente#171
No description provided.