Repository navigation
Work the review queue without the mouse - #1
Merged
Merged
Conversation
Forty-four rows at roughly eight tab stops each is where a queue stops being reviewed and starts being dismissed. The whole loop now has keys: j/k move a cursor, 1-9 choose a candidate, Enter confirms, x rejects, / reaches the search field and Escape leaves it. No skip key. Moving the cursor is skipping, and a second key for it would only be a second way to lose your place. The cursor is an index into a list that shrinks underneath it, which is how it advances: settle the row it points at, the row after it takes that position, and the cursor is already on the next thing to do. Clamped rather than corrected, so settling the last row lands on the new last row instead of running off the end. Focus follows it for real rather than as a class, so a screen reader is told the cursor moved and the row scrolls into view without asking - but only once a key has been pressed, because moving focus on mount yanks the page for somebody who arrived with a mouse. keyAction is pure and tested; the listeners are not. Same split as idleDelayMs: two addEventListener calls and a cleanup that mirrors them is what reading the file shows, while which keystrokes this claims is what can be quietly wrong. A window handler sees every keystroke on the screen, so typing "flux" into a LOINC search must not reject the row on the x. Text fields, modifier combinations, and Enter on an already focused button are all returned as not ours, each with a test. Two bugs the screen had all along surfaced while driving it. The tally counted rejections as rules learned, when /reject answers alias_written: false and means it - the one counter whose job is to make convergence visible rather than claimed was overstating it. And a queue of one read "1 result need you". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The review queue is the one screen somebody works down. Today every row costs
roughly eight tab stops — search field, each candidate button, confirm, reject —
and HANDOFF has been carrying the note for a while: "Confirm/reject/skip
without the mouse is the difference between reviewing forty-four items and
abandoning it."
The keys
j/k,↓/↑1–9Enterx/Escapeto leave itNo skip key. Moving the cursor is skipping, and a second key for it would
only be a second way to lose your place.
The legend sits above the queue, and is hidden under
(hover: none)along withthe candidate digits — a phone showing key caps it cannot press is describing
somebody else's screen.
The cursor advances by not moving
It is an index into a list that shrinks underneath it. Settle the row it points
at, the row after it takes that position, and the cursor is already on the next
thing to do. Clamped rather than corrected, so settling the last row lands on
the new last row instead of running off the end.
Focus follows it for real —
tabIndex={-1}on the row — rather than as a class,so a screen reader is told the cursor moved and the row scrolls into view without
asking. Only once a key has been pressed, though: moving focus on mount yanks the
page for somebody who arrived with a mouse. Settling a row also parks focus on
its replacement, whether a key or a click did it, instead of dropping focus to
<body>and putting a keyboard user back at the top of the document.What is tested, and what deliberately is not
keyActionis pure and has its own suite; the listeners do not. Same split asidleDelayMs/useIdleSignOut— twoaddEventListenercalls and a cleanupthat mirrors them is what reading the file shows, while which keystrokes this
claims is what can be quietly wrong.
The expensive failure is claiming one it should not have. This handler is on
window, so every keystroke on the screen passes through it, and typing "flux"into a LOINC search must not reject the row on the
x. Text fields,contenteditable, every modifier combination, andEnteron analready-focused candidate button are all returned as "not ours" — each with a
test.
Two bugs that surfaced while driving it
/rejectanswersalias_written: falseand means it. The one counter whose job is to makeconvergence visible rather than claimed was overstating it.
Verification
npx vitest run— 18 passing, 7 of them new.oxlintclean on the touchedfiles,
vite buildclean.The interaction itself was driven in a browser against the real component: the
cursor moves on
j/k,2selects the second candidate,Enterconfirms andthe cursor lands on the row that took its place,
xrejects without touchingthe tally,
/reaches the search field,x/j/k/1typed into that fielddo nothing to the queue, and
Escapereturns focus to the row. Under(hover: none)the legend and digits compute todisplay: none.