Repaint the key line when a drag crosses into a selection - #177
Conversation
The Escape rung reads the live selection, but nothing repainted the key line inside a pointer press: `selectionchange` returns early while `pointerSelecting` stands, so the word the line showed was whatever the frame the press itself scheduled happened to catch. Idle, that frame landed after the drag had run and the line said "unselect"; loaded, it landed first and the line went on promising "let go" — the wrong key — until the two-second heartbeat repainted it. The handler now repaints when the answer crosses, which is once per press in each direction. The bar still waits for the release; only the line follows the drag, and a drag growing a standing selection paints nothing, so no whole `paintHere` lands inside every frame of one. `hold_selection` gains `frame_the_press`, which lets that frame land before the drag begins, and the label-press test states it — the ordering CI gives every drag and an idle machine gives almost none, and the ordering under which the line's only route to the word is the selection.
|
Second occurrence, and an independent measurement that the runtime change alone carries the fix. Run 33447477986 on What that run supplies that a single-shot cannot is a rate. On One more datum for the merge order: run 33448680853 on Runs, and what each was measured againstAll on
Every failure printed this PR's line verbatim: The variant I measured is the unguarded form — A probe run before either change agrees with this PR's Suites with the unguarded variant applied: |
# Conflicts: # skills/leaf/assets/runtime/composing/surface.js
Problem
Run 33445784773 on
bdf9f44came back 4 failed, 1586 passed, 6 skipped. Three belong elsewhere: the anchor pair is what #174 already carries and fixes, and the catalog preview digest is gone from the suite as of4b42d15. The fourth is new —test_render_navigation::test_a_label_press_keeps_the_controls_keyboard_standingread'/\nsearch page\nesc\nlet go\n?\nmore\nd / u\npage down / up'where it wantedunselect, mid-drag, withgetSelection()already holdingafter state.rung()'s first rung isunselectwheneverpageSelection()stands, so at that moment Escape would have cleared the selection while the line promised to let go of the control. The line was not wrong about the selection — nothing had repainted it.selectionchangereturns early whilepointerSelectingstands, deliberately, so the bar is not re-decided under a live drag, and no other writer repaints inside a press. The word the test read was therefore whichever side of the drag the frame that the press itself scheduled (throughfocusout→paintHere) happened to land on: after the moves on an idle machine, before them on a loaded one, and then stale until the two-second heartbeat.Solution
The
pointerSelectingbranch now repaints when the answer crosses — once per press in each direction. The bar keeps waiting for the release, so nothing aboutupdateFabor the snap changes; only the line follows the drag. A drag that grows a selection already standing paints nothing, which is what keeps a wholepaintHereout of every frame of a drag.hold_selectiongainsframe_the_press, which lets the press's own frame land before the drag begins, and the test states it. Under that ordering the line's only route to the word is the selection the drag makes, which is the behaviour the assertion was always meant to be about.Testing
The failure reproduces deterministically once the ordering is stated, and the fix is what clears it.
frame_the_press=Trueand the runtime change reverted:FAILED … assert 'unselect' in '/\nsearch page\nesc\nlet go\n?\nmore\nd / u\npage down / up'— the run's exact line.1 passed.Suites, all on
4b42d15:uv run pytest tests(the PR gate) 749 passed, 6 skipped in 4:48. Under--run-nightly,test_render_navigation.py63 passed;test_render_aim/reactions/conversations/commands/margin.py155 passed;test_render_controls/drafts/options/options_addition/options_settled/outbox/projection/widgets/startup.py394 passed.test_render_anchors.pyfails only on the two #174 already owns.node --checkon the module,ruff check/ruff format --checkandprettier@3.9.6 --checkon the touched files: all clean.pre-commititself is not on this sandbox's PATH, soeslintandtyposwent unrun here and CI'slintjob is the first to run them.How the ordering was measured
A probe recorded every change to
.lf-keylinethrough aMutationObserverwhile driving the same drag. Press, then drag, reading at the end:One repaint at 39ms — the press's frame, landing after the ten moves — and then nothing until the heartbeat at 1895ms. Inserting a 120ms wait between the press and the moves, which is what a loaded runner supplies for free:
The same run's line, and the word arriving 1.9 seconds later from the heartbeat rather than from anything the reader did. With the fix, the same probe repaints during the drag and the press's frame no longer decides it.
Automated fix for failed run