Search palette shifts 8 px when results grow (search.mjs 'result growth stays centred' fails) #499

Open
opened 2026-09-30 08:09:17 +00:00 by kayg · 34 comments
Owner

Problem

apps/web/e2e/search.mjs fails on dev at 369ab6a2f (production build, perf-test VM, 1440×900):

AssertionError [ERR_ASSERTION]: result growth stays centred: {"fewCentre":450.5,"manyCentre":442.234375}

The palette's vertical centre moves by 8.3 px when the result list grows. The same assertion failed in two separate runs (442.2 and 436.7 px). The server part of the probe passed (server search round trip: p50=2.8ms p95=5.8ms).

Found by the #367 perf round, where it stopped the search-ui profile. The probe's other assertion in the smoke run (mono screenshot uses the requested browser colour scheme) also failed once; check it at the same time.

Repro

CALTERNAL_SERVER_BIN=<release server> bun apps/web/e2e/search.mjs --json /tmp/s.json

## Problem `apps/web/e2e/search.mjs` fails on `dev` at `369ab6a2f` (production build, perf-test VM, 1440×900): ```text AssertionError [ERR_ASSERTION]: result growth stays centred: {"fewCentre":450.5,"manyCentre":442.234375} ``` The palette's vertical centre moves by 8.3 px when the result list grows. The same assertion failed in two separate runs (442.2 and 436.7 px). The server part of the probe passed (`server search round trip: p50=2.8ms p95=5.8ms`). Found by the #367 perf round, where it stopped the `search-ui` profile. The probe's other assertion in the smoke run (`mono screenshot uses the requested browser colour scheme`) also failed once; check it at the same time. ## Repro `CALTERNAL_SERVER_BIN=<release server> bun apps/web/e2e/search.mjs --json /tmp/s.json`
Author
Owner

Starting fix-499 on branch job/fix-499, based on 6c87f5ff9442cd658572139bc536d018fd5222a4 (the merge base with origin/dev at start). I am tracing the Search palette sizing, existing motion behavior and e2e harness before implementing content-sized height, anchored input, and capped-list scrolling for desktop and phone.

Starting fix-499 on branch `job/fix-499`, based on `6c87f5ff9442cd658572139bc536d018fd5222a4` (the merge base with `origin/dev` at start). I am tracing the Search palette sizing, existing motion behavior and e2e harness before implementing content-sized height, anchored input, and capped-list scrolling for desktop and phone.
Author
Owner

Finding: search-dialog.svelte measures and writes a fixed pillHeight; .sw then fills that height, .results-panel has a 7rem minimum, and .sw-action-bar is absolutely pinned to the column bottom. The list also reserves action clearance. This keeps the capsule tall when content is short and puts the action pill at the panel edge instead of after the final result. The same measured pillTop is recomputed from the changing height, which moves the palette during result growth. I am changing the Search surface to retain a fixed top, size from its content until the viewport cap, scroll only the results panel, and keep the action row in flow.

Finding: `search-dialog.svelte` measures and writes a fixed `pillHeight`; `.sw` then fills that height, `.results-panel` has a 7rem minimum, and `.sw-action-bar` is absolutely pinned to the column bottom. The list also reserves action clearance. This keeps the capsule tall when content is short and puts the action pill at the panel edge instead of after the final result. The same measured `pillTop` is recomputed from the changing height, which moves the palette during result growth. I am changing the Search surface to retain a fixed top, size from its content until the viewport cap, scroll only the results panel, and keep the action row in flow.
Author
Owner

The first production-build browser run showed that a two-result palette and a thirteen-result palette both measured 360px tall (the 1440x900 desktop cap). The compact .sw had height: 100%, so its child filled the capped surface before the content measurement ran. I removed that percentage height in compact mode; expanded Search keeps its full-height layout. I am rerunning the real-server browser pass against a rebuilt production app.

The first production-build browser run showed that a two-result palette and a thirteen-result palette both measured 360px tall (the 1440x900 desktop cap). The compact `.sw` had `height: 100%`, so its child filled the capped surface before the content measurement ran. I removed that percentage height in compact mode; expanded Search keeps its full-height layout. I am rerunning the real-server browser pass against a rebuilt production app.
Author
Owner

The rebuilt browser run found a second height source: at 1440x900, both the 2-result budget set and 12-result atlas set measured 360px (the cap). The desktop preview's intrinsic content height was expanding the row layout and defeating the result-driven measurement. I contained the preview's size contribution so it scrolls within the result-height band while preserving its flex width; I am rebuilding and rerunning the browser checks.

The rebuilt browser run found a second height source: at 1440x900, both the 2-result `budget` set and 12-result `atlas` set measured 360px (the cap). The desktop preview's intrinsic content height was expanding the row layout and defeating the result-driven measurement. I contained the preview's size contribution so it scrolls within the result-height band while preserving its flex width; I am rebuilding and rerunning the browser checks.
Author
Owner

The motion check showed a keyboard-to-pointer edge case: after keyboard expansion, a pointer collapse changed the measured surface from 828px to 243px but emitted no height transition because the zero-duration class remained active. The keyboard override now lasts for the immediate layout paint only, then re-arms spring motion for later pointer changes. I am verifying both transitions in the real browser run.

The motion check showed a keyboard-to-pointer edge case: after keyboard expansion, a pointer collapse changed the measured surface from 828px to 243px but emitted no height transition because the zero-duration class remained active. The keyboard override now lasts for the immediate layout paint only, then re-arms spring motion for later pointer changes. I am verifying both transitions in the real browser run.
Author
Owner

The empty-result probe showed a stale palette height: after a no-hit query, the panel had 84px of content but only 57px of viewport, and the palette measured 110px instead of the 137px natural content height. The count stayed zero while the state changed from searching to no results, so the count-only measurement did not run. The measurement now also follows query, mode, empty and searching state changes.

The empty-result probe showed a stale palette height: after a no-hit query, the panel had 84px of content but only 57px of viewport, and the palette measured 110px instead of the 137px natural content height. The count stayed zero while the state changed from searching to no results, so the count-only measurement did not run. The measurement now also follows query, mode, empty and searching state changes.
Author
Owner

The pointer collapse trace showed the surface changing from 828px to 243px without a height transition event, even though the spring rule was active. The expanded-state effect cleared the cached compact height, so collapse first removed the expanded height and only then measured the compact target. I am retaining the last compact measurement through expansion so collapse can apply both state changes in one render.

The pointer collapse trace showed the surface changing from 828px to 243px without a `height` transition event, even though the spring rule was active. The expanded-state effect cleared the cached compact height, so collapse first removed the expanded height and only then measured the compact target. I am retaining the last compact measurement through expansion so collapse can apply both state changes in one render.
Author
Owner

The broader Search e2e stopped after the palette checks because its existing result-group selector used span:first-child. Real groups now place the sticky blur <div> before the label <span>; the rendered list had 14 results, including Notes, while that selector returned no headings. I changed the checks to span:first-of-type, preserving the original required group assertions.

The broader Search e2e stopped after the palette checks because its existing result-group selector used `span:first-child`. Real groups now place the sticky blur `<div>` before the label `<span>`; the rendered list had 14 results, including Notes, while that selector returned no headings. I changed the checks to `span:first-of-type`, preserving the original required group assertions.
Author
Owner

The full Search flow reaches the pre-existing sticky section wash assertion after all #499 palette checks pass. The rendered wash is linear-gradient(color(srgb 1 1 1 / 0.08), rgba(0, 0, 0, 0) 75%); the assertion still requires the serialized word transparent. I left that existing expectation unchanged because #499 does not change the wash.

The full Search flow reaches the pre-existing sticky section wash assertion after all #499 palette checks pass. The rendered wash is `linear-gradient(color(srgb 1 1 1 / 0.08), rgba(0, 0, 0, 0) 75%)`; the assertion still requires the serialized word `transparent`. I left that existing expectation unchanged because #499 does not change the wash.
Author
Owner

Finished — fix-499

Head SHA: c46822922f6ae61b7412d8d5f2e22f62a5e7f96a

The Search palette now keeps its top edge fixed while result content grows below the input. At the height cap, only the results panel scrolls and the action row stays in flow below it. Preview content cannot set compact height. Typing and pointer changes use the shared spring; keyboard layout changes are immediate per #527.

Files

  • apps/web/src/lib/components/search-dialog.svelte
  • apps/web/e2e/search.mjs
  • bench/run.sh

Visual evidence

All 18 screenshots are attached below. Each cell links to few, many, and scrolled-end result states.

Width Paper White Tokyo Night
1440 few · many · end few · many · end
820 few · many · end few · many · end
390 few · many · end few · many · end

Verification

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/fix-499/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test summary:

 Test Files  140 passed (140)
      Tests  914 passed (914)
   Start at  00:40:57
   Duration  170.43s (transform 53%, environment 17%, import 16%, tests 10%, setup 4%)

Production build completed: ✓ built in 42.13s, Wrote site to "build", ✔ done (existing Rolldown vendor use client warnings remain).

Focused production-build E2E passed with --palette-layout-only --screenshots artifacts/fix-499: content height and input anchoring for 0, 3, 10, and 100 rows; capped scrolling on desktop and phone; keyboard-immediate and typing/pointer spring behavior; action row placement; 18 screenshots across 1440/820/390 and both themes. CSP REPORTS search: 0 across 4 pages.

cargo clean output:

     Removed 1 file, 356B total

No Rust crates changed, so no Rust crate gates ran. Build output and .svelte-kit were removed after verification. Screenshots remain attached to this issue and are not committed.

Performance

Local focused profile, 60 Search requests: p50 48.3 ms, p95 122.7 ms; server mean CPU 42.33%, peak CPU 89.96%, mean RSS 273.6 MiB, peak RSS 313.0 MiB (33 samples). The profile also exercises 100 result rows and the capped scrollport.

docs/perf/baseline.json has search_100k_palette p50 100.5 ms, p95 218.4 ms (46 samples). That baseline measures first-result frame latency against a 100k corpus; this local sample measures server round-trip latency on the seeded E2E corpus, so they are different measurements and are not an A/B comparison.

Adversarial round and known gaps

One real-server API-only round produced 79 findings; most were marked SLOW under shared-host load. Non-SLOW findings were recorded separately: upload size/type validation was reproduced in #553, stale Appearance/DAV probe expectations in #557, and VTODO completion not surviving readback in #558. The adversarial command exited with findings; these API issues are outside #499.

The broader Search E2E attempt before the origin/dev merge stopped on an existing sticky-section wash assertion: computed CSS serialized its zero-alpha stop as rgba(...) while the assertion expected the word transparent. I left that existing expectation unchanged. The focused #499 E2E passed after the merge.

Decisions not specified by DESIGN

  • On desktop, cap the compact palette bottom at two-thirds of the viewport and at 44rem; keep the input top fixed. This implements the owner’s “one third from the bottom” limit.
  • On phones, use the visual viewport, subtract both safe areas and 16px, and keep an 8px top inset. This keeps the results above the software keyboard.
  • Keep a 6px gap between the results scroller and action row; keep the result list as the sole scroller. Test-only cloned rows cover 10 and 100 results because the compact API result limit is lower.
## Finished — fix-499 **Head SHA:** `c46822922f6ae61b7412d8d5f2e22f62a5e7f96a` The Search palette now keeps its top edge fixed while result content grows below the input. At the height cap, only the results panel scrolls and the action row stays in flow below it. Preview content cannot set compact height. Typing and pointer changes use the shared spring; keyboard layout changes are immediate per #527. ### Files - `apps/web/src/lib/components/search-dialog.svelte` - `apps/web/e2e/search.mjs` - `bench/run.sh` ### Visual evidence All 18 screenshots are attached below. Each cell links to few, many, and scrolled-end result states. | Width | Paper White | Tokyo Night | | --- | --- | --- | | 1440 | [few](https://git.kayg.org/attachments/07d55f10-8e2a-40d4-918b-2b467f865ca4) · [many](https://git.kayg.org/attachments/d769d49d-300f-4be7-ab0a-98f1fdb30ad4) · [end](https://git.kayg.org/attachments/94895b32-1805-4bcb-8cfd-bc640a5a6985) | [few](https://git.kayg.org/attachments/0788905b-646a-4ce9-abf2-87966f8e21e8) · [many](https://git.kayg.org/attachments/cec71b76-174b-4521-8548-d924ee1ce55a) · [end](https://git.kayg.org/attachments/4a89d69c-4b3d-4555-b8f7-5aa74166286e) | | 820 | [few](https://git.kayg.org/attachments/06c8276b-69ff-4673-807e-84326c659461) · [many](https://git.kayg.org/attachments/bccf006a-6ffb-4fe6-acda-03e84b8db172) · [end](https://git.kayg.org/attachments/97d32841-257d-4fcf-9345-40871bffcb0a) | [few](https://git.kayg.org/attachments/08b7ae03-b205-4e0e-81b3-baaf154d2b83) · [many](https://git.kayg.org/attachments/61d65b2a-4608-463d-9ead-d4b95340c075) · [end](https://git.kayg.org/attachments/1a793d0d-639a-4d98-a451-abed956c56a8) | | 390 | [few](https://git.kayg.org/attachments/ed43e7e5-ac87-4684-bd88-a26f528180f8) · [many](https://git.kayg.org/attachments/2d779912-0d06-4c03-81fc-61e2efbe92a1) · [end](https://git.kayg.org/attachments/5fe20ed5-59b7-4e35-b1e1-edd90c39adb4) | [few](https://git.kayg.org/attachments/44e637c7-56b8-4e0c-b6d3-95e20e9ce7a8) · [many](https://git.kayg.org/attachments/78881d59-c1f5-40d2-811c-fb710a310233) · [end](https://git.kayg.org/attachments/eb17c7ed-0d96-430b-a9dc-30c997b38684) | ### Verification `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/fix-499/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test` summary: ```text Test Files 140 passed (140) Tests 914 passed (914) Start at 00:40:57 Duration 170.43s (transform 53%, environment 17%, import 16%, tests 10%, setup 4%) ``` Production build completed: `✓ built in 42.13s`, `Wrote site to "build"`, `✔ done` (existing Rolldown vendor `use client` warnings remain). Focused production-build E2E passed with `--palette-layout-only --screenshots artifacts/fix-499`: content height and input anchoring for 0, 3, 10, and 100 rows; capped scrolling on desktop and phone; keyboard-immediate and typing/pointer spring behavior; action row placement; 18 screenshots across 1440/820/390 and both themes. `CSP REPORTS search: 0 across 4 pages`. `cargo clean` output: ```text Removed 1 file, 356B total ``` No Rust crates changed, so no Rust crate gates ran. Build output and `.svelte-kit` were removed after verification. Screenshots remain attached to this issue and are not committed. ### Performance Local focused profile, 60 Search requests: p50 `48.3 ms`, p95 `122.7 ms`; server mean CPU `42.33%`, peak CPU `89.96%`, mean RSS `273.6 MiB`, peak RSS `313.0 MiB` (33 samples). The profile also exercises 100 result rows and the capped scrollport. `docs/perf/baseline.json` has `search_100k_palette` p50 `100.5 ms`, p95 `218.4 ms` (46 samples). That baseline measures first-result frame latency against a 100k corpus; this local sample measures server round-trip latency on the seeded E2E corpus, so they are different measurements and are not an A/B comparison. ### Adversarial round and known gaps One real-server API-only round produced 79 findings; most were marked SLOW under shared-host load. Non-SLOW findings were recorded separately: upload size/type validation was reproduced in [#553](https://git.kayg.org/kayg/calternal/issues/553), stale Appearance/DAV probe expectations in [#557](https://git.kayg.org/kayg/calternal/issues/557), and VTODO completion not surviving readback in [#558](https://git.kayg.org/kayg/calternal/issues/558). The adversarial command exited with findings; these API issues are outside #499. The broader Search E2E attempt before the origin/dev merge stopped on an existing sticky-section wash assertion: computed CSS serialized its zero-alpha stop as `rgba(...)` while the assertion expected the word `transparent`. I left that existing expectation unchanged. The focused #499 E2E passed after the merge. ### Decisions not specified by DESIGN - On desktop, cap the compact palette bottom at two-thirds of the viewport and at 44rem; keep the input top fixed. This implements the owner’s “one third from the bottom” limit. - On phones, use the visual viewport, subtract both safe areas and 16px, and keep an 8px top inset. This keeps the results above the software keyboard. - Keep a 6px gap between the results scroller and action row; keep the result list as the sole scroller. Test-only cloned rows cover 10 and 100 results because the compact API result limit is lower.
Author
Owner

Round 2 started on job/fix-499 at c46822922f6ae61b7412d8d5f2e22f62a5e7f96a. Before syncing, the branch's common base with origin/dev was 15e17aeafc8ea160c109e62fba57f6961c39d21f. I fetched origin/dev at 687ff703136e71e89f8dfba139e93cd0788b25c1; it includes merge 10651bb34 for #544/#545, and search-dialog.svelte has the Show all rows and indexing status behavior. I am merging this base before changing the palette sizing and font assertions.

Round 2 started on `job/fix-499` at `c46822922f6ae61b7412d8d5f2e22f62a5e7f96a`. Before syncing, the branch's common base with `origin/dev` was `15e17aeafc8ea160c109e62fba57f6961c39d21f`. I fetched `origin/dev` at `687ff703136e71e89f8dfba139e93cd0788b25c1`; it includes merge `10651bb34` for #544/#545, and `search-dialog.svelte` has the Show all rows and indexing status behavior. I am merging this base before changing the palette sizing and font assertions.
Author
Owner

Round 2 finding: measureCompactPalette() used only the result panel height, while .preview-pane had min-height: 0. With a short result list, the outer palette could be shorter than the active file preview and clip its title at the rounded lower edge. I am making the palette use the greater of result content and a preview minimum, with the preview as its own scrollport.

Motion decision: the shared rules in this job withdraw the old #527 instant-keyboard rule. The palette height will use the shared spring for keyboard, pointer and typing changes; reduced-motion settings still disable the transition.

Round 2 finding: `measureCompactPalette()` used only the result panel height, while `.preview-pane` had `min-height: 0`. With a short result list, the outer palette could be shorter than the active file preview and clip its title at the rounded lower edge. I am making the palette use the greater of result content and a preview minimum, with the preview as its own scrollport. Motion decision: the shared rules in this job withdraw the old #527 instant-keyboard rule. The palette height will use the shared spring for keyboard, pointer and typing changes; reduced-motion settings still disable the transition.
Author
Owner

The existing 1440×900 review capture shows the two-result palette ending at the title baseline: budget-2026.txt is clipped by the rounded lower edge. The preview content needs a 260px inner minimum to show its 150px thumbnail, title and two metadata rows. With the current two-thirds viewport cap, this minimum leaves less than the old fixed 80px growth gap for the later many-results state. I am keeping the growth and anchored-field checks, but measuring positive growth up to the cap instead of requiring 80px.

The existing 1440×900 review capture shows the two-result palette ending at the title baseline: `budget-2026.txt` is clipped by the rounded lower edge. The preview content needs a 260px inner minimum to show its 150px thumbnail, title and two metadata rows. With the current two-thirds viewport cap, this minimum leaves less than the old fixed 80px growth gap for the later many-results state. I am keeping the growth and anchored-field checks, but measuring positive growth up to the cap instead of requiring 80px.
Author
Owner

Follow-up on the growth probe: I am preserving the existing >80px assertion. The result-growth and spring checks now run at 1440×1200 to leave headroom below the viewport cap. The requested 1440×900 cases still test the preview minimum, 0/2/3/10/100 result sizing, scrolling and fixed input position.

Follow-up on the growth probe: I am preserving the existing `>80px` assertion. The result-growth and spring checks now run at 1440×1200 to leave headroom below the viewport cap. The requested 1440×900 cases still test the preview minimum, 0/2/3/10/100 result sizing, scrolling and fixed input position.
Author
Owner

Sizing e2e finding: settle() only checked that the search spinner was absent. It could return before the debounce started the next request. The zero-result probe then measured the previous Atlas response: 12 rows and 660px of result scroll height. I am waiting for the real Search API response with the typed query before measuring each zero, two-row and motion state.

Sizing e2e finding: `settle()` only checked that the search spinner was absent. It could return before the debounce started the next request. The zero-result probe then measured the previous Atlas response: 12 rows and 660px of result scroll height. I am waiting for the real Search API response with the typed query before measuring each zero, two-row and motion state.
Author
Owner

A focused 1440×900 probe confirmed a second geometry bug: the two-result content height was 313px, but the Search surface stayed at its 360px viewport cap. The component's windowEl.clientHeight - panelEl.clientHeight value already included the action row; the preview minimum then added it again. The measurement now counts the field separately and compares the full result column (list + action row) with the preview minimum.

A focused 1440×900 probe confirmed a second geometry bug: the two-result content height was 313px, but the Search surface stayed at its 360px viewport cap. The component's `windowEl.clientHeight - panelEl.clientHeight` value already included the action row; the preview minimum then added it again. The measurement now counts the field separately and compares the full result column (list + action row) with the preview minimum.
Author
Owner

The spring probe exposed an inherited #527 global keyboard-mode rule: it set every shared duration to 0ms and applied transition: none !important, even when prefers-reduced-motion was false. I removed the shared modality suppression and the Settings-only instant-entry path, keeping keyboard and pointer actions on the same shared motion contract. Reduced-motion still selects immediate transitions. I updated the shared motion and activation tests to assert this owner override.

The spring probe exposed an inherited #527 global keyboard-mode rule: it set every shared duration to 0ms and applied `transition: none !important`, even when `prefers-reduced-motion` was false. I removed the shared modality suppression and the Settings-only instant-entry path, keeping keyboard and pointer actions on the same shared motion contract. Reduced-motion still selects immediate transitions. I updated the shared motion and activation tests to assert this owner override.
Author
Owner

The full Search e2e found a theme-reset race after the #544 responsive captures. On the Calendar week route, setTheme() reloaded before the Appearance request had consumed the updated test storage and timed out with expected paper/light, actual tokyo-night-day/light. I changed the screenshot helper to seed the test User settings before navigation, wait for /api/v1/appearance, then verify the loaded route. This follows the #427 harness pattern used by the palette screenshots.

The full Search e2e found a theme-reset race after the #544 responsive captures. On the Calendar week route, `setTheme()` reloaded before the Appearance request had consumed the updated test storage and timed out with expected `paper/light`, actual `tokyo-night-day/light`. I changed the screenshot helper to seed the test User settings before navigation, wait for `/api/v1/appearance`, then verify the loaded route. This follows the #427 harness pattern used by the palette screenshots.
Author
Owner

The full Search e2e reaches the grouped-results flow after the #499 checks pass, then fails at expectedHeadings in apps/web/e2e/search.mjs:1297. The real atlas response has headings Notes, Journal, Days, Photos, Files; the existing assertion requires Notes, Log entries, Photos, Files. CONTEXT.md §Journal sets the UI source label to Journal, and the current search kind map uses Journal and Days. I left this existing expectation unchanged because #499 does not decide Search grouping vocabulary. The full-flow gate remains limited by this stale assertion.

The full Search e2e reaches the grouped-results flow after the #499 checks pass, then fails at `expectedHeadings` in `apps/web/e2e/search.mjs:1297`. The real `atlas` response has headings `Notes, Journal, Days, Photos, Files`; the existing assertion requires `Notes, Log entries, Photos, Files`. `CONTEXT.md` §Journal sets the UI source label to **Journal**, and the current search kind map uses `Journal` and `Days`. I left this existing expectation unchanged because #499 does not decide Search grouping vocabulary. The full-flow gate remains limited by this stale assertion.
Author
Owner

Finished #499/#529 on job/fix-499.

Head: f262d7cb5ee450d24b1974d3d7dba7df9758c9ba

Commits: 05d7a4105 merges origin/dev after round 4; f262d7cb5 contains this job's Search and motion fixes.

Built

  • Search palette height now uses the field row plus the taller of the complete results column (including its action row) and a 260px preview minimum. The preview scrolls inside its column when the palette reaches the viewport cap. Result growth keeps the field anchored.
  • Typing, pointer, and keyboard sizing use the shared spring. Reduced-motion preferences remain immediate.
  • The shared e2e harness waits for the Appearance response and document.fonts.ready. Search checks the result row's computed Google Sans family and document.fonts.check.
  • Added sizing assertions for 0, 2, 3, 10, and 100 rows, preview containment/scrolling, and input anchoring.

Files

  • Search and harness: apps/web/src/lib/components/search-dialog.svelte, apps/web/e2e/search.mjs, apps/web/e2e/harness.mjs
  • Shared keyboard motion and coverage: packages/ui/src/motion.ts, packages/ui/src/tokens.css, packages/ui/src/components/OverlaySurface.svelte, packages/ui/src/components/FloatingSidebar.svelte, packages/ui/src/components/SegmentedControl.svelte, packages/ui/src/components/TabBar.svelte, packages/ui/src/components/calendar/GridColumn.svelte, packages/ui/src/components/menu/FloatingSurface.svelte, apps/web/src/routes/+layout.svelte, apps/web/src/routes/settings/[...path]/+page.svelte, apps/web/src/app.d.ts, apps/web/src/lib/a11y/inputModality.ts, apps/web/src/lib/actions/pillFeedback.ts, apps/web/src/lib/actions/pillFeedback.test.ts, apps/web/src/lib/capsule-motion.test.ts, apps/web/src/lib/components/AppToaster.svelte, apps/web/src/lib/components/SidebarLinks.svelte, apps/web/src/lib/files/FilesSidebar.svelte, apps/web/src/lib/notifications/InboxPanel.svelte, apps/web/e2e/kbd-motion-527.mjs

Validation

bun run check:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
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/fix-499/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test:

 Test Files  148 passed (148)
      Tests  1011 passed (1011)
   Start at  03:07:17
   Duration  134.97s (transform 50%, environment 19%, import 15%, tests 12%, setup 4%)

Focused Search sizing e2e passed, including:

PASS mocked Index progress indicator, announcements and stable action bounds at phone, tablet and desktop widths in both themes
PASS complete Navigate rows and keyboard-accessible Files Show all
PASS phone search action hit targets
PASS content height for 0, 3, 10 and 100 results; capped desktop and phone scrolling
PASS keyboard, pointer and typing use shared spring sizing
PASS content-sized palette, anchored field and in-flow action row
PASS open, Esc and click-outside close
search palette layout e2e: ok
CSP REPORTS search: 0 across 2 pages

bun apps/web/e2e/kbd-motion-527.mjs passed:

PASS keyboard and pointer selection, Tab switch and overlay actions share motion

The full Search e2e reached a pre-existing grouped-results assertion and stopped: actual atlas groups are Notes, Journal, Days, Photos, Files; the existing expectation at apps/web/e2e/search.mjs:1297 is Notes, Log entries, Photos, Files. I left that expectation unchanged because #499 does not change group labels, as required by the test rule. The focused layout run passed the #499 sizing cases.

Screenshots (390/820/1440px; light and dark)

Performance: local focused Search profile, small fixture and debug server: p50 53.2ms, p95 139.7ms across 60 requests; mean RSS 519,174,030 bytes, peak RSS 521,109,504 bytes; mean CPU 70.51%, peak CPU 100.06%. The baseline search_1m_palette is p50 53.8ms and p95 116ms on the perf VM with a 1M corpus and 21 samples, so the environments and data sizes are not directly comparable.

Decision: use a 260px CSS preview minimum, derived from the 150px thumbnail, title, two metadata lines, and their spacing. The keyboard animation behavior follows the explicit 2026-10-01 owner override; reduced motion remains immediate.

cargo clean completed; apps/web/build was removed. No Rust source was changed by this job, so Rust crate gates were not run.

Finished #499/#529 on `job/fix-499`. **Head:** `f262d7cb5ee450d24b1974d3d7dba7df9758c9ba` **Commits:** `05d7a4105` merges `origin/dev` after round 4; `f262d7cb5` contains this job's Search and motion fixes. **Built** - Search palette height now uses the field row plus the taller of the complete results column (including its action row) and a 260px preview minimum. The preview scrolls inside its column when the palette reaches the viewport cap. Result growth keeps the field anchored. - Typing, pointer, and keyboard sizing use the shared spring. Reduced-motion preferences remain immediate. - The shared e2e harness waits for the Appearance response and `document.fonts.ready`. Search checks the result row's computed Google Sans family and `document.fonts.check`. - Added sizing assertions for 0, 2, 3, 10, and 100 rows, preview containment/scrolling, and input anchoring. **Files** - Search and harness: `apps/web/src/lib/components/search-dialog.svelte`, `apps/web/e2e/search.mjs`, `apps/web/e2e/harness.mjs` - Shared keyboard motion and coverage: `packages/ui/src/motion.ts`, `packages/ui/src/tokens.css`, `packages/ui/src/components/OverlaySurface.svelte`, `packages/ui/src/components/FloatingSidebar.svelte`, `packages/ui/src/components/SegmentedControl.svelte`, `packages/ui/src/components/TabBar.svelte`, `packages/ui/src/components/calendar/GridColumn.svelte`, `packages/ui/src/components/menu/FloatingSurface.svelte`, `apps/web/src/routes/+layout.svelte`, `apps/web/src/routes/settings/[...path]/+page.svelte`, `apps/web/src/app.d.ts`, `apps/web/src/lib/a11y/inputModality.ts`, `apps/web/src/lib/actions/pillFeedback.ts`, `apps/web/src/lib/actions/pillFeedback.test.ts`, `apps/web/src/lib/capsule-motion.test.ts`, `apps/web/src/lib/components/AppToaster.svelte`, `apps/web/src/lib/components/SidebarLinks.svelte`, `apps/web/src/lib/files/FilesSidebar.svelte`, `apps/web/src/lib/notifications/InboxPanel.svelte`, `apps/web/e2e/kbd-motion-527.mjs` **Validation** `bun run check`: ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. 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/fix-499/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text Test Files 148 passed (148) Tests 1011 passed (1011) Start at 03:07:17 Duration 134.97s (transform 50%, environment 19%, import 15%, tests 12%, setup 4%) ``` Focused Search sizing e2e passed, including: ```text PASS mocked Index progress indicator, announcements and stable action bounds at phone, tablet and desktop widths in both themes PASS complete Navigate rows and keyboard-accessible Files Show all PASS phone search action hit targets PASS content height for 0, 3, 10 and 100 results; capped desktop and phone scrolling PASS keyboard, pointer and typing use shared spring sizing PASS content-sized palette, anchored field and in-flow action row PASS open, Esc and click-outside close search palette layout e2e: ok CSP REPORTS search: 0 across 2 pages ``` `bun apps/web/e2e/kbd-motion-527.mjs` passed: ```text PASS keyboard and pointer selection, Tab switch and overlay actions share motion ``` The full Search e2e reached a pre-existing grouped-results assertion and stopped: actual `atlas` groups are `Notes, Journal, Days, Photos, Files`; the existing expectation at `apps/web/e2e/search.mjs:1297` is `Notes, Log entries, Photos, Files`. I left that expectation unchanged because #499 does not change group labels, as required by the test rule. The focused layout run passed the #499 sizing cases. **Screenshots** (390/820/1440px; light and dark) - Light: [390](https://git.kayg.org/attachments/9d2325f6-2f67-4c1e-9578-613d8231f4be), [820](https://git.kayg.org/attachments/8f633d74-806b-4ac8-9a52-237c5b5195d2), [1440](https://git.kayg.org/attachments/369eaace-05bf-4e70-ae80-0bdf489e0f59) - Dark: [390](https://git.kayg.org/attachments/a0d2ec76-12dc-4639-8bda-cc7ffeb77a2f), [820](https://git.kayg.org/attachments/714af896-3fe7-4124-8894-1607f65ec9a8), [1440](https://git.kayg.org/attachments/1cba7776-5ad3-4cf4-9aa4-84a5668dfd68) **Performance:** local focused Search profile, small fixture and debug server: p50 53.2ms, p95 139.7ms across 60 requests; mean RSS 519,174,030 bytes, peak RSS 521,109,504 bytes; mean CPU 70.51%, peak CPU 100.06%. The baseline `search_1m_palette` is p50 53.8ms and p95 116ms on the perf VM with a 1M corpus and 21 samples, so the environments and data sizes are not directly comparable. **Decision:** use a 260px CSS preview minimum, derived from the 150px thumbnail, title, two metadata lines, and their spacing. The keyboard animation behavior follows the explicit 2026-10-01 owner override; reduced motion remains immediate. `cargo clean` completed; `apps/web/build` was removed. No Rust source was changed by this job, so Rust crate gates were not run.
Author
Owner

Owner (2026-10-02): the bottom band is back in the latest screenshots. Agreed design: the action hints are separate small floating glass pills at the bottom right of the results column, floating over the results (ProgressiveBlur behind), no band, no joined bar, no divider line; no hairline under the search field either. Round 3 carries this.

Owner (2026-10-02): the bottom band is back in the latest screenshots. Agreed design: the action hints are separate small floating glass pills at the bottom right of the results column, floating over the results (ProgressiveBlur behind), no band, no joined bar, no divider line; no hairline under the search field either. Round 3 carries this.
Author
Owner

Starting round 3 of #499/#529 on job/fix-499. Current HEAD is f262d7cb5ee450d24b1974d3d7dba7df9758c9ba; the merge base with origin/dev is 687ff703136e71e89f8dfba139e93cd0788b25c1. I am tracing the palette input focus styles, preview caption metrics, and agreed floating result actions before making the visual fixes and recapturing both themes at 390/820/1440.

Starting round 3 of #499/#529 on `job/fix-499`. Current HEAD is `f262d7cb5ee450d24b1974d3d7dba7df9758c9ba`; the merge base with `origin/dev` is `687ff703136e71e89f8dfba139e93cd0788b25c1`. I am tracing the palette input focus styles, preview caption metrics, and agreed floating result actions before making the visual fixes and recapturing both themes at 390/820/1440.
Author
Owner

Round 3 adversarial note (local real server): the API-only selector completed without a server crash or any 5xx response. It repeated the existing findings already tracked in #664: cross-source tag rename returned 409; reconciliation after restoring malformed folder metadata and XMP still returned 409; after deleting rebuildable tag rows, reconciliation returned 409 and the tag page was empty. The CalDAV update returned 204, while the probe expected 201; its later cross-date update and DELETE returned 412 after the probe kept an old ETag. Those later 412 results are a stale-probe-ETag cascade, not separate findings. I did not change any existing expectation.

The API-only selector also invoked mcp_probe.py without creating mcp-passwords.json; setup.mjs only creates that fixture for the MCP-only and combined MCP selectors. MCP coverage therefore stopped at FileNotFoundError. The profile printed its concurrency cases as SLOW; I treated those as load only. No duplicate issue was opened because #664 already records the same findings.

Round 3 adversarial note (local real server): the API-only selector completed without a server crash or any 5xx response. It repeated the existing findings already tracked in [#664](https://git.kayg.org/kayg/calternal/issues/664): cross-source tag rename returned 409; reconciliation after restoring malformed folder metadata and XMP still returned 409; after deleting rebuildable tag rows, reconciliation returned 409 and the tag page was empty. The CalDAV update returned 204, while the probe expected 201; its later cross-date update and DELETE returned 412 after the probe kept an old ETag. Those later 412 results are a stale-probe-ETag cascade, not separate findings. I did not change any existing expectation. The API-only selector also invoked `mcp_probe.py` without creating `mcp-passwords.json`; `setup.mjs` only creates that fixture for the MCP-only and combined MCP selectors. MCP coverage therefore stopped at `FileNotFoundError`. The profile printed its concurrency cases as SLOW; I treated those as load only. No duplicate issue was opened because #664 already records the same findings.
Author
Owner

Finished Round 3 for #499/#529.

What changed

  • The Search field uses the shared rounded accent focus ring for keyboard focus. The native input has no inner square outline or shadow; pointer focus does not show the keyboard ring.
  • The result actions are separate shared Pill controls at the bottom right of the results column. They float over scrolling results with ProgressiveBlur behind them. The field divider and the joined footer band are gone.
  • Keyboard navigation keeps a selected result above the floating actions. A temporary scroll tail appears only when the selected row would overlap them.
  • The preview keeps the full filename line box and scales its thumbnail to min(150px, 10dvh) so the caption fits the compact palette.
  • Calendar's delayed focus work no longer takes focus from an open overlay.

Files: apps/web/src/lib/components/search-dialog.svelte, apps/web/src/lib/search/SearchField.svelte, apps/web/src/lib/search/SearchPreview.svelte, apps/web/src/routes/calendar/[view]/[date]/+page.svelte, apps/web/e2e/search.mjs, and docs/perf/baseline.json.

Screenshots

Palette with a few results:

Palette at the end of its results:

Commits

  • e3abef98f Fix Search palette focus and action overlays
  • 45c93747c Record Search palette layout benchmark

Head SHA: 45c93747cfc25bf95f4d4fde523b48f252272aa8.

Gates

bun run check output:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
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/fix-499/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test summary, verbatim:

 Test Files  153 passed (153)
      Tests  1053 passed (1053)
   Start at  09:03:14
   Duration  83.66s (transform 52%, environment 18%, import 16%, tests 11%, setup 4%)

Production build completed with ✓ built in 29.59s, Wrote site to "build", and ✔ done. The focused Search E2E ended with PASS content-sized palette, anchored field and floating action Pills, PASS open, Esc and click-outside close, search palette layout e2e: ok, and CSP REPORTS search: 0 across 4 pages.

No Rust files changed, so Rust crate gates were not run. cargo clean removed 7237 files (4.6 GiB); apps/web/build and apps/web/.svelte-kit were deleted.

Performance

The search-palette-layout profile ran locally on the shared host. It measured server round trip p50 52.6 ms / p95 248.3 ms over 60 queries; mean CPU 56.07%, mean RSS 518,978,163 bytes, peak CPU 96.75%, and peak RSS 521,576,448 bytes. The test covered 0, 3, 10, and 100 results. docs/perf/baseline.json now records this as a local reference with no feature-specific baseline; load average was not recorded for that run.

Adversarial round and known gaps

ADVERSARIAL_API_ONLY=1 completed on a real local server. It reported no crash or 5xx. Non-SLOW tag reconciliation and CalDAV observations are already recorded for triage in #664; the CalDAV probe's later 412 responses followed its retained old ETag. I did not alter its expectations. MCP coverage stopped because this selector did not create mcp-passwords.json. The profile marked its concurrency findings SLOW; I treated them as load only. No duplicate issue was opened.

Decisions

  • DESIGN does not set the exact thumbnail height for a clipped filename. I used min(150px, 10dvh) to keep the caption visible at the 1440×900 cap while retaining a 150px maximum.
  • DESIGN does not define selected-row clearance from floating Pills. The keyboard path adds a temporary scroll tail only when geometry shows an overlap, then leaves an 8px clearance.
  • The new floating-action test only requires row movement when the panel has scroll range. Short palettes do not scroll; the test still checks movement when content overflows.
Finished Round 3 for #499/#529. ## What changed - The Search field uses the shared rounded accent focus ring for keyboard focus. The native input has no inner square outline or shadow; pointer focus does not show the keyboard ring. - The result actions are separate shared Pill controls at the bottom right of the results column. They float over scrolling results with ProgressiveBlur behind them. The field divider and the joined footer band are gone. - Keyboard navigation keeps a selected result above the floating actions. A temporary scroll tail appears only when the selected row would overlap them. - The preview keeps the full filename line box and scales its thumbnail to `min(150px, 10dvh)` so the caption fits the compact palette. - Calendar's delayed focus work no longer takes focus from an open overlay. Files: `apps/web/src/lib/components/search-dialog.svelte`, `apps/web/src/lib/search/SearchField.svelte`, `apps/web/src/lib/search/SearchPreview.svelte`, `apps/web/src/routes/calendar/[view]/[date]/+page.svelte`, `apps/web/e2e/search.mjs`, and `docs/perf/baseline.json`. ## Screenshots Palette with a few results: - 390 px: [paper white](https://git.kayg.org/attachments/ee15b046-227c-4e08-b08e-68b1e887a4f5), [Tokyo Night](https://git.kayg.org/attachments/01dcc501-3335-45fe-9395-10813dfbf0ff) - 820 px: [paper white](https://git.kayg.org/attachments/677db3aa-5b24-4546-bd54-690d3b6d88da), [Tokyo Night](https://git.kayg.org/attachments/a453a0b2-f22a-4a46-811e-1c563f2b91dc) - 1440 px: [paper white](https://git.kayg.org/attachments/b6974945-655f-402e-a075-45bc611930a0), [Tokyo Night](https://git.kayg.org/attachments/f2052e14-d626-43ac-bbda-a0e6a200ae2b) Palette at the end of its results: - 390 px: [paper white](https://git.kayg.org/attachments/ceaf80c8-b339-4443-9d20-d7fe3593e403), [Tokyo Night](https://git.kayg.org/attachments/db01b62e-9bfa-4902-ab64-49e360989fb0) - 820 px: [paper white](https://git.kayg.org/attachments/fba6adb3-328c-4644-9763-577fbced5bb1), [Tokyo Night](https://git.kayg.org/attachments/ff3ed583-53a0-48fc-ac31-35c68191349f) - 1440 px: [paper white](https://git.kayg.org/attachments/a38fb2bd-3639-49e6-b515-009f42948773), [Tokyo Night](https://git.kayg.org/attachments/20e76bf6-98b4-4631-b4b1-1d77c3d1b63e) ## Commits - `e3abef98f` Fix Search palette focus and action overlays - `45c93747c` Record Search palette layout benchmark Head SHA: `45c93747cfc25bf95f4d4fde523b48f252272aa8`. ## Gates `bun run check` output: ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. 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/fix-499/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test` summary, verbatim: ```text Test Files 153 passed (153) Tests 1053 passed (1053) Start at 09:03:14 Duration 83.66s (transform 52%, environment 18%, import 16%, tests 11%, setup 4%) ``` Production build completed with `✓ built in 29.59s`, `Wrote site to "build"`, and `✔ done`. The focused Search E2E ended with `PASS content-sized palette, anchored field and floating action Pills`, `PASS open, Esc and click-outside close`, `search palette layout e2e: ok`, and `CSP REPORTS search: 0 across 4 pages`. No Rust files changed, so Rust crate gates were not run. `cargo clean` removed 7237 files (4.6 GiB); `apps/web/build` and `apps/web/.svelte-kit` were deleted. ## Performance The `search-palette-layout` profile ran locally on the shared host. It measured server round trip p50 52.6 ms / p95 248.3 ms over 60 queries; mean CPU 56.07%, mean RSS 518,978,163 bytes, peak CPU 96.75%, and peak RSS 521,576,448 bytes. The test covered 0, 3, 10, and 100 results. `docs/perf/baseline.json` now records this as a local reference with no feature-specific baseline; load average was not recorded for that run. ## Adversarial round and known gaps `ADVERSARIAL_API_ONLY=1` completed on a real local server. It reported no crash or 5xx. Non-SLOW tag reconciliation and CalDAV observations are already recorded for triage in [#664](https://git.kayg.org/kayg/calternal/issues/664); the CalDAV probe's later 412 responses followed its retained old ETag. I did not alter its expectations. MCP coverage stopped because this selector did not create `mcp-passwords.json`. The profile marked its concurrency findings SLOW; I treated them as load only. No duplicate issue was opened. ## Decisions - DESIGN does not set the exact thumbnail height for a clipped filename. I used `min(150px, 10dvh)` to keep the caption visible at the 1440×900 cap while retaining a 150px maximum. - DESIGN does not define selected-row clearance from floating Pills. The keyboard path adds a temporary scroll tail only when geometry shows an overlap, then leaves an 8px clearance. - The new floating-action test only requires row movement when the panel has scroll range. Short palettes do not scroll; the test still checks movement when content overflows.
Author
Owner

Starting #499 round 4 on job/fix-499 at 45c93747cfc25bf95f4d4fde523b48f252272aa8; base SHA (merge-base origin/dev) is c4a61e8cf090170f35b1bed3350d9de20c83ecd5. Scope: separate each Search palette floating pill label from its single active shortcut using the shared Kbd keycap presentation, then capture the requested production screenshots.

Starting #499 round 4 on `job/fix-499` at `45c93747cfc25bf95f4d4fde523b48f252272aa8`; base SHA (`merge-base origin/dev`) is `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`. Scope: separate each Search palette floating pill label from its single active shortcut using the shared `Kbd` keycap presentation, then capture the requested production screenshots.
Author
Owner

Finding in apps/web/src/lib/components/search-dialog.svelte: the three desktop result pills used Kbd's plain inline-text variant, and the palette set .kbd { margin-left: 0 }. That removed Kbd's menu-row auto spacing while leaving no local gap; the shortcut also had no keycap surface. I changed the hint group to use Kbd variant="caps" inside a flex label/key wrapper with the shared --icon-label-gap token. Kbd continues to choose the single active platform/browser binding from the shortcut registry.

Finding in `apps/web/src/lib/components/search-dialog.svelte`: the three desktop result pills used Kbd's plain inline-text variant, and the palette set `.kbd { margin-left: 0 }`. That removed Kbd's menu-row auto spacing while leaving no local gap; the shortcut also had no keycap surface. I changed the hint group to use `Kbd variant="caps"` inside a flex label/key wrapper with the shared `--icon-label-gap` token. Kbd continues to choose the single active platform/browser binding from the shortcut registry.
Author
Owner

#499 round 4 report

Built: Search result action hints now pair their labels with one active shortcut from the shared Kbd component. The key uses the shared caps style. The flex pair uses --icon-label-gap, so the separation does not depend on inline whitespace.

Files: apps/web/src/lib/components/search-dialog.svelte

Head: 43b545fe4ced8c171a496c23a629671c1a80981b

Base: c4a61e8cf090170f35b1bed3350d9de20c83ecd5; git fetch origin && git merge origin/dev reported Already up to date.

Production screenshots: Captured from this worktree's production SPA on a local Calternal server with real Search API fixtures.

Width Paper Tokyo Night
390 px PNG PNG
820 px PNG PNG
1440 px PNG PNG

3× shortcut crops: The label and keycap centre lines were checked in both themes.

Paper, 3× shortcut crop

Tokyo Night, 3× shortcut crop

Gate output

bun run check:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
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/fix-499/apps/web
Getting Svelte diagnostics...
svelte-check found 0 errors and 0 warnings

bun run test:

 Test Files  153 passed (153)
      Tests  1053 passed (1053)
   Start at  10:46:18
   Duration  112.70s (transform 48%, environment 20%, import 15%, tests 11%, setup 6%)

Focused production Search capture:

PASS phone search action hit targets
PASS content height for 0, 3, 10 and 100 results; capped desktop and phone scrolling
PASS keyboard, pointer and typing use shared spring sizing
PASS content-sized palette, anchored field and floating action Pills
PASS open, Esc and click-outside close
search palette layout e2e: ok
CSP REPORTS search: 0 across 4 pages

UX gaps closed: Added a standard gap and keycap surface to all three desktop hints. Touch hit targets, keyboard motion and palette layout checks passed. The shared button labels and shortcut bindings stay attached to their controls.

UX gaps left: None found in the changed hints.

Known verification note: An extra 2× review pass also tried 2560 px. It timed out at apps/web/e2e/search.mjs:614 while waiting for a group heading to reach the results-panel top. That pass logged local search p50 79.2 ms / p95 249.4 ms while other cargo jobs were active. The requested 390/820/1440 matrix passed in the focused run. I left the existing 2560 assertion unchanged.

Decisions: DESIGN §34 requires shortcuts from the shared registry and one active binding, but does not set the label-to-key spacing or Kbd variant. I used the existing --icon-label-gap token and Kbd variant="caps".

Performance: No benchmark profile change. This is a presentation fix in the existing Search palette; the existing #367 search-ui profile covers that path. No Rust source changed, so Rust crate gates did not apply.

Cleanup: cargo clean printed Removed 7249 files, 3.8GiB total; the web build output was deleted. No push, deploy or merge was made.

## #499 round 4 report **Built:** Search result action hints now pair their labels with one active shortcut from the shared `Kbd` component. The key uses the shared caps style. The flex pair uses `--icon-label-gap`, so the separation does not depend on inline whitespace. **Files:** `apps/web/src/lib/components/search-dialog.svelte` **Head:** `43b545fe4ced8c171a496c23a629671c1a80981b` **Base:** `c4a61e8cf090170f35b1bed3350d9de20c83ecd5`; `git fetch origin && git merge origin/dev` reported `Already up to date.` **Production screenshots:** Captured from this worktree's production SPA on a local Calternal server with real Search API fixtures. | Width | Paper | Tokyo Night | |---|---|---| | 390 px | [PNG](https://git.kayg.org/attachments/ee727e31-b9d4-4b85-9ae4-54a2158f3d5c) | [PNG](https://git.kayg.org/attachments/7901f237-290a-49a5-b1c5-5351410df79a) | | 820 px | [PNG](https://git.kayg.org/attachments/6e9e1cb8-c88e-46c7-bc29-0f3e2c7b6303) | [PNG](https://git.kayg.org/attachments/b39caba5-a31a-412e-9abd-79669efb0c29) | | 1440 px | [PNG](https://git.kayg.org/attachments/24cd267c-5efe-4f77-8658-e73e5757eba0) | [PNG](https://git.kayg.org/attachments/d5b7fcd9-6f10-45fb-abc2-4c1b84863789) | **3× shortcut crops:** The label and keycap centre lines were checked in both themes. ![Paper, 3× shortcut crop](https://git.kayg.org/attachments/edc31405-f144-4306-abd0-3900d740b5fe) ![Tokyo Night, 3× shortcut crop](https://git.kayg.org/attachments/a761cf7b-ee63-41de-a169-714300de8650) ### Gate output `bun run check`: ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. 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/fix-499/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text Test Files 153 passed (153) Tests 1053 passed (1053) Start at 10:46:18 Duration 112.70s (transform 48%, environment 20%, import 15%, tests 11%, setup 6%) ``` Focused production Search capture: ```text PASS phone search action hit targets PASS content height for 0, 3, 10 and 100 results; capped desktop and phone scrolling PASS keyboard, pointer and typing use shared spring sizing PASS content-sized palette, anchored field and floating action Pills PASS open, Esc and click-outside close search palette layout e2e: ok CSP REPORTS search: 0 across 4 pages ``` **UX gaps closed:** Added a standard gap and keycap surface to all three desktop hints. Touch hit targets, keyboard motion and palette layout checks passed. The shared button labels and shortcut bindings stay attached to their controls. **UX gaps left:** None found in the changed hints. **Known verification note:** An extra 2× review pass also tried 2560 px. It timed out at `apps/web/e2e/search.mjs:614` while waiting for a group heading to reach the results-panel top. That pass logged local search p50 79.2 ms / p95 249.4 ms while other cargo jobs were active. The requested 390/820/1440 matrix passed in the focused run. I left the existing 2560 assertion unchanged. **Decisions:** DESIGN §34 requires shortcuts from the shared registry and one active binding, but does not set the label-to-key spacing or Kbd variant. I used the existing `--icon-label-gap` token and `Kbd variant="caps"`. **Performance:** No benchmark profile change. This is a presentation fix in the existing Search palette; the existing #367 `search-ui` profile covers that path. No Rust source changed, so Rust crate gates did not apply. Cleanup: `cargo clean` printed `Removed 7249 files, 3.8GiB total`; the web build output was deleted. No push, deploy or merge was made.
Author
Owner

Round 5 started on job/fix-499 at base c4a61e8cf090170f35b1bed3350d9de20c83ecd5 (origin/dev). Current head: 43b545fe4ced8c171a496c23a629671c1a80981b.

I read CLAUDE.md, CONTEXT.md, and DESIGN §32–34, including the shared glass material rules. I am checking the Search palette's bottom action area, its rendering checks, and the shared glass token classes before making the requested correction.

Round 5 started on `job/fix-499` at base `c4a61e8cf090170f35b1bed3350d9de20c83ecd5` (`origin/dev`). Current head: `43b545fe4ced8c171a496c23a629671c1a80981b`. I read `CLAUDE.md`, `CONTEXT.md`, and DESIGN §32–34, including the shared glass material rules. I am checking the Search palette's bottom action area, its rendering checks, and the shared glass token classes before making the requested correction.
Author
Owner

Finding: apps/web/src/lib/components/search-dialog.svelte still mounts .sw-actions-blur as a full-width ProgressiveBlur at the bottom of .results-column; the three selected-result Pills do not carry .glass-chrome. The palette background uses material="clear", which is only a compatibility role. I will remove the band, use the shared glass-chrome role on each action Pill, and make the palette use the shared glass material role directly. The e2e checks will verify that the shared token material is present on the palette and every Pill.

Finding: `apps/web/src/lib/components/search-dialog.svelte` still mounts `.sw-actions-blur` as a full-width `ProgressiveBlur` at the bottom of `.results-column`; the three selected-result Pills do not carry `.glass-chrome`. The palette background uses `material="clear"`, which is only a compatibility role. I will remove the band, use the shared glass-chrome role on each action Pill, and make the palette use the shared glass material role directly. The e2e checks will verify that the shared token material is present on the palette and every Pill.
Author
Owner

Finding from the production e2e check: ActionPill renders the correct shared chrome tint from its base Pill style, but it does not forward the caller's class="glass-chrome"; computed styles show no .glass-chrome, backdrop-filter: none, and a dark rgb(99, 106, 115) border. I am checking the existing Pill props for the supported shared material hook and will use that API. The test caught this before review capture.

Finding from the production e2e check: `ActionPill` renders the correct shared chrome tint from its base Pill style, but it does not forward the caller's `class="glass-chrome"`; computed styles show no `.glass-chrome`, `backdrop-filter: none`, and a dark `rgb(99, 106, 115)` border. I am checking the existing Pill props for the supported shared material hook and will use that API. The test caught this before review capture.
Author
Owner

The Pill API provides frosted for this case. The component applies .glass-chrome through that prop; a caller class with the same name is removed by its false class directive. Search now uses frosted on each selected-result Pill, so the shared filter and glass hairline come from tokens.css without local material values.

The Pill API provides `frosted` for this case. The component applies `.glass-chrome` through that prop; a caller class with the same name is removed by its false class directive. Search now uses `frosted` on each selected-result Pill, so the shared filter and glass hairline come from `tokens.css` without local material values.
Author
Owner

Round 5 report

Removed the full-width ProgressiveBlur strip from Search. The last result rows stay crisp. Open, See all and Actions each use the shared frosted glass material on the pill itself, so scrolling results blur only inside each pill. The palette surface now uses the translucent shared overlay material. E2E checks compare the computed material against shared glass tokens and assert that no full-width blur is mounted.

Files

  • apps/web/src/lib/components/search-dialog.svelte
  • apps/web/e2e/search.mjs

Commit

242022301673dc6746d89985ee36078743723591 — fix(search): keep glass blur inside action pills

Visual evidence

Production Search captures use macOS platform emulation. Both themes are covered at 1440×900 and 390×844. The crop shows a result row under the pills at 3× scale.

Search, macOS emulation, 1440px, paper-white
Search, macOS emulation, 390px, paper-white
Search, macOS emulation, 1440px, Tokyo Night
Search, macOS emulation, 390px, Tokyo Night
3× crop of action pills over a result row

Gates

bun run check:

$ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json
User browser caches use userStorage; only documented device/public-link exceptions remain.
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/fix-499/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test:

$ vitest run

 RUN  v5.0.1 /home/kayg/Developer/calternal-wt/fix-499/apps/web

 Test Files  153 passed (153)
      Tests  1053 passed (1053)
   Start at  12:40:12
   Duration  84.83s (transform 50%, import 18%, environment 17%, tests 11%, setup 3%)

  Transform  |component| transforming modules took 133.64s · 43% of tracked time, re-done on every run
             persist transforms across runs with fsModuleCache: true
             learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns

Production screenshot flow:

screenshot search-glass-macos-1440-paper-white.png
screenshot search-glass-macos-390-paper-white.png
screenshot search-glass-macos-1440-tokyo-night.png
screenshot search-glass-macos-390-tokyo-night.png
3x action-pill crop search-glass-macos-pills-over-row-3x.png
CSP REPORTS search: 0 across 6 pages

UX gaps closed

  • Removed the blurred strip across the whole results column.
  • Gave each action pill its own shared blur and glass border.
  • Changed the palette from a clear/opaque surface to the shared translucent overlay material.
  • Added desktop and touch assertions for the missing full-width band and added shared-token checks for the surface and pills.

UX gaps left / known gaps

  • The macOS VM is offline. These are production screenshots with the requested macOS platform signals emulated in Playwright, not captures from a real Mac.

Decisions

  • The requested 3× overlap crop uses the paper-white theme. Both paper-white and Tokyo Night are captured at the requested desktop and phone widths.
## Round 5 report Removed the full-width ProgressiveBlur strip from Search. The last result rows stay crisp. Open, See all and Actions each use the shared frosted glass material on the pill itself, so scrolling results blur only inside each pill. The palette surface now uses the translucent shared overlay material. E2E checks compare the computed material against shared glass tokens and assert that no full-width blur is mounted. ### Files - `apps/web/src/lib/components/search-dialog.svelte` - `apps/web/e2e/search.mjs` ### Commit `242022301673dc6746d89985ee36078743723591` — `fix(search): keep glass blur inside action pills` ### Visual evidence Production Search captures use macOS platform emulation. Both themes are covered at 1440×900 and 390×844. The crop shows a result row under the pills at 3× scale. ![Search, macOS emulation, 1440px, paper-white](https://git.kayg.org/attachments/65f2c0f4-66cf-41cf-ae06-cd28d987300d) ![Search, macOS emulation, 390px, paper-white](https://git.kayg.org/attachments/a1ee401e-9082-459d-8834-ffa74c694af9) ![Search, macOS emulation, 1440px, Tokyo Night](https://git.kayg.org/attachments/f69c17a5-8275-4def-8a20-43d06d9b4e8b) ![Search, macOS emulation, 390px, Tokyo Night](https://git.kayg.org/attachments/9bc7bb1b-9f13-4f8b-9222-4e0ca0371fe5) ![3× crop of action pills over a result row](https://git.kayg.org/attachments/04e7e1b5-b9e8-43fc-9e5b-37aaf0cf5f11) ### Gates `bun run check`: ```text $ node scripts/check-user-storage.mjs && node scripts/check-type-tokens.mjs && node scripts/check-motion-tokens.mjs && svelte-kit sync && svelte-check --tsconfig ./tsconfig.json User browser caches use userStorage; only documented device/public-link exceptions remain. 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/fix-499/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text $ vitest run RUN v5.0.1 /home/kayg/Developer/calternal-wt/fix-499/apps/web Test Files 153 passed (153) Tests 1053 passed (1053) Start at 12:40:12 Duration 84.83s (transform 50%, import 18%, environment 17%, tests 11%, setup 3%) Transform |component| transforming modules took 133.64s · 43% of tracked time, re-done on every run persist transforms across runs with fsModuleCache: true learn more: https://vitest.dev/guide/improving-performance#caching-between-reruns ``` Production screenshot flow: ```text screenshot search-glass-macos-1440-paper-white.png screenshot search-glass-macos-390-paper-white.png screenshot search-glass-macos-1440-tokyo-night.png screenshot search-glass-macos-390-tokyo-night.png 3x action-pill crop search-glass-macos-pills-over-row-3x.png CSP REPORTS search: 0 across 6 pages ``` ### UX gaps closed - Removed the blurred strip across the whole results column. - Gave each action pill its own shared blur and glass border. - Changed the palette from a clear/opaque surface to the shared translucent overlay material. - Added desktop and touch assertions for the missing full-width band and added shared-token checks for the surface and pills. ### UX gaps left / known gaps - The macOS VM is offline. These are production screenshots with the requested macOS platform signals emulated in Playwright, not captures from a real Mac. ### Decisions - The requested 3× overlap crop uses the paper-white theme. Both paper-white and Tokyo Night are captured at the requested desktop and phone widths.
Author
Owner

Search palette pills and field focus ring: new branch job/palette-pills from origin/job/fix-499 (242022301), not pushed. Head 91c4562fb.

Pills: job/fix-499 already renders Open / See all / Actions as three separate frosted Pills (each .glass-chrome with its own backdrop blur, no band, no divider). The joined capsule in job/reuse screenshots comes from that branch's older PillGroup markup, so whoever merges job/reuse must take fix-499's .sw-action-pills version. This branch adds an e2e check that the Pills keep a visible gap (never one joined capsule).

Focus ring (91c4562fb fix(search): draw the field focus ring on the rounded palette):

  • The input row no longer draws a ring of its own inside the palette (the input keeps its outline cleared).
  • The palette surface (.surface.search-window) is the visible field, so it carries the shared keyboard ring (--focus-ring, accent colour) with an inset offset var(--focus-ring-inset-offset, -3px) (the #658 token once job/focus-658 merges). The ring follows the palette radius at every width. Keyboard modality only; pointer focus shows no ring.
  • e2e search.mjs: ring on the surface, negative offset, rounded radius, no ring/fill/border on the input row.

Gates: bun run check 0 errors, 0 warnings (1989 files). Vitest src/lib/search/: 9 files, 50 tests passed.
e2e (prebuilt merge-round-7a server, current assets): SEARCH_E2E_MACOS=1 bun e2e/search.mjs --glass-review-only --screenshots … passed (CSP 0). --palette-layout-only passed the focus-ring and phone Pill checks (PASS phone search action hit targets), then failed later at the budget fixture presents the two-result preview case: 1 (search fixture count on a heavily loaded host, load average ~37; not related to this change).
Screenshots: artifacts/palette-pills/search-glass-macos-{1440,390}-{paper-white,tokyo-night}.png, search-glass-macos-pills-over-row-3x.png.

Search palette pills and field focus ring: new branch `job/palette-pills` from `origin/job/fix-499` (`242022301`), not pushed. Head `91c4562fb`. Pills: `job/fix-499` already renders Open / See all / Actions as three separate `frosted` Pills (each `.glass-chrome` with its own backdrop blur, no band, no divider). The joined capsule in `job/reuse` screenshots comes from that branch's older `PillGroup` markup, so whoever merges `job/reuse` must take fix-499's `.sw-action-pills` version. This branch adds an e2e check that the Pills keep a visible gap (never one joined capsule). Focus ring (`91c4562fb` fix(search): draw the field focus ring on the rounded palette): - The input row no longer draws a ring of its own inside the palette (the input keeps its outline cleared). - The palette surface (`.surface.search-window`) is the visible field, so it carries the shared keyboard ring (`--focus-ring`, accent colour) with an inset offset `var(--focus-ring-inset-offset, -3px)` (the #658 token once `job/focus-658` merges). The ring follows the palette radius at every width. Keyboard modality only; pointer focus shows no ring. - e2e `search.mjs`: ring on the surface, negative offset, rounded radius, no ring/fill/border on the input row. Gates: `bun run check` 0 errors, 0 warnings (1989 files). Vitest `src/lib/search/`: 9 files, 50 tests passed. e2e (prebuilt merge-round-7a server, current assets): `SEARCH_E2E_MACOS=1 bun e2e/search.mjs --glass-review-only --screenshots …` passed (CSP 0). `--palette-layout-only` passed the focus-ring and phone Pill checks (`PASS phone search action hit targets`), then failed later at `the budget fixture presents the two-result preview case: 1` (search fixture count on a heavily loaded host, load average ~37; not related to this change). Screenshots: `artifacts/palette-pills/search-glass-macos-{1440,390}-{paper-white,tokyo-night}.png`, `search-glass-macos-pills-over-row-3x.png`.
Author
Owner

Orchestrator screenshot review (palette-pills 91c4562fb, search-glass-macos-1440-tokyo-night.png), follow-ups: (1) the first result row under the 'PHOTOS 5' group header overlaps/crowds the header (atlas-5.jpg row sits under the label); (2) Notes hits show raw file names (20261003-atlas-retro-50f5b45b.md) under FILES instead of the note title under a Notes group; (3) the photo preview pane shows an empty grey box for a photo hit. Pills and focus ring are right.

Orchestrator screenshot review (palette-pills 91c4562fb, search-glass-macos-1440-tokyo-night.png), follow-ups: (1) the first result row under the 'PHOTOS 5' group header overlaps/crowds the header (atlas-5.jpg row sits under the label); (2) Notes hits show raw file names (20261003-atlas-retro-50f5b45b.md) under FILES instead of the note title under a Notes group; (3) the photo preview pane shows an empty grey box for a photo hit. Pills and focus ring are right.
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#499
No description provided.