fix(layout): reserve UpdateBanner height to kill admin login layout shift #167

Merged
nrupard merged 1 commit from fix/PMS-313-update-banner-layout-shift into main 2026-06-15 22:38:25 +02:00
Owner

Summary

Fixes the admin "Update available" banner causing a layout shift on every admin login (PMS-313). The banner's version check is async; the component rendered nothing until it resolved, then popped a ~40px row in and pushed the TopBar, sidebar nav, and main content down. Non-zero CLS on every admin login on a stale client, repeated on every AppLayout re-mount.

Approach (reserved-height container, option 1 from the issue)

For admins the outer container now always renders, so the banner height is reserved from first paint:

  • In flight (Reserving): reserve one banner-line of height via a transparent spacer at opacity-0. Invisible on first paint, but the geometry is already final.
  • Update available (Show): fade the banner into the already-reserved space. Zero shift, which is the case the bug was about.
  • No update / fetch error / dismissed (Collapsed): collapse the reserved height away.

Collapse uses the grid-template-rows: 1fr -> 0fr pattern with transition-all duration-200 ease-in-out, so it animates to the content's natural height with no hard-coded pixel value and no clipping of a wrapped multi-line banner on narrow viewports. Dismiss runs through the same collapse transition.

Notes

  • Non-admins render nothing at all: the pre-hook is_admin early-return is unchanged, so they get no reserved band and no extra render.
  • Hook order is untouched (use_auth / use_signal / use_resource all run before any return), preserving the Dioxus stable-hook-set invariant the original code documented.
  • The transient reserved band during the in-flight check (then collapsing for the 99.9% no-update case) is the accepted trade-off of option 1: the update status is unknowable at first paint because the check is a network round-trip. The downward pop that the bug filed is eliminated.
  • Out of scope per the issue: the version-check polling cadence (hooks/update_check.rs, which is the unrelated SPA auto-reload hook), banner wording, and the dismiss persistence key.

Verification

cargo fmt --check, cargo check --target wasm32-unknown-unknown, and cargo clippy --all-targets -D warnings all clean. Tailwind v4 emits the new grid-rows-[0fr] / grid-rows-[1fr] utilities (confirmed in the generated assets/styles.css).

Acceptance criteria mapping:

  • First-paint geometry is final; TopBar does not move while the check is in flight (height reserved). [AC1]
  • Sidebar nav does not shift when the banner appears (fades into reserved space). [AC2]
  • Dismiss collapses through the transition, no jump. [AC3]
  • Non-admin and admin-no-update see no banner content; the reserved container is invisible on first paint. [AC4]
  • CLS for the appear path is 0; the no-update collapse is a deliberate post-resolve transition. [AC5]

#PMS-313

## Summary Fixes the admin "Update available" banner causing a layout shift on every admin login (PMS-313). The banner's version check is async; the component rendered nothing until it resolved, then popped a ~40px row in and pushed the TopBar, sidebar nav, and main content down. Non-zero CLS on every admin login on a stale client, repeated on every AppLayout re-mount. ## Approach (reserved-height container, option 1 from the issue) For admins the outer container now always renders, so the banner height is reserved from first paint: - **In flight (`Reserving`):** reserve one banner-line of height via a transparent spacer at `opacity-0`. Invisible on first paint, but the geometry is already final. - **Update available (`Show`):** fade the banner into the already-reserved space. Zero shift, which is the case the bug was about. - **No update / fetch error / dismissed (`Collapsed`):** collapse the reserved height away. Collapse uses the `grid-template-rows: 1fr -> 0fr` pattern with `transition-all duration-200 ease-in-out`, so it animates to the content's natural height with no hard-coded pixel value and no clipping of a wrapped multi-line banner on narrow viewports. Dismiss runs through the same collapse transition. ## Notes - Non-admins render nothing at all: the pre-hook `is_admin` early-return is unchanged, so they get no reserved band and no extra render. - Hook order is untouched (`use_auth` / `use_signal` / `use_resource` all run before any return), preserving the Dioxus stable-hook-set invariant the original code documented. - The transient reserved band during the in-flight check (then collapsing for the 99.9% no-update case) is the accepted trade-off of option 1: the update status is unknowable at first paint because the check is a network round-trip. The downward pop that the bug filed is eliminated. - Out of scope per the issue: the version-check polling cadence (`hooks/update_check.rs`, which is the unrelated SPA auto-reload hook), banner wording, and the dismiss persistence key. ## Verification `cargo fmt --check`, `cargo check --target wasm32-unknown-unknown`, and `cargo clippy --all-targets -D warnings` all clean. Tailwind v4 emits the new `grid-rows-[0fr]` / `grid-rows-[1fr]` utilities (confirmed in the generated `assets/styles.css`). Acceptance criteria mapping: - First-paint geometry is final; TopBar does not move while the check is in flight (height reserved). [AC1] - Sidebar nav does not shift when the banner appears (fades into reserved space). [AC2] - Dismiss collapses through the transition, no jump. [AC3] - Non-admin and admin-no-update see no banner content; the reserved container is invisible on first paint. [AC4] - CLS for the appear path is 0; the no-update collapse is a deliberate post-resolve transition. [AC5] #PMS-313
fix(layout): reserve UpdateBanner height to kill admin login layout shift
All checks were successful
Check / clippy + fmt + tests (pull_request) Successful in 54s
Create release / Create release from merged PR (pull_request) Has been skipped
da0e809883
The admin-only "Update available" banner ran an async version check and rendered nothing until the result arrived, then popped a ~40px row in and shoved the TopBar, sidebar nav, and main content down. That is a visible jump and non-zero CLS on every admin login on a stale client, and it repeated on every navigation that re-mounts AppLayout.

Fix (PMS-313, reserved-height container): for admins the banner's outer container now always renders, so its height is reserved from first paint. While the version check is in flight the height is reserved invisibly (opacity-0 over a one-line transparent spacer); once it resolves we either fade the banner into the already-reserved space (zero shift, the case the bug was about) or collapse the reserved height away. Collapse uses the grid-template-rows 1fr to 0fr pattern with transition-all duration-200, so it animates to the content's natural height with no hard-coded pixel value and no clipping of a wrapped multi-line banner on narrow viewports. Dismiss runs through the same collapse transition.

Non-admins still render nothing at all (the pre-hook is_admin early-return is unchanged, so no reserved band and no extra render for them). Hook order is untouched: use_auth / use_signal / use_resource still all run before any return.

#PMS-313

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
nrupard deleted branch fix/PMS-313-update-banner-layout-shift 2026-06-15 22:38:25 +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!167
No description provided.