Skip to content

fix(mobile/iOS): double-scroll container in orbit shell — real root of header overlay/logo cut

The real root of the iOS header saga — double scroll container (verified, not theorized)

Measured on live prod with Linux WebKit 26.5 (iPhone emulation), matching Catalyst's on-device symptoms exactly:

  • Page has two scroll containers: the body AND orbit's .notLoggedIn shell (which carries inline overflow: hidden).
  • On WebKit, the inner container accumulates its own scrollTop (measured 48px) which window.scrollTo(0,0) cannot reset (body's 76px and the inner 48px hold independently).
  • At "scroll zero" the header/logo/main still sat at −48px — the top ~100–150px of the page is unreachable. Fixed menu displaced. Exactly Catalyst's report.
  • With the fix applied live (overflow: visible !important) + a scroll-down-and-back cycle: scrollY=0, inner=0, headerTop=0, logoTop=0, mainTop=0 — state fully healed.

Why this explains every observed pattern:

  • First-load logo full-screen flash (~0.1s, sometimes): content paints while the two scrollers negotiate offsets.
  • Short routes (appels, consultations) heal the page after visiting: they never scroll, so the inner scroller holds no offset — tall routes keep drift.
  • Deterministic on first load, Android always fine: Blink reconciles nested scrollers differently — no negative offset at scroll 0.
  • Why it "came with !65 (merged)": before, the same broken state rendered as an invisible overlap (header painted over content); blockifying the header made the offset state visible instead.

The fix (11 lines, CSS-only, upgrade-safe)

.notLoggedIn { overflow: visible !important } — orbit's inline overflow:hidden beats plain class rules, hence !important. Removes the inner scroll container entirely; body becomes the only scroller. Horizontal clipping is unaffected: body's own overflow-x: hidden propagates to the viewport per spec.

Light-DOM global CSS — applies before component hydration, immune to the iOS shadow-style registration race we also identified today.

Falsified along the way (kept for the record)

  • Service worker stale cache (cleared on-device, no change)
  • Document height limit (no height change in !65 (merged)/!66 (merged); short/long pages both misbehave depending on route history)
  • risefor-img height-cycle (MR !68 (merged)) — closed; cut exists only when the route state puts the header above the viewport.
  • Deploy/CF cache staleness — real ops hazard, but not this bug.

Deploy note (⚠️ important)

/static/* sits behind Cloudflare with a 4h TTL. After merge + prod pull: purge CF (or wait) or the phone keeps the old core.css and the test reads false. (Burned once today.)

iPhone checklist

  1. Listing pages: titles fully visible, header/logo intact at top.
  2. Menu icon: positioned normally (top-right, not displaced).
  3. The route-alternation pattern (appels fix it, tall pages break it) should be gone entirely.
  4. Desktop/Android: zero visual change (Blink rendered both states the same).

Merge request reports