Header and Tab Bar stay on the previous Tab after switching to Calendar (redirect never clears the header snapshot) #571

Closed
opened 2026-10-01 04:55:19 +00:00 by kayg · 6 comments
Owner

Owner report (2026-10-01, screenshot on calternal.cloud cc25c441b)

After switching Photos → Calendar, the URL is /calendar/week/2026-09-27 and the Calendar week renders, but the header title still says "Photos ⌄", the Tab Bar still shows Photos selected, and the tray's contextual action is still Photos' upload. "Calendar takes so long to load that the tab doesn't change but has actually changed."
Root cause (read in code, apps/web/src/routes/+layout.svelte ~153–181):

  • beforeNavigate stores pendingHeaderNavigation = { target: to.url.pathname, … }, and displayedHeader shows pendingHeaderNavigation.header (the old Photos header) while it is set.
  • afterNavigate clears it only if (navigation.target === to.url.pathname).
  • The Calendar tab navigates to /calendar (or /today), which redirects to /calendar/<view>/<date>, so the paths never match. The snapshot is never cleared, and the old header stays until the next navigation.
  • Calendar is the only Tab whose entry route redirects, which is why only Calendar shows it. Slowness only makes it more visible.
    Fix:
  • In afterNavigate, settle the latest pending navigation by id, not by an exact path match: any completed navigation whose id is the latest settles it.
  • Decide overlay vs tab from the final to.url.pathname (modeForPath(to.url.pathname)).
  • Check the redirect chain case (a load redirect, and a client goto redirect that fires beforeNavigate again).
  • Comment the invariant.
    Test (e2e, production build, must fail before the fix):
  • A User on Photos clicks the Calendar tab. Assert the header title reads "Calendar", the Calendar tab is aria-selected="true", and the contextual tray action is Calendar's, within 1 frame of the route settling.
  • Repeat via the keyboard shortcut and via a deep link to /calendar and /today.
  • Also run Files/Notes/Mail/Money/Photos → Calendar and Calendar → each.
  • Add the case to header-sweep.mjs.
    Web gates (bun run check, bun run test). Keep the change minimal. Time limit 1.5 h.
## Owner report (2026-10-01, screenshot on calternal.cloud cc25c441b) After switching Photos → Calendar, the URL is `/calendar/week/2026-09-27` and the Calendar week renders, but the header title still says **"Photos ⌄"**, the Tab Bar still shows **Photos** selected, and the tray's contextual action is still Photos' upload. "Calendar takes so long to load that the tab doesn't change but has actually changed." **Root cause (read in code, apps/web/src/routes/+layout.svelte ~153–181):** - `beforeNavigate` stores `pendingHeaderNavigation = { target: to.url.pathname, … }`, and `displayedHeader` shows `pendingHeaderNavigation.header` (the old Photos header) while it is set. - `afterNavigate` clears it only `if (navigation.target === to.url.pathname)`. - The Calendar tab navigates to `/calendar` (or `/today`), which **redirects** to `/calendar/<view>/<date>`, so the paths never match. The snapshot is never cleared, and the old header stays until the next navigation. - Calendar is the only Tab whose entry route redirects, which is why only Calendar shows it. Slowness only makes it more visible. **Fix:** - In `afterNavigate`, settle the latest pending navigation by **id**, not by an exact path match: any completed navigation whose `id` is the latest settles it. - Decide overlay vs tab from the **final** `to.url.pathname` (`modeForPath(to.url.pathname)`). - Check the redirect chain case (a load redirect, and a client `goto` redirect that fires beforeNavigate again). - Comment the invariant. **Test (e2e, production build, must fail before the fix):** - A User on Photos clicks the Calendar tab. Assert the header title reads "Calendar", the Calendar tab is `aria-selected="true"`, and the contextual tray action is Calendar's, within 1 frame of the route settling. - Repeat via the keyboard shortcut and via a deep link to `/calendar` and `/today`. - Also run Files/Notes/Mail/Money/Photos → Calendar and Calendar → each. - Add the case to `header-sweep.mjs`. Web gates (`bun run check`, `bun run test`). Keep the change minimal. Time limit 1.5 h.
Author
Owner

Starting #571 on job/header-571, based on origin/dev at cc25c441b7a974185622a1dee853cf38686d2b67. I have read CLAUDE.md, docs/DESIGN.md §§34, 38 and CONTEXT.md. I am tracing the redirect settlement and adding the redirect cases to the existing production-build header sweep.

Starting #571 on `job/header-571`, based on `origin/dev` at `cc25c441b7a974185622a1dee853cf38686d2b67`. I have read `CLAUDE.md`, `docs/DESIGN.md` §§34, 38 and `CONTEXT.md`. I am tracing the redirect settlement and adding the redirect cases to the existing production-build header sweep.
Author
Owner

Baseline evidence from HEADER_SECTIONS=redirects bun e2e/layout-sweep.mjs --only headers on the production SPA: client deep links from Photos to both /calendar and /today reached the final /calendar/... route while the header still reported title Photos, selected Tab Photos, and primary action Upload photos and videos. The Tab and keyboard cases also showed that their first sample was too early to treat the Calendar route as settled; the regression sweep now waits for the real Calendar grid before its one-frame sample.

Baseline evidence from `HEADER_SECTIONS=redirects bun e2e/layout-sweep.mjs --only headers` on the production SPA: client deep links from Photos to both `/calendar` and `/today` reached the final `/calendar/...` route while the header still reported title `Photos`, selected Tab `Photos`, and primary action `Upload photos and videos`. The Tab and keyboard cases also showed that their first sample was too early to treat the Calendar route as settled; the regression sweep now waits for the real Calendar grid before its one-frame sample.
Author
Owner

Implemented and committed as bda672aad79082c5f055bec87c9bc1828bfa6de8 on job/header-571.

  • apps/web/src/routes/+layout.svelte: settle the latest pending header navigation by ID, then classify the settled route from its final URL. This handles Calendar load redirects and client goto navigation.
  • apps/web/e2e/header-sweep.mjs: add pointer, keyboard, deep-link, client goto, and reverse route coverage for Files, Notes, Mail, Money, Photos, and Calendar. The production screenshot matrix writes 36 PNGs to artifacts/header-571/ at 390, 820, and 1440 px in both themes.

Checks

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/header-571/apps/web
Getting Svelte diagnostics...

svelte-check found 0 errors and 0 warnings

bun run test:

Test Files  140 passed (140)
Tests  930 passed (930)

Production E2E route assertions passed. The full command exited nonzero on its 820 px light screenshot palette check:

CSP REPORTS layout-sweep: 0 across 3 pages
checked 0 screens
FAIL header redirect screenshots 820 paper: paper screenshot uses the requested palette: {"mode":"light","palette":"mono","dark":false,"colorScheme":"light","storedMode":"light","storedFamily":"mono","serverMode":"light"}

I updated the capture helper to reapply the theme after each real route load. I could not rerun the full sweep within the 1.5 hour job limit. Treat the 820 px light screenshot as unverified. The PNGs are local, ignored artifacts. The fj issue comment command has no attachment option, so I could not attach them to this issue.

node --check e2e/header-sweep.mjs and git diff --check both exited 0. cargo clean removed 7169 files (4.5 GiB), and apps/web/build was deleted.

Decision

The test uses /today as Calendar's entry route and reads its settled title from that user's selected view and date. Notes is covered by a client link to /today because Notes is a Calendar sub-view, not a separate top-level Tab. Money's /money route covers a mount-time client goto that replaces the pending navigation ID.

Known gaps

The final screenshot theme reapplication change was not rerun. Screenshots need visual review and attachment to the issue.

Implemented and committed as `bda672aad79082c5f055bec87c9bc1828bfa6de8` on `job/header-571`. - `apps/web/src/routes/+layout.svelte`: settle the latest pending header navigation by ID, then classify the settled route from its final URL. This handles Calendar load redirects and client `goto` navigation. - `apps/web/e2e/header-sweep.mjs`: add pointer, keyboard, deep-link, client `goto`, and reverse route coverage for Files, Notes, Mail, Money, Photos, and Calendar. The production screenshot matrix writes 36 PNGs to `artifacts/header-571/` at 390, 820, and 1440 px in both themes. **Checks** `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/header-571/apps/web Getting Svelte diagnostics... svelte-check found 0 errors and 0 warnings ``` `bun run test`: ```text Test Files 140 passed (140) Tests 930 passed (930) ``` Production E2E route assertions passed. The full command exited nonzero on its 820 px light screenshot palette check: ```text CSP REPORTS layout-sweep: 0 across 3 pages checked 0 screens FAIL header redirect screenshots 820 paper: paper screenshot uses the requested palette: {"mode":"light","palette":"mono","dark":false,"colorScheme":"light","storedMode":"light","storedFamily":"mono","serverMode":"light"} ``` I updated the capture helper to reapply the theme after each real route load. I could not rerun the full sweep within the 1.5 hour job limit. Treat the 820 px light screenshot as unverified. The PNGs are local, ignored artifacts. The `fj issue comment` command has no attachment option, so I could not attach them to this issue. `node --check e2e/header-sweep.mjs` and `git diff --check` both exited 0. `cargo clean` removed 7169 files (4.5 GiB), and `apps/web/build` was deleted. **Decision** The test uses `/today` as Calendar's entry route and reads its settled title from that user's selected view and date. Notes is covered by a client link to `/today` because Notes is a Calendar sub-view, not a separate top-level Tab. Money's `/money` route covers a mount-time client `goto` that replaces the pending navigation ID. **Known gaps** The final screenshot theme reapplication change was not rerun. Screenshots need visual review and attachment to the issue.
Author
Owner

Additional known gap: I did not add or run a bench profile for this UI navigation change within the 1.5 hour job limit.

Additional known gap: I did not add or run a bench profile for this UI navigation change within the 1.5 hour job limit.
Author
Owner

Merge note (orchestrator, round 4 + #571): the header-sweep.mjs conflict was resolved by keeping round 4's version (breadcrumb/title cases). The #571 redirect cases in header-sweep.mjs (Photos→Calendar redirect, keyboard, deep link, client goto, reverse; job/header-571 bda672aad) were dropped and must be re-added on top of dev. The product fix itself is merged.

Merge note (orchestrator, round 4 + #571): the header-sweep.mjs conflict was resolved by keeping round 4's version (breadcrumb/title cases). The #571 redirect cases in header-sweep.mjs (Photos→Calendar redirect, keyboard, deep link, client goto, reverse; job/header-571 bda672aad) were dropped and must be re-added on top of dev. The product fix itself is merged.
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:58 +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#571
No description provided.