Let go of the search field when Enter opens a card, so Escape closes it #249
No reviewers
Labels
No labels
No milestone
No project
No assignees
5 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente!249
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/search-enter-escape"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
search.spec.js("search finds a card by what is written on it…") failed intermittently: the card opened from search ignored Escape, and the test then broke at the 960px click because the centred dialog's scrim covered the field.Cause
A focus/Escape log captured during a failing run on an isolated stack:
Enter opened the card but left the search field focused.
modal-escape.jsonly redirects an Escape that targets<body>, so the field's own Escape handler took the key and the dialog stayed open. A member pressing Escape right after opening a card from search ran into the same thing. #241 made the gap wider:CardModalimported the inspector module (two round trips) before it focused anything.Fix
search-interop.js: Enter on a listed row blurs the field. A clicked row already lets go of focus by disappearing.SearchBoxnow opens a row on Enter only while the panel is open, so C# and the JS check ([role=option]in the panel) agree.CardModal: focuses first and imports the inspector module afterwards. Now already onmainvia #248.activeElementright after Enter. Against the oldsearch-interop.jsthis check failed 5/5, so it catches a regression every time, not only when the race hits.openComposerwaits for the title's focus before typing.search.spec.jsuses the sharedcreateCard.Date.now()stamps from runs a few seconds apart were sometimes only two edits apart, and search tolerates two edits, so earlier repeats' cards matched (3/40 under--repeat-each).docs/ui.md(Escape before focus lands) anddocs/search.mdupdated.No changelog line: before the inspector this was a sub-perceptible race, and the widened window was never released.
Verification
All runs were on an isolated, port-shifted copy of the test stack (
ponenteescape):tools/verify.sh: build and 584 unit tests greenNot touched:
share.spec.jshas its own copy ofcreateCard, which can hit the same title-focus race.🤖 Generated with Claude Code
🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
The fix matches the cause in the PR's focus log.
[role=option].SearchBoxhas no@onblurhandler, so blurring does not close the panel or interfere with the C# Enter handler.Enter when _open && _hits.Count > 0lines up with the JS check on the rendered panel.CardModal: focusing before the inspector import is safe. TheDockableearly return keeps the same behaviour as before.activeElementcheck is deterministic, the random stamp removes the near-clock collision, andopenComposernow waits for the title's focus. The docs are updated alongside the code.One small thing, not blocking:
share.spec.jsstill has its owncreateCardcopy with the title-focus race, as the PR notes. It would be worth a follow-up to move it onto the shared helper.🧠 claude-sonnet-5-5 · ⏱ 0m 9s · 3 turns · tokens: 14.9k in / 701 out (+38.8k cache-read)
📜 Full session transcript
✅ Verdict: approve — no blocking issues found.
Good change. The diagnosis is coherent and each fix is the minimal one:
search-interop.js: the JSlistsHitscheck (panel in DOM with[role="option"]) and C#'sEnter when _open && _hits.Count > 0now agree, and since the JS keydown listener is on the document (bubble phase, after Blazor's own listener on the input), the blur happens synchronously during the same keydown dispatch — so Playwright readingactiveElementright afterpress('Enter')really is deterministic, not another race. Escape then targets<body>andmodal-escape.jshandles it, as the captured log wanted.CardModal: focusing before the inspector import is right;FocusAsyncwas already awaited on this path, so no new failure mode. For!Dockablethe early return also skips nothing else.openComposernow waits for the title's focus before any typing, which protects every caller that fills fields after opening (checked the existing call sites — none assumed a non-focused dialog). The random 8-hex stamp is the right call: two random stamps within two edits has ~10⁻⁴ probability versus Date.now()'s systematic near-misses. The removed local helper's object-form callers are all in the sharedcreateCardnow, andshare.spec.js's private copy is unaffected.Two non-blocking notes:
isComposing: true, and it now also blurs the field and (as before, via C#) navigates. That would be an odd way to commit a composition. The C# side already navigated in that case, so this PR doesn't make it worse, butlistsHitscould skipisComposingevents the waymodal-escape.js's capture listener does — a candidate for a follow-up rather than this PR.Changelog.Unreleasedbullet looks defensible given the widened window was never released (orientation.md gates the bullet on "a member would notice"); just noting the house rule so the choice is visible.⏱ 21m 46s · 16 steps · $0.0468 · tokens: 233.4k in / 1.1k out (+138.1k cache-read) · Go: 5h 0% · wk 10% · mo 88%
📜 Full session transcript
0bd2137f839ce5ba0c54🤖 Reviewing this PR — the review will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the review runs.
🤖 Reviewing this PR — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
✅ Verdict: approve — no blocking issues found.
I didn't re-read the files after the rebase onto #248. This is based on the diff and my earlier pass, and my earlier approval still holds.
CardModalreorder is now onmainvia #248. The diff keeps only the app fix, which is the Enter blur insearch-interop.jsplus the_openguard inSearchBox.activeElementright after Enter instead of waiting for the dialog's focus. That tests the member-facing bug rather than hiding it.ui.mdandsearch.mdare updated to match.Non-blocking:
share.spec.jsstill has its owncreateCardcopy with the title-focus race. It would be worth moving it onto the shared helper in a follow-up.🧠 claude-sonnet-5-5 · ⏱ 0m 5s · 2 turns · tokens: 14.9k in / 424 out (+12.2k cache-read)
📜 Full session transcript
✅ Verdict: approve — no blocking issues found.
Re-review: the rebase onto #248 is clean and the state in the tree matches the PR description end to end.
search-interop.jsblurs the field synchronously in the document-level keydown listener on the bubble phase — Blazor's input handler runs during the target phase, so C# still opens the row, andactiveElementis off the field by the time Playwright'spress('Enter')resolves. The immediateactiveElementcheck in the spec is therefore deterministic, not another race.modal-escape.jsonly redirects Escape from<body>/<html>, so a blurred field's Escape reaches it and closes the dialog; the spec's removal of the former backdrop-focus wait before pressing Escape is exactly what tests the fixed behaviour, and the assertion now sits where the race happens rather than downstream at the 960px block.Enter when _open && _hits.Count > 0inSearchBox.razormatcheslistsHits(panel in DOM with[role="option"]); tips rows are not options, so the tips-only Enter case no longer navigates. Other keys fall throughlistsHitscorrectly, and the slash shortcut is still gated byisTyping.CardModal(already onmainvia #248):OnAfterRenderAsyncawaits the title/backdropFocusAsyncbefore importinginspector.js, closing the widened window.openComposer's title-focus wait protects sharedcreateCard, whose{ description }form covers the spec's converted call sites. The random 8-hex stamp removesDate.now()'s near-clock collisions.Non-blocking, unchanged from last pass and both acknowledged in the PR:
share.spec.jskeeps its owncreateCardcopy (worth a follow-up), and a mid-IME Enter now also blurs —listsHitscould skipisComposingevents likemodal-escape.js's capture listener does.⏱ 0m 48s · 6 steps · $0.0073 · tokens: 29.4k in / 762 out (+54.4k cache-read) · Go: 5h 0% · wk 10% · mo 88%
📜 Full session transcript