fix(suggest): the dropdown reopened on top of the search results #131

Merged
tudor merged 1 commits from fix/suggest-reopens-over-results into main 2026-08-26 21:07:55 +00:00
Owner

Three staging-gate failures. Two of them are one real bug, and it is not about autosuggest.

The dropdown reopened on top of the results

After a search, the results-page bar still holds the term in its input. On every render the query was >= 2 characters, so the suggestion list opened again — on top of the very results the search had just produced.

Playwright reported it while trying to click the first result:

<li role="option" id="school-suggest-bar-option-4"> ... intercepts pointer events

Both school detail page renders name and performance data and school hero map opens fullscreen on mobile failed on it. Neither test is about autosuggest — they just try to click a search result, which a reader could no longer do either.

Suggestions now answer typing, not the mere presence of a value. hasTyped gates the hook, is set on change, and is cleared when a search is submitted or a suggestion is chosen. A pre-filled input makes no request and shows no list.

The third failure was my test, not the product

An unphased place page renders one table per phase, and an all-through school legitimately appears in both. So the page's school links were never one alphabetical run:

table 1 (primary):   Abbots Langley, Bedmond, Breakspeare, Divine Saviour, Tanners Wood
table 2 (secondary): Breakspeare School

The assertion collected them all together and only ever passed because no town it picked happened to hold an all-through school. When the data gave Abbots Langley one, it failed on correct behaviour. It now checks each table separately — and passes against the exact data that broke it.

Guards

  • jest: a pre-filled input neither fetches nor opens a listbox. Verified by reverting the gate — it is the only test that fails.
  • E2E: submit a search, then require the first result to be clickable. That is the reader-facing version of the same bug.

I could not run the E2E guard against a live unfixed build: the school_autosuggest flag was on during the failing CI run and is off on staging now. The CI log is the demonstration instead.

Worth knowing separately

That flag flapping means the E2E gate is currently non-deterministic — twelve journeys skipped in that run because flags evaluated false, and which ones skip depends on whether Unleash happens to be reachable when the gate runs. A green gate with a dozen skips is not the same as a green gate. Fixing the Unleash connection resolves it; until then the gate is weaker than it looks.

Verification: backend 158, frontend 289, tsc clean, next build green, 100 E2E collected.

Three staging-gate failures. Two of them are one real bug, and it is not about autosuggest. ## The dropdown reopened on top of the results After a search, the results-page bar still holds the term in its input. On every render the query was >= 2 characters, so the suggestion list opened again — on top of the very results the search had just produced. Playwright reported it while trying to click the first result: ``` <li role="option" id="school-suggest-bar-option-4"> ... intercepts pointer events ``` Both `school detail page renders name and performance data` and `school hero map opens fullscreen on mobile` failed on it. **Neither test is about autosuggest** — they just try to click a search result, which a reader could no longer do either. Suggestions now answer *typing*, not the mere presence of a value. `hasTyped` gates the hook, is set on change, and is cleared when a search is submitted or a suggestion is chosen. A pre-filled input makes no request and shows no list. ## The third failure was my test, not the product An unphased place page renders **one table per phase**, and an all-through school legitimately appears in both. So the page's school links were never one alphabetical run: ``` table 1 (primary): Abbots Langley, Bedmond, Breakspeare, Divine Saviour, Tanners Wood table 2 (secondary): Breakspeare School ``` The assertion collected them all together and only ever passed because no town it picked happened to hold an all-through school. When the data gave Abbots Langley one, it failed on correct behaviour. It now checks each table separately — and **passes against the exact data that broke it**. ## Guards - **jest:** a pre-filled input neither fetches nor opens a listbox. Verified by reverting the gate — it is the only test that fails. - **E2E:** submit a search, then require the first result to be clickable. That is the reader-facing version of the same bug. I could not run the E2E guard against a live unfixed build: the `school_autosuggest` flag was **on** during the failing CI run and is **off** on staging now. The CI log is the demonstration instead. ## Worth knowing separately That flag flapping means **the E2E gate is currently non-deterministic** — twelve journeys skipped in that run because flags evaluated false, and which ones skip depends on whether Unleash happens to be reachable when the gate runs. A green gate with a dozen skips is not the same as a green gate. Fixing the Unleash connection resolves it; until then the gate is weaker than it looks. Verification: backend 158, frontend 289, `tsc` clean, `next build` green, 100 E2E collected.
tudor added 1 commit 2026-08-26 20:38:37 +00:00
fix(suggest): the dropdown reopened on top of the search results
PR Checks / Frontend Typecheck + Tests (pull_request) Successful in 1m2s
PR Checks / Backend Smoke (pull_request) Successful in 8s
PR Checks / Build Backend (no push) (pull_request) Successful in 11s
PR Checks / Build Frontend (no push) (pull_request) Successful in 44s
PR Checks / Build Pipeline (no push) (pull_request) Successful in 10s
PR Checks / AI Code Review (Claude) (pull_request) Successful in 55s
55363cbd18
Three staging-gate failures, two of them one real bug.

After a search, the results-page bar still holds the term in its input,
so on every render the query was >= 2 characters and the suggestion list
opened again — on top of the very results the search had just produced.
Playwright reported it as "<li role=option ...> intercepts pointer
events" while trying to click the first result; a reader would simply
have found their first result unclickable. Both the school-detail and
hero-map journeys failed on it, and neither is about autosuggest.

Suggestions now answer typing, not the mere presence of a value:
`hasTyped` gates the hook, is set on change, and is cleared when a
search is submitted or a suggestion is chosen. A pre-filled input makes
no request and shows no list.

Third failure was my test, not the product. An unphased place page
renders one table per phase, and an all-through school legitimately
appears in both — so the page's school links were never one alphabetical
run. The assertion collected them all together and only passed because
no town it picked had held an all-through school. When the data gave
Abbots Langley one, Breakspeare School appeared in the primary table and
again in the secondary, and the test failed on correct behaviour. It now
checks each table separately, and passes against the data that broke it.

Guards: a jest test that a pre-filled input neither fetches nor opens
(verified by reverting — it is the only one that fails), and an E2E
journey that submits a search and then requires the first result to be
clickable, which is the reader-facing version of the same thing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015mWQnpye9F299NVRCCSRvj

🤖 AI Code Review (Claude Code)

This PR fixes a real bug where the FilterBar's autosuggest dropdown could reopen over search results (since the results-page input arrives pre-filled, making the old length-based suggestEnabled check true on every render). The fix gates suggestions behind a hasTyped flag set only on genuine input events and cleared on submit/selection, and is backed by solid new unit and e2e coverage; the accompanying fix to the flaky alphabetical-order e2e test (checking per-table instead of per-page, to account for all-through schools appearing in two phase tables) is also correct and well-reasoned.

🟡 Minor

  • e2e/tests/journeys.spec.ts: The new per-table ordering check uses the generic page.locator('table'), which will match any table on the place page, not just phase/school tables. If the page ever gains an unrelated table (e.g. a stats or comparison widget) containing a[href^="/school/"]-style links or just noise, this could produce spurious failures or silently skip real school tables; scoping to a more specific selector (e.g. a school-table class/data-testid) would be more robust.
## 🤖 AI Code Review (Claude Code) This PR fixes a real bug where the FilterBar's autosuggest dropdown could reopen over search results (since the results-page input arrives pre-filled, making the old length-based `suggestEnabled` check true on every render). The fix gates suggestions behind a `hasTyped` flag set only on genuine input events and cleared on submit/selection, and is backed by solid new unit and e2e coverage; the accompanying fix to the flaky alphabetical-order e2e test (checking per-table instead of per-page, to account for all-through schools appearing in two phase tables) is also correct and well-reasoned. ### 🟡 Minor - **e2e/tests/journeys.spec.ts**: The new per-table ordering check uses the generic `page.locator('table')`, which will match any table on the place page, not just phase/school tables. If the page ever gains an unrelated table (e.g. a stats or comparison widget) containing `a[href^="/school/"]`-style links or just noise, this could produce spurious failures or silently skip real school tables; scoping to a more specific selector (e.g. a school-table class/data-testid) would be more robust.
tudor merged commit a3c09d9b67 into main 2026-08-26 21:07:55 +00:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: tudor/school_compare#131