Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -1105,3 +1105,4 @@ This file is append-only. Never rewrite or delete an existing review record; app
| 2026-07-26 | `codex/phone-footer-glass` | `5ad4f8b28` | Final phone footer glass and scroll-stability review | APPROVE. Replaced the opaque phone footer/safe-area slab with localized translucent glass across shared docks and page-owned calculator/document composers; hidden chrome releases paint, pointer ownership, and reserve. Final review found and fixed calculator reserve under-budgeting plus Chromium scroll anchoring feedback, with insufficient-runway collapse refusal and sufficient-runway frame-monotonic hide/reveal. No P0-P3 findings remain. Highest residual risk is physical iOS/WebKit safe-area and momentum compositing beyond simulated Chromium. | `verify:cheap` PASS (393 files; 3518 passed / 2 skipped); focused Services/Calculators and calculator transition Chromium PASS; `verify:ui` production build 312/313 with the sole unchanged Answer short-runway geometry outlier immediately passing exact rerun 1/1; final diff review APPROVE; no provider-backed checks. |

| 2026-07-26 | `codex/phone-footer-glass` | `7e4fd1a23` | Review-follow-up and CI hydration-race review | APPROVE. Scoped the expanded collapse runway only to combined in-flow header plus reserve owners, cleared the calculator dock focus latch after sheet teardown, and made mode-home UI assertions wait for one settled owner during production hydration. Both automated review threads were addressed and resolved. No P0-P3 findings remain; physical iOS/WebKit compositing remains the only material unverified surface. | `verify:cheap` PASS; focused scroll-hide/static contracts 25/25 PASS; focused calculator teardown/geometry Chromium 3/3 PASS; affected mode-home production Chromium 5/5 PASS; no provider-backed checks. |
| 2026-07-27 | `codex/phone-footer-paint` | `e1657a4a4e3e25b4a35a1dcf544b3efddb123c3f` | Protected-main release-readiness review of phone footer safe-area paint | APPROVE. The prior footer remained visually opaque because its normal/fallback terminal tints reached 72-90%, all blur masks painted through the physical edge, and hidden WebKit paint had no explicit terminal visibility state. The shared dock now has bounded localized tint, transparent gradient/mask terminals, an 88% pill, and delayed `visibility: hidden` synchronized to the 240ms exit; reduced-motion hides immediately. Therapy and calculator ownership prove the standard shared behavior. No P0-P3 finding remains. Highest residual risk is physical iOS momentum/compositor behavior beyond local WebKit emulation. | `verify:cheap` PASS (393 files; 3518 passed / 2 skipped); focused Therapy Chromium + WebKit PASS; calculator shared-dock Chromium + WebKit PASS; `verify:ui` PASS (314/314); `verify:pr-local` PASS including production build/client-secret scan and 36-case offline RAG fixtures; no live provider-backed checks. |
Comment thread
coderabbitai[bot] marked this conversation as resolved.
2 changes: 1 addition & 1 deletion docs/search-chrome-behaviour.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This repo uses one shared search experience across the global shell, dashboard r
2. Keep the CSS token `--phone-dock-hidden-pad` aligned with `mobileComposerHiddenReserve`.
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 a localized translucent gradient/blur through the safe area; it must move and fade with the dock, never become a viewport-fixed or opaque slab.
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.
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.
Expand Down
111 changes: 88 additions & 23 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -870,19 +870,39 @@ summary::-webkit-details-marker {
z-index: 0;
height: var(--footer-scrim-height);
overflow: hidden;
/* The safe-area tint stays visibly translucent: this is localized glass
around the pill, never a second opaque footer surface. */
/* Localized glass only: tint around the pill, then return to true zero at
the physical edge. A high terminal tint reads as an opaque safe-area bar
on light pages even though color-mix technically contains transparency. */
background: linear-gradient(
180deg,
transparent 0%,
color-mix(in srgb, var(--background) 12%, transparent) 32%,
color-mix(in srgb, var(--background) 48%, transparent) 62%,
color-mix(in srgb, var(--background) 64%, transparent) 84%,
color-mix(in srgb, var(--background) 72%, transparent) 100%
color-mix(in srgb, var(--background) 8%, transparent) 30%,
color-mix(in srgb, var(--background) 28%, transparent) 58%,
color-mix(in srgb, var(--background) 18%, transparent) 78%,
transparent 100%
);
backdrop-filter: blur(2px) saturate(130%);
mask-image: linear-gradient(180deg, rgb(0 0 0 / 20%) 0%, rgb(0 0 0 / 55%) 28%, black 52%, black 100%);
-webkit-mask-image: linear-gradient(180deg, rgb(0 0 0 / 20%) 0%, rgb(0 0 0 / 55%) 28%, black 52%, black 100%);
mask-image: linear-gradient(
180deg,
transparent 0%,
rgb(0 0 0 / 32%) 26%,
black 50%,
rgb(0 0 0 / 45%) 78%,
transparent 100%
);
-webkit-mask-image: linear-gradient(
180deg,
transparent 0%,
rgb(0 0 0 / 32%) 26%,
black 50%,
rgb(0 0 0 / 45%) 78%,
transparent 100%
);
opacity: 1;
visibility: visible;
transition:
opacity 180ms var(--ease-out-soft),
visibility 0s linear;
}

.answer-footer-search-dock .answer-footer-search-backdrop::before,
Expand All @@ -894,22 +914,43 @@ summary::-webkit-details-marker {
}

.answer-footer-search-dock .answer-footer-search-backdrop::before {
backdrop-filter: blur(8px) saturate(130%);
mask-image: linear-gradient(180deg, transparent 0%, rgb(0 0 0 / 35%) 20%, black 45%, black 80%, transparent 100%);
backdrop-filter: blur(6px) saturate(125%);
mask-image: linear-gradient(
180deg,
transparent 0%,
rgb(0 0 0 / 30%) 24%,
black 48%,
rgb(0 0 0 / 40%) 72%,
transparent 100%
);
-webkit-mask-image: linear-gradient(
180deg,
transparent 0%,
rgb(0 0 0 / 35%) 20%,
black 45%,
black 80%,
rgb(0 0 0 / 30%) 24%,
black 48%,
rgb(0 0 0 / 40%) 72%,
transparent 100%
);
}

.answer-footer-search-dock .answer-footer-search-backdrop::after {
backdrop-filter: blur(22px) saturate(140%);
mask-image: linear-gradient(180deg, transparent 0%, transparent 50%, black 68%, black 100%);
-webkit-mask-image: linear-gradient(180deg, transparent 0%, transparent 50%, black 68%, black 100%);
backdrop-filter: blur(12px) saturate(130%);
mask-image: linear-gradient(
180deg,
transparent 0%,
transparent 46%,
black 64%,
rgb(0 0 0 / 35%) 80%,
transparent 100%
);
-webkit-mask-image: linear-gradient(
180deg,
transparent 0%,
transparent 46%,
black 64%,
rgb(0 0 0 / 35%) 80%,
transparent 100%
);
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
Expand All @@ -923,9 +964,10 @@ summary::-webkit-details-marker {
background: linear-gradient(
180deg,
transparent 0%,
color-mix(in srgb, var(--background) 45%, transparent) 36%,
color-mix(in srgb, var(--background) 82%, transparent) 68%,
color-mix(in srgb, var(--background) 88%, transparent) 100%
color-mix(in srgb, var(--background) 16%, transparent) 34%,
color-mix(in srgb, var(--background) 38%, transparent) 62%,
color-mix(in srgb, var(--background) 20%, transparent) 80%,
transparent 100%
);
}

Expand Down Expand Up @@ -968,9 +1010,10 @@ summary::-webkit-details-marker {
background: linear-gradient(
180deg,
transparent 0%,
color-mix(in srgb, var(--background) 50%, transparent) 36%,
color-mix(in srgb, var(--background) 86%, transparent) 72%,
color-mix(in srgb, var(--background) 90%, transparent) 100%
color-mix(in srgb, var(--background) 24%, transparent) 34%,
color-mix(in srgb, var(--background) 52%, transparent) 62%,
color-mix(in srgb, var(--background) 32%, transparent) 80%,
transparent 100%
);
}

Expand Down Expand Up @@ -1867,6 +1910,16 @@ summary::-webkit-details-marker {
pointer-events: none;
}

/* WebKit can retain a backdrop-filter compositor layer after its translated
parent reaches opacity: 0. Fade the scrim with the dock, then make that
paint layer non-rendering at the end of the hide transition. Visibility
becomes visible immediately on reveal, so the reverse path stays smooth. */
.answer-footer-search-dock[data-scroll-hidden="true"] .answer-footer-search-backdrop {
opacity: 0;
visibility: hidden;
transition-delay: 0ms, 240ms;
}

/* Compare addon adds in-flow chrome above the pill; clear a little extra so
the Compare bar does not leave a 1px/subpixel peep on the viewport edge. */
.answer-footer-search-dock.document-mobile-search-edge.answer-footer-search-edge[data-footer-addon="differentials-compare"][data-scroll-hidden="true"] {
Expand Down Expand Up @@ -1897,7 +1950,7 @@ summary::-webkit-details-marker {

.answer-footer-search-dock .answer-footer-search-pill {
border-color: var(--border-strong);
background: color-mix(in srgb, var(--surface) 92%, transparent);
background: color-mix(in srgb, var(--surface) 88%, transparent);
box-shadow:
0 -1px 0 color-mix(in srgb, var(--border) 60%, transparent),
0 1px 3px rgb(16 24 40 / 5%);
Expand Down Expand Up @@ -1929,6 +1982,18 @@ summary::-webkit-details-marker {
}
}

@media (max-width: 639px) and (prefers-reduced-motion: reduce) {
.answer-footer-search-dock[data-scroll-hidden="true"] .answer-footer-search-backdrop {
transition-delay: 0ms;
}
}

@media (max-width: 639px) {
html[data-motion="reduced"] .answer-footer-search-dock[data-scroll-hidden="true"] .answer-footer-search-backdrop {
transition-delay: 0ms;
}
}

/* Tablet rail range: the collapsed icon rail occupies the left edge from md
until the full sidebar takes over at lg, so fixed composers must centre on
the remaining content area instead of the viewport. Shells set
Expand Down
20 changes: 12 additions & 8 deletions tests/mobile-chrome-paint-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ describe("mobile chrome paint baseline", () => {
/\.answer-footer-search-dock\.answer-footer-search-edge\[data-scroll-hidden="true"\][\s\S]*?opacity: 0;[\s\S]*?pointer-events: none/,
);
expect(phoneStyles).toMatch(
/\.answer-footer-search-dock \.answer-footer-search-pill[\s\S]*?color-mix\(in srgb, var\(--surface\) 92%, transparent\)/,
/\.answer-footer-search-dock \.answer-footer-search-pill[\s\S]*?color-mix\(in srgb, var\(--surface\) 88%, transparent\)/,
);
expect(phoneStyles).toMatch(
/\.answer-footer-search-dock\[data-scroll-hidden="true"\] \.answer-footer-search-backdrop[\s\S]*?opacity: 0;[\s\S]*?visibility: hidden;[\s\S]*?transition-delay: 0ms, 240ms/,
);
});

it("keeps every footer-glass fallback terminal translucent", () => {
it("keeps every footer-glass variant localized with a fully transparent physical edge", () => {
const footerBackdropStart = globalStylesSource.indexOf(
".answer-footer-search-dock .answer-footer-search-backdrop {",
);
Expand All @@ -45,12 +48,13 @@ describe("mobile chrome paint baseline", () => {
const supportsFallbackSource = globalStylesSource.slice(supportsFallbackStart, reducedTransparencyStart);
const reducedTransparencySource = globalStylesSource.slice(reducedTransparencyStart);

expect(footerBackdropSource).toContain("color-mix(in srgb, var(--background) 72%, transparent) 100%");
expect(supportsFallbackSource).toContain("color-mix(in srgb, var(--background) 88%, transparent) 100%");
expect(reducedTransparencySource).toContain("color-mix(in srgb, var(--background) 90%, transparent) 100%");
expect(`${footerBackdropSource}\n${supportsFallbackSource}\n${reducedTransparencySource}`).not.toContain(
"var(--background) 100%",
);
expect(footerBackdropSource).toContain("color-mix(in srgb, var(--background) 28%, transparent) 58%");
expect(supportsFallbackSource).toContain("color-mix(in srgb, var(--background) 38%, transparent) 62%");
expect(reducedTransparencySource).toContain("color-mix(in srgb, var(--background) 52%, transparent) 62%");
for (const variant of [footerBackdropSource, supportsFallbackSource, reducedTransparencySource]) {
expect(variant).toContain("transparent 100%");
expect(variant).not.toMatch(/color-mix\([^\n]+\) 100%/);
}
});

it("registers the calculator page reserve with the shared hide and transition contracts", () => {
Expand Down
Loading
Loading