From e41ceaf9f885cf929b32b4d6e17c445b617cde1f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 21:00:51 +0000 Subject: [PATCH 1/2] docs(issues): close #290 as not-reproducible, capture the browser-preflight traps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #290 archived. Could not reproduce on current main (3dc1f37): full ui-smoke --project=chromium run three times gave 97 passed / 0 failed / 0 flaky each (291 test executions, zero failures), and the named reliable reproducer run in isolation with --repeat-each=5 gave 5 passed at 0.98-1.2s against the row's documented ~11.5s failure signature. Recorded as evidence, not proof, and the archive says so: the row measured 2/5 and 3/5 full-run failure rates, so one clean run proves little and three puts it at roughly 6-22% under the old behaviour. Resolved by drift — main has moved several hundred commits past the row's base 9ab3b73a and nothing here touched src/. Reopen rather than re-file if it recurs. #312 (new) captures two things that cost two failed runs first: - check:playwright-browser-revision reporting "OK (managed-or-unconstrained)" means no browser root is FORCED, not that browsers exist. The container had chromium-1194 against Playwright 1.62.1's required 1234. - Installing the matching revision is a viable first option that archived #255 does not mention: npx playwright install chromium took ~1 minute, and --project=chromium skips the unused firefox/webkit requirement because the preflight honours project scoping. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_017paT42ZVMf8jaLtkjFxdy5 --- docs/outstanding-issues.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index 9d8a9ce3c5..f8e82ff3f8 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -137,7 +137,7 @@ removed after current-main verification; it is not missing recommended work. | 82 | `#257` | Optional | High — formulation/specifiers flake | Standing until second reproduction | 15–30 min | Single unreproduced ui-formulation flake when run with ui-specifiers — record a second sighting only; do not quarantine until three on the same SHA. **Stop:** do not weaken assertions. | - + ## Open items > **Merged-main canary update (2026-07-23, run `30018289898`):** the new structured report correctly recorded evaluated tree `c24f2e8f2d30d0c59fc1eba025d3dcd63478137e`, run/attempt identity and `cross-region-runner` latency context. Golden retrieval remained 36/36 with document/content recall 1.0 and no failed cases. The 44-case answer gate had grounded-supported and unsupported-correct rates of 1.0, but failed because `neuroleptic-side-effect-escalation` again returned one citation where two are required (citation-failure rate 0.0227). `admission-discharge-comparison` again omitted the specific AKG admission document after `comparison_source_extractive_fallback`; `admission-discharge-coverage-paraphrase` was advisory-only at 24,870 ms. Answer cost was reported as `$0.234736`. Do not retry immediately: retain this as the first structured datapoint, compare it with the scheduled 2026-07-26 report, and keep retrieval/ranking unchanged. @@ -255,7 +255,6 @@ removed after current-main verification; it is not missing recommended work. | #281 | P2 | rec | The phone document route renders two clinical-summary surfaces and neither is canonical | **Outcome:** one clinical summary on the document route, chosen deliberately. **Detail:** a phone reader gets the gradient 'High-yield clinical summary' card (DocumentClinicalSummary, built by buildDocumentClinicalSummaryModel) and, further down, the rail's '#source-summary' / 'high-yield-summary' disclosure (DocumentSectionSummary + FormattedHighYieldSummary + BadgeCluster). They render the same document.summary row two different ways. The rail is not hidden on phones — only its DocumentSectionIndexCard is lg:block — so both appear. Only the rail panel carries the section anchor, so the more prominent card is the unnavigable one. Note the two disagree about emptiness as well: the card now renders nothing when the model yields no usable text, while the rail panel still renders for its label badges, which is why 'hasStoredSummary' was deliberately left keyed to the stored row rather than to card content. **Next:** decide which rendering is canonical — this is a clinical-content judgement about how a summary should read, not a layout fix — then delete the other and give the survivor the 'source-summary' anchor. If the rail's badges are the part worth keeping, they can move without the second summary body. **Stop:** do not merge the two renderings mechanically; they format clinical text differently and the difference is the decision. | session 2026-08-08 document-viewer optimisation; document-rail-panels.tsx; document-clinical-summary.tsx | 2026-08-08 | | #282 | P3 | task | Probe the corpus for JBIG2/JPX before deciding whether pdf.js needs its decoder assets shipped | **Outcome:** a measured decision about pdf.js's cMap/standard-font/WASM assets rather than an assumption either way. **Detail:** getDocument is configured with url plus the on-demand fetch flags and nothing else, so 'wasmUrl', 'standardFontDataUrl', 'cMapUrl' and 'iccUrl' are all unset. pdfjs-dist ships those assets (wasm 1.5 MB, standard_fonts 804 KB, cmaps 1.7 MB) and nothing copies them into public/. With wasmUrl null, 'useWorkerFetch' resolves false and the WASM image decoders cannot load, so JBIG2 and JPEG2000 images fall back to the JS decoders or fail; those are exactly the encodings a scanned guideline uses, and this repo runs an OCR pipeline, which implies scanned sources exist. Non-embedded standard-14 fonts fall back to system fonts, which is a fidelity risk on a clinical document rather than a failure. **Next:** sample the real corpus for JBIG2/JPX-encoded images and for PDFs relying on the standard 14 before shipping ~2 MB of static assets; if the corpus does use them, copy into public/pdfjs, set the URLs, and add immutable cache headers in next.config.ts (public/ is not counted by check:bundle-budget, so there is no budget risk — the cost is bytes over the wire on first use). **Stop:** do not ship the assets on the assumption alone. | session 2026-08-08 document-viewer optimisation; node_modules/pdfjs-dist/types/src/display/api.d.ts | 2026-08-08 | | #283 | P3 | rec | The 100-id batch signed-URL route still has no caller | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: No caller for src/app/api/images/signed-urls/route.ts anywhere outside app/api — the batch route is still unused. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** either the batch minter is used or it is retired, rather than sitting as an untested, unreachable privileged surface. **Detail:** src/app/api/images/signed-urls/route.ts POSTs up to 100 image ids and returns their signed URLs, with its own rate limit, owner scoping and committed-generation filter. Nothing in src/ calls it — only tests/private-access-routes.test.ts imports it. **DEFERRED AGAIN, DELIBERATELY, 2026-08-09 (document viewer Phase 3, Task 3).** The user chose deferral over wiring when asked. Two reasons beyond cost: (a) wiring it puts a privileged owner-scoped API route into a diff that is otherwise confined to src/components/document-viewer/**, and it matches clinicalRiskPatterns (/^src\/app\/api\//) so pr-policy hard-blocks the merge without a complete Clinical Governance Preflight; (b) Phase 3 Task 2 windowed the rail to six rows and tightened its IntersectionObserver root margin from 640px to 240px, so the many-distinct-images case the batch route was meant to serve is now materially smaller — a page of N figures no longer mounts N rows at once. The batching win should be re-measured against the windowed rail before it is wired at all, rather than assumed from the pre-window numbers. **Next:** decide deliberately — measure concurrent distinct-image requests on a figure-heavy document with the windowed rail, then either wire the batch route in its own PR or delete it and its tests. **Stop:** if wiring it, keep the per-image endpoint for the lightbox's retry path; do not make the batch the only way to mint a URL. | session 2026-08-08 document-viewer optimisation; src/app/api/images/signed-urls/route.ts | 2026-08-08 | -| #290 | P2 | issue | ui-smoke phone scroll-hide tests are flaky on main at 9ab3b73a | Blocking Chromium tests in tests/ui-smoke.spec.ts fail intermittently at base commit 9ab3b73ad. Observed members: 'answer glass header overlays main and fully hides while scrolling on phones' (the reliable reproducer), 'document viewer content disclosures are naturally closed and mutually exclusive by default', 'non-answer phone header keeps the in-flow collapse hide', and 'document search stays separate from the shared answer stream and summary action'. A different subset fails each full-suite run. Measured 2026-08-09 during the in-page-nav PR 2. The decisive control: reverting ALL of src/ to the base commit in the SAME worktree, and moving the branch's new untracked modules aside so the build was pristine, still failed 2/5 — so this is main, not the branch. Repeat-each runs on the branch gave 3/5 and 2/5; a targeted 3x3 across the three names above gave 8/9 with only the answer-glass one flaking. The passing case takes ~1.4s; the failing case times out at ~11.5s on expect(header).toHaveAttribute('data-scroll-hidden','true'), i.e. the universal header never hides after the test scrolls the document owner. A worktree at the older origin/main e8adde1b9 passed 5/5 twice on the same machine, so the regression window is e8adde1b9..9ab3b73a. Not quarantined: repo policy needs three reproductions on the same SHA recorded in tests/flake-ledger.json, and the right fix is probably the scroll-hide reporter's settle timing rather than a quarantine tag. Next: reproduce on a clean checkout of 9ab3b73a, bisect e8adde1b9..9ab3b73a, and fix or quarantine per docs/testing.md. Stop: do not weaken the assertion to a longer poll — it already waits 10s, far past the animation, so a longer wait would hide a real hang. **Quarantine arithmetic (recorded 2026-08-09):** this is the row's blocking constraint, so state it as a count rather than as policy prose. `tests/flake-ledger.json` is currently empty (`"flakes": []`, with a comment recording that stale non-reproducible entries were removed), so this flake has ZERO recorded reproductions in the ledger that governs quarantine. `docs/testing.md` requires three reproductions on the same SHA before a `@quarantine` tag is accepted. The 2026-08-09 measurement above is one — it is same-SHA (9ab3b73a) and reproducible on demand, but it was never written to the ledger. Net: **two more same-SHA reproductions** are needed before a quarantine entry can be added at all, and they must be recorded in `tests/flake-ledger.json`, not merely observed in a session. Until then the test stays blocking and every red run on it is unexplained-by-record. **Next (supersedes nothing above; this is the cheapest unblocking step):** run the reliable reproducer ('answer glass header overlays main and fully hides while scrolling on phones') twice more against 9ab3b73a and record each observation in THIS row — not in `tests/flake-ledger.json`. Two separate constraints make an early append invalid rather than merely premature, and both were missed by the original wording here: `docs/testing.md` (Flake policy) requires three same-SHA reproductions BEFORE an entry is added or retained, and `scripts/flake-ledger.mjs:48` throws `exact title must include @quarantine` while the test at `tests/ui-smoke.spec.ts:4470` is currently untagged — so an entry written today fails validation outright and the ledger is not a per-run log. Only after the third reproduction: tag that test `@quarantine` (never alongside `@critical`, which the next line rejects), then add ONE complete entry carrying owner, reproduction command, local tracking reference, first/last-seen dates and an expiry no more than 30 days out. Or, preferably, spend the same effort on the bisect of e8adde1b9..9ab3b73a already recorded above, since a fix retires the row outright and a quarantine only defers it. **Stop:** do not backfill the two outstanding reproductions from memory or from the 3/5 and 2/5 repeat-each runs on the branch — those were on branch SHAs, not 9ab3b73a, and the same-SHA requirement is the whole point of the count. | session 2026-08-09; in-page-nav PR 2; controls in scratchpad pw-truebase2 / pw-growth-base logs | 2026-08-08 | | #292 | P2 | rec | Two assistants built the same queued conversion twice because neither workflow checks the open-PR list before starting | **Outcome:** picking up a queued ledger item cannot silently duplicate work another session already has in flight. **Detail:** on 2026-08-09 two assistants took the same queued `/issues` item roughly four hours apart and independently built the same in-page-nav conversion — PR #1766 (merged) and PR #1767 (closed as duplicate). Neither had any way to see the other: the ledger row was the only shared state. Correcting an earlier version of this row after CodeRabbit's review on PR #1773: it is not true that the ledger "has no in-progress state" — some rows do carry a progress marker in their prose (`IN PROGRESS` appears on two, and `IMPLEMENTED in PR #1766` on another). The accurate gap is narrower and worse: there is no structured status field and no atomic claim, so a marker is written by whoever did the work, usually after the fact, and nothing requires or checks one — which means the ABSENCE of a marker carries no information at all. Both sessions read it, both correctly concluded it was open, both built it. The wasted effort is the smaller cost; the larger one is that the two implementations diverged in shape, which is what forced the separate `PageSection` ownership decision recorded in `docs/search-chrome-behaviour.md`. Distinct from `#156`/`#168`, which are about two branches colliding on an **id** while appending; this is two sessions colliding on the **work** a row describes, and a collision-free id scheme would leave it untouched. **Mitigation landed 2026-08-09 (same PR as this row):** the check is now written into the three places an assistant actually reads before starting queued work — `.claude/skills/newtask/SKILL.md` "Before you start" (which already performed an open-PR read for PR bundling, so this asks that same list a second question and costs no extra call), `.claude/skills/issues/SKILL.md` after the read-only flow, and the `/issues` section of `AGENTS.md` so Codex and Cursor get it too rather than Claude Code only. All three say to scan for the **route, component or surface**, not the ledger id, because a duplicate PR rarely quotes the id; all three degrade to a warning when GitHub is unreachable so an offline session can still start work. **Next:** leave open for one or two queued-item cycles to see whether prose is enough. If a second duplicate lands anyway, this becomes the same class as `#258` — a rule enforced for one tool by prose with no gate — and the answer is a check, not more wording. **Stop:** do not implement a claim marker written back into the row when a session starts an item; that reintroduces exactly the read-modify-write contention `#168` exists to remove. Do not make the open-PR read a hard blocker. | session 2026-08-09; PR #1766 (merged); PR #1767 (closed duplicate) | 2026-08-09 | | #293 | P2 | issue | Gate 2 needs a phone-viewport deterministic surface; the `min-h-tap` 0px finding is REFUTED | **CORRECTS this row's original text, which was wrong on its central claim.** FINDING 1 IS REFUTED (2026-08-12). As first written it asserted that controls carrying `min-h-tap` have their declaration "overridden to 0", blamed "likely an unlayered component class in globals.css", and treated the six shapes as a live 48px-floor defect. All of that is wrong, and acting on it would have caused a regression. **What actually zeroes the min-height is the source itself, deliberately:** the sites carry an explicit `sm:` step-down beside `min-h-tap`. The two 36px shapes are exact matches — `services-navigator-page.tsx:217` is `grid min-h-tap min-w-tap … sm:h-9 sm:min-h-0 sm:w-9 sm:min-w-0` and `:286` is `inline-flex min-h-tap min-w-[94px] … sm:h-9 sm:min-h-0`. `sm:min-h-0` IS the computed `min-height: 0px`, and `sm:h-9` IS the rendered 36px. Seven `min-h-tap` sites carry `sm:min-h-0`; the wider pattern is larger still — `inline-flex min-h-tap items-center` alone appears with `sm:min-h-0` (4), `sm:min-h-7` (2), `sm:min-h-8` (2), `sm:min-h-9` (4), `sm:min-h-10` (8) and `sm:min-h-12` (1). **`min-h-tap` is a PHONE floor that desktop deliberately releases**, which is why the audit only sees it below the floor: `tests/ui-style-contract.spec.ts:97` navigates at the project's desktop viewport, so every `sm:`-and-up override is in force at measurement time. The audit was measuring intended design and reporting it as an overridden floor. **Do NOT "fix" these** — removing the step-downs would pin every desktop control to 48px and is a visual regression across the app, not a WCAG improvement (the phone contract already exceeds both AA 2.5.8 and AAA 2.5.5). The `declared < tapFloor - 0.5 continue` skip at `:116` is therefore correct at desktop width and is NOT the same structural blind spot as the `h-10` case in `#265`. FINDING 2 STANDS UNCHANGED and is the whole of the remaining work: a rendered-interactive enumeration on `/services?q=CMHT&run=1` is NOT DETERMINISTIC — six runs against one production build returned 6, 5, 4, 3, 3 and 9 distinct control shapes, largely disjoint; `waitForLoadState('networkidle')` plus deduplication to distinct shapes did not fix it, and two consecutive agreeing runs were coincidence. The enumeration was written, shown to find genuine defects, and REVERTED rather than landed, because that spec runs in the required Production UI job via `productionSpecPattern` and `scripts/playwright-pr-shards.mjs`, so an intermittent version would block every merge. **Next, revised:** (1) build the deterministic surface — a static route with no async search, or a fixed seeded state; (2) run the tap enumeration **at a phone viewport**, where `min-h-tap` is unreleased and the measurement is meaningful, rather than at desktop where the floor is intentionally lifted; a phone layout is also the simpler, more deterministic surface, so (1) and (2) push the same way. Step (2) of the original row — "find what zeroes min-height on the min-h-tap carriers" — is CLOSED by this correction: the answer is `sm:min-h-0`, and it is intended. **Stop:** do not re-land the enumeration on a live-search route; do not quarantine a brand-new test to get it merged (quarantine is for flaky tests already trusted, and policy needs three reproductions on one SHA via `tests/flake-ledger.json`); do not lower any production tap target, and never to `min-h-11` (known `ui-smoke` sub-pixel flake; production uses the 48px token). | session 2026-08-09 — M2 gate 2 enumeration (#265); finding 1 refuted session 2026-08-12 against `origin/main` 4587f78 (`services-navigator-page.tsx:217,286`; `tests/ui-style-contract.spec.ts:97,116`) | 2026-08-09 | | #294 | P3 | rec | OffscreenCanvas for the PDF raster is unjustified until the page-flip cost is read from CI | VERIFIED CORRECT 2026-08-12 — re-checked against merged main during the full ledger sweep and left unchanged: No `OffscreenCanvas` usage anywhere under src/ — the row's 'unjustified until measured' position still holds. This stamp exists so a later reader can tell "checked and still true" from "never looked at"; the two were indistinguishable before. **Outcome:** the worker-raster question is settled by a number rather than left as a standing 'optional' item in the redesign plan. **Detail:** docs/plans/document-viewer-redesign-plan.md conditions OffscreenCanvas on 'measured main-thread paint cost'. Phase 3 (Task 5) did not implement it, deliberately: virtualization now keeps the reader's page and one neighbour already rastered, so the cold-render-per-flip cost that motivated a worker raster is largely gone before any threading work starts, and moving pdf.js rendering off the main thread would put the canvas the clinical source is drawn into behind a transfer boundary — a real risk on the one surface where a blank page is a clinical failure. **No number exists yet and none could be produced locally:** pdfjs-dist@6 needs Map.prototype.getOrInsertComputed, which this container's Chromium 141 lacks and Node 24.13.0 also lacks, so neither a browser nor a headless harness here can raster a page (see #279). **Next:** read the measurement the gate already captures. tests/ui-document-canvas.spec.ts attaches page-flip-raster-cost.json (flipToPaintedMs, longTaskCount, longTaskTotalMs, longestTaskMs, canvasBackingPixels) and logs a '[viewer-canvas] page flip painted in Nms' line, on every Production UI run and on any host with the pinned Chromium 151 build: npm ci --include=dev && npx playwright install chromium && npm run ensure && npm run test:e2e -- tests/ui-document-canvas.spec.ts --project=chromium. Close this as not-worth-doing and strike the row from the plan's Phase 3 table only when a Production UI (or equivalent Chromium 151) run records decisive log lines for all three: longestTaskMs comfortably under ~50ms, plus explicit flipToPaintedMs and longTaskTotalMs budgets agreed for that host class and met on the same run. Do not close on longestTaskMs alone. **Stop:** do not implement OffscreenCanvas on principle because the plan lists it — the plan conditions it on the measurement, and the measurement is now cheap to obtain. | session 2026-08-09 document viewer Phase 3, Task 5; docs/plans/document-viewer-phase3-handover.md | 2026-08-09 | @@ -269,6 +268,7 @@ removed after current-main verification; it is not missing recommended work. | #309 | P2 | task | Facet groups of 6-20 options render as chips, not the dense list docs/filter-contract.md section 5 requires | Raised by the Codex reviewer on PR #1858 (P2) and correct. Section 5 of docs/filter-contract.md sets density by option count: <=5 chips, 6-20 dense full-width list with a right-aligned count column and group headings, >20 or >3 groups adds find-a-filter and collapse-by-default. Formulation is the first real facet adoption and renders NINE derived domains, so it should be in the 6-20 band, but ResultFilterFacetChips only has one layout — a wrapping chip row — and it is used at both breakpoints. So the first adoption sets the precedent of bypassing the density rule. **Why it was not fixed in #1858:** making the shared renderer density-aware changes a component every facet consumer will use, needs a layout decision at both breakpoints, and overlaps the >20 tier that PR F is scheduled to port up from the documents panel (which already has its own dense list, find-a-filter and collapse). That is a design decision and a PR of its own, not a scoped fix inside a mode adoption. **Next:** decide whether the shared renderer grows a density prop derived from options.length, or whether PR F's port-up supplies the dense tier and facet modes adopt it then; either way add a nine-option DOM assertion so the band is pinned rather than incidental. **Stop:** do not add a per-mode dense list — a second hand-rolled facet layout is exactly the breakpoint drift the shared renderer was extracted to remove. Services (PR C) will have six groups and hits the >3-groups branch of the same rule, so this should be settled before or with it. | Codex review on PR #1858; docs/filter-contract.md section 5 | 2026-08-12 | | #310 | P2 | issue | Fuzzy catalogue search can match a DIFFERENT drug: fluoxetine to duloxetine at edit distance 2 | MEASURED 2026-08-12 by running the matcher itself, not by reading it. PR #1851 adds Damerau-Levenshtein typo recovery to `src/lib/catalog-search.ts` (`fuzzySearchTokenCount`, `boundedTypoDistance`, `typoDistanceLimit`) and folds it into the score. The tier `term.length >= 8 -> 2 edits` is the problem: Damerau counts an adjacent transposition as ONE edit, so `fluoxetine` -> `duloxetine` is distance 2 (substitute f->d, transpose lu->ul) and both are 10 characters. Confirmed hits against the PR's own algorithm: **fluoxetine -> duloxetine** (SSRI vs SNRI, different drugs), **prednisone -> prednisolone** (different drugs). Intended cases also confirmed working: sertraline -> setraline, olanzapine -> olanzepine. The existing guards DO hold — SSRI/SNRI, ADHD/ODD, citalopram/escitalopram, clozapine/clonazepam and quetiapine/olanzapine all correctly return no match. ONE MITIGATION, stated so this is not over-read: terms under 5 characters are excluded entirely. The fuzzy trigger is evaluated independently for each candidate record, so the hazard persists when both the exact drug and a two-edit near-match are present: the exact record receives a literal score while the wrong drug can independently receive a fuzzy score and appear as an additional result. Blast radius is wide because `catalog-search.ts` feeds ELEVEN modules — medications.ts (prescribing), dsm.ts, differentials.ts, differential-stream.ts, universal-search.ts, specifiers-search-index.ts, tools-catalog.ts, form-ranker.ts, service-ranker.ts. TESTED FIX: capping the >=8 tier at 1 edit removes both cross-drug hits and preserves every legitimate typo recovery in the sample — a one-line change to `typoDistanceLimit`. Next: if PR #1851 is still open, raise this on it; if it merged, apply the cap directly and add a test over real catalogue drug names with both the exact and near-match records present, asserting the wrong drug is excluded while the exact drug remains. Stop: do not remove fuzzy search outright — the typo recovery is genuinely useful and the guards are otherwise well judged. Note `classifyPullRequestFiles` returns clinicalRisk:true for this path (governance preflight fires) but ragRanking:false, which is correct — this is catalogue ranking, not the pgvector retrieval path. | session 2026-08-12; PR #1851 (codex/investigate-recent-regression-issues); algorithm re-run locally against real drug-name pairs; src/lib/catalog-search.ts | 2026-08-12 | | #311 | P3 | task | Promote the derived ledger loss-detector into scripts/ — it has now earned its place twice | During the 2026-08-12 sweep, two main-merges silently reverted edits to `docs/outstanding-issues.md`, including the ENTIRE #293 refutation (a `grep sm:min-h-0` returned 0; the text survived only in commit a6bfc6f). It went unnoticed because the recovery script was HAND-ENUMERATED — it listed 15 archives and 8 updates from one commit and could therefore only restore what the author remembered. The replacement is derived rather than listed: read every row id this branch has ever stamped out of `git rev-list ..HEAD` plus `git show :docs/outstanding-issues.md`, then assert each of those ids that is still OPEN carries its stamp text, and exit non-zero listing any that lost it. It has now proved itself twice — it caught the intentional #262 divergence (main's version was newer than the branch's, correctly left alone) and would have caught the #293 loss the hand-written list missed. The plan that created it said it should stay a scratch script 'unless it proves useful more than once'; that condition is met. Next: port it to scripts/ (suggested `check-ledger-stamp-retention.mjs`), generalise the stamp token from the hard-coded 2026-08-12 date to a `--since` or marker argument, add a self-test in the style of the other ledger scripts, and document it beside `ledger:dedupe` for use after any main sync that touches the ledger. Stop: do NOT wire it into verify:cheap or CI — it is a branch-local safety net for a human or agent mid-sweep, and it has no meaning on a branch that has not stamped rows. Related: #156 and #168, which track the id-allocation race that produces these merges in the first place. | session 2026-08-12 ledger sweep; scratch loss-check.mjs; #293 restoration from a6bfc6f | 2026-08-12 | +| #312 | P3 | issue | check:playwright-browser-revision reporting OK does NOT mean browsers are installed — and installing the matching revision is a cheap first option | Two corrections learned the expensive way on 2026-08-12, both about browser proof in a cloud container. (1) **The check is easy to misread.** `npm run check:playwright-browser-revision` returned 'Playwright browser revision check OK (managed-or-unconstrained): No designated container browser root is forced; use the Playwright-managed cache or install matching browsers.' That reports that no browser root is FORCED — it does not assert any browser exists. It was read as a green light for `verify:ui`, and two subsequent Playwright runs died at preflight instead: the container carried chromium-1194 while Playwright 1.62.1 requires chromium_headless_shell-1234, with firefox-1538 and webkit-2336 absent entirely. Suggested fix: have the check say plainly which browsers are present and which the locked Playwright version requires, so 'OK' cannot be mistaken for 'ready'. (2) **Installing the matching revision works and is fast**, which archived #255's 'delegate browser proof to CI Production UI' guidance does not mention. `npx playwright install chromium` fetched 114.7 MiB in about a minute and made local Chromium proof possible — three full ui-smoke runs then completed at 2.8-3.0m each (this is how #290 was settled). It is a cheaper first option than deferring to CI. Two things that matter alongside it: `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` was EMPTY in this container, so the download was never blocked despite the environment note implying otherwise; and only Chromium is needed, because `scripts/playwright-browser-preflight.mjs:127-152` honours `--project`, so `--project=chromium` skips the firefox/webkit requirement rather than forcing two unused ~100MB downloads. Stop: do NOT set PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH at the stale 1194 binary to get past the preflight — archived #255 warns against forcing a mismatched path, and the preflight's own message warns that a later 'N failed' summary must not then be read as a product regression. | session 2026-08-12; scripts/playwright-browser-preflight.mjs:127-152; scripts/run-playwright.mjs:50-53; #290 close-out; archived #255 | 2026-08-12 | ## Resolved / archive @@ -478,3 +478,4 @@ Move resolved rows here with the resolution date and a one-line outcome. Keep th | #202 | rec | Recommendation answers must revalidate the ledger against origin/main, not a stale checkout | Resolved 2026-08-12: issues:report reads cached origin/main when available and emits explicit revalidation and checkout-lag metadata, with a visible stale fallback. | 2026-08-12 | | #249 | rec | Extend issues/plan with an agent-safe wins classifier | Resolved 2026-08-12: issues:report provides a conservative optional agent-safe-wins classifier while retaining A1 blockers and queue order. | 2026-08-12 | | #251 | rec | Handoff checklist should pair gates skill with verification-router | Resolved 2026-08-12: handoff pairs verification routing with one smallest gate and requires a decisive proof line without default broad-gate stacking. | 2026-08-12 | +| #290 | issue | ui-smoke phone scroll-hide tests are flaky on main at 9ab3b73a | RESOLVED 2026-08-12 — could not reproduce on current main (3dc1f37) across three consecutive full-suite runs plus a targeted repeat, and the row's own failure signature is absent. Evidence: full `tests/ui-smoke.spec.ts --project=chromium` x3 → **97 passed / 0 failed / 0 flaky** each (3.0m, 2.8m, 2.8m), i.e. 291 test executions with zero failures; plus the named reliable reproducer ('answer glass header overlays main and fully hides while scrolling on phones') run in isolation with --repeat-each=5 → **5 passed**, each in 0.98-1.2s against this row's documented failure signature of an ~11.5s timeout, so they sit in the passing regime rather than marginally inside it. All four named members passed in every full run. WHY THIS IS EVIDENCE AND NOT PROOF, stated because the row deserves it: this row measured failure rates of 2/5 and 3/5 on full-suite runs, so a single clean run had roughly even odds even under the old behaviour; three consecutive clean runs put that at roughly 6-22%, which is meaningful but not conclusive for an intermittent failure. RESOLVED BY DRIFT, not by any change here: main has advanced several hundred commits past this row's base commit 9ab3b73a (2026-08-09) and nothing in this session touched src/. If it recurs, REOPEN rather than treat it as new — the reliable reproducer and the ~11.5s-vs-~1.4s timing signature in this row remain the fastest way to recognise it. | 2026-08-12 | From 92c77255b4ef7d02cfda779809737b3706dd1b0b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 12 Aug 2026 21:01:53 +0000 Subject: [PATCH 2/2] chore(ledger): record the #290 reproduction attempt Co-Authored-By: Claude Opus 5 --- 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 6fe4a6548e..f1616e208a 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -930,3 +930,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-12 | codex/specifiers-results-polish-20260813 | 3e82cca69a72a66c1be87c5b9357336c3a95b7b0 | specifier result-card layout and interaction | No findings after resolving reduced-motion, dark-mode, and focus-ring review items | focused Chromium 1/1; lint pass; typecheck pass; RAG fixtures 36/36; full unit suite has 17 unrelated Windows/tooling baseline failures | | 2026-08-12 | codex/specifiers-results-polish-20260813 | bbdb8337c2784941664a88a0d35a96a8c96a2edb | specifier result-card layout and interaction | Current-main sync introduced no changes to reviewed Specifiers scope; no findings after resolved review items | focused Chromium 1/1; lint pass; typecheck pass; RAG fixtures 36/36; full unit suite has 17 unrelated Windows/tooling baseline failures | | 2026-08-12 | PR #1870 / claude/design-issues-triage-wnr7k9 | 6edfeceeb768b5714f98dad355361ad9148374b0 | review-and-fix | Merged current main; corrected #310's per-record fuzzy-trigger analysis and regression-test condition; preserved #311; removed the temporary self-mutating workflow; no additional P0-P2 findings in a distinct adversarial pass. | verify:pr-local -- --files docs/branch-review-ledger.md,docs/outstanding-issues.md; check:outstanding-issues; check:branch-review-ledger; exact-head hosted CI pending | +| 2026-08-12 | claude/ui-smoke-flake-290 | e41ceaf9f885cf929b32b4d6e17c445b617cde1f | #290 ui-smoke flake reproduction attempt on current main; browser-preflight findings | #290 archived not-reproducible: 3x full ui-smoke chromium runs 97 passed/0 failed/0 flaky each (291 executions) plus isolated repeat-each=5 on the named reproducer 5 passed at ~1.1s vs the ~11.5s failure signature; recorded as evidence not proof (row measured 2/5 and 3/5 failure rates) and resolved by drift past base 9ab3b73a. New #312 records that check:playwright-browser-revision OK means no root FORCED not browsers present, and that npx playwright install chromium + --project=chromium is a cheap first option | verify:pr-local 10/10 green; check:outstanding-issues 114 open/196 archived; 3 full ui-smoke runs captured to file with real exit codes |