diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 729fd6f6a7..a0ef51e01d 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -1305,6 +1305,10 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-07-29 | 1391 | ff4ddfabb8d8594da0c09e04dc37e9286745140b | PR #1391 merge | merged as 39f2bcea. Three review P2s fixed: both ui-smoke retries made idempotent (toPass re-runs after a late-landing click; the mode-menu one clicked a toggle and oscillated) plus a second guard so the <768px branch can recover at all, and #111 archived instead of left open-and-done. One Production UI red on ui-phone-scroll.spec.ts:574 was proven a flake before re-running: the delta from the passing head baecef05 was two ui-smoke guards and one docs row, neither reachable from that spec, and the PR touches no document-viewer phone-scroll surface. Re-run green | verify:cheap exit 0 (429 files / 4404 tests); design-token orphan guard proven red on a reintroduced class; PR required success with UI_RESULT success on re-run | | 2026-07-29 | 1391 | baecef05cac86c4d52af895d483a33ba3c40cd61 | PR #1391 review | reviewed clean — text-4xs retirement confirmed against globals.css (--text-3xs 0.625rem present, --text-4xs absent); orphan guard proven to fail on a reintroduced class; six Playwright retries all retry action-plus-effect so a genuine regression still fails. Resolved the outstanding-issues #108/#109 double-allocation (renumbered to #110/#111, marker to 112) and recorded #111 done | verify:cheap exit 0 (429 files / 4404 tests); design-token-contract 28 passed; check:branch-review-ledger passed | | 2026-07-29 | 1374 | c14edb9c6f0bdbbfb147752503e016f2543fd803 | PR #1374 review + merge | merged as 3704007c — DocumentViewer identity-bound state clear (P1) implemented and verified red without it; all 12 review threads resolved | verify:cheap exit 0 (429 files / 4403 tests); PR required success; Production UI success | +| 2026-07-30 | claude/white-element-positioning-t607pk | db341491b0d94cf11641eb98aa8726629580a652 | phone-chrome safe-area paint fix | APPROVED — no P0/P1. One P2 pre-existing residual (spacer/bar mismatch on notched tablet sm+ in PWA mode — worse before, slightly better after; no action needed). P3: contract test slice brittleness (indexOf '/>') and invariant-6 prose length. No defects introduced. | vitest header-scroll-hide-contract 27/27 green; source read: master-search-header.tsx, globals.css forced-colors block, tests/header-scroll-hide-contract.test.ts, docs/search-chrome-behaviour.md | +| 2026-07-30 | claude/white-element-positioning-t607pk | db341491b0d94cf11641eb98aa8726629580a652 | pr-diff-review | No P0/P1. Phone chrome-safe-area-top paint fix from --background to --surface is correct for collapse-strategy modes; overlay answer path unchanged (no spacer). Scroll-hide h-0 release untouched. Residual: sm+ translucent glass vs opaque spacer (pre-existing, improved); hosted Cursor Bugbot suite stuck queued with 0 runs. | vitest:header-scroll-hide-contract:27-pass; frontend-ui-reviewer; local-bug-hunt; gh-threads:0; no-provider-gates | +| 2026-07-30 | claude/white-element-positioning-t607pk | db341491b0d94cf11641eb98aa8726629580a652 | bugbot | pr-bugbot: no cursor[bot] Bugbot findings on PR #1393 head. Hosted Cursor check suite 82712209672 stuck queued (0 check-runs) since 2026-07-30T02:33:11Z; rerequest 403. Local Bugbot-equivalent: no P0/P1 confirmed. | gh-graphql-threads:empty; gh-review-comments:0; pr-bugbot-agent; rerequest:403; no-fixes | +| 2026-07-30 | claude/white-element-positioning-t607pk | e7a27bbfb2fca907aa235c36f8966164e1ad9e27 | pr-babysit | No product fix needed. Prior PR-required failures were production-ui cancelled by repeated main merges superseding runs. Current head: merge-tree clean, 0 behind main, hosted CI success including Production UI + PR required. No unresolved review threads. Bugbot: no cursor[bot] findings; Cursor suite still queued. Local A/B proved 3 Playwright failures identical on --surface and --background (env flake). | verify:cheap:4451-pass; format:check:pass; phone-contracts:82-pass; hosted:PR-required:pass; hosted:Production-UI:pass; A/B-playwright:same-3-fail; bugbot:no-findings | | 2026-07-30 | claude/top-search-design-mockups-w53znc | 7577a1ea60ab5f0918885f90e849bbac754234b1 | PR #1394 search-results-band-adoption + #096/#115 | No P0/P1. Disposition1 partial: isAlwaysStandaloneShellPath fixes services/etc; /tools still layout-false-positive (P2). Disposition2 verified: import-as-rendered deferred as #115 (P3). #096 closure text accurate for root-path; row still open with stale Still-live clause. | vitest tests/search-results-band-adoption.test.ts 6/6; offline gutting repro tools vs services; static read search-route-ownership + outstanding-issues | | 2026-07-30 | PR #1394 / `claude/top-search-design-mockups-w53znc` | `8c39158d99876338613d5bb3195847fd253ef5ff` | CI/review closeout: /tools page-only roots + thread disposition | FIXED. Layout false-positive for `/tools` closed via `isStandaloneModeHomePath` in reachabilityRoots. Import-as-rendered finding left as `#115` (pre-existing; lint catches the plausible slip). Both Codex threads dispositioned. Merge clean vs main. | vitest adoption 6/6; full unit 4451 passed / 4 skipped; typecheck; prettier; Bugbot pr-bugbot | | 2026-07-30 | PR #1394 / `claude/top-search-design-mockups-w53znc` | `d658f120253ac0a2dbfe1c9b1a14c6a0744af14d` | CI/review closeout tip (supersedes 8c39158d row) | FIXED layout `/tools` false-positive; `#115` deferred with human disposition; threads resolved; merge-tree clean; CI settling | vitest adoption 6/6; full unit 4451/4; typecheck; Bugbot | @@ -1312,3 +1316,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-07-30 | PR #1394 / `claude/top-search-design-mockups-w53znc` | `0d47141fc030684299dcb265e3d853c93b9e2a91` | CI/review closeout — merged | MERGED as squash `0d47141f`. Prior tip `4a001efa` had required CI green after prettier fix `61314887` (Static PR/CircleCI red on `#096` padding) and main sync. Layout/`/tools` false-positive fixed; `#115` deferred; review threads resolved. Post-merge ledger-only follow-up. | hosted Static/Unit/PR-required/CircleCI pass on pre-merge tip; vitest adoption 6/6; typecheck; Bugbot no open P0/P1; merge-tree clean | | 2026-07-30 | cursor/pr-1394-ledger-closeout-c2bf | f734dc4d4c8b19d5fec43bbd388c2a421e47668a | PR #1399 babysit / CI+Bugbot closeout | MERGE-READY. No failing CI, no unresolved review threads, merge-tree clean vs origin/main, Bugbot no bugs. Docs-only ledger append for merged #1394; no code fix required. | hosted PR required SUCCESS; Static PR SUCCESS (lint/typecheck/format/ledger); CircleCI verify SUCCESS; local check:branch-review-ledger PASS; prettier PASS; lint PASS; Bugbot pr-bugbot no findings | | 2026-07-30 | claude/test-coverage-analysis-2vcd8a | d5842e62238237ff5c47da0b32ef8d9f12819714 | PR #1398 babysit | COMPLETE for tip: cleared main conflict; fixed Codex P2 (reject refs/*→origin/* nesting); prior Codex P2 (destination check) already fixed in de594186 and resolved; 0 unresolved threads; merge-tree clean. Hosted CI re-running. | repo-hygiene 40/40; verify:cheap earlier PASS on pre-tip; format:changed PASS; Bugbot none; Codex P2 resolved | +| 2026-07-30 | claude/white-element-positioning-t607pk | b82ff088436cd936d219a4eb54a54d09a88fbd7c | pr-babysit | Product tip sound; no code fix. Hosted CI fully green once at e7a27bbf (Production UI+PR required). Recurring blocker: repeated Merge main into PR cancels Production UI mid-run so PR required fails with production-ui=cancelled. merge-tree clean / MERGEABLE when left alone. No review threads. Bugbot: no cursor[bot] findings; suite stays queued. Local A/B: 3 Playwright fails identical on --surface/--background. | verify:cheap:pass; hosted:e7a27bbf:PR-required+Production-UI:pass; A/B-playwright:env-flake; bugbot:no-findings; churn:main-merges-cancel-ui | diff --git a/docs/search-chrome-behaviour.md b/docs/search-chrome-behaviour.md index 669bae9f17..610fe35e0d 100644 --- a/docs/search-chrome-behaviour.md +++ b/docs/search-chrome-behaviour.md @@ -21,7 +21,7 @@ This repo uses one shared search experience across the global shell, dashboard r 3. A visible fixed phone dock may include `var(--safe-area-bottom)` so the pill clears the home indicator. 4. A hidden phone dock must release the content-facing reserve to `0rem`; do not use `env(safe-area-inset-bottom)` or `var(--safe-area-bottom)` for hidden content padding. 5. Edge-to-edge phone dock mode is `left: 0; right: 0; bottom: 0; width: 100%`; inset the pill with padding, not with a non-zero bottom offset. Keep the dock form transparent and use its absolute `.answer-footer-search-backdrop` child for localized translucent gradient/blur around the pill. The gradient and every blur mask must return to fully transparent at the physical bottom edge. It must move and fade with the dock, then become `visibility: hidden` after the hide transition so WebKit cannot retain a safe-area compositor strip; it must never become a viewport-fixed or opaque slab. -6. Header and footer chrome that share the same scroll signal should hide/reveal symmetrically for the surfaces that actually hide: when the phone top bar is hidden, `chrome-safe-area-top` and the controls both release to `0rem` so underlying content paints to the physical viewport edge. The visible phone header still owns `var(--safe-area-top)`; tablet/desktop sticky chrome keeps its pinned inset. Top-bar hide/reveal is cross-breakpoint; the search field stays pinned on tablets, while desktop search belongs to page flow and scrolls away naturally; the bottom search dock is phone-only. Hidden bottom dock reserve stays `0rem` (invariant 4). Read "Scroll hide/reveal" below before changing either. +6. Header and footer chrome that share the same scroll signal should hide/reveal symmetrically for the surfaces that actually hide: when the phone top bar is hidden, `chrome-safe-area-top` and the controls both release to `0rem` so underlying content paints to the physical viewport edge. The visible phone header still owns `var(--safe-area-top)`; tablet/desktop sticky chrome keeps its pinned inset. While visible that spacer is the top of the header, so it paints `var(--surface)` — the bar's own opaque phone colour — never `var(--background)`: the page colour there reads as a status-bar band above the bar, the seam overlay-strategy answer mode never shows because its header pads the inset itself. Keep it opaque so the sm+ pinned inset still hides scrolled content. Top-bar hide/reveal is cross-breakpoint; the search field stays pinned on tablets, while desktop search belongs to page flow and scrolls away naturally; the bottom search dock is phone-only. Hidden bottom dock reserve stays `0rem` (invariant 4). Read "Scroll hide/reveal" below before changing either. 7. Do not add page-local dock-sized `pb-[calc(...safe-area...)]` under a shell-owned dock. Put clearance in the shared reserve or the page-owned composer, never both. 8. `GlobalSearchShell` uses an inner `mobile-composer-reserve-pad` so phone padding contributes to scroll height; do not move phone shell clearance back to scrollport padding without a browser proof. 9. Page-owned fixed phone composers follow the same release contract: calculators use the shared footer backdrop; DocumentViewer keeps its floating pill but synchronizes transform, opacity, pointer release, and its own zero-reserve content padding. In-flow hero composers remain free of fixed-footer glass. diff --git a/src/components/clinical-dashboard/master-search-header.tsx b/src/components/clinical-dashboard/master-search-header.tsx index 8d14879d38..4849152a7b 100644 --- a/src/components/clinical-dashboard/master-search-header.tsx +++ b/src/components/clinical-dashboard/master-search-header.tsx @@ -2169,7 +2169,15 @@ export function MasterSearchHeader({ // header row's timing to avoid a one-frame gap during hide/reveal. // sm+ keeps its pinned inset because the sticky [bar | search] stack // is a separate wide-layout contract. - "relative z-40 shrink-0 bg-[color:var(--background)] motion-reduce:transition-none sm:h-[var(--safe-area-top)]", + // + // Paint the header's own surface, not the page background. While the + // spacer is visible it is the top of the header, so `--background` + // drew a page-coloured status-bar band above a `--surface` bar on + // every collapse-strategy mode — the seam answer mode never had, + // because its overlay header pads the inset itself and paints + // straight through. Opaque on purpose: the spacer must keep hiding + // scrolled content at the sm+ pinned inset. + "relative z-40 shrink-0 bg-[color:var(--surface)] motion-reduce:transition-none sm:h-[var(--safe-area-top)]", phoneOverlayMotion ? "max-sm:h-[var(--safe-area-top)]" : cn( diff --git a/tests/header-scroll-hide-contract.test.ts b/tests/header-scroll-hide-contract.test.ts index 9896a9a27c..d204bc33f5 100644 --- a/tests/header-scroll-hide-contract.test.ts +++ b/tests/header-scroll-hide-contract.test.ts @@ -289,6 +289,21 @@ describe("shared header hide/reveal wiring", () => { expect(behaviourDocSource).toContain("`h-0` while hidden"); }); + it("paints the visible top safe-area with the header surface, not the page background", () => { + // While visible the spacer is the top of the header. `--background` there + // drew a page-coloured status-bar band above the `--surface` bar on every + // collapse-strategy mode, which answer mode (overlay, pads the inset in the + // header itself) never showed. + const spacerStart = headerSource.indexOf('data-testid="chrome-safe-area-top"'); + expect(spacerStart).toBeGreaterThan(-1); + // Bound the window to this element so a later call site cannot satisfy or + // break the assertion from outside the spacer. + const safeAreaSpacer = headerSource.slice(spacerStart, headerSource.indexOf("/>", spacerStart)); + expect(safeAreaSpacer).toContain("bg-[color:var(--surface)]"); + expect(safeAreaSpacer).not.toContain("bg-[color:var(--background)]"); + expect(behaviourDocSource).toContain("paints `var(--surface)`"); + }); + it("keeps the header out of sticky positioning wherever its row collapses", () => { // Sticky pins the bar inside the viewport and fights the 1fr -> 0fr grid. expect(headerSource).toMatch(/sticksAbovePhones \|\| collapsesAtEveryWidth\s*\?\s*"relative"/);