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 numberDiff line numberDiff line change
Expand Up@@ -1124,3 +1124,4 @@ This file is append-only. Never rewrite or delete an existing review record; app
| 2026-07-27 | branch-cleanup exact merged-head batch | `multi-head` | branch-cleanup | REMOVED. Deleted 30 additional clean worktrees and 34 additional local branches whose exact tips matched GitHub merged PR head records. Preserved the newly merged `codex/settings-ux` worktree under a recent-work grace rule, along with all checked-out, dirty, open-PR, closed-unmerged, secret-safeguarded, process-locked, or unmatched refs. | GitHub merged/open PR head inventory; exact OID match immediately before removal; no force worktree removal; no application tests or provider-backed application workflows run. |
| 2026-07-27 | branch-cleanup aged and closed-ref batch | `multi-head` | branch-cleanup | REMOVED. Deleted 36 clean branch-backed worktrees older than 72 hours while retaining their refs, then deleted 16 old temporary or exact closed-PR local refs and 11 exact closed-PR remote refs after verified recovery bundles. Restored and retained one permission-locked Antigravity worktree; retained all dirty, open-PR, recent, archive/preserve, secret-bearing, high-risk, divergent, detached, or potentially useful UI refs. | Fresh GitHub open/all/closed PR inventories; 72-hour creation/closure cutoff; exact tip checks; four verified incremental bundles for 21 refs in this pass; no force worktree removal, application tests, or provider-backed application workflows run. |
| 2026-07-27 | `codex/publish-document-nav-20260727` (PR #1278) | `71d442e7921c36fe036128207c9925484a908fd0` | Protected-main review of preserved cleanup records, reusable review prompts, and document navigation mockups | APPROVE pending final exact-head hosted required checks. The unique preserved work was transplanted onto current `origin/main`; stale phone-chrome and unsafe 15-minute lock-expiry patches were excluded. The first hosted static run found arbitrary mockup font sizes, which were replaced with the established named type-scale tokens. Review found no remaining P0-P3 issue and no retrieval, clinical-output, provider, or production-route behavior change. | Flight plan, Prettier, docs index/scripts/links, sitemap, branch-ledger, type-scale, icon-scale, brand, design-system, and `git diff --check` PASS; hosted build, static, unit coverage, advisory mockup UI, safety, policy, Semgrep, and secret checks PASS on reviewed head; Production UI pending at ledger append; local heavy gates deferred behind legitimate shared exclusive owners; no non-GitHub provider-backed checks. |
| 2026-07-27 | `codex/settings-followup` | `806fcc4c3167d9e2f9fbd832c39e53d3491f270a` | Protected-main release-readiness review of settings follow-up browser reliability | APPROVE. The test-only diff waits for one settled React owner before strict answer/search interactions, makes universal-search mocks echo the requested query, and retries scroll-to-live-endpoint geometry after late dock layout. Review found no P0-P3 issue and no product, retrieval, ranking, clinical-output, or provider behavior change. Highest residual risk is physical iOS/WebKit behavior outside local Chromium coverage. | Focused integrated production Chromium PASS (5/5); exact integrated-head `verify:pr-local` PASS (runtime, formatting, lint, typecheck, 393 files, 3,538 passed / 2 skipped, 36 offline RAG fixtures); `verify:ui` PASS (323/323); `git diff --check` PASS; no non-GitHub provider-backed checks. |
49 changes: 38 additions & 11 deletions tests/answer-progress-ui-smoke.spec.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { expect, test, type Page } from "playwright/test";
import { expect, test, type Locator, type Page } from "playwright/test";
import { demoAnswer, demoDocuments } from "../src/lib/demo-data";

const readySetupChecks = [
Expand All@@ -9,6 +9,39 @@ const readySetupChecks = [
{ id: "openai", label: "Answer provider", status: "ready", detail: "Mock stream ready." },
];

async function waitForReactEventHandler(locator: Locator, eventName: "onChange" | "onSubmit") {
await expect
.poll(
async () =>
locator.evaluate((element, reactEventName) => {
const propsKey = Object.keys(element).find((key) => key.startsWith("__reactProps$"));
if (!propsKey) return false;
const props = (element as unknown as Record<string, Record<string, unknown>>)[propsKey];
return typeof props?.[reactEventName] === "function";
}, eventName),
{ timeout: 15_000 },
)
.toBe(true);
}

async function fillHydratedAnswerQuestion(page: Page, value: string) {
const input = page.locator('[aria-label^="Search indexed guidelines by question or keyword"]:visible');
const submit = page.locator('[aria-label="Generate source-backed answer"]:visible');

await expect(async () => {
await expect(input).toHaveCount(1, { timeout: 30_000 });
await expect(submit).toHaveCount(1, { timeout: 30_000 });
const form = input.locator("xpath=ancestor::form[1]");
await waitForReactEventHandler(input, "onChange");
await waitForReactEventHandler(form, "onSubmit");
await input.fill(value);
await expect(input).toHaveValue(value);
await expect(submit).toBeEnabled();
}).toPass({ timeout: 30_000 });

return submit;
}

async function mockDashboardApis(page: Page) {
await page.route("**/*", async (route) => {
const url = new URL(route.request().url());
Expand DownExpand Up@@ -195,10 +228,7 @@ test("answer progress remains user-safe through fallback and keeps a compact com
await installTimedAnswerStream(page);
await page.goto("/?mode=answer", { waitUntil: "domcontentloaded" });

const input = page.locator('[aria-label^="Search indexed guidelines by question or keyword"]:visible').first();
const submit = page.locator('[aria-label="Generate source-backed answer"]:visible').first();
await expect(input).toBeEditable({ timeout: 30_000 });
await input.fill("Lithium dosing");
const submit = await fillHydratedAnswerQuestion(page, "Lithium dosing");
await submit.click();

const progress = page.getByTestId("answer-progress-stepper");
Expand DownExpand Up@@ -234,17 +264,14 @@ test("a completion frame cannot mark a previous answer complete when final is in
await installSuccessfulThenInvalidAnswerStreams(page);
await page.goto("/?mode=answer", { waitUntil: "domcontentloaded" });

const input = page.locator('[aria-label^="Search indexed guidelines by question or keyword"]:visible').first();
const submit = page.locator('[aria-label="Generate source-backed answer"]:visible').first();
await expect(input).toBeEditable({ timeout: 30_000 });
await input.fill("Lithium dosing");
const submit = await fillHydratedAnswerQuestion(page, "Lithium dosing");
await submit.click();

await expect(page.getByText(/In the synthetic lithium document/i)).toBeVisible({ timeout: 8_000 });
await expect(page.getByTestId("answer-progress-stepper")).toHaveAttribute("data-progress-state", "complete");

await input.fill("What about monitoring?");
await submit.click();
const followUpSubmit = await fillHydratedAnswerQuestion(page, "What about monitoring?");
await followUpSubmit.click();

await expect(page.getByTestId("answer-error")).toContainText("Answer stream returned an invalid final payload", {
timeout: 10_000,
Expand Down
13 changes: 10 additions & 3 deletions tests/ui-smoke.spec.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -135,18 +135,25 @@ async function isVisibleWithoutThrow(locator: Locator) {
}

async function fillVisibleQuestionInput(page: Page, value: string) {
const questionInput = visibleQuestionInput(page);
const submitAnswer = visibleAnswerSubmitButton(page);
const questionInput = page.locator('[aria-label^="Search indexed guidelines by question or keyword"]:visible');
const submitAnswer = page.locator('[aria-label="Generate source-backed answer"]:visible');

await expect(async () => {
// A production navigation can briefly overlap or replace the server-rendered
// composer. Require one settled React owner before filling so the new client
// tree cannot discard the value and leave submit disabled.
await expect(questionInput).toHaveCount(1, { timeout: uiAssertionTimeoutMs });
await expect(submitAnswer).toHaveCount(1, { timeout: uiAssertionTimeoutMs });
await waitForReactEventHandler(questionInput, "onChange");
await waitForReactEventHandler(questionInput.locator("xpath=ancestor::form[1]"), "onSubmit");
await expect(submitAnswer).toHaveAttribute("title", /Enter a clinical question|Generate a source-backed answer/, {
timeout: uiAssertionTimeoutMs,
});
await expect(questionInput).toBeEditable({ timeout: uiAssertionTimeoutMs });
await questionInput.fill(value);
await expect(questionInput).toHaveValue(value, { timeout: uiAssertionTimeoutMs });
await expect(submitAnswer).toBeEnabled({ timeout: uiAssertionTimeoutMs });
}).toPass({ timeout: 15_000 });
}).toPass({ timeout: uiAssertionTimeoutMs });

return questionInput;
}
Expand Down
37 changes: 31 additions & 6 deletions tests/ui-stress.spec.ts
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
import type { Route } from "playwright-core";
import { expect, test, type Page } from "playwright/test";
import { expect, test, type Locator, type Page } from "playwright/test";
import { stubZeroTouchPoints } from "./helpers/zero-touch";
import { loadMedicationSnapshot } from "../src/lib/medication-snapshot";
import { readPrimaryScrollGeometry } from "./playwright-scroll";

const longTitle =
"Extremely long synthetic shared-care guideline title covering lithium clozapine perinatal risk ADHD medication review emergency escalation and outpatient monitoring pathways";

async function waitForReactEventHandler(locator: Locator, eventName: "onChange" | "onSubmit") {
await expect
.poll(
async () =>
locator.evaluate((element, reactEventName) => {
const propsKey = Object.keys(element).find((key) => key.startsWith("__reactProps$"));
if (!propsKey) return false;
const props = (element as unknown as Record<string, Record<string, unknown>>)[propsKey];
return typeof props?.[reactEventName] === "function";
}, eventName),
{ timeout: 15_000 },
)
.toBe(true);
}

function makeDocument(index: number) {
return {
id: `10000000-0000-4000-8000-${String(index).padStart(12, "0")}`,
Expand DownExpand Up@@ -356,11 +371,21 @@ test.describe("Clinical KB long-content stress coverage", () => {
await page.goto("/?mode=answer", { waitUntil: "domcontentloaded" });
await expect(page.getByRole("button", { name: "Mode Answer" })).toBeVisible();

await page
.locator('[aria-label^="Search indexed guidelines by question or keyword"]:visible')
.first()
.fill("Show all stress citations and source cards");
await page.locator('[aria-label="Generate source-backed answer"]:visible').first().click();
// Production hydration can briefly replace the server-rendered composer.
// Require one settled owner with live React handlers before typing, or the
// replacement can lose the value and leave the submit button disabled.
const answerSurface = page.getByTestId("answer-empty-state");
await expect(answerSurface).toHaveCount(1, { timeout: 15_000 });
const questionInput = answerSurface.locator('[aria-label^="Search indexed guidelines by question or keyword"]');
await expect(questionInput).toHaveCount(1);
const answerForm = questionInput.locator("xpath=ancestor::form[1]");
await waitForReactEventHandler(questionInput, "onChange");
await waitForReactEventHandler(answerForm, "onSubmit");
await questionInput.fill("Show all stress citations and source cards");
await expect(questionInput).toHaveValue("Show all stress citations and source cards");
const submit = answerForm.getByRole("button", { name: "Generate source-backed answer" });
await expect(submit).toBeEnabled({ timeout: 15_000 });
await submit.click();

await expect(page.getByLabel("Source-backed answer")).toBeVisible();
await expect(page.getByTestId("plain-answer-response")).toBeVisible();
Expand Down
22 changes: 14 additions & 8 deletions tests/ui-tools.spec.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -1772,8 +1772,12 @@ test.describe("Clinical KB tools launcher", () => {
await page.setViewportSize({ width: 390, height: 844 });
await gotoLauncher(page, "/differentials");

const input = page.locator('input[placeholder="Ask or search a presentation"]:visible').first();
const input = page.locator('input[placeholder="Ask or search a presentation"]:visible');
const submit = page.locator('button[aria-label="Search differential presentations"]:visible');
await expect(input).toHaveCount(1, { timeout: 15_000 });
await expect(submit).toHaveCount(1, { timeout: 15_000 });
await waitForReactEventHandler(input, "onChange");
await waitForReactEventHandler(input.locator("xpath=ancestor::form[1]"), "onSubmit");
await input.fill("acute confusion");
await expect(submit).toBeEnabled();
const searchResponse = page.waitForResponse(
Expand All@@ -1797,13 +1801,15 @@ test.describe("Clinical KB tools launcher", () => {
await input.focus();
await expect(dock).not.toHaveAttribute("data-scroll-hidden", "true");
await expect.poll(async () => readMobileComposerReservePx(mainContent)).toBeGreaterThan(180);
await scrollPrimarySurface(page, "end");
await expect
.poll(async () => {
const geometry = await readPrimaryScrollGeometry(page);
return geometry.maxScrollTop - geometry.scrollTop;
})
.toBeLessThanOrEqual(1);
// The visible dock/reserve can finish its layout commit after the first
// endpoint scroll, increasing document height. Treat scrolling to the live
// endpoint and measuring it as one retriable action; a persistent clearance
// regression still fails this assertion.
await expect(async () => {
await scrollPrimarySurface(page, "end");
const geometry = await readPrimaryScrollGeometry(page);
expect(geometry.maxScrollTop - geometry.scrollTop).toBeLessThanOrEqual(1);
}).toPass({ timeout: 15_000 });
expect((await readPrimaryScrollGeometry(page)).owner).toBe("document");
await expect(dock).not.toHaveAttribute("data-scroll-hidden", "true");
await expect.poll(async () => readMobileComposerReservePx(mainContent)).toBeGreaterThan(180);
Expand Down
32 changes: 29 additions & 3 deletions tests/ui-universal-search.spec.ts
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
import { expect, test, type Page, type Route } from "playwright/test";
import { expect, test, type Locator, type Page, type Route } from "playwright/test";
import { stubZeroTouchPoints } from "./helpers/zero-touch";

// Cross-entity universal typeahead in the command surface. The universal endpoint is
Expand DownExpand Up@@ -86,7 +86,9 @@ async function fulfillUniversalSearch(route: Route, response: typeof universalPa

async function mockUniversalSearch(page: Page) {
await page.route(/\/api\/search\/universal(?:\?.*)?$/, async (route) => {
const mode = new URL(route.request().url()).searchParams.get("mode") ?? "documents";
const requestUrl = new URL(route.request().url());
const mode = requestUrl.searchParams.get("mode") ?? "documents";
const query = requestUrl.searchParams.get("q") ?? "";
const preferredByMode: Record<string, string[]> = {
answer: ["documents"],
documents: ["documents"],
Expand All@@ -102,17 +104,41 @@ async function mockUniversalSearch(page: Page) {
const responseOrder = universalPayload.groups.map((group) => group.kind);
await fulfillUniversalSearch(route, {
...universalPayload,
query,
contextMode: mode,
preferredDomains,
domainOrder: [...preferredDomains, ...responseOrder.filter((domain) => !preferredDomains.includes(domain))],
});
});
}

async function waitForReactChangeHandler(locator: Locator) {
await expect
.poll(
async () =>
locator.evaluate((element) => {
const propsKey = Object.keys(element).find((key) => key.startsWith("__reactProps$"));
if (!propsKey) return false;
const props = (element as unknown as Record<string, Record<string, unknown>>)[propsKey];
return typeof props?.onChange === "function";
}),
{ timeout: 15_000 },
)
.toBe(true);
}

async function openComposer(page: Page, href = "/?mode=documents&focus=1") {
await page.goto(href, { waitUntil: "domcontentloaded" });
const input = page.getByTestId("global-search-input").first();
// Do not hide a transient server/client overlap with `.first()`. Wait for one
// settled composer and its React handler so a hydration replacement cannot
// discard the subsequent fill while the full browser suite is under load.
const input = page.getByTestId("global-search-input");
await expect(input).toHaveCount(1, { timeout: 15_000 });
await expect(input).toBeVisible();
await expect(input).toBeEnabled();
await waitForReactChangeHandler(input);
await input.click();
await expect(input).toBeFocused();
return input;
}

Expand Down
Loading