fix(ui): paint the phone top safe-area with the header surface - #1393
Conversation
The `chrome-safe-area-top` spacer painted `var(--background)` while the bar directly below it paints `var(--surface)`, so every collapse-strategy mode drew a page-coloured status-bar band above the white header on phones. Answer mode never showed the seam because it is the only mode on the overlay hide strategy: it renders no spacer and pads the inset inside the header itself, so the bar's surface paints straight through to the viewport top. Collapse-strategy modes need the separate spacer so the inset can be released on scroll, and that spacer simply had the wrong colour. Measured at 440x956 with the inset injected as tests/ui-phone-scroll.spec.ts does, top-edge pixel before -> after: /?mode=answer rgb(252,253,254) -> unchanged /?mode=documents rgb(241,244,248) -> rgb(252,253,254) /services rgb(241,244,248) -> rgb(252,253,254) /services?q=Community&run=1 rgb(241,244,248) -> rgb(252,253,254) Kept opaque so the sm+ pinned inset still hides scrolled content, and the release contract is unchanged: on scroll-hide the spacer still collapses to height 0 with data-scroll-hidden="true" and the top pixel returns to page content (light rgb(241,244,248), dark rgb(6,7,8)). Dark mode paints rgb(16,19,21), matching the dark `--surface`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MYeZh7qEEXc36AX4JuDjzF
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in:7 minutes Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe phone header’s ChangesPhone chrome safe-area contract
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
BigSimmo
commented
Jul 30, 2026
@cursoragent review PR and run bugbot |
PR #1393 review — no P0/P1Reviewed FindingsNo high-confidence P0–P2 defects.
Bugbot
Local Bugbot-equivalent + frontend-ui review: same verdict — no P0/P1. Checks
VerdictSafe to merge from a defect standpoint once required CI settles. Hosted Bugbot never produced a report in this environment — local triage covers it. Ledger commit: |
Ledger append for claude/white-element-positioning-t607pk at db34149 after local review and Bugbot triage (no hosted cursor[bot] findings; suite stuck queued). Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
BigSimmo
commented
Jul 30, 2026
@cursoragent Inspect the PR and repository state, identify the root cause of every failing CI check, unresolved review comment, merge conflict, and related defect. Apply the smallest robust fix using existing project patterns and best practices, without unrelated changes. Run Bugbot and all relevant lint, type-check, test, build, and CI-equivalent commands, fix any issues found, then resolve only the review comments that are fully addressed. Summarise the root causes, changes made, validation results, and any remaining blockers. |
ResultNo product defect to fix. The spacer paint change is sound. The CI “failures” were Production UI cancelled by repeated Root causes
Proof the product is fine: head Changes made
Validation
Remaining blockerStop merging Current state: No review threads to resolve. |
BigSimmo
commented
Jul 30, 2026
|
| Time (UTC+8) | Event | Effect on Production UI |
|---|---|---|
| 10:58:58 | 7b34ecf merge main → branch | cancelled run on aa4a6cf |
| 11:11:18 | bfd764b (#1392) lands on main | — |
| 11:11:43 | e7a27bb merge main → branch | cancelled run on 7b34ecf |
Earlier, aa4a6cf (the review-ledger push) cancelled the run on db34149 the same way. The job has not yet been allowed to finish once.
This is not fixable by changing code in this PR, and pushing a commit to "fix CI" actively makes it worse — each push is another synchronize that cancels the job again. It is the anti-churn case described in AGENTS.md → "Open PR branch sync (anti-churn)".
What actually clears it: let the branch sit un-synced long enough for production-ui to run to completion (~15 min), and auto-merge takes it. If main keeps advancing faster than that, hold / skip-branch-sync on this PR stops the auto-sync so the job can finish.
For anyone auditing the diff itself: it is a single design-token value (--background → --surface) on one aria-hidden spacer, plus a doc line and a contract assertion. verify:phone-chrome static/unit contracts are 101/101 and its focused phone/PWA journeys 13/13. The two verify:ui failures noted in the description were A/B-proved independent of the change — identical 2-failed results with the token set both ways — and hosted Advisory UI is green on this head.
Generated by Claude Code
BigSimmo
commented
Jul 30, 2026
@cursoragent fix merge conflict |
No merge conflict to fix. GitHub reports
Leave the branch alone until that run finishes (~15 min). |
Hosted CI green on e7a27bb (Production UI + PR required). Prior PR-required failures were Production UI cancelled by superseding main merges, not product defects. No Bugbot findings; no unresolved review threads. Local A/B confirmed Playwright env flakes unrelated to the spacer paint token. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Sync one commit from main (#1394) so the PR is not stale. git merge-tree was clean; GitHub DIRTY was behind-by-1 staleness. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Product change is sound (hosted green at e7a27bb). Remaining merge blocker is repeated main merges cancelling Production UI before PR required can pass. No product code change in this commit. Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.


Summary
chrome-safe-area-topspacer inmaster-search-header.tsxpaintedbg-[color:var(--background)](#f1f4f8, the page canvas) while the bar immediately below it paintsvar(--surface)(#fcfdfe). Answer mode never showed the seam because it is the only mode on the overlay hide strategy: it renders no spacer and pads the inset inside the header itself (pt-[max(0.5rem,var(--safe-area-top))]), so the bar's surface paints straight through to the viewport top. Collapse-strategy modes need the separate spacer so the inset can be released on scroll, and that spacer simply carried the wrong colour.var(--surface)so it reads as the top of the header at every width and in every colour fallback. Kept opaque deliberately, so thesm+pinned inset still hides scrolled content.docs/search-chrome-behaviour.mdand adds a contract assertion so the paint cannot drift back.Measured in Chromium at 440×956 with the inset injected exactly as
tests/ui-phone-scroll.spec.tsdoes (:root{--safe-area-top:62px !important}), sampling the top-left pixel. The "before" column was reproduced in place on the same element, and it matches the reporter's screenshots pixel-for-pixel:/?mode=answerrgb(252,253,254)rgb(252,253,254)(unchanged)/?mode=documentsrgb(241,244,248)rgb(252,253,254)/servicesrgb(241,244,248)rgb(252,253,254)/services?q=Community&run=1rgb(241,244,248)rgb(252,253,254)The main regression risk was that a white band would be worse than a grey one if it ever failed to collapse, so the release contract (invariant 6) was checked explicitly. On scroll-hide the spacer still collapses to height
0— with the collapse wrapper (universal-header-collapse) reportingdata-scroll-hidden="true"; the spacer itself carries onlyaria-hiddenand itsdata-testid— and the top pixel returns to page content:rgb(241,244,248)light,rgb(6,7,8)dark. Dark mode paintsrgb(16,19,21), matching the dark--surface. Forced-colors is unaffected:--backgroundand--surfaceboth resolve toCanvas.Verification
npm run verify:phone-chrome -- --files src/components/clinical-dashboard/master-search-header.tsx,tests/header-scroll-hide-contract.test.ts,docs/search-chrome-behaviour.md— the prescribed gate for phone-chrome work.Static/unit contracts:
Focused phone/PWA browser journeys:
npm run verify:ui(run as the gate's escalation stage): 331 passed, 2 failed (11.4m).The two failures are pre-existing in this container and independent of this change. Established by A/B — the same two specs run twice in the same tree and build pipeline, toggling only the one changed token:
var(--surface)(this PR)var(--background)(pre-change baseline)Identical in both arms, so the change is not the cause:
tests/ui-pwa.spec.ts:143— manifest/icons/service-worker headers/Chromium installability.tests/ui-smoke.spec.ts:3614— fails at line 3670,expect(pdfScroller.locator("canvas")).toBeVisible(); the pdf.js canvas never paints.Neither asserts anything a
background-colorcan reach, and Tailwindbg-*sets no layout property (the element was already opaque, so occlusion is unchanged too). Hosted Advisory UI is green on this head, which is consistent with these being local-environment artefacts.npm run verify:pr-localEnvironment note for anyone reproducing: the gate's browser stages need
PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/opt/pw-browsers/chromium. On the default path all 13 focused journeys failed in 1–3 ms withbrowserType.launch: Executable doesn't exist at /opt/pw-browsers/chromium_headless_shell-1234/...— this container ships headless-shell build 1194 against Playwright 1.62.0's expected 1234. That substituted binary is also the most likely source of the two pdf.js/installability failures above.Risk and rollout
aria-hiddenspacer; no geometry, timing, ownership, or layout change, and no change to the hide/reveal release contract.var(--background).Notes
docs/phone-chrome-physical-acceptance.md, local Chromium cannot certify iOS paint. The change moves the visible band from the page colour to the bar colour and leaves the release-to-zero behaviour untouched, so it does not touch the system-owned-pixel concern in invariant 23, but a physical check is still the honest final word.Generated by Claude Code