diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 74e9fca0a..654136201 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -971,3 +971,4 @@ This file is append-only. Never rewrite or delete an existing review record; app | 2026-07-25 | cursor/pr1190-dark-mode-salvage-f453 (PR #1214) | pending-ci-retrigger-2 | CI unblock + skip-branch-sync | Repeated pr-branch-sync bot merges left CI `action_required`. Applied `skip-branch-sync` label and agent retrigger so required checks can finish for squash-merge; then close #1190. | Hosted CI pending on tip; no provider-backed checks. | | 2026-07-25 | cursor/pr1190-dark-mode-salvage-f453 (PR #1214) | `e19442240afbd7f28c321e399c6b4dcb0a7c9fdf` / squash `bb6b394617cbd906285ebe19e0e452912793320a` | prlanded after squash merge | MERGED. Two-dot content diff vs `origin/main` empty; clinical gate preserved; `no-hardcoded-hex` + theme CSS on main; PWA manifest theme colours intentionally absent. Hosted PR required SUCCESS (Unit/Build/Static/Production UI). Remote salvage branch deleted by squash `--delete-branch`. | `gh pr view` MERGED; `git diff origin/main e19442240` empty; trustGated grep on main; no provider-backed checks. | | 2026-07-25 | PR #1190 / `remediate-dark-mode-audit` | `00eca49b9b0d7e5fbfa5703a15e9e930963984a6` | Close without merge after #1214 salvage | CLOSED (not merged). Unsafe tip superseded by #1214. Remote branch `remediate-dark-mode-audit` retained pending optional cleanup; do not merge. | `gh pr view` state=CLOSED mergedAt=null; no provider-backed checks. | +| 2026-07-25 | cursor/formulation-disabled-accessibility-14d4 | 5be3d6ccccc4c974539612fb6fbbbfb6dfb3d105 | #064 clean replacement PR-readiness review | Current-main clean-room replay contains only the formulation disabled-state improvement, focused browser coverage, and issue disposition. Product/test content is identical to the independently reviewed change; no P0-P2 findings. Conflicted PR #1219 is superseded and must not merge. | Focused Chromium 2/2; `verify:cheap` 3,421/3,421; `verify:pr-local` passed with build, client-secret scan, and offline RAG fixtures; no provider-backed checks. | diff --git a/docs/outstanding-issues.md b/docs/outstanding-issues.md index 0acd9bb2c..5f331193c 100644 --- a/docs/outstanding-issues.md +++ b/docs/outstanding-issues.md @@ -61,7 +61,7 @@ removed after current-main verification; it is not missing recommended work. | 9 | `#029` | A2 | Specialist — answer quality/clinical safety | After `#051`/`#023` and `#018` | 0.5–1 day inventory; 1–3 days per fix | Re-enumerate current fallback stubs and fix one causal cluster at a time without weakening grounding/citation gates. Stop if a change merely makes the metric easier to pass. | | 10 | `#001` | A2 | Specialist — retrieval/ranking | After `#051`/`#023` and rollout approval | 0.5–1 day plus canary | Keep semantic reranking off unless an approved ambiguity comparison preserves 36/36, recall 1.0, zero per-case regressions, and shows measured gain; otherwise record keep-off and stop. | | 11 | `#025` | A2 | Operator — Railway/GitHub/chat/Supabase | Next approved observability window | 1–3 hours/channel | Choose owned deployment, CI, ingestion, and SLO alerts; mock first, then one approved controlled provider event/channel. The merged Supabase trigger remains inert until its verified inputs are configured. Stop without an accountable responder. | -| 12 | `#064` | A2 | High — frontend/browser | After higher-acuity local fixes; before the release UI gate | 4–8 hours | Preserve the isolated dirty formulation/contrast patch, reconcile its intent against current `main`, and run focused Playwright plus `verify:ui`. Stop rather than overwriting unrelated work or weakening access-control assertions. | +| 12 | `#064` | A2 | High — frontend/browser | Clean replacement branch ready for hosted review | Merge only after required CI is green | `cursor/formulation-disabled-accessibility-14d4` contains only the reviewed formulation disabled-state fix, focused Playwright coverage, and consolidated ledger evidence on current `main`; the conflicted PR #1219 must not merge. | | 13 | `#055` | A2 | Specialist release owner + Operator | Before next full-confidence release/handoff | 2–4 hours plus runtime | On one exact SHA, run local/provider gates, Firefox/WebKit, required hosted CI, and close actionable GitHub threads. Stop at first failure and rerun only the repaired smallest gate. | | 14 | `#056` | A2 | Operator — Supabase/Railway + Specialist | After cost/ownership approval | 0.5–1 day | Provision isolated `Clinical KB Staging` with synthetic data and distinct secrets. Verify identity, schema, indexing, health, and data boundary; never copy production clinical documents. | | 15 | `#057` | A2 | High — release/SRE + Operator | After `#056` | 2–4 hours plus soak | Run documented staging soak and rollback against an exact candidate. Retain latency/error/rollback evidence; stop on unsafe data, identity mismatch, or unowned rollback. | @@ -94,7 +94,7 @@ removed after current-main verification; it is not missing recommended work. | ID | Pri | Type | Summary | Detail / next action | Source | Added | | ---- | --- | ----- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | ---------- | | #059 | P1 | task | Verify containment of every credential reported exposed in chat | **Outcome:** every reported exposed credential is rejected or retired. **Next:** in approved security windows, verify and revoke or rotate the GitHub token, OpenAI key, Supabase service-role JWT, database password, and E2E credential; create replacements only when required and update only intended secret stores. **Success:** provider evidence confirms the old credentials cannot authenticate, replacements are distinct and minimally scoped, presence/readiness checks pass, and secret scans remain clean. **Stop:** no provider or secret-store action without approval; never print or paste values into Git, logs, issues, or chat. | session 2026-07-24 security reconciliation; AI Agent Target Manifest | 2026-07-24 | -| #064 | P2 | task | Reconcile the preserved browser and contrast patch | **Outcome:** the isolated dirty formulation/contrast patch is safely landed or explicitly dispositioned. **Next:** rebase its intent against current `main` without overwriting the worktree, then run focused Playwright coverage and `verify:ui`. **Success:** intended disabled/contrast behavior is accessible, browser assertions remain meaningful, and unrelated work is preserved. **Stop:** do not discard or auto-merge the dirty worktree; pause on ambiguous ownership or scope. | `agent/formulation-disabled-contrast`; session 2026-07-24 | 2026-07-24 | +| #064 | P2 | task | Reconcile the preserved browser and contrast patch | **Clean replacement ready 2026-07-25:** commit `70dd8cd9` on `cursor/formulation-disabled-accessibility-14d4` applies the independently reviewed opacity-free disabled styling and native-disabled/focus/axe coverage directly to current `main`. The unavailable historical worktree remains untouched. The earlier PR #1219 passed hosted checks but became GitHub-conflicted after automated branch sync and must not merge. **Next:** open the clean replacement PR and require all hosted checks, especially Production UI, to pass before merge. **Stop:** do not merge #1219, weaken PWA/access-control assertions, or alter the historical worktree. | `agent/formulation-disabled-contrast`; clean replacement `cursor/formulation-disabled-accessibility-14d4`; session 2026-07-25 | 2026-07-24 | | #065 | P2 | task | Complete the paused compact document source-text accordion | **Outcome:** the document viewer uses compact nested disclosures while retaining complete text, citation/search navigation, print behavior, and composer clearance. **Next:** only when the user explicitly resumes, reconcile `codex/chat-document-text-accordion-7cb4` with current `main` and complete the focused 320/390/1280 px tests. **Success:** default disclosures are closed; deep links and search open only the active passage; printing expands/restores state; no overflow. **Verify:** focused document-viewer Playwright, `verify:cheap`, `verify:ui`, and static production-readiness. **Stop:** remain paused until explicit user return; no provider calls. | paused document-viewer task; `codex/chat-document-text-accordion-7cb4` | 2026-07-24 | | #051 | P2 | task | Stabilise the live answer-quality canary before more RAG tuning | Diagnostics landed in PR #1095: structured JSON/Markdown artifacts now record the actual checked-out SHA, run identity and latency context, and the offline trend tool separates content, provider-route and latency outcomes. First validating run `30018289898` recorded the expected tree and cost, with 36/36 retrieval green, but one report cannot establish variability; PR #1097 prevents a single failure being mislabeled as repeated. Next: compare the scheduled 2026-07-26 structured report with this run. Do not spend on an immediate retry or reapply the archived lithium guard before that comparison. | PR #1095; run `30018289898`; PR #1097; archive ref `refs/archive/rejected-rag/20260723/monitoring-subject-gate` | 2026-07-23 | | #001 | P2 | task | Semantic reranking still gated off | `RAG_SEMANTIC_RERANK_ENABLED=false` from PR #901. Do not enable until the provider-backed 36/36 retrieval-quality gate **and** an ambiguity-focused canary are explicitly approved and recorded. | `docs/process-hardening.md` (Semantic reranking rollout debt); PR #901 | 2026-07-21 | diff --git a/src/components/formulation/formulation-builder-page.tsx b/src/components/formulation/formulation-builder-page.tsx index 8728bd205..ae41a4aaf 100644 --- a/src/components/formulation/formulation-builder-page.tsx +++ b/src/components/formulation/formulation-builder-page.tsx @@ -678,7 +678,7 @@ export function FormulationBuilderPage({ type="button" onClick={() => move(-1)} disabled={activeIndex === 0} - className="inline-flex min-h-tap items-center gap-2 rounded-lg border border-[color:var(--border-strong)] bg-[color:var(--surface)] px-4 text-sm font-bold text-[color:var(--text-muted)] disabled:opacity-40" + className="inline-flex min-h-tap items-center gap-2 rounded-lg border border-[color:var(--border-strong)] bg-[color:var(--surface)] px-4 text-sm font-bold text-[color:var(--text-muted)] disabled:cursor-not-allowed disabled:border-[color:var(--border)] disabled:bg-[color:var(--surface-inset)]" > Previous @@ -688,7 +688,7 @@ export function FormulationBuilderPage({ type="button" onClick={() => move(1)} disabled={activeStep === "select" && selectedMechanisms.length === 0} - className="inline-flex min-h-tap items-center gap-2 rounded-lg bg-[color:var(--command)] px-4 text-sm font-bold text-[color:var(--command-contrast)] shadow-[var(--shadow-tight)] disabled:cursor-not-allowed disabled:opacity-45" + className="inline-flex min-h-tap items-center gap-2 rounded-lg border border-transparent bg-[color:var(--command)] px-4 text-sm font-bold text-[color:var(--command-contrast)] shadow-[var(--shadow-tight)] disabled:cursor-not-allowed disabled:border-[color:var(--border)] disabled:bg-[color:var(--surface-inset)] disabled:text-[color:var(--text-muted)] disabled:shadow-none" > {activeStep === "select" ? "Continue to framework" diff --git a/tests/ui-formulation.spec.ts b/tests/ui-formulation.spec.ts index 9ae2f4c7f..1b2f35147 100644 --- a/tests/ui-formulation.spec.ts +++ b/tests/ui-formulation.spec.ts @@ -138,13 +138,42 @@ test("keeps long mobile formulation pages inside the app scrollport", async ({ p expect(geometry.mainScrollHeight).toBeGreaterThan(geometry.mainClientHeight + 40); }); +test("keeps unavailable builder navigation natively disabled without fading its text", async ({ page }, testInfo) => { + await gotoApp(page, "/formulation/builder?template=5Ps"); + + const previousStep = page.getByRole("button", { name: "Previous", exact: true }); + const continueStep = page.getByRole("button", { name: "Continue to framework", exact: true }); + + await expect(previousStep).toBeDisabled(); + await expect(continueStep).toBeDisabled(); + await expect(previousStep).toHaveCSS("opacity", "1"); + await expect(continueStep).toHaveCSS("opacity", "1"); + + const disabledControlsAcceptedFocus = await page.evaluate(() => { + const controls = Array.from(document.querySelectorAll("button:disabled")).filter((button) => + ["Previous", "Continue to framework"].includes(button.textContent?.trim() ?? ""), + ); + return controls.map((control) => { + control.focus(); + return document.activeElement === control; + }); + }); + expect(disabledControlsAcceptedFocus).toEqual([false, false]); + await expectNoBlockingAxeViolations(page, testInfo); +}); + test("moves a selected mechanism through framework, quality review, and an editable draft", async ({ page, }, testInfo) => { await gotoApp(page, "/formulation/builder?mechanism=rumination&template=5Ps"); await expect(page.getByRole("checkbox", { name: /Rumination/ })).toBeChecked(); - await page.getByRole("button", { name: /Continue to framework/ }).click(); + const previousStep = page.getByRole("button", { name: "Previous", exact: true }); + const continueStep = page.getByRole("button", { name: "Continue to framework", exact: true }); + await expect(previousStep).toBeDisabled(); + await expect(previousStep).toHaveCSS("opacity", "1"); + await expect(continueStep).toBeEnabled(); + await continueStep.click(); await expect(page.getByTestId("formulation-builder-structure")).toBeVisible(); const cbtCycle = page.getByRole("radio", { name: /CBT cycle/ }); await page.getByText("CBT cycle", { exact: true }).click(); @@ -173,12 +202,9 @@ 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 }); + // Regression guard for matrix run 4012: the old disabled opacity utility could + // remain stale in WebKit after this button re-enabled, which made axe measure a + // phantom low-contrast enabled state. Keep the live state explicit before scanning. await expect(previousStep).toBeEnabled(); await expect(previousStep).toHaveCSS("opacity", "1"); await expectNoBlockingAxeViolations(page, testInfo);