From f9a14e8e38a568d8675c4154597f73340161e4ea Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 29 Jul 2026 08:33:08 +0800 Subject: [PATCH 1/6] fix(search): preserve composer focus during viewport settling --- docs/branch-review-ledger.md | 1 + .../global-search-shell.tsx | 9 ---- .../clinical-dashboard/use-hide-on-scroll.ts | 31 +++++++++--- tests/header-scroll-hide-contract.test.ts | 14 +++--- tests/ui-phone-scroll.spec.ts | 43 ++++++++++++++++ tests/use-hide-on-scroll.dom.test.tsx | 50 +++++++++++++++++++ 6 files changed, 124 insertions(+), 24 deletions(-) create mode 100644 tests/use-hide-on-scroll.dom.test.tsx diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index ad0c238860..289d82551d 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -1248,3 +1248,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-07-28 | PR #1267 / dependabot/github-actions (merged) | 359b0e1a21a489978f00df8075ebcd906d57de4b | open-pr-merge-sweep | MERGED. Allowlisted anthropics/claude-code-action be7b93b (v1.0.183 peeled tag) in github-action-pins.mjs; pin check PASS. | hosted-pr-required,static,unit,check-github-action-pins,tag-peel-verify | | 2026-07-28 | PR #1351 / audit-design-fixes-clean (merged) | c2ec2d75553e7718fd89194cbc8362d9905151d3 | open-pr-merge-sweep | MERGED after EmptyState actions= fix, DocumentViewerRail re-home, review-thread clearance. Supersedes prior LEFT OPEN handoff row. | hosted-pr-required,static,build,production-ui,merge-tree-clean | | 2026-07-28 | PR #1352 / execute-audit-remediation-fixes (closed) | dcdcb5b2b0546eddbb5bfd6efdb68122be2eabaa | open-pr-merge-sweep | CLOSED as superseded junk: stale continuation of merged #1305 with real merge-tree conflicts. Supersedes prior SKIP-CLOSE 403 row; closed via ManagePullRequest. | merge-tree-conflicts,gh-pr-1305-MERGED,manage-pr-close | +| 2026-07-29 | origin/main | 02144ab6e5f2040110cc5e15cfcd15455c2a0615 | cross-page PWA search composer click-close defect | P1 confirmed; explicit gesture-only focus release implemented locally | manual PWA/browser repro; focused Vitest 48/48; typecheck | diff --git a/src/components/clinical-dashboard/global-search-shell.tsx b/src/components/clinical-dashboard/global-search-shell.tsx index b40898901e..7f1b679961 100644 --- a/src/components/clinical-dashboard/global-search-shell.tsx +++ b/src/components/clinical-dashboard/global-search-shell.tsx @@ -659,12 +659,6 @@ function GlobalStandaloneSearchShellBody({ function handleMainScroll(event: UIEvent) { const target = event.currentTarget; - // Scrolling the result canvas while the dock input is focused (user - // retapped the pill) must release the focus pin before the hide reporter - // runs; otherwise both chrome edges stay locked for the whole session. - if (target.scrollTop > 8 && inputRef.current && document.activeElement === inputRef.current) { - inputRef.current.blur(); - } chromeScrollHide.reportScroll({ offset: target.scrollTop, maxOffset: Math.max(0, target.scrollHeight - target.clientHeight), @@ -688,9 +682,6 @@ function GlobalStandaloneSearchShellBody({ const target = event.target; if (!(target instanceof HTMLElement) || !main.contains(target)) return; if (target.scrollHeight <= target.clientHeight + 1) return; - if (target.scrollTop > 8 && inputRef.current && document.activeElement === inputRef.current) { - inputRef.current.blur(); - } reportChromeScrollHideRef.current({ offset: target.scrollTop, maxOffset: Math.max(0, target.scrollHeight - target.clientHeight), diff --git a/src/components/clinical-dashboard/use-hide-on-scroll.ts b/src/components/clinical-dashboard/use-hide-on-scroll.ts index c13e3c455e..3009c2d34e 100644 --- a/src/components/clinical-dashboard/use-hide-on-scroll.ts +++ b/src/components/clinical-dashboard/use-hide-on-scroll.ts @@ -448,20 +448,25 @@ export function useDocumentScrollHideReporter( useEffect(() => { let frame = 0; + const releaseComposerFocusOnScrollIntent = (event: Event) => { + const input = focusInputRef?.current; + if (!input || document.activeElement !== input) return; + if (!window.matchMedia(phoneMediaQuery).matches) return; + // Keyboard opening, focus scrolling, and viewport settling can all emit + // `scroll` without a user scroll gesture. Only explicit wheel/touch + // movement outside the composer may dismiss the active input. Gestures + // that start inside the composer retain its existing swipe threshold. + const composer = input.closest("form"); + if (event.target instanceof Node && composer?.contains(event.target)) return; + input.blur(); + }; + const evaluate = () => { frame = 0; const scrollingElement = document.scrollingElement ?? document.documentElement; const maxOffset = Math.max(0, scrollingElement.scrollHeight - window.innerHeight); if (maxOffset <= 0) return; const offset = window.scrollY; - if ( - window.matchMedia(phoneMediaQuery).matches && - offset > topRevealOffset && - focusInputRef?.current && - document.activeElement === focusInputRef.current - ) { - focusInputRef.current.blur(); - } reportScroll({ offset, maxOffset, @@ -482,8 +487,18 @@ export function useDocumentScrollHideReporter( }; window.addEventListener("scroll", onScroll, { passive: true }); + window.addEventListener("wheel", releaseComposerFocusOnScrollIntent, { + capture: true, + passive: true, + }); + window.addEventListener("touchmove", releaseComposerFocusOnScrollIntent, { + capture: true, + passive: true, + }); return () => { window.removeEventListener("scroll", onScroll); + window.removeEventListener("wheel", releaseComposerFocusOnScrollIntent, true); + window.removeEventListener("touchmove", releaseComposerFocusOnScrollIntent, true); if (frame) window.cancelAnimationFrame(frame); }; }, [collapseMetricsRoot, focusInputRef, reportScroll]); diff --git a/tests/header-scroll-hide-contract.test.ts b/tests/header-scroll-hide-contract.test.ts index 607a95576f..801ac7c804 100644 --- a/tests/header-scroll-hide-contract.test.ts +++ b/tests/header-scroll-hide-contract.test.ts @@ -289,10 +289,11 @@ describe("shared header hide/reveal wiring", () => { expect(hookSource).toContain("headerRelease + phoneSafeAreaRelease + reserveRelease"); }); - it("only blurs the focused dock input on phone document scroll", () => { - expect(hookSource).toMatch( - /window\.matchMedia\(phoneMediaQuery\)\.matches\s*&&\s*offset > topRevealOffset[\s\S]*?focusInputRef\.current\.blur\(\)/, - ); + it("only blurs the focused dock input for explicit outside scroll intent", () => { + expect(hookSource).toContain('window.addEventListener("wheel", releaseComposerFocusOnScrollIntent'); + expect(hookSource).toContain('window.addEventListener("touchmove", releaseComposerFocusOnScrollIntent'); + expect(hookSource).toContain('const composer = input.closest("form")'); + expect(hookSource).not.toMatch(/const offset = window\.scrollY;[\s\S]{0,300}?\.blur\(\)/); }); it("rebases the reporter when a host swaps its scroll geometry", () => { @@ -335,9 +336,8 @@ describe("shared header hide/reveal wiring", () => { expect(shellSource).toContain("focus: !trimmedQuery"); expect(shellSource).toContain("queryInputAutoFocus={requestedFocus && !hasSubmittedModeSearch}"); expect(shellSource).toContain("if (hasSubmittedModeSearch)"); - expect(shellSource).toContain( - "if (target.scrollTop > 8 && inputRef.current && document.activeElement === inputRef.current)", - ); + expect(shellSource).not.toContain("target.scrollTop > 8 && inputRef.current"); + expect(hookSource).toContain("releaseComposerFocusOnScrollIntent"); expect(behaviourDocSource).toContain("Do not carry composer focus into submitted result views"); }); diff --git a/tests/ui-phone-scroll.spec.ts b/tests/ui-phone-scroll.spec.ts index 8737eb80a3..299e404aee 100644 --- a/tests/ui-phone-scroll.spec.ts +++ b/tests/ui-phone-scroll.spec.ts @@ -487,6 +487,49 @@ test("compiled standalone PWA rules bind full-height footer chrome to the inner expect(hidden.mainScrollTop, "the PWA footer must follow the inner scroll owner").toBeGreaterThan(120); }); +for (const scrollOwner of ["browser document", "standalone PWA main"] as const) { + test(`${scrollOwner} search keeps focus through passive scroll settling and releases it on a user gesture`, async ({ + page, + }) => { + await page.setViewportSize(phoneViewport); + await gotoPhoneSurface(page, "/services?q=clinic&run=1&focus=1"); + if (scrollOwner === "standalone PWA main") { + expect(await forceCompiledStandalonePhoneCss(page), "compiled CSS must expose standalone rules").toBeGreaterThan( + 0, + ); + } + await addPhoneScrollRunway(page); + + const input = page.getByTestId("global-search-input"); + const main = page.locator("#main-content"); + await expect(input).toBeVisible({ timeout: 20_000 }); + await page.evaluate((owner) => { + const main = document.getElementById("main-content"); + const target = owner === "standalone PWA main" ? main : (document.scrollingElement ?? document.documentElement); + if (!target) throw new Error("search focus proof did not find its scroll owner"); + target.scrollTop = 40; + (owner === "standalone PWA main" ? main : window)?.dispatchEvent(new Event("scroll")); + }, scrollOwner); + + await input.click(); + await expect(input).toBeFocused(); + + // iOS keyboard opening, scrollIntoView(), and viewport settling can emit a + // passive scroll notification without any user scroll intent. It must not + // close the keyboard or make the composer eligible to hide. + await page.evaluate((owner) => { + const target = owner === "standalone PWA main" ? document.getElementById("main-content") : window; + target?.dispatchEvent(new Event("scroll")); + }, scrollOwner); + await expect(input).toBeFocused(); + + // A real gesture outside the composer still releases the focus pin so the + // shared header/footer may hide while the user reads results. + await main.dispatchEvent("touchmove"); + await expect(input).not.toBeFocused(); + }); +} + for (const footerCase of standalonePageOwnedFooterRoutes) { test(`standalone ${footerCase.name} is frame-owned and stays anchored while main scrolls`, async ({ page }) => { await page.emulateMedia({ reducedMotion: "no-preference" }); diff --git a/tests/use-hide-on-scroll.dom.test.tsx b/tests/use-hide-on-scroll.dom.test.tsx new file mode 100644 index 0000000000..00a010113a --- /dev/null +++ b/tests/use-hide-on-scroll.dom.test.tsx @@ -0,0 +1,50 @@ +import { renderHook, waitFor } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +import { useDocumentScrollHideReporter } from "@/components/clinical-dashboard/use-hide-on-scroll"; + +describe("useDocumentScrollHideReporter composer focus", () => { + afterEach(() => { + document.body.replaceChildren(); + vi.restoreAllMocks(); + }); + + it("preserves focus through passive scroll settling and releases it on an outside gesture", async () => { + vi.spyOn(window, "matchMedia").mockImplementation( + (query) => + ({ + matches: query === "(max-width: 639px)", + media: query, + onchange: null, + addListener: vi.fn(), + removeListener: vi.fn(), + addEventListener: vi.fn(), + removeEventListener: vi.fn(), + dispatchEvent: vi.fn(), + }) satisfies MediaQueryList, + ); + const form = document.createElement("form"); + const input = document.createElement("input"); + const results = document.createElement("main"); + form.append(input); + document.body.append(form, results); + input.focus(); + + vi.spyOn(document.documentElement, "scrollHeight", "get").mockReturnValue(1_200); + vi.spyOn(window, "innerHeight", "get").mockReturnValue(600); + vi.spyOn(window, "scrollY", "get").mockReturnValue(40); + + renderHook(() => useDocumentScrollHideReporter(vi.fn(), null, { current: input })); + + window.dispatchEvent(new Event("scroll")); + await waitFor(() => expect(input).toHaveFocus()); + + // Movement that begins inside the composer keeps its existing swipe + // threshold in MasterSearchHeader rather than dismissing on finger jitter. + input.dispatchEvent(new Event("touchmove", { bubbles: true })); + expect(input).toHaveFocus(); + + results.dispatchEvent(new Event("touchmove", { bubbles: true })); + expect(input).not.toHaveFocus(); + }); +}); From a892ce14642f47d821ac27bcc49285acdeff6a2b Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 29 Jul 2026 08:34:51 +0800 Subject: [PATCH 2/6] docs(review): record search focus fix --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 289d82551d..92b186249c 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -1249,3 +1249,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-07-28 | PR #1351 / audit-design-fixes-clean (merged) | c2ec2d75553e7718fd89194cbc8362d9905151d3 | open-pr-merge-sweep | MERGED after EmptyState actions= fix, DocumentViewerRail re-home, review-thread clearance. Supersedes prior LEFT OPEN handoff row. | hosted-pr-required,static,build,production-ui,merge-tree-clean | | 2026-07-28 | PR #1352 / execute-audit-remediation-fixes (closed) | dcdcb5b2b0546eddbb5bfd6efdb68122be2eabaa | open-pr-merge-sweep | CLOSED as superseded junk: stale continuation of merged #1305 with real merge-tree conflicts. Supersedes prior SKIP-CLOSE 403 row; closed via ManagePullRequest. | merge-tree-conflicts,gh-pr-1305-MERGED,manage-pr-close | | 2026-07-29 | origin/main | 02144ab6e5f2040110cc5e15cfcd15455c2a0615 | cross-page PWA search composer click-close defect | P1 confirmed; explicit gesture-only focus release implemented locally | manual PWA/browser repro; focused Vitest 48/48; typecheck | +| 2026-07-29 | codex/search-composer-focus-pwa | f9a14e8e38a568d8675c4154597f73340161e4ea | current diff vs origin/main: PWA search composer focus | P1 fix reviewed; passive viewport scroll no longer blurs search | focused Vitest 48/48; typecheck; ledger guard; phone and PR plans inspected | From 5c6a75c21833dc31d4f8658cec15154f58918a36 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:07:46 +0800 Subject: [PATCH 3/6] test(search): cover queued and keyboard scroll intent --- .../clinical-dashboard/use-hide-on-scroll.ts | 38 ++++++++++++++----- tests/header-scroll-hide-contract.test.ts | 9 +++-- tests/ui-phone-scroll.spec.ts | 22 +++++++++-- tests/use-hide-on-scroll.dom.test.tsx | 9 +++++ 4 files changed, 63 insertions(+), 15 deletions(-) diff --git a/src/components/clinical-dashboard/use-hide-on-scroll.ts b/src/components/clinical-dashboard/use-hide-on-scroll.ts index 3009c2d34e..750d237820 100644 --- a/src/components/clinical-dashboard/use-hide-on-scroll.ts +++ b/src/components/clinical-dashboard/use-hide-on-scroll.ts @@ -448,17 +448,30 @@ export function useDocumentScrollHideReporter( useEffect(() => { let frame = 0; - const releaseComposerFocusOnScrollIntent = (event: Event) => { + const releaseComposerFocus = () => { + const input = focusInputRef?.current; + if (!input || document.activeElement !== input) return; + if (!window.matchMedia(phoneMediaQuery).matches) return; + input.blur(); + }; + + const releaseComposerFocusOnOutsideScrollIntent = (event: Event) => { const input = focusInputRef?.current; if (!input || document.activeElement !== input) return; if (!window.matchMedia(phoneMediaQuery).matches) return; // Keyboard opening, focus scrolling, and viewport settling can all emit - // `scroll` without a user scroll gesture. Only explicit wheel/touch - // movement outside the composer may dismiss the active input. Gestures - // that start inside the composer retain its existing swipe threshold. + // `scroll` without a user scroll gesture. Only explicit wheel, touch, or + // pointer activity outside the composer may dismiss the active input. + // Gestures that start inside the composer retain its swipe threshold. const composer = input.closest("form"); if (event.target instanceof Node && composer?.contains(event.target)) return; - input.blur(); + releaseComposerFocus(); + }; + + const releaseComposerFocusOnKeyboardScrollIntent = (event: KeyboardEvent) => { + if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey) return; + if (event.key !== "PageDown" && event.key !== "PageUp") return; + releaseComposerFocus(); }; const evaluate = () => { @@ -487,18 +500,25 @@ export function useDocumentScrollHideReporter( }; window.addEventListener("scroll", onScroll, { passive: true }); - window.addEventListener("wheel", releaseComposerFocusOnScrollIntent, { + window.addEventListener("wheel", releaseComposerFocusOnOutsideScrollIntent, { + capture: true, + passive: true, + }); + window.addEventListener("touchmove", releaseComposerFocusOnOutsideScrollIntent, { capture: true, passive: true, }); - window.addEventListener("touchmove", releaseComposerFocusOnScrollIntent, { + window.addEventListener("pointerdown", releaseComposerFocusOnOutsideScrollIntent, { capture: true, passive: true, }); + window.addEventListener("keydown", releaseComposerFocusOnKeyboardScrollIntent, true); return () => { window.removeEventListener("scroll", onScroll); - window.removeEventListener("wheel", releaseComposerFocusOnScrollIntent, true); - window.removeEventListener("touchmove", releaseComposerFocusOnScrollIntent, true); + window.removeEventListener("wheel", releaseComposerFocusOnOutsideScrollIntent, true); + window.removeEventListener("touchmove", releaseComposerFocusOnOutsideScrollIntent, true); + window.removeEventListener("pointerdown", releaseComposerFocusOnOutsideScrollIntent, true); + window.removeEventListener("keydown", releaseComposerFocusOnKeyboardScrollIntent, true); if (frame) window.cancelAnimationFrame(frame); }; }, [collapseMetricsRoot, focusInputRef, reportScroll]); diff --git a/tests/header-scroll-hide-contract.test.ts b/tests/header-scroll-hide-contract.test.ts index 801ac7c804..a3e5221d9b 100644 --- a/tests/header-scroll-hide-contract.test.ts +++ b/tests/header-scroll-hide-contract.test.ts @@ -290,9 +290,12 @@ describe("shared header hide/reveal wiring", () => { }); it("only blurs the focused dock input for explicit outside scroll intent", () => { - expect(hookSource).toContain('window.addEventListener("wheel", releaseComposerFocusOnScrollIntent'); - expect(hookSource).toContain('window.addEventListener("touchmove", releaseComposerFocusOnScrollIntent'); + expect(hookSource).toContain('window.addEventListener("wheel", releaseComposerFocusOnOutsideScrollIntent'); + expect(hookSource).toContain('window.addEventListener("touchmove", releaseComposerFocusOnOutsideScrollIntent'); + expect(hookSource).toContain('window.addEventListener("pointerdown", releaseComposerFocusOnOutsideScrollIntent'); + expect(hookSource).toContain('window.addEventListener("keydown", releaseComposerFocusOnKeyboardScrollIntent'); expect(hookSource).toContain('const composer = input.closest("form")'); + expect(hookSource).toContain('event.key !== "PageDown" && event.key !== "PageUp"'); expect(hookSource).not.toMatch(/const offset = window\.scrollY;[\s\S]{0,300}?\.blur\(\)/); }); @@ -337,7 +340,7 @@ describe("shared header hide/reveal wiring", () => { expect(shellSource).toContain("queryInputAutoFocus={requestedFocus && !hasSubmittedModeSearch}"); expect(shellSource).toContain("if (hasSubmittedModeSearch)"); expect(shellSource).not.toContain("target.scrollTop > 8 && inputRef.current"); - expect(hookSource).toContain("releaseComposerFocusOnScrollIntent"); + expect(hookSource).toContain("releaseComposerFocusOnOutsideScrollIntent"); expect(behaviourDocSource).toContain("Do not carry composer focus into submitted result views"); }); diff --git a/tests/ui-phone-scroll.spec.ts b/tests/ui-phone-scroll.spec.ts index 299e404aee..518a4949bb 100644 --- a/tests/ui-phone-scroll.spec.ts +++ b/tests/ui-phone-scroll.spec.ts @@ -521,12 +521,28 @@ for (const scrollOwner of ["browser document", "standalone PWA main"] as const) const target = owner === "standalone PWA main" ? document.getElementById("main-content") : window; target?.dispatchEvent(new Event("scroll")); }, scrollOwner); + await page.evaluate( + () => + new Promise((resolve) => { + requestAnimationFrame(() => requestAnimationFrame(() => resolve())); + }), + ); await expect(input).toBeFocused(); - // A real gesture outside the composer still releases the focus pin so the - // shared header/footer may hide while the user reads results. - await main.dispatchEvent("touchmove"); + // Keyboard scrolling carries explicit user intent without a wheel/touch + // event. Release focus first, then prove that the active owner can hide the + // shared chrome as it scrolls through the result canvas. + await input.dispatchEvent("keydown", { key: "PageDown" }); await expect(input).not.toBeFocused(); + await page.evaluate((owner) => { + const main = document.getElementById("main-content"); + const target = owner === "standalone PWA main" ? main : (document.scrollingElement ?? document.documentElement); + if (!target) throw new Error("keyboard scroll proof did not find its scroll owner"); + target.scrollTop = 720; + (owner === "standalone PWA main" ? main : window)?.dispatchEvent(new Event("scroll")); + }, scrollOwner); + await expect(page.getByTestId("universal-header-collapse")).toHaveAttribute("data-scroll-hidden", "true"); + await expect(page.locator("form.answer-footer-search-dock")).toHaveAttribute("data-scroll-hidden", "true"); }); } diff --git a/tests/use-hide-on-scroll.dom.test.tsx b/tests/use-hide-on-scroll.dom.test.tsx index 00a010113a..3334c851b8 100644 --- a/tests/use-hide-on-scroll.dom.test.tsx +++ b/tests/use-hide-on-scroll.dom.test.tsx @@ -37,6 +37,7 @@ describe("useDocumentScrollHideReporter composer focus", () => { renderHook(() => useDocumentScrollHideReporter(vi.fn(), null, { current: input })); window.dispatchEvent(new Event("scroll")); + await new Promise((resolve) => window.requestAnimationFrame(() => resolve())); await waitFor(() => expect(input).toHaveFocus()); // Movement that begins inside the composer keeps its existing swipe @@ -44,6 +45,14 @@ describe("useDocumentScrollHideReporter composer focus", () => { input.dispatchEvent(new Event("touchmove", { bubbles: true })); expect(input).toHaveFocus(); + input.dispatchEvent(new KeyboardEvent("keydown", { bubbles: true, key: "PageDown" })); + expect(input).not.toHaveFocus(); + + input.focus(); + results.dispatchEvent(new Event("pointerdown", { bubbles: true })); + expect(input).not.toHaveFocus(); + + input.focus(); results.dispatchEvent(new Event("touchmove", { bubbles: true })); expect(input).not.toHaveFocus(); }); From f1bdeac359a8274a01e8bfcdf103b25921902e54 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:08:00 +0800 Subject: [PATCH 4/6] docs(review): record PR 1373 remediation --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 92b186249c..83a0ff999e 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -1250,3 +1250,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-07-28 | PR #1352 / execute-audit-remediation-fixes (closed) | dcdcb5b2b0546eddbb5bfd6efdb68122be2eabaa | open-pr-merge-sweep | CLOSED as superseded junk: stale continuation of merged #1305 with real merge-tree conflicts. Supersedes prior SKIP-CLOSE 403 row; closed via ManagePullRequest. | merge-tree-conflicts,gh-pr-1305-MERGED,manage-pr-close | | 2026-07-29 | origin/main | 02144ab6e5f2040110cc5e15cfcd15455c2a0615 | cross-page PWA search composer click-close defect | P1 confirmed; explicit gesture-only focus release implemented locally | manual PWA/browser repro; focused Vitest 48/48; typecheck | | 2026-07-29 | codex/search-composer-focus-pwa | f9a14e8e38a568d8675c4154597f73340161e4ea | current diff vs origin/main: PWA search composer focus | P1 fix reviewed; passive viewport scroll no longer blurs search | focused Vitest 48/48; typecheck; ledger guard; phone and PR plans inspected | +| 2026-07-29 | codex/search-composer-focus-pwa | 5c6a75c21833dc31d4f8658cec15154f58918a36 | PR #1373 unresolved review comment remediation | P2 test race and keyboard/scrollbar intent gaps fixed | focused Vitest 48/48; typecheck; diff check | From 3f7cd1e4069b7ef8f8b18519adfb0dba0dc349a8 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:13:26 +0800 Subject: [PATCH 5/6] test(search): remove stale scroll locator --- tests/ui-phone-scroll.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ui-phone-scroll.spec.ts b/tests/ui-phone-scroll.spec.ts index 518a4949bb..d7202d88ff 100644 --- a/tests/ui-phone-scroll.spec.ts +++ b/tests/ui-phone-scroll.spec.ts @@ -501,7 +501,6 @@ for (const scrollOwner of ["browser document", "standalone PWA main"] as const) await addPhoneScrollRunway(page); const input = page.getByTestId("global-search-input"); - const main = page.locator("#main-content"); await expect(input).toBeVisible({ timeout: 20_000 }); await page.evaluate((owner) => { const main = document.getElementById("main-content"); From bbac22b1a708ae15395d1e722e310c75e399c836 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 29 Jul 2026 09:13:38 +0800 Subject: [PATCH 6/6] docs(review): record PR 1373 CI remediation --- docs/branch-review-ledger.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 83a0ff999e..c06324abd9 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -1251,3 +1251,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-07-29 | origin/main | 02144ab6e5f2040110cc5e15cfcd15455c2a0615 | cross-page PWA search composer click-close defect | P1 confirmed; explicit gesture-only focus release implemented locally | manual PWA/browser repro; focused Vitest 48/48; typecheck | | 2026-07-29 | codex/search-composer-focus-pwa | f9a14e8e38a568d8675c4154597f73340161e4ea | current diff vs origin/main: PWA search composer focus | P1 fix reviewed; passive viewport scroll no longer blurs search | focused Vitest 48/48; typecheck; ledger guard; phone and PR plans inspected | | 2026-07-29 | codex/search-composer-focus-pwa | 5c6a75c21833dc31d4f8658cec15154f58918a36 | PR #1373 unresolved review comment remediation | P2 test race and keyboard/scrollbar intent gaps fixed | focused Vitest 48/48; typecheck; diff check | +| 2026-07-29 | codex/search-composer-focus-pwa | 3f7cd1e4069b7ef8f8b18519adfb0dba0dc349a8 | PR #1373 CircleCI lint remediation | Deterministic unused locator warning removed | CircleCI format passed; lint root cause captured; focused Vitest 48/48; typecheck |