Update progress: Task 4b completed (scroll condensation fix)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 00:31:03 +00:00
parent d16656b954
commit 86e0015393
2 changed files with 35 additions and 1 deletions
+1 -1
View File
@@ -40,7 +40,7 @@ Also read `CLAUDE.md` for font setup instructions (Elvaro Grotesque and Blumir c
- [x] **Task 4: Rebuild PatientBanner.** Read `Ralph/refs/ref-banner-sidebar.md` (Patient Banner section). Full banner (80px) with surname-first format, demographic details, action buttons. Condensed banner (48px) via IntersectionObserver at 100px scroll. Mobile minimal banner with overflow menu. Uses [UI font] throughout. NHS Number tooltip: "GPhC Registration Number".
- [ ] **Task 4b: Fix PatientBanner scroll condensation.** Read `Ralph/refs/ref-banner-sidebar.md` (Patient Banner section + Implementation Patterns). The full 3-row banner (80px — name/status, demographics, contact) never displays because the IntersectionObserver sentinel is broken. The sentinel (`absolute top-0` with `h-0`) is inside a React fragment next to the sticky header — it positions relative to the viewport, and the `-100px` rootMargin means it's immediately "not intersecting", so the banner always shows as condensed. Fix: ensure the sentinel is placed in the document flow ABOVE the scrollable content area (not absolute-positioned inside the banner fragment), so it's naturally visible on load and only scrolls out of view when the user scrolls 100px. Verify that on page load the full banner displays, and after scrolling 100px it smoothly condenses to the single-row 48px layout.
- [x] **Task 4b: Fix PatientBanner scroll condensation.** Read `Ralph/refs/ref-banner-sidebar.md` (Patient Banner section + Implementation Patterns). The full 3-row banner (80px — name/status, demographics, contact) never displays because the IntersectionObserver sentinel is broken. The sentinel (`absolute top-0` with `h-0`) is inside a React fragment next to the sticky header — it positions relative to the viewport, and the `-100px` rootMargin means it's immediately "not intersecting", so the banner always shows as condensed. Fix: ensure the sentinel is placed in the document flow ABOVE the scrollable content area (not absolute-positioned inside the banner fragment), so it's naturally visible on load and only scrolls out of view when the user scrolls 100px. Verify that on page load the full banner displays, and after scrolling 100px it smoothly condenses to the single-row 48px layout.
- [ ] **Task 5: Rebuild ClinicalSidebar.** Read `Ralph/refs/ref-banner-sidebar.md` (Left Sidebar + Navigation sections). CV-friendly labels: Summary, Experience, Skills, Achievements, Projects, Education, Contact. 220px fixed width. Header branding, search input, navigation items with exact states (default/hover/active), separator line, footer with session info. Tablet mode: 56px icon-only. Keyboard shortcuts: Alt+1-7, arrow keys, "/" for search. URL hash routing.
+34
View File
@@ -306,3 +306,37 @@ Do NOT invoke the `/frontend-design` skill at runtime — it was pre-run and the
- Previous iterations skipped visual review because Chrome tools weren't available — Playwright MCP should now work
### New guardrails added: None
### Iteration 5 — Task 4b: Fix PatientBanner scroll condensation
**Completed:** Task 4b
**Changes made:**
- **Root cause identified:** The sentinel element (`absolute top-0 h-0`) was positioned at the viewport top inside a non-positioned parent. The IntersectionObserver with `-100px` rootMargin immediately reported "not intersecting", so the banner was permanently stuck in condensed state.
- **PMRInterface.tsx:** Restructured layout from document-scroll (`min-h-screen`) to flex container (`flex h-screen overflow-hidden`). Sidebar and content column are siblings. Content column is `flex-1 flex flex-col min-w-0` with banner (flex-shrink-0) above scrollable main (`overflow-y-auto`).
- **PatientBanner.tsx:** Now accepts `isCondensed` prop from parent instead of managing its own scroll detection. Removed sentinel element, removed `useScrollCondensation` import, removed `sticky top-0`. Banner is positioned above the scroll container, so it stays fixed naturally.
- **ClinicalSidebar.tsx:** Changed `h-screen sticky top-0` to `h-full` — parent flex container handles sizing.
- **useScrollCondensation.ts:** Replaced IntersectionObserver with scroll event listener. Accepts `scrollContainer` element directly (not a ref). Uses callback ref pattern in PMRInterface to handle Framer Motion mounting timing.
**Codebase patterns discovered:**
- **Callback ref pattern for Framer Motion:** `motion.main` elements may not be in the DOM when `useEffect` first runs. Using `useState` + callback ref (`setScrollContainer` via `useCallback`) triggers a re-render when the element mounts, ensuring the scroll listener attaches correctly.
- **Flex h-screen overflow-hidden layout:** The recommended clinical system layout: sidebar + content column in a viewport-height flex container. Content column has banner (flex-shrink-0) + scrollable main (flex-1 overflow-y-auto). No sticky positioning needed — elements above the scroll container stay fixed.
- **Scroll event vs IntersectionObserver:** For scroll-position-based condensation in a contained scroll area, a simple scroll event listener is more reliable than IntersectionObserver with rootMargin tricks.
**Quality checks:** All passed (typecheck, lint, build — 394.61 KB bundle)
**Visual review:** Completed via Playwright MCP at 1280x800.
- Full banner (80px, 3 rows) displays correctly on page load at scrollTop=0
- Condensed banner (48px, single row) activates after scrolling 100px+
- Banner returns to full state when scrolling back to top
- Layout: sidebar fixed, banner fixed, only main content scrolls
**Issues encountered:**
- First attempt placed sentinel in `<main>` but kept IntersectionObserver with default root (viewport) — failed because `overflow-y-auto` on main creates a separate scroll context
- Second attempt used IntersectionObserver with `root: scrollContainerRef.current` — failed due to timing: Framer Motion hadn't mounted the element when the effect ran, so `ref.current` was null
- Final solution: replaced IntersectionObserver with scroll event listener + callback ref pattern for reliable element access
**Design decisions:**
- Chose scroll event listener over IntersectionObserver for simplicity and reliability
- Used `{ passive: true }` on scroll listener for performance
- Removed min-height calculations from main (`min-h-[calc(100vh-48px)]` etc.) — flex-1 handles sizing naturally
**Next task:** Task 5 — Rebuild ClinicalSidebar