fix(layout): UpdateBanner re-mount on nav causes the 'page jump' MAPPS-203 reports #191

Merged
YousifShkara merged 1 commit from fix/mapps-203-update-banner-no-shift-on-nav into main 2026-06-16 09:07:46 +02:00
Owner

The user-reported symptom is the sidebar scrolling to the top + the whole page jumping on every nav click. Source-of-truth investigation found there is no separate 'payments banner' the issue body hypothesised; the actual offender is the existing UpdateBanner whose layout-shift behaviour on first paint PMS-313 fixed but whose behaviour on every subsequent navigation it did not.

Each page in the SPA wraps its content in a fresh AppLayout, which re-mounts the UpdateBanner from scratch. The banner's local use_resource therefore starts at None every time, dropping into BannerState::Reserving (the 1fr-tall invisible row PMS-313 added so the first paint already has the banner's height reserved). One tick later the fetch resolves to 'no update' and state flips to Collapsed, which animates the row from 1fr to 0fr over 200ms via the grid-template-rows transition. That collapse is exactly the layout shift the user sees every time they click a nav item.

Fix: lift the result of GET /api/v1/version into a context signal owned by App (new hooks/version_cache.rs, mirroring the use_sidebar_provider pattern). The UpdateBanner reads the cache first; when it carries Some(result) the banner derives its final state (Show or Collapsed) directly with no Reserving step and no transition. The component's own use_resource still runs on each re-mount so the banner refreshes on every nav, and a use_effect writes the resolved result through to the cache so the next re-mount benefits. Only the App's first-ever admin mount sees None on both signals, so PMS-313's first-paint reserved-height behaviour is preserved exactly as before; every subsequent admin nav skips it.

#MAPPS-203

The user-reported symptom is the sidebar scrolling to the top + the whole page jumping on every nav click. Source-of-truth investigation found there is no separate 'payments banner' the issue body hypothesised; the actual offender is the existing UpdateBanner whose layout-shift behaviour on first paint PMS-313 fixed but whose behaviour on every subsequent navigation it did not. Each page in the SPA wraps its content in a fresh AppLayout, which re-mounts the UpdateBanner from scratch. The banner's local use_resource therefore starts at None every time, dropping into BannerState::Reserving (the 1fr-tall invisible row PMS-313 added so the first paint already has the banner's height reserved). One tick later the fetch resolves to 'no update' and state flips to Collapsed, which animates the row from 1fr to 0fr over 200ms via the grid-template-rows transition. That collapse is exactly the layout shift the user sees every time they click a nav item. Fix: lift the result of GET /api/v1/version into a context signal owned by App (new hooks/version_cache.rs, mirroring the use_sidebar_provider pattern). The UpdateBanner reads the cache first; when it carries Some(result) the banner derives its final state (Show or Collapsed) directly with no Reserving step and no transition. The component's own use_resource still runs on each re-mount so the banner refreshes on every nav, and a use_effect writes the resolved result through to the cache so the next re-mount benefits. Only the App's first-ever admin mount sees None on both signals, so PMS-313's first-paint reserved-height behaviour is preserved exactly as before; every subsequent admin nav skips it. #MAPPS-203
fix(layout): UpdateBanner re-mount on nav causes the 'page jump' MAPPS-203 reports
All checks were successful
Create release / Create release from merged PR (pull_request) Has been skipped
Check / clippy + fmt + tests (pull_request) Successful in 1m5s
8b5fe3a0b6
The user-reported symptom is the sidebar scrolling to the top + the whole page jumping on every nav click. Source-of-truth investigation found there is no separate 'payments banner' the issue body hypothesised; the actual offender is the existing UpdateBanner whose layout-shift behaviour on first paint PMS-313 fixed but whose behaviour on every subsequent navigation it did not.

Each page in the SPA wraps its content in a fresh AppLayout, which re-mounts the UpdateBanner from scratch. The banner's local use_resource therefore starts at None every time, dropping into BannerState::Reserving (the 1fr-tall invisible row PMS-313 added so the first paint already has the banner's height reserved). One tick later the fetch resolves to 'no update' and state flips to Collapsed, which animates the row from 1fr to 0fr over 200ms via the grid-template-rows transition. That collapse is exactly the layout shift the user sees every time they click a nav item.

Fix: lift the result of GET /api/v1/version into a context signal owned by App (new hooks/version_cache.rs, mirroring the use_sidebar_provider pattern). The UpdateBanner reads the cache first; when it carries Some(result) the banner derives its final state (Show or Collapsed) directly with no Reserving step and no transition. The component's own use_resource still runs on each re-mount so the banner refreshes on every nav, and a use_effect writes the resolved result through to the cache so the next re-mount benefits. Only the App's first-ever admin mount sees None on both signals, so PMS-313's first-paint reserved-height behaviour is preserved exactly as before; every subsequent admin nav skips it.

#MAPPS-203
YousifShkara deleted branch fix/mapps-203-update-banner-no-shift-on-nav 2026-06-16 09:07:46 +02:00
Sign in to join this conversation.
No reviewers
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
psa-systems/mokosh-apps!191
No description provided.