Search: group header counts hidden rows (Navigate says 7, shows 5); no way to reach the rest #544

Closed
opened 2026-09-30 17:34:18 +00:00 by kayg · 9 comments
Owner

Owner report (2026-09-30, screenshot): "'Navigate' says 7 but has 5?"

Cause (read in code):

  • apps/web/src/lib/search/window.svelte.ts groups counts every result in total. In the palette (not expanded), it then slices entries to PALETTE_ROWS_PER_GROUP (5).
  • search-dialog.svelte shows group.total in the header.
  • So the header says 7 while 5 rows show, and there is no control to reach the other 2 enabled tabs (here, 2 of the 7 enabled plugins, for example Money).
    Fix:
  • A group count must never promise rows the User cannot reach. When a palette group is truncated, the last row of the group is a "Show all 7" row. It opens the expanded view scrolled to that group (keyboard reachable, in rows order, announced to screen readers). This follows the Spotlight/Raycast "Show more" convention; cite a source for the pattern in the issue.
  • Navigate is never truncated: it holds at most one row per enabled tab plus a few pages, and all tabs must be reachable (the same class of bug as the 5-tab cap fixed in 7c6fd3ce9). Put the no-cap rule in kinds.ts per group rather than special-casing it in the dialog.
  • Keep the header count equal to the total, and make it consistent: the same count component in the palette and the expanded view. Check the Photos group head (line ~805) for the same issue.
    Test:
  • a unit test: a truncated group exposes a "Show all" row and total; Navigate with 8 enabled tabs shows 8 rows;
  • an e2e: a User with 7 enabled plugins opens search with an empty query, sees 7 Navigate rows including Money, and presses ↓ to reach the last one; a truncated Files group shows "Show all N", and choosing it opens the expanded view with N rows.
    Attach screenshots (a production build, both themes, 390/1440).
## Owner report (2026-09-30, screenshot): "'Navigate' says 7 but has 5?" **Cause (read in code):** - `apps/web/src/lib/search/window.svelte.ts` `groups` counts every result in `total`. In the palette (not expanded), it then slices `entries` to `PALETTE_ROWS_PER_GROUP` (5). - `search-dialog.svelte` shows `group.total` in the header. - So the header says 7 while 5 rows show, and there is no control to reach the other 2 enabled tabs (here, 2 of the 7 enabled plugins, for example Money). **Fix:** - A group count must never promise rows the User cannot reach. When a palette group is truncated, the last row of the group is a "Show all 7" row. It opens the expanded view scrolled to that group (keyboard reachable, in `rows` order, announced to screen readers). This follows the Spotlight/Raycast "Show more" convention; cite a source for the pattern in the issue. - **Navigate** is never truncated: it holds at most one row per enabled tab plus a few pages, and all tabs must be reachable (the same class of bug as the 5-tab cap fixed in 7c6fd3ce9). Put the no-cap rule in `kinds.ts` per group rather than special-casing it in the dialog. - Keep the header count equal to the total, and make it consistent: the same count component in the palette and the expanded view. Check the Photos group head (line ~805) for the same issue. **Test:** - a unit test: a truncated group exposes a "Show all" row and `total`; Navigate with 8 enabled tabs shows 8 rows; - an e2e: a User with 7 enabled plugins opens search with an empty query, sees 7 Navigate rows including Money, and presses ↓ to reach the last one; a truncated Files group shows "Show all N", and choosing it opens the expanded view with N rows. Attach screenshots (a production build, both themes, 390/1440).
Author
Owner

Starting #544 on branch job/search-count-544, based on aa372eef6c9312403f59a65d5b90cb3648ff52ed (origin/dev). I am tracing palette row grouping and expanded-view navigation, then I will add coverage and capture the requested production-build screenshots.

Starting #544 on branch `job/search-count-544`, based on `aa372eef6c9312403f59a65d5b90cb3648ff52ed` (origin/dev). I am tracing palette row grouping and expanded-view navigation, then I will add coverage and capture the requested production-build screenshots.
Author
Owner

Confirmed the issue's root cause in window.svelte.ts: groups retains the full total but slices compact entries to five, while the Navigate group has no unlimited policy. The search dialog also renders Photos with a separate heading, so I will share the heading/count renderer across both paths. For the “Show more” convention, Apple's Spotlight guide documents a “Show More” action for additional results: https://support.apple.com/en-ca/118232

Confirmed the issue's root cause in `window.svelte.ts`: `groups` retains the full `total` but slices compact `entries` to five, while the Navigate group has no unlimited policy. The search dialog also renders Photos with a separate heading, so I will share the heading/count renderer across both paths. For the “Show more” convention, Apple's Spotlight guide documents a “Show More” action for additional results: https://support.apple.com/en-ca/118232
Author
Owner

The real-server browser run passed the new #544 checks: seven Navigate rows are keyboard-reachable (including Money), and selecting “Show all 8 Files” expands to 16 indexed rows and scrolls to Files. The existing centered-growth e2e then failed with measured palette centers 450.5 px and 446.90625 px (3.59 px difference; existing limit remains 2 px). I am investigating the layout timing/centering without changing that expectation.

The real-server browser run passed the new #544 checks: seven Navigate rows are keyboard-reachable (including Money), and selecting “Show all 8 Files” expands to 16 indexed rows and scrolls to Files. The existing centered-growth e2e then failed with measured palette centers 450.5 px and 446.90625 px (3.59 px difference; existing limit remains 2 px). I am investigating the layout timing/centering without changing that expectation.
Author
Owner

A second full browser run timed out while waiting for the new real indexed fixture to expose its eight Notes and eight Files. The check timed out at 60 seconds; the host load average was 40.12 during that run. The same #544 behavior had passed on the first run. I raised only this fixture readiness timeout to 120 seconds (result counts and assertions stay unchanged) and moved the six screenshot captures directly after the #544 checks so later legacy flows do not prevent the requested evidence.

A second full browser run timed out while waiting for the new real indexed fixture to expose its eight Notes and eight Files. The check timed out at 60 seconds; the host load average was 40.12 during that run. The same #544 behavior had passed on the first run. I raised only this fixture readiness timeout to 120 seconds (result counts and assertions stay unchanged) and moved the six screenshot captures directly after the #544 checks so later legacy flows do not prevent the requested evidence.
Author
Owner

Correction to my previous comment: the timeout edit had changed the earlier Atlas index warmup poll, while the palette544row fixture poll was still at 60 seconds. The failure was in that second poll. I have moved the 120-second bound to the unique fixture poll and restored the Atlas warmup poll to its original 60-second bound. No result count or assertion changed.

Correction to my previous comment: the timeout edit had changed the earlier Atlas index warmup poll, while the palette544row fixture poll was still at 60 seconds. The failure was in that second poll. I have moved the 120-second bound to the unique fixture poll and restored the Atlas warmup poll to its original 60-second bound. No result count or assertion changed.
Author
Owner

The unique fixture poll timed out again after 120 seconds. The host load average was 41.58. The first full run passed the #544 interaction assertions, so this repeated timeout is isolated to preparing the larger legacy e2e fixture under load. I am adding a focused mode to the existing real-server e2e harness that seeds only the eight Notes and eight Files needed for #544; the assertions stay the same.

The unique fixture poll timed out again after 120 seconds. The host load average was 41.58. The first full run passed the #544 interaction assertions, so this repeated timeout is isolated to preparing the larger legacy e2e fixture under load. I am adding a focused mode to the existing real-server e2e harness that seeds only the eight Notes and eight Files needed for #544; the assertions stay the same.
Author
Owner

The focused run's timeout snapshot shows that the eight seeded Notes produce 16 raw API hits, while all eight Files are present and the index reports indexing: false. The API can return duplicate Note matches; Search deduplicates them before building the visible group. I had made the readiness poll require exactly eight raw Note hits. I changed that poll to require at least eight; the UI assertions still require an exact Notes count of 8 and Files count of 8.

The focused run's timeout snapshot shows that the eight seeded Notes produce 16 raw API hits, while all eight Files are present and the index reports `indexing: false`. The API can return duplicate Note matches; Search deduplicates them before building the visible group. I had made the readiness poll require exactly eight raw Note hits. I changed that poll to require at least eight; the UI assertions still require an exact Notes count of 8 and Files count of 8.
Author
Owner

Completed: Search group counts and reachability (#544)

What changed

  • Added a per-group palette limit in apps/web/src/lib/search/kinds.ts. Navigate has no cap, so every enabled Tab stays reachable.
  • A group with more than five results now shows five results plus a keyboard-selectable Show all N row. Selecting it opens the existing expanded Search view and scrolls to that group.
  • Group headings now use one component and always show the full result count, including Photos and the expanded view. The disclosure row has a screen-reader label with its count and group.
  • Added unit, production e2e and benchmark coverage.

Files

  • apps/web/src/lib/search/kinds.ts
  • apps/web/src/lib/search/registry.ts
  • apps/web/src/lib/search/groups.ts
  • apps/web/src/lib/search/groups.test.ts
  • apps/web/src/lib/search/window.svelte.ts
  • apps/web/src/lib/search/SearchGroupHeading.svelte
  • apps/web/src/lib/search/SearchResultRow.svelte
  • apps/web/src/lib/components/search-dialog.svelte
  • apps/web/e2e/search.mjs
  • bench/search-group-rows.mjs
  • bench/run.sh

E2E and screenshots

The focused production e2e passed against a real local server. It checked seven enabled Navigate rows, ArrowDown to Ask, and keyboard selection of Show all 8 Files; the expanded view showed 16 indexed rows and scrolled to Files. Output:

PASS complete Navigate rows and keyboard-accessible Files Show all
search count e2e: ok
CSP REPORTS search: 0 across 1 pages

Six production screenshots are attached: phone 390 px, tablet 820 px and desktop 1440 px, in both themes.

The full Search e2e reached and passed the #544 checks, then stopped at the existing phone action-pill hit-area assertion: all three buttons measured 44×36 px, while the probe expects 44×44 px. I filed this separate accessibility finding as #554. An earlier run sampled a 3.59 px center difference during the palette morph; the existing 2 px assertion was not changed. Existing issue #499 tracks palette centering.

Performance

Local grouping benchmark; docs/perf/baseline.json Search baseline is p50 100.5 ms / p95 218.4 ms for search_100k_palette.

  • Average palette, 20 rows: p50 0.002 ms / p95 0.012 ms; CPU 0.008 ms per run; sampled RSS peak 41,783,296 bytes.
  • Worst case, 200 rows: p50 0.006 ms / p95 0.015 ms; CPU 0.018 ms per run; sampled RSS peak 50,741,248 bytes.
  • Burst, 1,000 runs × 200 rows: p50 0.004 ms / p95 0.014 ms; CPU 0.007 ms per run; sampled RSS peak 51,920,896 bytes.

Final web gates

bun run check:

$ node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
Text sizes and UI shape values use shared role tokens.
UI transitions and animation options use shared motion tokens or documented exceptions.
Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/search-count-544/apps/web
Getting Svelte diagnostics...
svelte-check found 0 errors and 0 warnings

bun run test:

Test Files  141 passed (141)
      Tests  917 passed (917)
   Start at  21:32:31
   Duration  134.29s (transform 54%, environment 18%, import 16%, tests 8%, setup 4%)

Environment  |component| jsdom was created 43 times · 104.03s total, 27% of tracked time
             create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files)
             learn more: https://vitest.dev/guide/improving-performance#test-environments

No Rust files changed; Rust format, clippy and unit-test gates do not apply. cargo build -p calternal-server succeeded for the browser fixture. cargo clean removed 7,169 files (4.5 GiB), and web build output was deleted.

Decisions not specified by DESIGN

  • Use the label Show all N and open the existing expanded view at the selected group.
  • Store a null group limit to mean uncapped; this keeps Navigate's rule with group configuration in kinds.ts.
  • The progressive-disclosure pattern follows Spotlight's Show More action: Apple Support: Use Spotlight Search on iPhone, iPad, or iPod touch.

Commits and state

  • 919a798e0 Keep search group counts reachable in palette
  • 5b77fc79a Test and benchmark search group disclosure
  • 0a3a25f7e Stabilize search count browser coverage
  • Merged origin/dev once; merge commit/head: 675f20e59bbcbeef5aaee4e4b66db96b5f7ca263
  • Worktree is clean. No push or deploy was made.
## Completed: Search group counts and reachability (#544) ### What changed - Added a per-group palette limit in `apps/web/src/lib/search/kinds.ts`. `Navigate` has no cap, so every enabled Tab stays reachable. - A group with more than five results now shows five results plus a keyboard-selectable `Show all N` row. Selecting it opens the existing expanded Search view and scrolls to that group. - Group headings now use one component and always show the full result count, including Photos and the expanded view. The disclosure row has a screen-reader label with its count and group. - Added unit, production e2e and benchmark coverage. ### Files - `apps/web/src/lib/search/kinds.ts` - `apps/web/src/lib/search/registry.ts` - `apps/web/src/lib/search/groups.ts` - `apps/web/src/lib/search/groups.test.ts` - `apps/web/src/lib/search/window.svelte.ts` - `apps/web/src/lib/search/SearchGroupHeading.svelte` - `apps/web/src/lib/search/SearchResultRow.svelte` - `apps/web/src/lib/components/search-dialog.svelte` - `apps/web/e2e/search.mjs` - `bench/search-group-rows.mjs` - `bench/run.sh` ### E2E and screenshots The focused production e2e passed against a real local server. It checked seven enabled Navigate rows, ArrowDown to Ask, and keyboard selection of `Show all 8 Files`; the expanded view showed 16 indexed rows and scrolled to Files. Output: ```text PASS complete Navigate rows and keyboard-accessible Files Show all search count e2e: ok CSP REPORTS search: 0 across 1 pages ``` Six production screenshots are attached: phone 390 px, tablet 820 px and desktop 1440 px, in both themes. - [1440 light](https://git.kayg.org/attachments/a3c223c1-c9db-4acb-b704-86edf813f04b) - [1440 dark](https://git.kayg.org/attachments/1d69e7fc-2032-4240-b597-563d2e0af498) - [820 light](https://git.kayg.org/attachments/88b99c7e-6ef4-4ec6-8752-a3653e205bf1) - [820 dark](https://git.kayg.org/attachments/ec85ed35-61da-4d58-b088-f10a9c84935c) - [390 light](https://git.kayg.org/attachments/fcf4d5a4-3904-4d89-a98a-6585c8fcbf7b) - [390 dark](https://git.kayg.org/attachments/0a39680b-f9df-4678-802a-07d2df87888e) The full Search e2e reached and passed the #544 checks, then stopped at the existing phone action-pill hit-area assertion: all three buttons measured 44×36 px, while the probe expects 44×44 px. I filed this separate accessibility finding as [#554](https://git.kayg.org/kayg/calternal/issues/554). An earlier run sampled a 3.59 px center difference during the palette morph; the existing 2 px assertion was not changed. Existing issue [#499](https://git.kayg.org/kayg/calternal/issues/499) tracks palette centering. ### Performance Local grouping benchmark; `docs/perf/baseline.json` Search baseline is p50 100.5 ms / p95 218.4 ms for `search_100k_palette`. - Average palette, 20 rows: p50 0.002 ms / p95 0.012 ms; CPU 0.008 ms per run; sampled RSS peak 41,783,296 bytes. - Worst case, 200 rows: p50 0.006 ms / p95 0.015 ms; CPU 0.018 ms per run; sampled RSS peak 50,741,248 bytes. - Burst, 1,000 runs × 200 rows: p50 0.004 ms / p95 0.014 ms; CPU 0.007 ms per run; sampled RSS peak 51,920,896 bytes. ### Final web gates `bun run check`: ```text $ node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json Text sizes and UI shape values use shared role tokens. UI transitions and animation options use shared motion tokens or documented exceptions. Loading svelte-check in workspace: /home/kayg/Developer/calternal-wt/search-count-544/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text Test Files 141 passed (141) Tests 917 passed (917) Start at 21:32:31 Duration 134.29s (transform 54%, environment 18%, import 16%, tests 8%, setup 4%) Environment |component| jsdom was created 43 times · 104.03s total, 27% of tracked time create it once per worker with pool: 'vmThreads' (keeps per-file isolation) or isolate: false (shares it across files) learn more: https://vitest.dev/guide/improving-performance#test-environments ``` No Rust files changed; Rust format, clippy and unit-test gates do not apply. `cargo build -p calternal-server` succeeded for the browser fixture. `cargo clean` removed 7,169 files (4.5 GiB), and web build output was deleted. ### Decisions not specified by DESIGN - Use the label `Show all N` and open the existing expanded view at the selected group. - Store a `null` group limit to mean uncapped; this keeps Navigate's rule with group configuration in `kinds.ts`. - The progressive-disclosure pattern follows Spotlight's `Show More` action: [Apple Support: Use Spotlight Search on iPhone, iPad, or iPod touch](https://support.apple.com/en-ie/118232). ### Commits and state - `919a798e0` Keep search group counts reachable in palette - `5b77fc79a` Test and benchmark search group disclosure - `0a3a25f7e` Stabilize search count browser coverage - Merged `origin/dev` once; merge commit/head: `675f20e59bbcbeef5aaee4e4b66db96b5f7ca263` - Worktree is clean. No push or deploy was made.
Author
Owner

Shipped in merge round 4, deployed to calternal.cloud in 1af8ead26 (healthy).

Shipped in merge round 4, deployed to calternal.cloud in 1af8ead26 (healthy).
kayg closed this issue 2026-10-01 09:17:42 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal#544
No description provided.