diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 3d927ab47..1af6de2af 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -623,3 +623,4 @@ Use this ledger to prevent repeated branch and PR reviews when the reviewed HEAD | 2026-07-19 | origin/main 24-hour merged window (d1937d78e..ef042cacd, ~30 PRs incl. #853/#859/#861/#865/#868/#871–#874/#879/#885–#888/#890–#894/#896) | ef042cacd | integrated post-merge regression audit of the full 24h window (code review + design/performance/defect angles) | No P0/P1 regression found at the merged tip. One confirmed P2: the new settings surface (`use-app-preferences.ts` + `settings-dialog.tsx`) presents jurisdiction, population, answer-style, landing, home-content, compact-citations, and all notification preferences as live controls, but no consumer reads them — only density/motion (html attributes + globals.css) and theme are functional; a clinician selecting "Conservative" answer style reasonably but wrongly believes generation changed. One plausible P2/P3: `AuthProvider.initializeSession` now requires a live `getUser()` round-trip, so a transient network failure on load resolves a valid stored session to signed_out (INITIAL_SESSION replay is also skipped); the deliberate stale/tampered-token defense does not distinguish retryable network errors. One P3: `/medications` legacy redirect drops the query string while `/applications` and `/differentials/presentations` preserve theirs. Cleared after inspection: worker image-placement dedupe (key symmetric on both sides), title-word purge/scope migration (matches its replayed review), RPC-layer-only cancellation consolidation (intentional per PR #861), services route-chunk fix intact with no other heavy client value-imports (type-only imports verified), forced-colors ButtonFace/ButtonText flip, codex-autofix/pr-policy workflow changes conform to AGENTS.md (pin retained, rename-aware routing, workflow_sha checkout), audit-metadata allowlist exhaustive over the closed AuditAction union, answer-stream merged abort signals. Environment note (not a repo defect): the session-start hook skips `npm install` when node_modules exists, so this window's dependency bumps left the container stale and `verify:cheap` failed at typecheck until `npm ci`; the hook should compare a lockfile hash. | `npm ci` then typecheck clean and full Vitest 2828 passed / 1 failed / 2 skipped — the sole failure is the long-baselined container-only `tests/pdf-extraction-budget.test.ts` artifact (hosted-CI-green through #826/#835/#872/#890). First `verify:cheap` run passed every static guard (runtime, actions-pin, ci-scope, ci-triage, pr-policy, sitemap, brand, type-scale, icon-scale, function-grants, owner-scope, lint) before the stale-deps typecheck stop. No OpenAI, Supabase, deployment, or provider-backed check ran. | | 2026-07-19 | origin/main foreign merges post-#896 landing (541da7b #871 remediation + f4557ca #892 policy parsing; explicit user review request) | ef042ca6a34d33862936e77e4b71068978382c1e | Bug review of recent main changes (diff-review protocol) | One P2 confirmed and fixed in PR #905: #892's widened heading matcher ended a required PR-body section at ANY next heading, so `###` sub-structure inside `## Verification` truncated the section and false-rejected valid bodies (fail-closed; repo template unaffected; repro via direct evaluatePullRequestPolicy probe old-vs-new). No P0/P1. Cleared after verification: `src/lib/client-env.ts` (no env leakage; production demote-to-demo removal deliberate and fail-safe — upload gating still locked via canUsePrivateApis), applications/medications redirect routes (fixed targets, 307+HEAD alias, no open-redirect/header-injection), test infra (no weakened assertions; route-coverage spec added to all projects), and the three biggest #871 UI diffs read inline (ClinicalDashboard upload tablist roving-tabindex + hydration-safe useSyncExternalStore role switch; visual-evidence unavailable-source rows became real non-interactive elements; favourites library demo-gates prototype items with sound menu keyboard nav). Residual (report-only): pr-policy `section()` remains fence-unaware (headings inside fenced code can satisfy required-section checks — pre-existing class, author-controlled attestation surface); dev-only Turbopack persistent-cache staleness served an old globals.css compile across restarts twice this session (fixed by setting `.next` aside). | Reviewer fan-out: general lane completed with concrete probes (pr-policy self-test, node repro on old parser from f4557ca^, adversarial body probes); UI lane agent lost to session limit and re-done inline on the three biggest diffs, leaning on the merged tree's green gates (verify:ui 236-passed run in this session covers #871's own new specs). No provider-backed checks run. | | 2026-07-19 | claude/clinical-kb-pwa-review-asi3wb (PR #905; commits b2afe66 visuals + 6531178 policy + this ledger follow-up) | 6531178c1a3427dfd58c4bfcf8e29020c5731179 | PWA install/update notice redesign (all breakpoints) + review-follow-up policy fix | Redesigned the five PWA notices (install, update, iOS hint, offline, restored) as glass lux cards: per-type semantic icon tiles, heading-ink titles, corner dismiss buttons, reduced-motion-safe 280ms entrance. Deliberate placement per screen size: phones keep the bottom card above the fixed composer (thumb zone, safe areas); ≥640px floats a 25rem card bottom-right; ≥1280px moves the stack to a top-right toast under the header (same 4.25rem+safe-area offset constant as the mode-menu popover) with the animation direction flipped. Copy, roles, and button names unchanged. Plus the outline-aware pr-policy section parser fix from the same-session review (see the review row above). | Focused vitest pwa-lifecycle.dom 9/9 + pwa-manifest 8/8; `check:pr-policy` self-test green incl. new sub-heading case; `verify:cheap` 2828/2831 (sole fail = known container-only pdf-extraction-budget artifact); `test:e2e:pwa` privacy/offline green (installability fail = known container `in-incognito` artifact); `verify:ui` 236 passed/2 failed (the two long-baselined container artifacts); production build + client-bundle secret scan + bundle budget within tolerance (1293.4 vs 1278.6 KiB baseline); visual evidence at 390/768/1440 light+dark+offline in session scratchpad. Dev caveat recorded: Turbopack persistent `.next` cache served stale globals.css across restarts twice; fixed by setting the cache aside. No provider-backed checks run. | +| 2026-07-19 | claude/audit-recent-changes-kde66i (audit-remediation follow-up to the ef042cacd audit row) | see PR head | remediation of the three 2026-07-19 audit findings | Fixed all three findings from the 24-hour merged-window audit. (1) Inert settings honesty (P2): every preference the app does not yet consume — jurisdiction, population, answer style, landing, both home-content toggles, compact citations, and all three notification toggles — now renders an explicit "Saved for later — not active yet" marker in `settings-dialog.tsx`; the functional appearance/density/motion rows stay unmarked. Wiring the preferences into answer generation was deliberately NOT done here (clinical-behavior change requiring governance review); the new dom test documents the contract for flipping a control live. (2) Auth offline resolution (P2/P3): `resolveInitialAuthState` gains `verificationUnavailable`, set from `isAuthRetryableFetchError(getUser().error)`, so an unreachable auth server keeps the stored session signed in while a reachable server that rejects the token still resolves signed_out; server-side bearer validation is unchanged. (3) `/medications` redirect (P3): now preserves the sanitized q/focus/run search context with the same allowlist as the root legacy-mode redirect. | Focused Vitest 30/30 across `settings-inert-preferences.dom` (new), `private-client-auth` (3 new cases), `audit-navigation-auth-regressions` (query-preservation case), `app-preferences`, and `site-map`. Full `verify:cheap` run recorded on the PR. No OpenAI, Supabase, deployment, or provider-backed check ran. | diff --git a/src/app/medications/route.ts b/src/app/medications/route.ts index 5ebeac9a8..e9284566a 100644 --- a/src/app/medications/route.ts +++ b/src/app/medications/route.ts @@ -1,7 +1,17 @@ -export function GET() { +import { type NextRequest } from "next/server"; + +// Legacy prescribing home. Preserve the search context the same way the root +// legacy-mode redirect does (q plus the focus/run flags, sanitized) so a +// bookmarked /medications?q=lithium link keeps its query after the redirect. +export function GET(request: NextRequest) { + const params = new URLSearchParams({ mode: "prescribing" }); + const query = request.nextUrl.searchParams.get("q")?.trim(); + if (query) params.set("q", query); + if (request.nextUrl.searchParams.get("focus") === "1") params.set("focus", "1"); + if (request.nextUrl.searchParams.get("run") === "1") params.set("run", "1"); return new Response(null, { status: 307, - headers: { Location: "/?mode=prescribing" }, + headers: { Location: `/?${params.toString()}` }, }); } diff --git a/src/components/clinical-dashboard/settings-dialog.tsx b/src/components/clinical-dashboard/settings-dialog.tsx index 720633e96..744211e87 100644 --- a/src/components/clinical-dashboard/settings-dialog.tsx +++ b/src/components/clinical-dashboard/settings-dialog.tsx @@ -517,10 +517,12 @@ export function SettingsDialog({ icon={Globe2} label="Jurisdiction" description="Prioritises guidance relevant to your region." + notYetActive htmlFor="settings-jurisdiction" > setPreference("jurisdiction", value)} options={JURISDICTION_OPTIONS} @@ -530,10 +532,12 @@ export function SettingsDialog({ icon={CircleUserRound} label="Default population" description="Frames answers for your usual patient group." + notYetActive htmlFor="settings-population" > setPreference("population", value)} options={POPULATION_OPTIONS} @@ -545,11 +549,13 @@ export function SettingsDialog({ description={ ANSWER_STYLE_OPTIONS.find((option) => option.value === preferences.answerStyle)?.description } + notYetActive labelId="settings-answer-style-label" stacked > setPreference("answerStyle", value)} options={ANSWER_STYLE_OPTIONS} @@ -593,11 +599,13 @@ export function SettingsDialog({ icon={PanelTop} label="Default landing view" description="The mode shown when you open the app." + notYetActive labelId="settings-landing-label" stacked > setPreference("landing", value)} options={LANDING_OPTIONS} @@ -618,6 +626,7 @@ export function SettingsDialog({ + Saved for later — not active yet + + ); +} + function SettingsField({ icon, label, @@ -788,6 +825,7 @@ function SettingsField({ htmlFor, labelId, stacked = false, + notYetActive = false, children, }: { icon: LucideIcon; @@ -797,6 +835,7 @@ function SettingsField({ htmlFor?: string; labelId?: string; stacked?: boolean; + notYetActive?: boolean; children?: ReactNode; }) { const LabelTag = htmlFor ? "label" : "span"; @@ -821,6 +860,9 @@ function SettingsField({ {description ? (

{description}

) : null} + {notYetActive ? ( + + ) : null} {children ? ( @@ -839,16 +881,19 @@ function SegmentedControl({ onChange, options, ariaLabelledBy, + ariaDescribedBy, }: { value: T; onChange: (value: T) => void; options: ReadonlyArray<{ value: T; label: string; icon?: LucideIcon }>; ariaLabelledBy?: string; + ariaDescribedBy?: string; }) { return (
({ value, onChange, options, + describedBy, }: { id: string; value: T; onChange: (value: T) => void; options: ReadonlyArray<{ value: T; label: string }>; + describedBy?: string; }) { return (