Skip to content

fix(ui): stop phone scroll locking to the bottom on mode homes (collapse-budget gate)#993

Merged
BigSimmo merged 8 commits into
mainfrom
claude/phone-scroll-fix
Jul 20, 2026
Merged

fix(ui): stop phone scroll locking to the bottom on mode homes (collapse-budget gate)#993
BigSimmo merged 8 commits into
mainfrom
claude/phone-scroll-fix

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Jul 20, 2026

Copy link
Copy Markdown
Owner

Problem

Since #964 docked the compact composer on phone mode homes, scrolling down on an iPhone (installed PWA) on /formulation (and every other standalone mode home) "locks to the bottom": scroll is janky, the header/composer thrash, and a large blank band appears below the content.

Root cause: one hide-on-scroll event now releases ~180–260px of scroll geometry (72–130px header grid-template-rows collapse + ~110px reserve-pad shrink, both animated over 200–240ms). Mode-home content is short, so the release exceeds the remaining scroll runway: scrollTop clamps straight onto the new bottom edge, a 12px upward drag re-reveals and snaps the geometry back under the finger, and the loop reads as "locked to the bottom". Quantified pre-fix in Chromium on /formulation @390×844: runway 266→84 on hide, 2 chrome flips in a single gesture.

Why every gate missed it: the Playwright suite runs with reducedMotion: "reduce", which disables exactly the transitions that produce this failure mode.

Fix

  • Collapse-budget gate in computeScrollHideUpdate: hosts report how much layout the chrome would release if it hid right now (readChromeCollapseBudget = collapsible header strip + every dock-clearance pad above the shared 0.75rem hidden size). Hiding is refused unless the remaining runway absorbs the release + reveal-intent distance + slack. Short pages keep their chrome and scroll plainly; long pages hide mid-scroll exactly as before but can never start a hide at the bottom edge. The existing one-sided bottom-clamp guard is untouched; budget-less consumers keep today's behavior.
  • Latent DocumentViewer bug (exposed by the new sweep): its one-shot #main-content lookup could permanently hold a detached node when the shell remounts the scroller, silently killing the viewer's hide-on-scroll. The discovery observer now stays alive (childList-only, deduped setState).

Guardrails

  • tests/ui-phone-scroll.spec.ts (new, registered in both config patterns): 18-test sweep — all 10 standalone mode homes, 3 dashboard modes, 4 long routes — at phone viewports with reducedMotion: "no-preference" and simulated PWA safe-area insets. Asserts sole-scrollport, no horizontal overflow, true-bottom reachability, and bottom-edge nudge stability (≤1 chrome flip). Verified to fail red against the pre-fix behavior.
  • tests/use-hide-on-scroll.test.ts: oscillation repro harness + runway-refusal / ample-runway / boundary / budget-omitted back-compat cases.
  • ui-smoke: bottom-hide scenario updated to the gate contract (hide with runway remaining, then ride the geometry clamp to the bottom while hidden).

Verification

  • vitest focused suites 24/24; full verify:cheap green (3020 tests)
  • ui-phone-scroll 18/18 (chromium) + 18/18 (webkit advisory)
  • ui-formulation + ui-tools + ui-smoke 180/180
  • verify:ui 264/264; verify:pr-local green (format, build, bundle scan, RAG fixtures)
  • Post-merge: needs a real-device confirmation on the iPhone PWA (short page: chrome stays, no blank band, no lock; long page: chrome hides mid-scroll, no bottom-edge thrash)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved phone “lock to bottom” scrolling by adding collapse-budget checks to prevent bottom clamp oscillation and reduce header/composer flicker near the bottom.
    • Made document viewer scroll-container syncing more resilient when the main content is replaced or removed.
  • Tests
    • Added/expanded phone scroll regression coverage for bottom-lock behavior across multiple routes and viewports.
    • Refined UI smoke scrolling to avoid premature header reveal near the bottom.
    • Expanded hide-on-scroll unit tests for collapse-budget edge cases.
    • Broadened Playwright production spec matching to include additional UI variants.

…pse-budget gate)

Since #964 docked the compact composer on phone mode homes, one hide-on-scroll
event releases ~180-260px of scroll geometry (72-130px header grid collapse +
~110px reserve-pad shrink, both animated). Mode-home content is short, so the
release exceeds the remaining scroll runway: scrollTop clamps straight onto the
new bottom edge, a 12px upward drag re-reveals and snaps the geometry back
under the finger, and the hide/reveal loop reads as "scroll locks to the
bottom" with a blank band below the content (reported on /formulation, iOS
installed PWA; reproduced and quantified in Chromium: runway 266->84 on hide,
2 chrome flips in a single gesture).

Fix: a collapse-budget gate in computeScrollHideUpdate. Hosts report how much
layout the chrome would release if it hid right now (readChromeCollapseBudget:
collapsible header strip + every dock-clearance pad above the shared 0.75rem
hidden size - shell reserve pad, DocumentViewer content pad, scroller-padding
fallback), and hiding is refused unless the remaining runway can absorb the
release plus the reveal-intent distance and slack. Short pages keep their
chrome and scroll plainly; long pages hide mid-scroll exactly as before but
can no longer start a hide at the bottom edge. The existing one-sided
bottom-clamp guard is untouched. Consumers that report no budget keep today's
behavior.

Also fixes a latent DocumentViewer bug the new sweep exposed: its one-shot
#main-content lookup could permanently hold a detached node when the shell
remounts the scroller, silently killing the viewer's hide-on-scroll. The
discovery observer now stays alive for the viewer's lifetime (childList-only,
deduped setState).

Guardrails:
- tests/ui-phone-scroll.spec.ts: 18-test sweep over all 10 standalone mode
  homes, the 3 dashboard modes, and 4 long routes at phone viewports with
  reducedMotion "no-preference" (the suite-wide "reduce" default disables the
  very transitions that caused this bug - that is how #964 shipped green) and
  simulated PWA safe-area insets. Asserts sole-scrollport, no horizontal
  overflow, true-bottom reachability, and bottom-edge nudge stability (at most
  one chrome flip). Verified to fail red against the pre-fix behavior.
- tests/use-hide-on-scroll.test.ts: oscillation reproduction harness plus
  runway-refusal, ample-runway, boundary, and budget-omitted back-compat cases.
- ui-smoke: bottom-hide scenario updated to the gate contract (hide with
  runway remaining, then ride the geometry clamp to the bottom while hidden).

Verified: vitest focused suites, ui-phone-scroll + ui-formulation + ui-tools +
ui-smoke (chromium), webkit advisory sweep, verify:cheap, verify:ui (264
passed), verify:pr-local.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@supabase

supabase Bot commented Jul 20, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: cd281c9f-37c4-49f1-9bbe-20178e0d0eaa

📥 Commits

Reviewing files that changed from the base of the PR and between 53ff882 and 739425b.

📒 Files selected for processing (3)
  • docs/branch-review-ledger.md
  • playwright.config.ts
  • tests/ui-smoke.spec.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • tests/ui-smoke.spec.ts
  • playwright.config.ts
  • docs/branch-review-ledger.md

📝 Walkthrough

Walkthrough

Phone scroll hiding now accounts for chrome-collapse geometry, prevents bottom-edge oscillation, keeps the shell scroll container synchronized, and adds unit and Playwright regression coverage.

Changes

Phone scroll collapse handling

Layer / File(s) Summary
Collapse budget measurement and hide decisions
src/components/clinical-dashboard/mobile-composer-reserve.ts, src/components/clinical-dashboard/use-hide-on-scroll.ts, tests/use-hide-on-scroll.test.ts, tests/mobile-composer-reserve.test.ts
Collapse space is measured from chrome geometry, incorporated into hide decisions, and covered by runway, compatibility, threshold, and reserve-consistency tests.
Collapse budget reporting
src/components/ClinicalDashboard.tsx, src/components/clinical-dashboard/global-search-shell.tsx
Scroll handlers compute collapse budgets and pass them through phone scroll-hide reporting.
Shell scroll-container synchronization
src/components/DocumentViewer.tsx
The mutation observer remains active for the viewer lifetime and resynchronizes #main-content after shell replacement.
Regression coverage and production spec selection
tests/ui-phone-scroll.spec.ts, tests/ui-smoke.spec.ts, playwright.config.ts, docs/branch-review-ledger.md
Phone scroll stability, smoke behavior, production spec matching, and verification records are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ClinicalDashboard
  participant GlobalSearchShell
  participant ChromeBudget
  participant ScrollHide
  participant MainContent
  ClinicalDashboard->>ChromeBudget: measure chrome collapse budget
  ChromeBudget-->>ClinicalDashboard: return releasable pixels
  ClinicalDashboard->>ScrollHide: report scroll metrics and budget
  GlobalSearchShell->>ChromeBudget: measure target or main collapse budget
  ChromeBudget-->>GlobalSearchShell: return releasable pixels
  GlobalSearchShell->>ScrollHide: report scroll metrics and budget
  ScrollHide->>MainContent: apply hide or reveal state
Loading

Possibly related PRs

Suggested labels: codex

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed It clearly summarizes the main change: preventing phone mode homes from locking to the bottom with a collapse-budget gate.
Description check ✅ Passed It covers the required content well with problem, fix, guardrails, verification, and follow-up notes, even though the template headings differ.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/phone-scroll-fix

Comment @coderabbitai help to get the list of available commands.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/DocumentViewer.tsx`:
- Around line 1748-1752: Update the sync function in DocumentViewer so it sets
shellScrollContainer to null when `#main-content` is absent, rather than returning
while retaining the detached element; preserve the existing assignment when the
node is present so useHideOnScroll falls back to window during remounts.

In `@tests/ui-phone-scroll.spec.ts`:
- Around line 84-97: Make the phone-scroll test fail loudly when the universal
header selector is missing instead of treating it as absent. Update readGeometry
and installFlipCounter to require the [data-testid="universal-header-collapse"]
element, raising a clear assertion/error before returning fallback geometry or
skipping observer installation, so readFlipCount and headerHidden assertions
remain active.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d1bad54-ca30-49c3-b780-0ec09047c6d8

📥 Commits

Reviewing files that changed from the base of the PR and between 33ecd62 and b028cbf.

📒 Files selected for processing (11)
  • docs/branch-review-ledger.md
  • playwright.config.ts
  • src/components/ClinicalDashboard.tsx
  • src/components/DocumentViewer.tsx
  • src/components/clinical-dashboard/global-search-shell.tsx
  • src/components/clinical-dashboard/mobile-composer-reserve.ts
  • src/components/clinical-dashboard/use-hide-on-scroll.ts
  • tests/mobile-composer-reserve.test.ts
  • tests/ui-phone-scroll.spec.ts
  • tests/ui-smoke.spec.ts
  • tests/use-hide-on-scroll.test.ts

Comment thread src/components/DocumentViewer.tsx
Comment thread tests/ui-phone-scroll.spec.ts
@BigSimmo
BigSimmo enabled auto-merge (squash) July 20, 2026 14:35
BigSimmo and others added 2 commits July 20, 2026 22:39
…oudly

Addresses two CodeRabbit findings on #993:
- DocumentViewer kept a detached #main-content node across a two-step
  shell remount, leaving useHideOnScroll subscribed to a scroller that
  never fires. Syncing null lets it fall back to window.
- installFlipCounter returned early when the header selector missed,
  pinning the counter at 0 so flip assertions passed vacuously.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BigSimmo
BigSimmo disabled auto-merge July 20, 2026 14:42
@BigSimmo
BigSimmo enabled auto-merge (squash) July 20, 2026 14:43
@github-actions

Copy link
Copy Markdown

CI triage

CI failed on this PR. Automated classification of the 3 failed job(s):

  • Static PR checksneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • Production UIneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

Compared with main CI run #4054 (success).

Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger.

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found.

The answer home hides its glass header via the overlay strategy (header#search
carries data-scroll-hidden; no collapse wrapper exists), so the fail-loudly
flip counter must observe whichever chrome element the route renders.
Condense the DocumentViewer discovery comments back under the 3166-line
no-growth maintainability budget while keeping the remount rationale.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BigSimmo
BigSimmo disabled auto-merge July 20, 2026 15:33
@BigSimmo
BigSimmo enabled auto-merge (squash) July 20, 2026 15:33
…ng CI fixes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@BigSimmo
BigSimmo disabled auto-merge July 20, 2026 15:36
@BigSimmo
BigSimmo enabled auto-merge (squash) July 20, 2026 15:36
@BigSimmo
BigSimmo merged commit 57b653a into main Jul 20, 2026
17 checks passed
@BigSimmo
BigSimmo deleted the claude/phone-scroll-fix branch July 20, 2026 15:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant