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 @@ -648,3 +648,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD
| 2026-07-20 | claude/clinical-kb-pwa-review-asi3wb (restarted; PR: #901 rerank regression closeout) | fd5dcd1582e1c7aa8bf92ff097575e0c68aa930d | #901 rerank regression: baseline re-eval + offline guard net + saturated-tie fix | Baseline eval-canary dispatch on remediated main b3ae061 (run 29731533081) PASSED — golden retrieval 36/36 + answer-quality green, confirming #913-#926 closed the live 4/36 regression (July 19 failing run tested the raw #901 state; fixes landed 1-8h after it, never re-evaled until now). Residual defect found by new offline repro and fixed: with byte-identical imputed fast-path primaries, selection's clamped keys tie exactly and the chunk-id fallback decides, which secondStageScore's position adjustment launders into releaseRankScore (buried the CIWA answer doc in repro); fix = contentRankScore carried on RetrievalCandidate as tie-break between clamped rerankScore and chunk id, never added to scores. Amended #901's own never-live-validated saturated-tie pin to content-rank-then-id. Guards: tests/rag-fast-path-ordering.test.ts (4 end-to-end eval-shape repros) + ranking-tuning gates (4 golden-mapped hard negatives at production weights + full-snapshot high-risk). Fixture aliases NOT changed (Step C not triggered — identity cases pass live). | Targeted 4-suite vitest 37/37; npm run test 2981 passed (sole fail = container-only pdf-extraction-budget artifact); verify:cheap green through all 18 pre-test checks incl. lint+typecheck; check:production-readiness PASS on substantive checks (2 FAILs = documented missing-secret class in this container); build + client-bundle scan + check:rag:fixtures PASS |
| 2026-07-20 | claude/clinical-kb-pwa-review-asi3wb (restarted; PR: coverage tie-break follow-up) | 57ec880b306a8e2b31c5f20dacc47256fc93b4e2 | Post-merge live-eval finding on #982 + corrective fix: saturated-tie key rankScore → query-term coverage | Post-#982 golden dispatch (eval-canary run #50, 29735004222, main b9057f0 + deps) came back 35/36: the three verifiable July-19 failures (lithium-therapy-monitoring, clozapine-anc-threshold, patient-safety-plan-include) all PASS live, but alcohol-ciwa-threshold flipped pass→FAIL vs the same-morning pre-#982 run #49 (29731533081, 36/36) — failing top-3 ordered by descending rankScore (1.85/1.75/1.53, all finalScore-saturated, releaseRankScore 1.09/1.086/1.07), i.e. #982's tie-break let generic clinicalSignalBoost stacking outvote the ciwa/score/threshold-bearing chunk; #982 is the only retrieval-path delta in the window. Fix: contentRankScore → contentCoverageScore sourced from lexicalCoverageScore (query-term coverage, immune to boost stacking; ties still fall to chunk id); saturated-tie contract test re-pinned so coverage beats a HIGHER rankScore (discriminating — old key fails it); fast-path CIWA guard gains the run-#50 screening-chunk shape + content-term assertion. Live validation: tonight's 18:00 UTC scheduled canary (dispatch cap 2/2 spent ≈$2-4). Separately: ci.yml dispatch 4012 survived 30+ min of main churn under #979's per-run concurrency group (fix working); duplicate dispatch 4017 cancelled. | Targeted vitest 38/38; npm run test 3012 passed / 1 known container-only pdf-budget artifact; verify:cheap green to the same artifact; build + client-bundle scan + check:rag:fixtures PASS; check:production-readiness expected missing-secret FAILs only (no secrets in container) |
| 2026-07-20 | claude/clinical-kb-pwa-review-asi3wb (restarted; PR: WebKit matrix diagnosis) | caa298972846ef52d472bfbff98c90fc26481ed8 | First completed release-browser-matrix in weeks (run 4012, main b9057f0, 38 min uncancelled under #979) — full triage of 28 failures + fix for the dominant cluster | Run 4012: 716 passed / 28 failed (25 webkit, 3 firefox) / 4 skipped. Dominant cluster (root-caused, FIXED here): all 8 ui-universal-search webkit failures share one signature — typeahead content never enters the DOM — because commandDropdownCanDisplay (added 42a3e3c 2026-07-17, AFTER the last completed matrix; never ran on WebKit until 4012) requires fine-pointer OR zero-touch; headless browsers fail the (hover:hover)+(pointer:fine) query (proven by the zero-touch escape existing for CI at all), Chromium/Firefox pass via maxTouchPoints===0, and Playwright's Linux WebKit build advertises phantom touch points — flunking both branches and disabling useUniversalSearch entirely. Fix: beforeEach addInitScript in ui-universal-search/ui-smoke/ui-tools specs stubbing Navigator.prototype.maxTouchPoints to the runner's true 0 (inert on Chromium/Firefox; product gate + tests/search-command-surface.test.ts pins untouched). Local chromium runs of ui-universal-search fail 16/20 IDENTICALLY on unmodified main (container artifact — hosted CI chromium green in 4012 is authoritative; verified by stash/run/pop baseline). Expected delta next matrix: ≥8 webkit failures clear; candidates ui-tools:1244 + several ui-smoke answer-flow cases (same surface). NOT yet root-caused (triaged remainder, hosted-matrix-only reproduction): webkit ui-stress overflow 330/409 (360px overflow at mobile), webkit ui-accessibility 195/252 (focus dismissal; forced-colors labels), webkit ui-formulation 132, webkit ui-smoke copy-table/retry/recovery/recent-searches/source-only/differential-context/viewer-hydration/document-questions, webkit ui-tools 2087 (goto interrupted by ?q= navigation), webkit ui-universal-search 183 strict-mode duplicate options (fallback-surface rendering; likely clears with the gate fix), firefox ui-smoke 946/2206/2932 (@critical document search fails on BOTH firefox and webkit — cross-browser, highest-priority remainder). | prettier PASS; eslint PASS; typecheck PASS; chromium stash-baseline no-delta (16 fail pre AND post — container artifact); webkit validation lands via tonight's 18:00 UTC scheduled matrix |
| 2026-07-20 | claude/clinical-kb-pwa-review-asi3wb (restarted; PR: matrix-remainder fixes) | ec070ccb20b9a067216e64e8399d9cd795027024 | Run-4012 remainder: 7 root-caused fixes (SW navigation hijack, WebKit focus/forced-colors/stale-style, Firefox tab order, CIWA alias, helper extraction) | Agent-diagnosed with probe evidence: (1) production-build PWA worker registers in EVERY matrix test (pwa-lifecycle.tsx:287 NODE_ENV gate), clients.claim()s the page and serves all navigations — Chromium probe proved SW-served reloads bypass page.route entirely (routeSawNav=[]); Playwright-Firefox wedges on its only two reloads (ui-smoke 2206/2932, both deterministic) → playwright.config.ts serviceWorkers:'block' + ui-pwa 'allow' opt-in (offline/CacheStorage journey verified passing locally under the opt-in). (2) Mode-menu dismissal relied solely on wrapper focusout; WebKit Tab navigation can move focus nowhere (links excluded) or wrap into the menu → keydown Tab-close in handleModeTriggerKeyDown (agent verified no test depends on old forward-Tab behavior). (3) WebKit has no forced-colors implementation → capability skip on the token-remap test. (4) WebKit stale :disabled computed style feeds axe a phantom 1.93 contrast for the re-enabled Previous button (blend arithmetic exact: 0.4×#475467+0.6×#fff=#b5bbc2) → toBeEnabled+opacity-1 pin before the scan, failure-at-pin = direct proof. (5) Firefox includes scrollable containers in tab order (sheet body scrollHeight 1125 vs 707 measured) → conditional step-over. (6) canary #50/#51 CIWA failure root cause: whitespace-delimited textContainsClinicalTerm can never match hyphenated 'CIWA-Ar' though the dosing-table region ranks top-5 → clinicalContentAliases ciwa:[ciwa, ciwa-ar] (plan-authorized fixture-alias route; content substance unchanged). (7) tests/helpers/zero-touch.ts shared helper replaces six #995 inline stubs (CodeRabbit follow-up). NOT claimed: ui-stress 409 overflow, ui-tools 2087 navigation race, webkit answer-flow subset — next matrix run (post-merge dispatch) measures these vs the 28-failure baseline; ui-smoke 2932 webkit-side is a distinct mock-data class deliberately deferred pending that run. | Targeted vitest 25/25 (eval-retrieval + search-command-surface); npm run test 3012 passed / 1 known container pdf-budget artifact; verify:cheap green to same artifact; build + client-bundle scan PASS; prettier/eslint/typecheck clean; chromium ui-pwa under new SW config: offline/CacheStorage privacy journey PASS, installability = known container in-incognito artifact; UI verification not run locally beyond that: chromium ui suites carry documented container artifacts — hosted ui-critical/ui-advisory + next matrix authoritative |
7 changes: 7 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ export default defineConfig({
// reduced-motion a11y spec emulates a per-test mode, so suite-wide settings
// remain stable across builds.
contextOptions: { reducedMotion: "reduce" },
// In production builds the PWA worker (public/sw.js) registers in every test,
// claims the page, and serves every subsequent navigation — bypassing route
// interception for navigations outright, and wedging Playwright-Firefox's
// reload path under an active route (the two ui-smoke reload hangs in matrix
// run 4012). Only ui-pwa.spec.ts is meant to exercise the worker; it opts
// back in with test.use({ serviceWorkers: "allow" }).
serviceWorkers: "block",
},
projects: [
{
Expand Down
4 changes: 4 additions & 0 deletions scripts/eval-retrieval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,10 @@ const clinicalDocumentAliases: Record<string, string[]> = {

const clinicalContentAliases: Record<string, string[]> = {
anc: ["anc", "absolute neutrophil count", "neutrophil", "neutrophils"],
// The scale's written name is the hyphenated token "CIWA-Ar"; textContainsClinicalTerm is
// whitespace-delimited, so the bare fixture term can never match it (canary runs #50/#51:
// the top-5 included the CIWA-Ar dosing-table region yet the content gate reported a miss).
ciwa: ["ciwa", "ciwa-ar"],
fbc: ["fbc", "full blood count", "full blood", "wbc", "white blood cell", "white cell"],
im: ["im", "intramuscular", "intramuscularly"],
mg: ["mg", "milligram", "milligrams", "dose", "doses"],
Expand Down
8 changes: 8 additions & 0 deletions src/components/clinical-dashboard/master-search-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,14 @@ export function MasterSearchHeader({
} else if (event.key === "ArrowUp") {
event.preventDefault();
openModeMenuWithFocus(selectedModeIndex - 1);
} else if (event.key === "Tab" && modeMenuOpen && !usesPhoneSearchLayout) {
// Desktop: Tab/Shift+Tab is leaving the trigger — close without trapping (APG
// menu-button pattern; arrow keys are the entry into the menu). WebKit's
// sequential focus navigation may never deliver a wrapper-escaping focusout
// (links are excluded from its Tab order, and backward navigation can wrap
// into the open menu), so keydown is the reliable dismiss signal; the wrapper
// onBlur remains the net for pointer and programmatic focus moves.
setModeMenuOpen(false);
}
}

Expand Down
14 changes: 14 additions & 0 deletions tests/helpers/zero-touch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Page } from "playwright/test";

/**
* Playwright's Linux WebKit build advertises phantom touch points on the touch-free CI
* runner, tripping the fine-pointer/zero-touch gate on the search command surface
* (commandDropdownCanDisplay) that Chromium and Firefox pass via the zero-touch fallback.
* Report the runner's real capability so WebKit exercises the same desktop surfaces.
* Register as a beforeEach (or call inside one) BEFORE the page under test navigates.
*/
export async function stubZeroTouchPoints({ page }: { page: Page }) {
await page.addInitScript(() => {
Object.defineProperty(Navigator.prototype, "maxTouchPoints", { configurable: true, get: () => 0 });
});
}
17 changes: 7 additions & 10 deletions tests/ui-accessibility.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import AxeBuilder from "@axe-core/playwright";
import { expect, test, type Page, type TestInfo } from "playwright/test";
import { stubZeroTouchPoints } from "./helpers/zero-touch";

const readySetupChecks = [
{ id: "env", label: ".env.local configured", status: "ready", detail: "Test environment ready." },
Expand Down Expand Up @@ -178,15 +179,7 @@ async function expectNoBlockingAxeViolations(page: Page, testInfo: TestInfo, opt
expect(summary, "axe found critical/serious WCAG A/AA violations").toEqual([]);
}

// Playwright's Linux WebKit build advertises phantom touch points on the touch-free CI
// runner, tripping the fine-pointer/zero-touch gate on the search command surface
// (commandDropdownCanDisplay) that Chromium and Firefox pass via the zero-touch fallback.
// Report the runner's real capability so WebKit exercises the same desktop surfaces.
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(Navigator.prototype, "maxTouchPoints", { configurable: true, get: () => 0 });
});
});
test.beforeEach(stubZeroTouchPoints);

test.describe("Clinical KB accessibility coverage", () => {
test.describe.configure({ timeout: 60_000 });
Expand Down Expand Up @@ -259,7 +252,11 @@ test.describe("Clinical KB accessibility coverage", () => {
await expectNoPageHorizontalOverflow(page);
});

test("solid-button label tokens stay legible with forced colors", async ({ page }) => {
test("solid-button label tokens stay legible with forced colors", async ({ browserName, page }) => {
test.skip(
browserName === "webkit",
"WebKit has no forced-colors implementation; Playwright's forcedColors emulation cannot engage the @media (forced-colors: active) token remap under test (the guarded glyph-backplate behavior is Chromium's).",
);
// Chromium paints a Canvas backplate behind every glyph run in forced-colors
// mode: a label whose color resolves into the Canvas/ButtonFace family
// disappears into its own backplate (axe cannot see this — it reads CSS,
Expand Down
17 changes: 10 additions & 7 deletions tests/ui-formulation.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import AxeBuilder from "@axe-core/playwright";
import { expect, test, type Page, type TestInfo } from "playwright/test";
import { stubZeroTouchPoints } from "./helpers/zero-touch";

const axeWcagTags = ["wcag2a", "wcag2aa", "wcag21a", "wcag21aa"];
const axeBlockingImpacts = new Set(["critical", "serious"]);
Expand Down Expand Up @@ -50,13 +51,7 @@ async function expectNoBlockingAxeViolations(page: Page, testInfo: TestInfo) {

test.beforeEach(async ({ page }) => {
await blockExternalRequests(page);
// Playwright's Linux WebKit build advertises phantom touch points on the touch-free CI
// runner, tripping the fine-pointer/zero-touch gate on the search command surface
// (commandDropdownCanDisplay) that Chromium and Firefox pass via the zero-touch fallback.
// Report the runner's real capability so WebKit exercises the same desktop surfaces.
await page.addInitScript(() => {
Object.defineProperty(Navigator.prototype, "maxTouchPoints", { configurable: true, get: () => 0 });
});
await stubZeroTouchPoints({ page });
});

test("searches patient language, opens a mechanism guide, and carries it into the builder", async ({
Expand Down Expand Up @@ -171,6 +166,14 @@ test("moves a selected mechanism through framework, quality review, and an edita
await expect(draft).not.toHaveValue("Stale edited draft");
await expect(draft).toHaveValue(/Select mechanisms and add case evidence/);
await expectNoHorizontalOverflow(page);
// WebKit can serve a stale :disabled computed style (opacity .4) for the step-nav
// Previous button after the select->draft transition re-enables it; axe folds that
// opacity into the glyph color and reports a phantom color-contrast violation for a
// state WCAG 1.4.3 exempts anyway (matrix run 4012). Poll the computed style so axe
// measures the live enabled state; a failure HERE is direct proof of the stale style.
const previousStep = page.getByRole("button", { name: "Previous", exact: true });
await expect(previousStep).toBeEnabled();
await expect(previousStep).toHaveCSS("opacity", "1");
await expectNoBlockingAxeViolations(page, testInfo);
});

Expand Down
4 changes: 4 additions & 0 deletions tests/ui-pwa.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ function isAllowedPublicCachePath(pathname: string) {
);
}

// The suite-wide Playwright config blocks service workers (they hijack navigations
// in production builds); this spec is the one place the worker itself is under test.
test.use({ serviceWorkers: "allow" });

test.describe("Clinical KB PWA", () => {
test.describe.configure({ timeout: 120_000 });

Expand Down
19 changes: 10 additions & 9 deletions tests/ui-smoke.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Route } from "playwright-core";
import { expect, test, type Locator, type Page } from "playwright/test";
import { stubZeroTouchPoints } from "./helpers/zero-touch";
import { readMobileComposerReservePx, scrollPrimarySurface } from "./playwright-scroll";
import { answerThreadStorageKey } from "../src/lib/answer-thread-storage";
import { documentSummaryQuestion } from "../src/lib/answer-contract";
Expand Down Expand Up @@ -873,15 +874,7 @@ async function openDailyActions(page: Page, triggerName: string | RegExp = /^Ope
return menu;
}

// Playwright's Linux WebKit build advertises phantom touch points on the touch-free CI
// runner, tripping the fine-pointer/zero-touch gate on the search command surface
// (commandDropdownCanDisplay) that Chromium and Firefox pass via the zero-touch fallback.
// Report the runner's real capability so WebKit exercises the same desktop surfaces.
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(Navigator.prototype, "maxTouchPoints", { configurable: true, get: () => 0 });
});
});
test.beforeEach(stubZeroTouchPoints);

test.describe("Clinical KB UI smoke coverage", () => {
test.describe.configure({ timeout: 60000 });
Expand Down Expand Up @@ -987,6 +980,14 @@ test.describe("Clinical KB UI smoke coverage", () => {
const restingButtonShadow = await newChat.evaluate((element) => getComputedStyle(element).boxShadow);
await closeMenu.focus();
await page.keyboard.press("Tab");
// Firefox includes scrollable containers in the tab order; the sheet body
// (overflow-y-auto) sits between Close and "New chat" in DOM order and
// genuinely overflows at this viewport. Step over it when focused.
const onScrollableBody = await page.evaluate(() => {
const element = document.activeElement;
return element instanceof HTMLElement && element.classList.contains("overflow-y-auto");
});
if (onScrollableBody) await page.keyboard.press("Tab");
await expect(newChat).toBeFocused();
const buttonFocus = await newChat.evaluate((element) => {
const style = getComputedStyle(element);
Expand Down
11 changes: 2 additions & 9 deletions tests/ui-stress.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { Route } from "playwright-core";
import { expect, test, type Page } from "playwright/test";
import { stubZeroTouchPoints } from "./helpers/zero-touch";
import { loadMedicationSnapshot } from "../src/lib/medication-snapshot";

const longTitle =
Expand Down Expand Up @@ -321,15 +322,7 @@ async function openScopeControl(page: Page) {
}).toPass({ timeout: 20_000 });
}

// Playwright's Linux WebKit build advertises phantom touch points on the touch-free CI
// runner, tripping the fine-pointer/zero-touch gate on the search command surface
// (commandDropdownCanDisplay) that Chromium and Firefox pass via the zero-touch fallback.
// Report the runner's real capability so WebKit exercises the same desktop surfaces.
test.beforeEach(async ({ page }) => {
await page.addInitScript(() => {
Object.defineProperty(Navigator.prototype, "maxTouchPoints", { configurable: true, get: () => 0 });
});
});
test.beforeEach(stubZeroTouchPoints);

test.describe("Clinical KB long-content stress coverage", () => {
for (const viewport of [
Expand Down
Loading
Loading