Review R4, search: a name match comes first, results open under their box, and the prep drawers can preview (#1020) #1026

Merged
claude-bot merged 9 commits from fix/1020-search-exact-first into main 2026-10-02 03:29:19 +00:00
Collaborator

Part of #1020, the owner's 1 October review. The last of its four PRs.

"Search does not put the exact result at the top (when I search for Odile I get Bastian at the top)." (l8-front, l6-lookups)

A wiki search had no ranking. Results came back in the wiki's browsing order (ORDER BY entry_type, title), and player_character is declared before npc, so a character who mentions her always came before Odile Marsh. Every search box inherited it.

  • app/services/search_rank.py is the one ranking: the name is the query; the name starts with it; a word in the name starts with it; the same three for aliases; then everything else, in the order that search had before. Case, accents and extra spaces are folded.
  • It serves the wiki search (GET …/lore?search= and the wiki kind of search-all: the app bar, the wiki front page, Table mode's Find, the prep Wiki drawer, the discussion pickers), the rules search (the Rules & bestiary drawer, Find, the sheet pickers), and the people and sessions the app bar finds.
  • It cannot leak. It sorts only the rows the caller already filtered for this reader, by the name that reader sees: a hidden page is never in the list, and an unidentified item is matched and ranked by its party name for a player. No counts include hidden rows. No new .title read; the allowlist tests pass unchanged.
  • Ranking happens before the limit, so the name match is never the one cut off.
  • A result found by its own name no longer repeats the name as its snippet: it shows how the page starts (in the text this reader may see), or just its kind.

"The results box is not under the search box, but is instead off to the right side." (l8-front)

The wiki front page's panel was hung from the right edge of a full-width row. It now starts at the box's left edge, directly under it, at the box's width, at every width.

"There's also no way to preview rules or items in the wiki." (l6-lookups)

In the prep sheet's Wiki and Rules & bestiary drawers (and Planning's), a result's title opens a preview inside the drawer: the same one Table mode's Find uses, with Insert (Keep on Planning) and "Back to results". Escape goes back to the results, without closing the phone's Tools sheet; focus returns to the title it came from; the preview is brought into view when it opens. Catalogue row IS-472.

The tool margin was sized to the window (100dvh − 6rem) while it sticks inside the page's scroller, which is shorter by the app bar and any banner, so its last rows sat past the scroller's edge. It is sized to the scroller now (useScrollerHeight). Re-checked on top of #1024, where the footer joined the page's scroll.

Also fixed (on main already)

Table mode's Find rows were wider than their list (the grid's one column sized to the longest snippet), so the list scrolled sideways and snippets were cut at its edge. Rows now fit and the snippet ends in an ellipsis. "In the sheet" no longer wraps to three lines on a phone.

Checked

  • Full backend suite on the merged tree: 6812 passed, 14 skipped; ruff 0.4.4 clean. Full vitest: 3677 passed, 1 skipped; eslint 0 errors. Every coverage/lane*.py and the changelog check pass. tests/test_search_ranking.py is new (36 tests).
  • Rendered twice in e2e stacks at 390×844 (touch) and 1440×900, with a wiki built for the purpose (a page named by the query, one that mentions it, an alias, prefix and word-prefix titles, ten body matches), and looked at:
    • "Odile" → Odile Marsh, the alias match, then Bastian Crowe, and "marsh" → title-prefix, word-prefix, then body, identically on the wiki front page, the app bar, the prep drawer and Find, for a player and the GM;
    • the front page's panel against its box (390: both 48→342; 1440: both 284→796);
    • the preview in both drawers by mouse and keyboard, Insert, Back to results, the phone's Tools sheet, Planning;
    • the margin scrolled to its last row at 1440×900 and 1280×700, and the page scrolled to its end: never over the footer;
    • Find's rows inside their list at 1440 and 641.
  • J6 changed one selector: the wiki search hit is matched by its title's start, not by a substring that also matched rows quoting the name.
  • No migration, no bot API change (the bot's /ask has its own query). docs/API.md has a "Search order" section.

Noted, not changed

Every list stops at ten results without saying so. The preview shows a page's summary where it has one, not its full text, as Find's does. A preview scrolls into view twice in quick succession on a phone (once on open, once when the entry arrives).

🤖 Generated with Claude Code

Part of #1020, the owner's 1 October review. The last of its four PRs. ## "Search does not put the exact result at the top (when I search for Odile I get Bastian at the top)." (l8-front, l6-lookups) A wiki search had no ranking. Results came back in the wiki's browsing order (`ORDER BY entry_type, title`), and `player_character` is declared before `npc`, so a character who mentions her always came before Odile Marsh. Every search box inherited it. - **`app/services/search_rank.py`** is the one ranking: the name is the query; the name starts with it; a word in the name starts with it; the same three for aliases; then everything else, in the order that search had before. Case, accents and extra spaces are folded. - It serves the wiki search (`GET …/lore?search=` and the `wiki` kind of `search-all`: the app bar, the wiki front page, Table mode's Find, the prep Wiki drawer, the discussion pickers), the rules search (the Rules & bestiary drawer, Find, the sheet pickers), and the people and sessions the app bar finds. - **It cannot leak.** It sorts only the rows the caller already filtered for this reader, by the name that reader sees: a hidden page is never in the list, and an unidentified item is matched and ranked by its party name for a player. No counts include hidden rows. No new `.title` read; the allowlist tests pass unchanged. - Ranking happens before the limit, so the name match is never the one cut off. - A result found by its own name no longer repeats the name as its snippet: it shows how the page starts (in the text this reader may see), or just its kind. ## "The results box is not under the search box, but is instead off to the right side." (l8-front) The wiki front page's panel was hung from the right edge of a full-width row. It now starts at the box's left edge, directly under it, at the box's width, at every width. ## "There's also no way to preview rules or items in the wiki." (l6-lookups) In the prep sheet's Wiki and Rules & bestiary drawers (and Planning's), a result's title opens a preview inside the drawer: the same one Table mode's Find uses, with Insert (Keep on Planning) and "Back to results". Escape goes back to the results, without closing the phone's Tools sheet; focus returns to the title it came from; the preview is brought into view when it opens. Catalogue row IS-472. ## "The bottom of the wiki search list is cut off by the footer." (l6-lookups) The tool margin was sized to the window (`100dvh − 6rem`) while it sticks inside the page's scroller, which is shorter by the app bar and any banner, so its last rows sat past the scroller's edge. It is sized to the scroller now (`useScrollerHeight`). Re-checked on top of #1024, where the footer joined the page's scroll. ## Also fixed (on main already) Table mode's Find rows were wider than their list (the grid's one column sized to the longest snippet), so the list scrolled sideways and snippets were cut at its edge. Rows now fit and the snippet ends in an ellipsis. "In the sheet" no longer wraps to three lines on a phone. ## Checked - Full backend suite on the merged tree: **6812 passed, 14 skipped**; ruff 0.4.4 clean. Full vitest: **3677 passed, 1 skipped**; eslint 0 errors. Every `coverage/lane*.py` and the changelog check pass. `tests/test_search_ranking.py` is new (36 tests). - Rendered twice in e2e stacks at 390×844 (touch) and 1440×900, with a wiki built for the purpose (a page named by the query, one that mentions it, an alias, prefix and word-prefix titles, ten body matches), and looked at: - "Odile" → Odile Marsh, the alias match, then Bastian Crowe, and "marsh" → title-prefix, word-prefix, then body, **identically** on the wiki front page, the app bar, the prep drawer and Find, for a player and the GM; - the front page's panel against its box (390: both 48→342; 1440: both 284→796); - the preview in both drawers by mouse and keyboard, Insert, Back to results, the phone's Tools sheet, Planning; - the margin scrolled to its last row at 1440×900 and 1280×700, and the page scrolled to its end: never over the footer; - Find's rows inside their list at 1440 and 641. - **J6** changed one selector: the wiki search hit is matched by its title's start, not by a substring that also matched rows quoting the name. - No migration, no bot API change (the bot's `/ask` has its own query). `docs/API.md` has a "Search order" section. ## Noted, not changed Every list stops at ten results without saying so. The preview shows a page's summary where it has one, not its full text, as Find's does. A preview scrolls into view twice in quick succession on a phone (once on open, once when the entry arrives). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge remote-tracking branch 'origin/main' into fix/1020-search-exact-first
Some checks failed
CI / Backend lint (ruff) (pull_request) Successful in 45s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 1m18s
CI / Feedback site tests (pull_request) Successful in 52s
CI / Does this change touch the recording path? (pull_request) Successful in 25s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Bot/backend version sync (pull_request) Successful in 56s
CI / Bot tests and audit (pull_request) Successful in 2m18s
CI / Docker image build (pull_request) Successful in 1m12s
CI / Backend migration, tests, and audit (pull_request) Has been cancelled
CI / End-to-end journeys (phone) (pull_request) Has been cancelled
CI / Frontend (shard 2/2) (pull_request) Has been cancelled
CI / End-to-end journeys (keyboard) (pull_request) Has been cancelled
CI / End-to-end journeys (desktop) (pull_request) Has been cancelled
CI / Frontend (shard 1/2) (pull_request) Has been cancelled
bcddae0355
Merge main; the catalogue is rebuilt from both branches' ledger rows (#1020)
All checks were successful
CI / Does this change touch the recording path? (pull_request) Successful in 32s
CI / Synthetic session harness (no GPU, no LLM) (pull_request) Has been skipped
CI / Backend lint (ruff) (pull_request) Successful in 37s
CI / Bot/backend version sync (pull_request) Successful in 40s
CI / Docker image build (pull_request) Successful in 43s
CI / Feedback site tests (pull_request) Successful in 1m6s
CI / Summarisation accuracy eval harness (stub provider) (pull_request) Successful in 2m31s
CI / Bot tests and audit (pull_request) Successful in 4m21s
CI / Frontend (shard 1/2) (pull_request) Successful in 9m45s
CI / End-to-end journeys (phone) (pull_request) Successful in 12m1s
CI / End-to-end journeys (keyboard) (pull_request) Successful in 13m0s
CI / End-to-end journeys (desktop) (pull_request) Successful in 13m8s
CI / Frontend (shard 2/2) (pull_request) Successful in 17m42s
CI / Backend migration, tests, and audit (pull_request) Successful in 54m15s
ba9535d5e6
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Sign in to join this conversation.
No description provided.