From 4e2d26e426f85cfd503e5e868d8e3f652271a308 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sun, 9 Aug 2026 22:57:06 +0800 Subject: [PATCH 1/3] ci: keep visual baseline drift advisory Report Chromium pixel differences as warnings while preserving review artifacts. Keep infrastructure and artifact failures visible. Verified with npm run test:ci-workflows and npm run check:github-actions. --- .github/workflows/ci.yml | 17 +++++++++++++++++ docs/testing.md | 5 ++++- tests/__screenshots__/README.md | 7 +++++-- tests/ci-cache-safety.test.ts | 10 ++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9609bc0542..49b64bfcd6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -700,8 +700,25 @@ jobs: uses: ./.github/actions/setup-ui-e2e - name: Chromium visual baselines + id: visual-comparison + # Pixel drift is evidence, not an execution failure. Keeping this at the + # step (rather than only the job) prevents GitHub from publishing a red + # check for the expected stale-golden case demonstrated by PR #1743. + continue-on-error: true run: npm run test:e2e:visual + - name: Report visual differences + if: steps.visual-comparison.outcome == 'failure' + run: | + echo "::warning title=Visual baseline drift::Chromium pixels differ from the committed Linux baseline. Review the visual-baseline-${{ github.run_id }} artifact; this advisory comparison does not block merging." + { + echo "### Visual baseline drift detected" + echo + echo "The Chromium comparison found changed pixels. This is advisory: required interaction, accessibility, and style-contract checks remain separate." + echo + echo "Download \`visual-baseline-${{ github.run_id }}\` and review the expected, actual, and diff images before refreshing the Linux baseline." + } >> "$GITHUB_STEP_SUMMARY" + # The actual/diff/expected PNGs live here. On a first run these are the images # to commit as the baseline; on a later run they are the evidence of what moved. - name: Upload visual diffs diff --git a/docs/testing.md b/docs/testing.md index 04b28a7f7c..0812f6213d 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -167,7 +167,10 @@ laptop-generated baseline makes every CI run red. A platform with no baseline fa than passing silently. The CI `visual-baseline` job is deliberately **off `pull_request` and `merge_group`** (owner decision on PR #1755 / `#118`): it still runs on pushes to main/release, the weekly schedule, and `workflow_dispatch`, and stays `continue-on-error` / outside `pr-required`. Do -not re-add pre-merge triggers or promote it without an explicit owner ask. +not re-add pre-merge triggers or promote it without an explicit owner ask. The pixel-comparison step +also uses `continue-on-error`: drift creates a workflow warning, job summary, and downloadable +expected/actual/diff artifact instead of a failed check. Setup or artifact-publication failures remain +visible as job failures because those runs produced no trustworthy comparison evidence. ## Performance budget diff --git a/tests/__screenshots__/README.md b/tests/__screenshots__/README.md index 8da5af80b0..c62b067f5b 100644 --- a/tests/__screenshots__/README.md +++ b/tests/__screenshots__/README.md @@ -54,5 +54,8 @@ without `--update-snapshots`: a baseline that fails its own immediate re-run is capturing a race, not a design. `maxDiffPixelRatio: 0.002` with `threshold: 0.2` absorbs antialiasing, not layout that has not settled. -Comparison is advisory until the jobs are flipped off `continue-on-error`. Until -then a real regression shows as a red _artifact_, not a red _check_. +Comparison is advisory. Pixel drift produces a workflow warning and summary while +the job uploads expected, actual, and diff images for review; it does not produce a +failed GitHub check. The job runs post-land, weekly, or manually rather than on pull +requests and merge queues. Setup or artifact-upload failures can still fail the job +because they leave no trustworthy evidence to review. diff --git a/tests/ci-cache-safety.test.ts b/tests/ci-cache-safety.test.ts index 1dc1245582..ba5918b713 100644 --- a/tests/ci-cache-safety.test.ts +++ b/tests/ci-cache-safety.test.ts @@ -352,6 +352,16 @@ describe("Visual baseline routing", () => { expect(prRequiredNeeds, "could not read pr-required's needs list from ci.yml").not.toBe(""); expect(prRequiredNeeds).not.toMatch(/\bvisual-baseline\b/); }); + + it("reports pixel drift as a warning while preserving review artifacts", () => { + expect(visualBaselineJob).toMatch( + /name: Chromium visual baselines\n\s+id: visual-comparison\n(?:\s+#.*\n)*\s+continue-on-error: true/, + ); + expect(visualBaselineJob).toContain("if: steps.visual-comparison.outcome == 'failure'"); + expect(visualBaselineJob).toContain("::warning title=Visual baseline drift::"); + expect(visualBaselineJob).toContain("$GITHUB_STEP_SUMMARY"); + expect(visualBaselineJob).toMatch(/name: Upload visual diffs\n\s+if: always\(\)/); + }); }); describe("Lighthouse budget routing", () => { From 6bc57714c36bc6d027561bb8f5f8b00bb92524b2 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 10 Aug 2026 10:17:08 +0000 Subject: [PATCH 2/3] =?UTF-8?q?fix(ci):=20classify=20visual=20drift=20and?= =?UTF-8?q?=20harden=20formulation=20Clear=E2=86=92Draft?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep pixel mismatches advisory while failing the visual-baseline job on missing baselines, setup/runtime, or other non-comparison errors. Wait for the Clear layout settle in ui-formulation so Production UI shard 2 does not lose the Draft step click. --- .github/workflows/ci.yml | 17 +- docs/scripts-index.md | 2 +- docs/testing.md | 22 ++- scripts/classify-visual-baseline-outcome.mjs | 154 ++++++++++++++++++ tests/__screenshots__/README.md | 7 +- tests/ci-cache-safety.test.ts | 10 +- .../classify-visual-baseline-outcome.test.ts | 47 ++++++ tests/ui-formulation.spec.ts | 6 + 8 files changed, 248 insertions(+), 17 deletions(-) create mode 100644 scripts/classify-visual-baseline-outcome.mjs create mode 100644 tests/classify-visual-baseline-outcome.test.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 49b64bfcd6..b872dd0b2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -662,7 +662,7 @@ jobs: # - Pixel comparison needs a soak before it can block a merge; this repo has # already paid for a sub-pixel rounding flake (the min-h-11 -> min-h-12 change). # Promote to a required check by adding it to `pr-required` once the baselines have - # held across a few runs, and drop `continue-on-error` at the same time. + # held across a few runs, and drop the pixel-step soft-fail / drift classifier then. visual-baseline: name: Visual baselines (advisory) needs: changes @@ -670,9 +670,11 @@ jobs: # a pixel gate that reports on every UI pull request taxes exactly the work it # exists to support. During a redesign the honest result is red on every push, # and the baselines cannot be refreshed until a run has produced the new - # images, so that red is unavoidable rather than actionable. It never blocked - # a merge (`continue-on-error`, and absent from `pr-required`), but a red check - # nobody can act on mid-iteration is friction whether or not it gates. + # images, so that red is unavoidable rather than actionable. Pixel drift is + # soft-failed at the comparison step (and classified) rather than with a + # job-level continue-on-error that would also hide setup failures; the job + # remains absent from `pr-required`. A red check nobody can act on + # mid-iteration is friction whether or not it gates. # `merge_group` is excluded for the same reason: queue runs are still # pre-merge UI churn, not a post-land refresh point. # @@ -687,7 +689,8 @@ jobs: if: > needs.changes.outputs.ui_changed == 'true' && contains(fromJSON('["push","schedule","workflow_dispatch"]'), github.event_name) - continue-on-error: true + # Deliberately NOT continue-on-error at the job: setup / artifact / non-drift + # failures must stay red. Only the pixel-comparison step soft-fails below. runs-on: ubuntu-24.04 timeout-minutes: 45 steps: @@ -710,6 +713,10 @@ jobs: - name: Report visual differences if: steps.visual-comparison.outcome == 'failure' run: | + if ! node scripts/classify-visual-baseline-outcome.mjs; then + echo "::error title=Visual baseline non-drift failure::Visual comparison failed for a reason other than pixel drift (missing baseline, setup/runtime, or assertion). See the visual-baseline-${{ github.run_id }} artifact." + exit 1 + fi echo "::warning title=Visual baseline drift::Chromium pixels differ from the committed Linux baseline. Review the visual-baseline-${{ github.run_id }} artifact; this advisory comparison does not block merging." { echo "### Visual baseline drift detected" diff --git a/docs/scripts-index.md b/docs/scripts-index.md index 75f50d7cb0..fb45e5bbe1 100644 --- a/docs/scripts-index.md +++ b/docs/scripts-index.md @@ -1,6 +1,6 @@ # Scripts index -Curated map of `scripts/` (224 files) and the `package.json` script surface (235 entries), +Curated map of `scripts/` (225 files) and the `package.json` script surface (235 entries), grouped by purpose. This is orientation, not an exhaustive per-file listing — the authoritative command list is `package.json`, and `npm run docs:check-scripts` verifies every `npm run ` referenced in docs resolves to a real script. `npm run docs:update` refreshes the exact counts above. diff --git a/docs/testing.md b/docs/testing.md index 0812f6213d..ea45745790 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -166,11 +166,13 @@ job's artifact, not from a developer machine** — font hinting and antialiasing laptop-generated baseline makes every CI run red. A platform with no baseline fails loudly rather than passing silently. The CI `visual-baseline` job is deliberately **off `pull_request` and `merge_group`** (owner decision on PR #1755 / `#118`): it still runs on pushes to main/release, the -weekly schedule, and `workflow_dispatch`, and stays `continue-on-error` / outside `pr-required`. Do -not re-add pre-merge triggers or promote it without an explicit owner ask. The pixel-comparison step -also uses `continue-on-error`: drift creates a workflow warning, job summary, and downloadable -expected/actual/diff artifact instead of a failed check. Setup or artifact-publication failures remain -visible as job failures because those runs produced no trustworthy comparison evidence. +weekly schedule, and `workflow_dispatch`, and stays outside `pr-required`. Do not re-add pre-merge +triggers or promote it without an explicit owner ask. Only the pixel-comparison step uses +`continue-on-error`, and only after `scripts/classify-visual-baseline-outcome.mjs` confirms the +failure is a `toHaveScreenshot` pixel mismatch: drift creates a workflow warning, job summary, and +downloadable expected/actual/diff artifact instead of a failed check. Missing baselines, setup, +runtime/assertion, and artifact-publication failures remain visible as job failures because those +runs produced no trustworthy comparison evidence. ## Performance budget @@ -269,7 +271,15 @@ UI scope runs a fail-fast `@critical` Chromium job on pull requests, then requir PR body synchronization is skipped unless the checked-out head actually contains `PR_POLICY_BODY.md`. The eval-canary liveness API probe runs once with the daily Ops Digest cadence rather than on every PR. These remove repeated provider-side work without weakening a required result. -Two further jobs are advisory (`continue-on-error`, deliberately outside `pr-required`): `visual-baseline` on UI scope and `lighthouse-budget` on the narrower perf scope (see "When the budget runs" above — `worker/**` and container surfaces, dependency manifests and the lockfile, Playwright/test surfaces, most of `src/app/api/**` other than initial-load handlers, and `src/app/mockups/**` are excluded; `src/proxy.ts` stays in). Both upload their evidence on every run, pass or fail, because the artifact is the whole point on a first run — the baselines to adopt and the reports to grade. Promote either to required by adding it to `pr-required` and removing `continue-on-error` in the same edit; for `lighthouse-budget` that edit must also restore `merge_group` to its `if:`. +Two further jobs are advisory (deliberately outside `pr-required`): `visual-baseline` on UI scope +(soft-fail only the classified pixel-drift step) and `lighthouse-budget` on the narrower perf scope +(`continue-on-error` — see "When the budget runs" above; `worker/**` and container surfaces, +dependency manifests and the lockfile, Playwright/test surfaces, most of `src/app/api/**` other than +initial-load handlers, and `src/app/mockups/**` are excluded; `src/proxy.ts` stays in). Both upload +their evidence on every run, pass or fail, because the artifact is the whole point on a first run — +the baselines to adopt and the reports to grade. Promote either to required by adding it to +`pr-required` and removing the soft-fail (`continue-on-error` / drift classifier) in the same edit; +for `lighthouse-budget` that edit must also restore `merge_group` to its `if:`. ## Contribution checklist (UI changes) diff --git a/scripts/classify-visual-baseline-outcome.mjs b/scripts/classify-visual-baseline-outcome.mjs new file mode 100644 index 0000000000..7bf7a7962b --- /dev/null +++ b/scripts/classify-visual-baseline-outcome.mjs @@ -0,0 +1,154 @@ +#!/usr/bin/env node +/** + * Decide whether a failed `test:e2e:visual` run was pixel drift (advisory) or an + * infrastructure / non-comparison failure that must stay red. + * + * Exit 0 → advisory drift only (or no failures found in an existing report). + * Exit 1 → missing report, missing baselines, runtime/assertion failures, or mixed. + */ +import { existsSync, readFileSync, readdirSync } from "node:fs"; +import { join } from "node:path"; +import { pathToFileURL } from "node:url"; + +const DEFAULT_JUNIT = "test-results/playwright-junit.xml"; +const DEFAULT_RESULTS = "test-results/playwright-results.json"; + +const decode = (value) => + value + .replaceAll(""", '"') + .replaceAll("'", "'") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll("&", "&"); + +const attribute = (attributes, name) => { + const match = attributes.match(new RegExp(`\\b${name}="([^"]*)"`)); + return match ? decode(match[1]) : ""; +}; + +/** Failure/error bodies from a Playwright JUnit report. */ +export function failureBodiesFromJunit(xml) { + return [...xml.matchAll(/]*)>([\s\S]*?)<\/testcase>/g)].flatMap((match) => { + const title = attribute(match[1], "name"); + const classname = attribute(match[1], "classname"); + return [...match[2].matchAll(/<(?:failure|error)\b([^>]*)>([\s\S]*?)<\/(?:failure|error)>/g)].map((failure) => ({ + title, + classname, + message: attribute(failure[1], "message"), + body: decode(failure[2]).trim(), + })); + }); +} + +function collectErrorMessages(node, out = []) { + if (!node || typeof node !== "object") return out; + if (Array.isArray(node)) { + for (const item of node) collectErrorMessages(item, out); + return out; + } + if (node.error?.message) out.push(String(node.error.message)); + if (Array.isArray(node.errors)) { + for (const error of node.errors) { + if (error?.message) out.push(String(error.message)); + } + } + for (const value of Object.values(node)) collectErrorMessages(value, out); + return out; +} + +export function failureMessagesFromResults(resultsJson) { + return collectErrorMessages(resultsJson); +} + +/** Pixel-drift only: toHaveScreenshot mismatch with an existing baseline. */ +export function isPixelDriftFailure(text) { + const haystack = String(text ?? ""); + if (!haystack) return false; + if (/snapshot doesn't exist/i.test(haystack)) return false; + if (/AWAITING_BASELINE/i.test(haystack)) return false; + return /toHaveScreenshot/i.test(haystack) || /Screenshot comparison failed/i.test(haystack); +} + +function listDiffPngs(root = "test-results") { + if (!existsSync(root)) return []; + const found = []; + const walk = (dir) => { + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.isFile() && /-diff\.png$/i.test(entry.name)) found.push(full); + } + }; + walk(root); + return found; +} + +export function classifyVisualBaselineOutcome({ + junitPath = DEFAULT_JUNIT, + resultsPath = DEFAULT_RESULTS, + testResultsDir = "test-results", +} = {}) { + const hasJunit = existsSync(junitPath); + const hasResults = existsSync(resultsPath); + if (!hasJunit && !hasResults) { + return { + kind: "infrastructure", + reason: "No Playwright JUnit or JSON report after visual comparison failure.", + }; + } + + const messages = []; + if (hasJunit) { + for (const failure of failureBodiesFromJunit(readFileSync(junitPath, "utf8"))) { + messages.push([failure.message, failure.body, failure.title].filter(Boolean).join("\n")); + } + } + if (hasResults) { + messages.push(...failureMessagesFromResults(JSON.parse(readFileSync(resultsPath, "utf8")))); + } + + const unique = [...new Set(messages.map((message) => message.trim()).filter(Boolean))]; + if (unique.length === 0) { + const diffs = listDiffPngs(testResultsDir); + if (diffs.length > 0) { + return { + kind: "pixel-drift", + reason: `Found ${diffs.length} screenshot diff artifact(s) without parsed failure text.`, + }; + } + return { + kind: "infrastructure", + reason: "Visual comparison failed but the report contains no failed testcases.", + }; + } + + const nonDrift = unique.filter((message) => !isPixelDriftFailure(message)); + if (nonDrift.length > 0) { + return { + kind: "non-drift", + reason: `Non-comparison failure(s) present (${nonDrift.length}/${unique.length}).`, + samples: nonDrift.slice(0, 3), + }; + } + + return { + kind: "pixel-drift", + reason: `All ${unique.length} failure(s) are toHaveScreenshot pixel mismatches.`, + }; +} + +function main() { + const outcome = classifyVisualBaselineOutcome(); + if (outcome.kind === "pixel-drift") { + console.log(`Visual baseline outcome: pixel-drift — ${outcome.reason}`); + process.exitCode = 0; + return; + } + console.error(`Visual baseline outcome: ${outcome.kind} — ${outcome.reason}`); + for (const sample of outcome.samples ?? []) { + console.error(`- ${sample.split("\n")[0].slice(0, 200)}`); + } + process.exitCode = 1; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) main(); diff --git a/tests/__screenshots__/README.md b/tests/__screenshots__/README.md index c62b067f5b..aac124c7a4 100644 --- a/tests/__screenshots__/README.md +++ b/tests/__screenshots__/README.md @@ -56,6 +56,7 @@ absorbs antialiasing, not layout that has not settled. Comparison is advisory. Pixel drift produces a workflow warning and summary while the job uploads expected, actual, and diff images for review; it does not produce a -failed GitHub check. The job runs post-land, weekly, or manually rather than on pull -requests and merge queues. Setup or artifact-upload failures can still fail the job -because they leave no trustworthy evidence to review. +failed GitHub check. Missing baselines and other non-comparison failures stay red. +The job runs post-land, weekly, or manually rather than on pull requests and merge +queues. Setup or artifact-upload failures can still fail the job because they leave +no trustworthy evidence to review. diff --git a/tests/ci-cache-safety.test.ts b/tests/ci-cache-safety.test.ts index ba5918b713..b14c4464fe 100644 --- a/tests/ci-cache-safety.test.ts +++ b/tests/ci-cache-safety.test.ts @@ -347,17 +347,23 @@ describe("Visual baseline routing", () => { // Owner decision (PR #1755 / #118): pre-merge UI churn is the wrong place for // an unavoidably-red pixel gate. merge_group is still pre-merge. expect(visualBaselineJob).toContain('["push","schedule","workflow_dispatch"]'); - expect(visualBaselineJob).toContain("continue-on-error: true"); const prRequiredNeeds = /\n pr-required:\n[\s\S]*?needs:\s*\n?\s*\[([\s\S]*?)\]/.exec(workflow)?.[1] ?? ""; expect(prRequiredNeeds, "could not read pr-required's needs list from ci.yml").not.toBe(""); expect(prRequiredNeeds).not.toMatch(/\bvisual-baseline\b/); }); - it("reports pixel drift as a warning while preserving review artifacts", () => { + it("soft-fails only the pixel-comparison step, not the whole advisory job", () => { + // Job-level continue-on-error would also swallow setup / upload failures. + // Job keys in the captured block are indented four spaces; step keys are deeper. + expect(visualBaselineJob).not.toMatch(/^ {4}continue-on-error:\s*true\s*$/m); expect(visualBaselineJob).toMatch( /name: Chromium visual baselines\n\s+id: visual-comparison\n(?:\s+#.*\n)*\s+continue-on-error: true/, ); + }); + + it("reports pixel drift as a warning while preserving review artifacts", () => { expect(visualBaselineJob).toContain("if: steps.visual-comparison.outcome == 'failure'"); + expect(visualBaselineJob).toContain("scripts/classify-visual-baseline-outcome.mjs"); expect(visualBaselineJob).toContain("::warning title=Visual baseline drift::"); expect(visualBaselineJob).toContain("$GITHUB_STEP_SUMMARY"); expect(visualBaselineJob).toMatch(/name: Upload visual diffs\n\s+if: always\(\)/); diff --git a/tests/classify-visual-baseline-outcome.test.ts b/tests/classify-visual-baseline-outcome.test.ts new file mode 100644 index 0000000000..8803090546 --- /dev/null +++ b/tests/classify-visual-baseline-outcome.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from "vitest"; + +import { + classifyVisualBaselineOutcome, + failureBodiesFromJunit, + isPixelDriftFailure, +} from "../scripts/classify-visual-baseline-outcome.mjs"; + +describe("classify-visual-baseline-outcome", () => { + it("treats toHaveScreenshot mismatches as pixel drift", () => { + expect( + isPixelDriftFailure( + "Error: expect(locator).toHaveScreenshot(expected) failed\n\nExpected: baseline.png\nReceived: actual.png", + ), + ).toBe(true); + }); + + it("keeps missing baselines and other assertions as non-drift", () => { + expect(isPixelDriftFailure(`A snapshot doesn't exist at tests/__screenshots__/linux/x.png.`)).toBe(false); + expect(isPixelDriftFailure("Expected heading to be visible")).toBe(false); + expect(isPixelDriftFailure("AWAITING_BASELINE still lists dashboard-shell")).toBe(false); + }); + + it("parses junit failure bodies for classification", () => { + const bodies = failureBodiesFromJunit(` + + + pixels differ + + + missing + + `); + expect(bodies).toHaveLength(2); + expect(isPixelDriftFailure(`${bodies[0].message}\n${bodies[0].body}`)).toBe(true); + expect(isPixelDriftFailure(`${bodies[1].message}\n${bodies[1].body}`)).toBe(false); + }); + + it("fails closed when no report exists after a comparison failure", () => { + const outcome = classifyVisualBaselineOutcome({ + junitPath: "/tmp/does-not-exist-junit.xml", + resultsPath: "/tmp/does-not-exist-results.json", + testResultsDir: "/tmp/does-not-exist-results-dir", + }); + expect(outcome.kind).toBe("infrastructure"); + }); +}); diff --git a/tests/ui-formulation.spec.ts b/tests/ui-formulation.spec.ts index 1298c75fcd..46b8aac002 100644 --- a/tests/ui-formulation.spec.ts +++ b/tests/ui-formulation.spec.ts @@ -226,8 +226,14 @@ test("moves a selected mechanism through framework, quality review, and an edita await draft.fill("Stale edited draft"); await page.getByRole("button", { name: /Select\s+Mechanisms/ }).click(); + await expect(page.getByTestId("formulation-builder-select")).toBeVisible(); await page.getByRole("button", { name: "Clear" }).click(); + // Clear unmounts the selected-hypotheses strip; wait for that layout settle + // before the step-rail click so it is not lost to a mid-reflow miss + // (Production UI shard flake on PR #1791). + await expect(page.getByRole("heading", { name: "No mechanisms selected" })).toBeVisible(); await page.getByRole("button", { name: /Draft\s+Formulation/ }).click(); + await expect(page.getByTestId("formulation-builder-draft")).toBeVisible(); await expect(draft).not.toHaveValue("Stale edited draft"); await expect(draft).toHaveValue(/Select mechanisms and add case evidence/); await expectNoHorizontalOverflow(page); From ba78101317c0a4258778ea566a02be8ca2c8917e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 10 Aug 2026 10:17:19 +0000 Subject: [PATCH 3/3] docs: record PR #1791 babysit unblock in review ledger --- 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 171e63bcdb..10f519323f 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -855,3 +855,4 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-08-09 | PR #1782 / cursor/fix-document-open-scroll-e5bf | 5709f2cc7a954197e02107c96d7896d8d13445c3 | document-viewer open-at-top | ship: remove chunk mount scrollIntoView so document opens stay at overview top | document-viewer-shell.dom 7 pass; document-section-summary.dom 8 pass; verify:pr-local dry-run | | 2026-08-09 | cursor/fix-document-open-scroll-e5bf (PR #1782) | 98029875db7d640d3e699829249bb33892296bff | PR #1782 unblock | before: static-pr+coverage failed on stale adoption-manifest (document-viewer-shell testFiles drift), merge-tree clean 0 behind, auto-merge armed, 1 advisory CodeRabbit waitFor thread; after: regenerated adoption-manifest, hardened scroll negative assertion, pre-commit+handoff adoption sync to prevent recurrence; CodeRabbit dispositioned as fixed by sync assert | check:design-system-adoption PASS; vitest design-system-adoption+document-viewer-shell+docs-inventory 63/63 PASS; format; no provider-backed checks | | 2026-08-09 | cursor/fix-document-open-scroll-e5bf (PR #1782) | 86698228533ebe10452c10c1bd7a3e1610d891ae | PR #1782 unblock | merged origin/main (behind-but-clean); fixed static-pr TS2322 on document-viewer-shell chunk fixture; fixed Production UI DSM compare remove stall via location.assign + DOM proof; prior adoption-manifest drift already fixed | tsc clean for changed files; vitest document-viewer-shell+dsm-compare-remove+design-system-adoption 59/59 PASS; check:design-system-adoption PASS; format; no provider-backed checks | +| 2026-08-10 | codex/visual-baseline-advisory-pr | 6bc57714c36bc6d027561bb8f5f8b00bb92524b2 | PR #1791 babysit unblock | fixed Production UI formulation Clear→Draft flake settle; classified visual drift vs non-drift failures | test:ci-workflows 263; classify-visual-baseline-outcome+ci-cache-safety 40 |