feat(ui): edge-to-edge glass header with progressive-blur scrolling - #570
Conversation
Bring the top header up to the bottom composer dock's standard: a translucent glass bar with a top progressive-blur scrim, edge-to-edge under the notch/status bar, that content scrolls beneath and frosts against — mirroring the dock's masked 2px/8px/22px three-pass blur but inverted so it is strongest at the physical top edge. - Glass bar: .edge-glass-header/.universal-header backgrounds become color-mix(var(--surface) 72%, transparent), activating the header's previously inert backdrop-blur; the hard border-bottom and the old below-header ::after gradient are superseded by the new .edge-glass-header-backdrop scrim (rendered inside <header> so it slides away with it). Mode pill and icon controls keep their opaque inline surfaces and stay legible. - Answer view overlays the header at every breakpoint: the header goes absolute inside the (now relative) content column and <main> reserves the bar's exact measured height (72px + safe-area-top) as top padding, so scrolled content passes beneath the glass; the reserve is constant (it sits at scroll-start and is already off-screen whenever the header hides). scroll-padding-top keeps scrollIntoView targets clear of the bar. The header slides away on scroll-down and returns on scroll-up at all widths (new allBreakpoints overlay in useScrollHideReporter / MasterSearchHeader; activation offset raised to the bar height). - Non-answer modes keep the in-flow header + phone-only collapse (their sm+ composer renders beneath the header, which an absolute bar would bury) while still gaining the glass + scrim visuals. - Accessibility fallbacks mirror the dock: no-backdrop-filter and prefers-reduced-transparency force an opaque bar + stronger scrim tint; forced-colors paints solid Canvas; motion-reduce disables the slide transition. - Tests: the header-hide smoke test is rewritten for the overlay mechanism (absolute header, data-scroll-hidden on <header>, reserve == bar height), plus new desktop hide/reveal and non-answer collapse coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1ekcXQZVFVKKzdoVpjyVQ
This pull request has been ignored for the connected project Preview Branches by Supabase. |
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe change adds translucent glass header styling, progressive backdrop scrims, all-breakpoint scroll hiding for answer mode, reserved main-content spacing, alert relocation, and mobile/desktop UI coverage. ChangesGlass header overlay
Estimated code review effort: 3 (Moderate) | ~25 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant ClinicalDashboard
participant useScrollHideReporter
participant MasterSearchHeader
participant Main
User->>ClinicalDashboard: Open answer mode
ClinicalDashboard->>useScrollHideReporter: Enable all-breakpoint reporting
ClinicalDashboard->>MasterSearchHeader: Configure overlay header
ClinicalDashboard->>Main: Reserve header height and scroll padding
User->>Main: Scroll content
Main->>useScrollHideReporter: Report scroll position
useScrollHideReporter->>MasterSearchHeader: Update hidden state
MasterSearchHeader->>Main: Overlay and hide or reveal header
Possibly related PRs
Caution Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional.
❌ Failed checks (1 error, 1 warning)
✅ Passed checks (9 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
✨ Simplify code
Comment |
CI format:check flagged the new .edge-glass-header-backdrop::before mask-image line; pure line wrapping, no value change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1ekcXQZVFVKKzdoVpjyVQ
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit:551b07b44e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| : "max-sm:transition-transform max-sm:duration-200 max-sm:ease-out motion-reduce:transition-none"), | ||
| hideStrategy === "overlay" && | ||
| headerChromeHidden && | ||
| (overlayAllBreakpoints ? "-translate-y-full" : "max-sm:-translate-y-full"), |
There was a problem hiding this comment.
Translate the full scrim offscreen
In answer mode after scrolling down, this -translate-y-full only moves the header by its own rendered height (~72px), but the newly added .edge-glass-header-backdrop is taller (max(6.5rem, safe-area + 5.5rem)) and is not clipped by the header. That leaves roughly 1.5–2rem of the blurred/tinted scrim still visible at the top after the header is marked hidden, so content never gets the fully clear viewport the hide-on-scroll behavior is meant to provide; the current bounding-rect test misses it because the overflowing backdrop does not affect the header rect.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/app/globals.css (1)
659-723: 🚀 Performance & Scalability | 🔵 TrivialStacked backdrop-filter layers now run at every breakpoint in answer mode.
The header itself carries
backdrop-blur-xl backdrop-saturate-150(Tailwind, on the<header>element) plus this new.edge-glass-header-backdropelement layers three morebackdrop-filterpasses (blur(2px),::before blur(8px),::after blur(22px)) to build the progressive frost. Previously this cost was scoped to phone widths only for answer mode's hide/show cycling; with the all-breakpoints overlay, these compositing-heavy layers now recompute continuously while scrolling on desktop too. Worth a quick perf check (Chromium profiling /npm run verify:ui) on lower-end hardware, since multiple stackedbackdrop-filters are one of the pricier paint operations in the platform.🤖 Prompt for 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. In `@src/app/globals.css` around lines 659 - 723, Restrict the stacked backdrop-filter treatment in .edge-glass-header-backdrop and its ::before/::after layers to the narrow answer-mode breakpoint where hide/show cycling requires it, while preserving the existing header glass styling and desktop behavior. Verify the all-breakpoints overlay no longer applies the three compositing-heavy blur passes during desktop scrolling.
🤖 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/clinical-dashboard/master-search-header.tsx`:
- Around line 246-256: Update the internal scroll fallback used by hideOnScroll
so overlay mode with allBreakpoints enabled also computes hide/reveal state on
non-phone breakpoints when scrollHidden is not supplied. Thread
hideOnScroll.allBreakpoints into the scroll hook or fallback configuration,
while preserving parent-provided scrollHidden behavior and existing phone-only
behavior otherwise.
In `@src/components/clinical-dashboard/use-hide-on-scroll.ts`:
- Around line 55-58: Thread allowAllBreakpoints through UseHideOnScrollOptions
and the useHideOnScroll implementation, forwarding it to both helper calls
including usePhoneScrollHideActive. Ensure the internal fallback in
master-search-header.tsx passes hideOnScroll.allBreakpoints so overlay mode can
operate beyond phone breakpoints.
---
Nitpick comments:
In `@src/app/globals.css`:
- Around line 659-723: Restrict the stacked backdrop-filter treatment in
.edge-glass-header-backdrop and its ::before/::after layers to the narrow
answer-mode breakpoint where hide/show cycling requires it, while preserving the
existing header glass styling and desktop behavior. Verify the all-breakpoints
overlay no longer applies the three compositing-heavy blur passes during desktop
scrolling.
🪄 Autofix (Beta)
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: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f79396cb-8cdf-4a6e-aca3-5cd959f9045f
📒 Files selected for processing (5)
src/app/globals.csssrc/components/ClinicalDashboard.tsxsrc/components/clinical-dashboard/master-search-header.tsxsrc/components/clinical-dashboard/use-hide-on-scroll.tstests/ui-smoke.spec.ts
| hideOnScroll?: { | ||
| strategy: "overlay" | "collapse"; | ||
| /** | ||
| * Overlay-only: apply the hide/reveal (and the out-of-flow absolute header) | ||
| * at every breakpoint instead of phones only. The host must reserve | ||
| * matching top padding on its scroll container. | ||
| */ | ||
| allBreakpoints?: boolean; | ||
| /** Parent-owned hidden state for hosts that report scroll via React `onScroll`. */ | ||
| scrollHidden?: boolean; | ||
| }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash# Description: Confirm every current caller that sets allBreakpoints also sets scrollHidden.
rg -n -B3 -A3 'allBreakpoints' src/componentsRepository: BigSimmo/Database
Length of output: 2463
🏁 Script executed:
#!/bin/bashset -euo pipefail
# Inspect the component logic around hideOnScroll and the hook implementation.
rg -n -A8 -B8 'internalScrollHidden|overlayAllBreakpoints|hideOnScroll\.scrollHidden|useHideOnScroll|strategy: "overlay"|allBreakpoints' src/components/clinical-dashboard/master-search-header.tsx src/components/ClinicalDashboard.tsx src/components -g '!**/*.map'# Show the relevant section of the header component with line numbers.
sed -n '1360,1465p' src/components/clinical-dashboard/master-search-header.tsx
# Show the hook implementation if present.
fd -a 'use-hide-on-scroll.ts' srcRepository: BigSimmo/Database
Length of output: 38165
🏁 Script executed:
#!/bin/bashset -euo pipefail
sed -n '1,220p' src/components/clinical-dashboard/use-hide-on-scroll.tsRepository: BigSimmo/Database
Length of output: 6591
Thread all-breakpoints into the scroll fallback. The overlay + allBreakpoints path still depends on scrollHidden, but the internal fallback stays phone-only. If a caller sets hideOnScroll={{ strategy: "overlay", allBreakpoints: true }} without also passing scrollHidden, the header will become absolute on desktop but never hide/reveal there. Either require scrollHidden for that mode or pass the all-breakpoints flag into the scroll hook.
🤖 Prompt for 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.
In `@src/components/clinical-dashboard/master-search-header.tsx` around lines 246
- 256, Update the internal scroll fallback used by hideOnScroll so overlay mode
with allBreakpoints enabled also computes hide/reveal state on non-phone
breakpoints when scrollHidden is not supplied. Thread
hideOnScroll.allBreakpoints into the scroll hook or fallback configuration,
while preserving parent-provided scrollHidden behavior and existing phone-only
behavior otherwise.
Source: Path instructions
| function usePhoneScrollHideActive(disabled = false, allowAllBreakpoints = false) { | ||
| const isPhone = useSyncExternalStore(subscribeToPhoneMedia, readPhoneMedia, readPhoneMediaServer); | ||
| return isPhone && !disabled; | ||
| return (allowAllBreakpoints || isPhone) && !disabled; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash# Description: Find other consumers of useHideOnScroll to confirm no caller currently# needs allBreakpoints through this path.
rg -n -B2 -A6 '\buseHideOnScroll\s*\(' srcRepository: BigSimmo/Database
Length of output: 3050
🏁 Script executed:
#!/bin/bashset -euo pipefail
sed -n '1,220p' src/components/clinical-dashboard/use-hide-on-scroll.ts | cat -nRepository: BigSimmo/Database
Length of output: 7907
🏁 Script executed:
#!/bin/bashset -euo pipefail
rg -n "allowAllBreakpoints|useScrollHideReporter|usePhoneScrollHideActive|useHideOnScroll" src/components/clinical-dashboard src/components/DocumentViewer.tsxRepository: BigSimmo/Database
Length of output: 2095
🏁 Script executed:
#!/bin/bashset -euo pipefail
sed -n '220,340p' src/components/clinical-dashboard/master-search-header.tsx | cat -nRepository: BigSimmo/Database
Length of output: 7872
useHideOnScroll should forward allowAllBreakpoints.master-search-header.tsx already exposes hideOnScroll.allBreakpoints, but the internal fallback still calls useHideOnScroll({ disabled: !hideOnScroll || hideOnScroll.scrollHidden !== undefined }), so overlay mode stays phone-only instead of widening past the breakpoint. Thread the option through UseHideOnScrollOptions and both helper calls.
🤖 Prompt for 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.
In `@src/components/clinical-dashboard/use-hide-on-scroll.ts` around lines 55 -
58, Thread allowAllBreakpoints through UseHideOnScrollOptions and the
useHideOnScroll implementation, forwarding it to both helper calls including
usePhoneScrollHideActive. Ensure the internal fallback in
master-search-header.tsx passes hideOnScroll.allBreakpoints so overlay mode can
operate beyond phone breakpoints.
Post-merge review of the glass header (#570) surfaced that the CSS build pipeline was silently dropping EVERY hand-authored backdrop-filter declaration: the manual -webkit-backdrop-filter duplicates in globals.css confused Lightning CSS's property merging, so the header scrim, the bottom dock's three-pass progressive blur, and the composer pill had been tint-only in every engine all along — the only real blur came from Tailwind utilities. Removing the manual -webkit- duplicates (the pipeline handles prefixing) restores genuine blur to all of them, verified via computed styles and visual probes. With real blur available, the review's remaining findings are fixed: - The header element no longer carries backdrop-blur utilities (an element with backdrop-filter forms a backdrop root that starves the nested scrim of the page behind it — redundant in Chromium, inert in WebKit). The retuned scrim (14/20/26px, top-biased masks) is now the single source of the bar's frost and samples the real page in every engine, cutting the always-active blur layers on the answer view. - Every scrim mask now fades to true zero before content rests, so no faint veil sits on the first line at rest, and the hidden-state residual is bounded by a new test assertion. - The private-scope recovery alert is sticky inside <main> so its actions stay reachable while scrolling in every mode (it previously scrolled away in non-answer modes); covered by a new test. - The scroll-hide reporter resets its direction state when the breakpoint gate changes, avoiding a stale hide/reveal blip on phone mode switches. - The no-blur / reduced-transparency fallback bar backgrounds are now layered so call-site utilities keep winning per the header-chrome layering contract; the forced-colors override stays unlayered on purpose. Review recorded in docs/branch-review-ledger.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01A1ekcXQZVFVKKzdoVpjyVQ
Summary
2px/8px/22pxthree-pass blur (.answer-footer-search-backdrop) but inverted so the blur is strongest at the physical top edge.src/app/globals.css):.edge-glass-header/.universal-headerbackgrounds becomecolor-mix(in srgb, var(--surface) 72%, transparent), which activates the header's previously-inertbackdrop-blur-xl(it was a no-op over the old opaque surface). The hardborder-bottomand the old below-header::aftermini-gradient are superseded by the new.edge-glass-header-backdropscrim, rendered as the first child of<header>so it slides away with the bar. The mode pill and icon controls keep their opaque inline surfaces and stay legible.ClinicalDashboard.tsx,master-search-header.tsx): the header goesabsoluteinside the now-relativecontent column, and<main>reserves the bar's exact measured height (72pxborderless bar →calc(4rem + max(0.5rem, env(safe-area-inset-top)))) as top padding — padding scrolls with content, which is what lets it slide up and frost beneath the glass. The reserve is constant (it sits at scroll-start and is already off-screen whenever the header hides, so reclaiming it would only jump content).scroll-padding-topkeepsscrollIntoViewtargets clear of the bar.allBreakpointsoverlay path (useScrollHideReporter,MasterSearchHeader.hideOnScroll) — the header translates up on scroll-down and returns on any deliberate scroll-up, mirroring the phone bottom dock; activation offset raised56 → 72to match the bar height. The existing open-menu/focus guards are reused unchanged (menus pin the bar; keyboard focus reveals it).sm+composer renders in-flow beneath the header, which an absolute bar would bury) while still gaining the glass + scrim visuals. Bottom dock behavior is untouched.@supports not (backdrop-filter)andprefers-reduced-transparency: reduceforce an opaque bar + stronger scrim tint;forced-colors: activepaints solidCanvaswith no blur/mask;motion-reducedisables the slide transition.Verification
npm run verify:pr-local— not run; ran the equivalent gates below.npm run verify:cheap— exit 0 (runtime, Actions pin, sitemap, brand, type-scale, icon-scale, full lint, typecheck, 1,929 unit tests incl. the merge-artifact guards).verify:uiinvocation, which in this sandbox needsPLAYWRIGHT_CHROMIUM_EXECUTABLE_PATHpointed at the pre-installed browser): fulltests/ui-smoke.spec.ts→ 70 passed; the only 4 failures are the pre-existing, unrelated/privacyheading test that fails identically on cleanmain(local-env quirk; untouched by this diff).Clinical Governance Preflight
N/A — pure presentation/layout change (header chrome, scrim CSS, scroll-hide wiring). No ingestion, answer generation, search/ranking, source rendering logic, document access, privacy, production env, or clinical output behavior is affected.
Notes
allBreakpoints: falseinClinicalDashboard.tsxmakes desktop a pinned glass bar while phones keep hiding — one-line toggle.manifest.tstheme_colorstays#ffffff(a static manifest can't be per-scheme; the per-schemeviewport.themeColoralready tints browser chrome for both themes — installed-PWA splash is the only surface affected).Generated by Claude Code