Repository navigation
test(reach-rank): wait for the remounted board before typing; prove Back restores the search - #90
Merged
adminopenclaw8-sketch merged 2 commits intoSep 24, 2026
Conversation
…ack restores the search test-reach-rank-e2e.js failed now and then at "search restored after Back": 15/50 plain and 13/20 instrumented on master e51272d locally, and once in CI. The app was right. The test acted before the board it navigated to was ready: - page.goto() on a same-document hash resolves when the history entry commits. app.js re-renders on the later hashchange, so openBoard()'s waits could pass on the previous board. The query was then typed into a node that was about to be replaced. - One frame after each mount, app.js (Kpa-clawbot#630-7) moves focus to the page heading. Typing across that frame lost the rest of the query. In both cases the search never ran. The unfiltered first page still held the target, so the step went on. Back then correctly returned to "#/reach-rank" without q. settleBoard() now waits, when the hash changes, for a #rrSearch that did not exist before, then for the Kpa-clawbot#630-7 focus hand-off, then for the mount's first fetch. The keyboard step: - checks that the search really applied (field, hash and the API's filtered rows) before it navigates on; - after Back, checks field, hash and rows twice, with Forward in between, in the same document with an unchanged history.length. A route hold (installRouteHold/holdNextRoute) holds the router, and the frame callbacks it schedules, for the keyboard step's navigation until the next key press. settleBoard releases them only after seeing that its condition does not hold yet. Master's waits therefore fail every time, and nothing is timed. The test asserts that the hold caught app.js's router. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes the two findings from the independent review: - A reload on Back failed the step, but only as "Execution context was destroyed". The step now requires the document token before Back. When settleBoard fails, it waits for the current document to load and compares __rrDoc; a different or missing token raises "Back N reloaded the page instead of traversing history". The success path already compared the token. An event counter was tried first and dropped: the destroyed evaluate can come before the new document's DOMContentLoaded is counted. - The readiness and search waits keep the original error message when they fail, instead of replacing it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adminopenclaw8-sketch
pushed a commit
that referenced
this pull request
Sep 24, 2026
Brings in #86 (Relay Airtime Share), #87 (blacklist QA hardening) and #90 (Reach Rank test stabilisation). None of them touch public/live.js or test-live-multibyte-only-e2e.js. The merge was conflict-free, and its tree equals the verified synthetic merge tree. Co-Authored-By: Claude Opus 5.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.
Summary
This fixes the intermittent failure of
test-reach-rank-e2e.jsat "Back restores search: search restored after Back". The cause was in the test, not in the product. This PR changes onlytest-reach-rank-e2e.js;public/, workflows and Go code are unchanged.The flake surfaced in CI on PR #88 (run 35987585041). #88 only touches the Live page and its test, so it was just where the flake was observed, not its cause.
Baseline
The same harness was run on the same server and fixture, alternating master's test and this branch's test, on macOS arm64. The fixture was prepared as the Playwright job does (freshen → seed SQL → migrate).
public/e51272d9Every master failure is the identical assertion.
Root cause (from an instrumented event timeline)
The keyboard step does:
openBoard()(page.goto('#/reach-rank'));Two failing timelines showed that the search was never applied: no
submit, noreplaceState(...?q=...). Two paths lead there:#/reach-rank?page=2.page.goto()on a same-document hash resolves as soon as the history entry commits (popstate).app.jsre-renders on the laterhashchange(window.addEventListener('hashchange', navigate)). SoopenBoard()'s waits (#rrRows tr, table notaria-busy) could pass on the previous board. The test focused that board's#rrSearch, the router then replaced it, focus fell to<body>, and every keystroke went nowhere.app.jsmoves focus to the page heading (its accessibility code marked#630-7). When the typing spanned that frame, the first character landed in the new box ("E") and the rest was lost.In both cases the unfiltered first page still contained the target row (it ranks second). So the test kept going, and Back correctly returned to
#/reach-rankwithoutq, leaving an empty box.Why the product is fine.
reach-rank.jsrenders#rrSearchwith the value from the hash synchronously ininit(), and keepsq/pagein the URL withreplaceState. A real user cannot type within the millisecond betweenpopstateandhashchange, or the single frame before the focus hand-off.A Chromium check at 1200 px and 375 px confirmed that search, field and filtered rows survive Back, Forward+Back, a real reload and an SPA round trip to Packets, with no errors.
Fix (test-only)
settleBoard(page, navigate)is the new readiness contract. When the hash changes, it waits in this order:#rrSearchthat did not exist before the navigation (the old node is marked first);app.jsfocus hand-off (#630-7):document.activeElementis the firsth1, h2, h3, [role="heading"]in#app, the same selectorapp.jsuses;#rrTablenotaria-busy).If the hash doesn't change, the router doesn't re-render, and the current board is kept.
Stronger keyboard step:
history.lengthis unchanged), not a reload.Deterministic race amplifier.
installRouteHoldwrapsapp.js's router, thehashchangelistener namednavigate. The test asserts the hook caught it.holdNextRouteholds the keyboard step's route change, and the animation-frame callbacks it schedules (including the focus hand-off), until the page's next key press.settleBoardreleases them only after seeing that its condition does not hold yet.Mutations (isolated scratch copies)
openBoardwaits in the new testsettleBoardaccepts a stale#rrSearch(faked readiness)q(search only in the URL)q(box shows it, list unfiltered)Verification (
4fc30fc7, re-run afterfedee520)4fc30fc7: 50/50 plain and 20/20 instrumented; master's test with the same harness: 35/50 and 7/20.4fc30fc7; master 15/20 and 6/10. Atfedee520, 5/5 plain more.4fc30fc7, and 3/3 after the review fixes.test-node-reach-coverage-e2e.js20/20;test-issue-1630-reach-mobile-e2e.js20/20;test-reach-rank.js,test-node-reach-coverage.jsandtest-node-reach-coverage-debounce.js20/20 each.node --checkandgit diff --checkare clean. eslint 8 (as in CI) finds 0 issues in the test file, as on master. The XSS diff gate reports nopublic/changes.deploy.yml. No server or Chromium processes were left behind. No staging, demo or production system was contacted.Limitations
test-node-reach-e2e.jsis not in CI and times out locally waiting for#nqMap .leaflet-container. It fails identically from a cleane51272d9checkout and is unrelated to this change.app.js's router function namenavigate. If it is renamed, the test fails loudly ("the route hold did not catch app.js's hashchange router") instead of silently losing the amplifier._leaflet_posteardown error is unchanged.Independent review
A fresh reviewer that did not write the change reproduced the root cause from
reach-rank.jsandapp.jsand agreed it is a test error, not a product bug.fedee520:The reviewer's re-check of
fedee520found no blockers. The PR test passed 8/8, and its reload-on-Back mutant failed with "Back 1 reloaded the page instead of traversing history".🤖 Generated with Claude Code