Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
ee4e9f3
test(ui): isolate mobile chrome apply contract
BigSimmo Jul 26, 2026
6b18a09
Merge branch 'main' into codex/standardize-header-and-footer-behavior
BigSimmo Jul 26, 2026
10d6ad9
Changes before error encountered
Copilot Jul 26, 2026
a5f0591
fix(test): use /formulation/worry so phone chrome dock is present in …
Copilot Jul 26, 2026
a8c2fae
fix: apply CodeRabbit auto-fixes
coderabbitai[bot] Jul 26, 2026
ee33efd
fix(ui): use showDesktopHomeComposer for heroComposerBreakpoint; fix …
Copilot Jul 26, 2026
330b756
Merge branch 'main' into codex/standardize-header-and-footer-behavior
github-actions[bot] Jul 26, 2026
a497041
Merge branch 'main' into codex/standardize-header-and-footer-behavior
github-actions[bot] Jul 26, 2026
a7088d1
Merge branch 'main' into codex/standardize-header-and-footer-behavior
github-actions[bot] Jul 26, 2026
b51ee15
fix(test): accept absent phone dock backdrop
BigSimmo Jul 26, 2026
a17f3e2
docs(review): record PR 1246 CI repair
BigSimmo Jul 26, 2026
e1f7dad
Merge branch 'main' into codex/standardize-header-and-footer-behavior
github-actions[bot] Jul 26, 2026
92a8636
chore(ci): retrigger PR 1246 after main sync
BigSimmo Jul 26, 2026
6a7048c
Merge branch 'main' into codex/standardize-header-and-footer-behavior
BigSimmo Jul 26, 2026
de4864e
fix(test): use a real phone dock surface
BigSimmo Jul 26, 2026
498cfac
docs(review): record PR 1246 browser repair
BigSimmo Jul 26, 2026
7dde5b7
Merge remote-tracking branch 'origin/codex/standardize-header-and-foo…
BigSimmo Jul 26, 2026
a7d8977
Merge remote-tracking branch 'origin/main' into codex/pr1246-review-m…
BigSimmo Jul 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -1046,6 +1046,12 @@ This file is append-only. Never rewrite or delete an existing review record; app
| 2026-07-25 | origin/execute-audit-remediation-plan (PR #1188 closed tip) | `8b8639113925601e1687bfe4f1f29c44a4308b61` | branch-cleanup | DELETED remote. Tip rejected (conflict markers + parse breakers); intentional maintainability work already on main via #1213 (`8e3a49d0`). IMP-04 mockup/export prune from tip commit `3bc391dff` was not ported (knip-only unexports; optional follow-up). Local Antigravity worktrees left untouched. | Content proof: notices/utils/Sheet autofocus on origin/main; tip marker count 12; `git push origin --delete execute-audit-remediation-plan`. No provider calls. |
| 2026-07-25 | PR #1195 / `subagent-Asset-Optimization-Implementer-self-b295a5bb` | `c612c88aae386a29715cadfb5dbd2b95163e1dc9` | prlanded after squash merge | MERGED. Squash tip `c612c88aa` on main; remote feature branch deleted. Product content verified on main (`check:assets`, icon Cache-Control, SignedImage/non-PDF a11y). Local review worktree retained for optional cleanup. | gh pr view MERGED; two-dot product-file diff empty vs origin/main; no provider checks. |

| 2026-07-26 | PR #1246 / `codex/standardize-header-and-footer-behavior` | `b51ee15e6961d46a14c288f621d25ab30a434c7d` | Explicit CI-failure review and focused repair | APPROVE pending hosted required CI. All three completed CI failures were the same new Playwright assertion: `/formulation/worry` legitimately omits the optional legacy dock backdrop, but the test required `display: none` and received `missing`; the downstream `PR required` failure was only the aggregate. Updated the test to accept absence or require `none` when rendered; no high-confidence product defect remains. | Focused Vitest 11/11; Prettier, ESLint and `git diff --check` pass; exact local Chromium rerun blocked by the shared heavyweight lock owned by another worktree, so hosted Production UI is the merge gate. |

| 2026-07-26 | PR #1246 / `codex/standardize-header-and-footer-behavior` | `e1f7dad583465a10231abc058ee4320177998894` | Post-review main sync and stable-CI retrigger | APPROVE pending hosted required CI. The automated branch-sync merge brought current `main` (`b91b4600171be08198e92bcf19b7d67e8207cb2f`) into the reviewed repair without content conflicts; `skip-branch-sync` was applied to prevent another bot-head cancellation while required checks run. | `git merge-tree --write-tree` clean; three-dot PR scope unchanged except the required ledger record; hosted CI retrigger pending. |

| 2026-07-26 | PR #1246 / `codex/standardize-header-and-footer-behavior` | `de4864ef06626931ecd6bd22b97387f80f529cb2` | Production UI second-failure repair | APPROVE pending final hosted required CI. The first repair exposed a second invalid assumption in the same new test: `/formulation/worry` does not own a fixed phone dock, so geometry used the `-1` missing sentinel. Replaced it with the established submitted Forms result route, explicitly waiting for the dock and unfocused composer before asserting paint and scroll-hide geometry. | Exact focused production Chromium test pass 1/1 (isolated Next build); Prettier, ESLint and `git diff --check` pass; prior hosted run had 290/291 Production UI tests pass with this single invalid-route assertion. |

| 2026-07-26 | PR #1238 / `cursor/header-hide-top-bar-only-4fd7` | `fdc20bfedb61a2c267c22a3d78ccc8214e6c0087` | Continue-executing: top-bar-only hide + CI green | APPROVE / MERGEABLE. Root cause fixed: collapse wraps only `header#search` (+ Therapy addon); sticky hosts pin outer [top bar \| search] below `chrome-safe-area-top` without translating search away; sticky-stack composers stay `relative`. Services rail overlay hardened (testids + center scrollIntoView); ui-tools accepts sticky ancestor; Therapy nav assert uses collapse-host top under safe-area spacer. Merged main safe-area + submitted-result focus rules. | Hosted PR required + Production UI SUCCESS on tip; contract 14/14; focused Playwright services/desktop composers 6/6, chrome-scroll 12/12, therapy-nav 1/1. No provider-backed checks. |
| 2026-07-26 | PR #1238 / `cursor/header-hide-top-bar-only-4fd7` | head `db4390b3814910d0210497e2982414904f2e0704` / squash `cdbe0e662366f9308813e8d5fe8951ca11a47d6b` | prlanded after squash merge | LANDED. Top-bar-only hide-on-scroll with sticky search stack below `chrome-safe-area-top`; two-dot content diff empty vs `origin/main`. Remote feature branch deleted at merge. Required CI green at merge (PR policy, PR required, Production UI). | `gh pr view` MERGED; `git diff origin/main db4390b3` empty; no provider-backed checks. |
| 2026-07-26 | cursor/formulation-a11y-linear2-14d4 (PR #1250) | head `14b4e80ee41b16a80c974b6a1f8201407a0df05b` / squash `b91b4600171be08198e92bcf19b7d67e8207cb2f` | prlanded after squash merge | LANDED. Formulation disabled-state accessibility (#064) on main; product two-dot diff empty vs pre-merge tip. Superseded conflicted PRs #1219, #1223, #1226, #1231, #1249 closed. Remote feature branch deleted at merge. | Focused Chromium formulation 7/7; verify:cheap 3473 tests; hosted Production UI + PR required SUCCESS; `git diff 14b4e80e origin/main -- formulation-builder-page.tsx ui-formulation.spec.ts` empty. No provider-backed checks. |
50 changes: 29 additions & 21 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -586,11 +586,9 @@ summary::-webkit-details-marker {
beat @layer components, so a literal there would reintroduce drift. */
padding-left: max(var(--header-edge-pad), var(--safe-area-left));
padding-right: max(var(--header-edge-pad), var(--safe-area-right));
/* Translucent glass bar: the header's backdrop-blur utilities frost the
content scrolling beneath it (they were inert over the old opaque
var(--surface)). The .edge-glass-header-backdrop scrim supersedes the
old hard border-bottom and below-header ::after gradient. Keep this
value in lock-step with .universal-header below (chrome parity). */
/* Wide layouts retain the glass treatment. Phones override this with a
solid surface below: a translucent scrim extending past the bar veils
mode-home titles instead of behaving like bounded chrome. */
background: color-mix(in srgb, var(--surface) 72%, transparent);
box-shadow: none;
}
Expand Down Expand Up @@ -1382,6 +1380,23 @@ summary::-webkit-details-marker {
because its override also changes other properties. A single mobile-scoped
rule leaves nothing for the compiler to merge away. */
@media (max-width: 639px) {
/* Phone baseline: a full-width opaque header. Controls still respect the
side safe areas, but no blur or tint extends below the actual bar. */
.edge-glass-header,
.universal-header {
background: var(--surface);
Comment thread
BigSimmo marked this conversation as resolved.
}

.edge-glass-header-backdrop,
.edge-glass-header-backdrop::before,
.edge-glass-header-backdrop::after {
display: none;
background: none;
backdrop-filter: none;
mask-image: none;
-webkit-mask-image: none;
}

.answer-suggestion-row-scroll {
mask-image: linear-gradient(90deg, black calc(100% - 1rem), transparent);
-webkit-mask-image: linear-gradient(90deg, black calc(100% - 1rem), transparent);
Expand Down Expand Up @@ -1815,10 +1830,9 @@ summary::-webkit-details-marker {
0 8px 22px rgb(16 24 40 / 8%);
}

/* Edge-to-edge phone dock: full-width footer flush to the glass. The pill is
inset via padding-bottom only — never by a non-zero `bottom` offset (that
reintroduces a white strip under the bar). Form background paints the
safe-area pad so it reads as dock chrome, not empty page margin. */
/* Edge-to-edge phone dock: one full-width footer surface flush to the
viewport. The pill is the only translucent layer; the dock itself paints
the safe-area/home-indicator region. Never add a non-zero bottom offset. */
.answer-footer-search-dock.answer-footer-search-edge,
.answer-footer-search-dock.dashboard-composer-edge.answer-footer-search-edge,
.answer-footer-search-dock.document-mobile-search-edge.answer-footer-search-edge {
Expand All @@ -1831,13 +1845,7 @@ summary::-webkit-details-marker {
padding-inline: max(0.75rem, var(--safe-area-left)) max(0.75rem, var(--safe-area-right));
padding-top: 0.5rem;
padding-bottom: max(0.5rem, var(--safe-area-bottom));
background: linear-gradient(
180deg,
transparent 0%,
color-mix(in srgb, var(--background) 72%, transparent) 42%,
color-mix(in srgb, var(--background) 94%, transparent) 72%,
var(--background) 100%
);
background: var(--surface);
}

.answer-footer-search-dock.document-mobile-search-edge.answer-footer-search-edge.document-mobile-search-compact,
Expand Down Expand Up @@ -1880,13 +1888,13 @@ summary::-webkit-details-marker {
pointer-events: none;
}

.answer-footer-search-dock .answer-footer-search-backdrop {
display: none;
}

.answer-footer-search-dock .answer-footer-search-pill {
border-color: var(--border-strong);
background: var(--surface);
/* The dock repaints the pill opaque, so the inherited blur has nothing
translucent to sample — drop it to save a compositing layer per scroll
frame (matches the chip override just below). */
backdrop-filter: none;
background: color-mix(in srgb, var(--surface) 92%, transparent);
Comment thread
BigSimmo marked this conversation as resolved.
box-shadow:
0 -1px 0 color-mix(in srgb, var(--border) 60%, transparent),
0 1px 3px rgb(16 24 40 / 5%);
Expand Down
6 changes: 5 additions & 1 deletion src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3063,7 +3063,11 @@ export function ClinicalDashboard({
const desktopHomeComposerSlotId = showDesktopHomeComposer ? modeHomeDesktopComposerSlotId : undefined;
const desktopResultComposerSlotId =
!desktopHomeComposerSlotId && searchMode !== "answer" ? desktopPageComposerSlotId : undefined;
const heroComposerBreakpoint = showDesktopHomeComposer || showAnswerHome ? "all" : "sm-up";
// Any mounted mode home (answer, documents, prescribing, differentials, tools,
// favourites) keeps the in-flow hero pill on phones ("all") per the
// page-ownership contract. Only result/submitted views fall back to "sm-up"
// so phones get the compact bottom dock.
const heroComposerBreakpoint = showDesktopHomeComposer ? "all" : "sm-up";
const heroOwnsPhoneComposer = Boolean(desktopHomeComposerSlotId) && heroComposerBreakpoint === "all";
const hasMobileBottomSearch = searchMode !== "answer" && !heroOwnsPhoneComposer;
// Favourites and Tools are content-rich hubs: they share the centred hero but
Expand Down
23 changes: 23 additions & 0 deletions tests/mobile-chrome-paint-contract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { readFileSync } from "node:fs";

import { describe, expect, it } from "vitest";

const globalStylesSource = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");

describe("mobile chrome paint baseline", () => {
it("uses one opaque edge-to-edge phone header and one footer surface", () => {
const phoneStyles = globalStylesSource.slice(globalStylesSource.indexOf("@media (max-width: 639px)"));

expect(phoneStyles).toMatch(
/\.edge-glass-header,\s*\n\s*\.universal-header\s*\{\s*\n\s*background: var\(--surface\)/,
);
expect(phoneStyles).toMatch(/\.edge-glass-header-backdrop[\s\S]*?display: none/);
expect(phoneStyles).toMatch(/\.answer-footer-search-dock\.answer-footer-search-edge[\s\S]*?bottom: 0;/);
expect(phoneStyles).toMatch(
/\.answer-footer-search-dock \.answer-footer-search-backdrop\s*\{\s*\n\s*display: none/,
);
expect(phoneStyles).toMatch(
/\.answer-footer-search-dock \.answer-footer-search-pill[\s\S]*?color-mix\(in srgb, var\(--surface\) 92%, transparent\)/,
);
});
});
15 changes: 7 additions & 8 deletions tests/mobile-composer-reserve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,17 +102,16 @@ describe("mobile composer reserve contract", () => {
).toBe(mobileComposerIdleReserve);
});

it("derives hero phone ownership from the mounted hero slot, not answer-home alone", () => {
// Answer-home + !canRunSearch keeps showAnswerHome true while the hero slot
// is unset (showDesktopHomeComposer requires !error). Ownership must follow
// the slot so the dock reserve stays and the fixed composer cannot cover the
// setup/error message.
it("derives hero phone ownership from the mounted hero slot; any mode home uses all-widths breakpoint", () => {
// Any mounted mode home (answer, documents, prescribing, tools, favourites)
// needs "all" (phones keep the in-flow hero pill) per the page-ownership
// contract. Only result/submitted views use "sm-up" so phones get the compact
// bottom dock. desktopHomeComposerSlotId is undefined on result views, so
// heroOwnsPhoneComposer stays false there regardless of the breakpoint value.
const dashboard = source("src/components/ClinicalDashboard.tsx");
const header = source("src/components/clinical-dashboard/master-search-header.tsx");
expect(dashboard).toContain('(activeModeResultKind === "favourites" && favouritesAccessible)');
expect(dashboard).toContain(
'const heroComposerBreakpoint = showDesktopHomeComposer || showAnswerHome ? "all" : "sm-up";',
);
expect(dashboard).toContain('const heroComposerBreakpoint = showDesktopHomeComposer ? "all" : "sm-up";');
expect(dashboard).toContain(
'const heroOwnsPhoneComposer = Boolean(desktopHomeComposerSlotId) && heroComposerBreakpoint === "all";',
);
Expand Down
59 changes: 59 additions & 0 deletions tests/ui-phone-scroll.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,65 @@ test.beforeEach(async ({ page }) => {
await blockExternalRequests(page);
});

test("phone chrome has an opaque header, one edge-to-edge footer, and releases both edges when hidden", async ({
page,
}) => {
await page.emulateMedia({ reducedMotion: "no-preference" });
await page.setViewportSize(phoneViewport);
// A submitted Forms search is a stable GlobalSearchShell result surface: it
// renders the compact bottom dock and has enough content to exercise the
// shared header/footer hide signal. Its legacy backdrop remains optional;
// when present, the phone paint contract requires CSS to hide it.
await gotoPhoneSurface(page, "/forms?q=Form&run=1&focus=1");
await expect(page.locator("form.answer-footer-search-dock")).toBeVisible({ timeout: 20_000 });
await expect(page.getByTestId("global-search-input")).not.toBeFocused({ timeout: 5_000 });

const visible = await page.evaluate(() => {
const header = document.querySelector<HTMLElement>("header#search");
const headerBackdrop = document.querySelector<HTMLElement>(".edge-glass-header-backdrop");
const dock = document.querySelector<HTMLElement>(".answer-footer-search-dock");
const dockBackdrop = dock?.querySelector<HTMLElement>(".answer-footer-search-backdrop");
const pill = dock?.querySelector<HTMLElement>(".answer-footer-search-pill");
const dockRect = dock?.getBoundingClientRect();
return {
headerBackground: header ? getComputedStyle(header).backgroundColor : "",
headerBackdropDisplay: headerBackdrop ? getComputedStyle(headerBackdrop).display : "missing",
dockBackdropDisplay: dockBackdrop ? getComputedStyle(dockBackdrop).display : "missing",
dockLeft: dockRect?.left ?? -1,
dockRight: dockRect?.right ?? -1,
dockBottom: dockRect?.bottom ?? -1,
pillBackground: pill ? getComputedStyle(pill).backgroundColor : "",
};
});

expect(visible.headerBackground).toMatch(/^rgb\(/);
expect(visible.headerBackdropDisplay).toBe("none");
expect(["missing", "none"]).toContain(visible.dockBackdropDisplay);
expect(visible.dockLeft).toBeCloseTo(0, 0);
expect(visible.dockRight).toBeCloseTo(phoneViewport.width, 0);
expect(visible.dockBottom).toBeCloseTo(phoneViewport.height, 0);
expect(visible.pillBackground).toMatch(/(?:^rgba\([^)]+,\s*0\.92\)|\/ 0\.92\))/);

const geometry = await readGeometry(page);
await dragScrollBy(page, Math.min(geometry.maxOffset, 500), 24);
await expect(page.getByTestId("universal-header-collapse")).toHaveAttribute("data-scroll-hidden", "true");
await expect(page.locator(".answer-footer-search-dock")).toHaveAttribute("data-scroll-hidden", "true");

const hidden = await page.evaluate(() => {
const collapse = document.querySelector<HTMLElement>('[data-testid="universal-header-collapse"]');
const dock = document.querySelector<HTMLElement>(".answer-footer-search-dock");
const main = document.getElementById("main-content");
return {
collapseHeight: collapse?.getBoundingClientRect().height ?? -1,
dockTop: dock?.getBoundingClientRect().top ?? -1,
reserve: main ? getComputedStyle(main).getPropertyValue("--mobile-composer-reserve").trim() : "",
};
});
expect(hidden.collapseHeight).toBeLessThanOrEqual(1);
expect(hidden.dockTop).toBeGreaterThanOrEqual(phoneViewport.height - 1);
expect(hidden.reserve).toBe("0rem");
});

for (const route of [...modeHomeRoutes, ...dashboardRoutes, ...longRoutes]) {
test(`phone scroll stays smooth and bottom-stable on ${route}`, async ({ page }) => {
await page.emulateMedia({ reducedMotion: "no-preference" });
Expand Down
Loading