Skip to content

dogfood: mobile navbar flickers on forward nav (backdrop-blur sticky header) #610

@vivek7405

Description

@vivek7405

Problem

On REAL mobile devices (iOS Safari and Android Chrome), the example-blog top navbar flickers on a FORWARD client-router navigation (scroll down the home post list, tap a post like "Hello World!"), but NOT when using the browser Back button. Desktop does not show it, and crucially Chrome DevTools mobile emulation does not show it either, so it can only be observed on a physical phone.

The navbar is the root layout's <header>, which sits OUTSIDE the <!--wj:children--> slot, so the client router preserves its DOM identity across a soft nav (it is never recreated). This is therefore NOT a layout-recreation bug. It is a paint artifact.

Design / approach

Mechanism (grounded in the code):

  • The header is position: sticky plus backdrop-blur-[18px] backdrop-saturate-[180%] (examples/blog/app/layout.ts ~L207). A backdrop-filter continuously re-samples and re-blurs the content behind it.
  • The blog has no loading.* boundary, so forward nav is a single content swap (no optimistic skeleton). The real forward-vs-back difference is the scroll: forward jumps to top via window.scrollTo({ top: 0, left: 0, behavior: 'instant' }) (added in fix: force instant scroll on navigation so smooth CSS does not animate it #603); back restores the previous scroll position.
  • After fix: force instant scroll on navigation so smooth CSS does not animate it #603 the forward scroll-to-top is INSTANT (it was animated scroll-behavior: smooth before). On a large single-frame jump the sticky header's backdrop-filter must re-rasterize its entire blurred backdrop in one frame. Weaker mobile GPUs show a one-frame stale-blur flicker; the desktop compositor (and DevTools device mode, which runs Blink and does not emulate the mobile GPU) composites it fast enough to hide it. iOS Safari is WebKit, with its own known backdrop-filter + sticky quirks that Chrome DevTools cannot reproduce at all.

The instant scroll itself is correct (it fixed #601 and matches a native page load), so the fix is NOT to revert it. The fix belongs in the blog CSS: promote the header to its own compositor layer so the blur composites cleanly through an instant scroll, e.g. add transform: translateZ(0) / will-change: transform (and the -webkit- equivalents for Safari) to the <header>, and/or trim the backdrop-filter. Try the compositor hint first (cheapest, most likely fix); fall back to reducing/removing the blur if it persists.

Implementation notes (for the implementing agent)

  • Where to edit: examples/blog/app/layout.ts, the <header class="sticky top-0 z-20 ... backdrop-blur-[18px] backdrop-saturate-[180%]"> element (~L207). This is light-DOM Tailwind in a page/layout; a one-off compositor hint can go via a utility class or a small tag-prefixed CSS rule in the layout's <style> block (invariant 7: a light-DOM custom CSS class selector must be tag-prefixed, but the header is a plain element in a layout, so a scoped rule is fine).
  • Landmines: this CANNOT be reproduced on desktop or in Chrome DevTools device mode. Verify ONLY on a real phone in BOTH iOS Safari and Android Chrome. Do not declare it fixed off desktop testing. will-change: transform left on permanently can raise memory on some devices; prefer transform: translateZ(0) (a static compositor promotion) over a permanent will-change if either works.
  • The instant scroll in packages/core/src/router-client.js (fix: force instant scroll on navigation so smooth CSS does not animate it #603) is correct and must NOT be reverted; the flicker is the blog header's rendering, not the router.
  • Invariants: light-DOM tag-prefix rule (refactor(ui): tailwindify installStyles in 5 native-HTML tier-2 components #7); no backtick in html template bodies (feat(server): replace esbuild TS stripping with Node 24+ strip-types #9); no banned prose punctuation in comments (release: bump core/server/cli versions, honest engines fields #11).
  • Tests + docs: this is a visual/rendering fix with no automated-test surface (the flicker is GPU/compositor-dependent and invisible to headless/emulated browsers). Note in the PR that verification is manual-on-device. No docs-site / AGENTS.md change (blog-app CSS only).

Acceptance criteria

  • On a real iOS Safari device, forward nav (scroll the post list, tap a post) shows no navbar flicker
  • On a real Android Chrome device, same: no navbar flicker
  • Back navigation still has no flicker (no regression)
  • The header still renders its intended blur/translucency (the fix does not visually degrade it on desktop)
  • No automated test added (documented why: GPU/compositor artifact, not reproducible headless); manual on-device verification recorded in the PR
  • The fix: force instant scroll on navigation so smooth CSS does not animate it #603 instant-scroll behavior is unchanged (not reverted)

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

Status
In progress

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions