diff --git a/docs/branch-review-ledger.md b/docs/branch-review-ledger.md index 5b23af9e86..748dfc7c51 100644 --- a/docs/branch-review-ledger.md +++ b/docs/branch-review-ledger.md @@ -1138,6 +1138,11 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-07-28 | PR #1291 / `claude/issues-upload-limit-sync-123366` | `1607558188283d3497683f1067835d96f1031d3c` | CI babysit + merge conflict + Bugbot | FIXED. GitHub CONFLICTING/DIRTY was a real content conflict in `docs/outstanding-issues.md`: main had claimed `#084` for completed per-result grading evidence, colliding with this PR's upload-limit capture. Resolved by keeping main's ledger, renumbering the upload-limit recommendation to `#085`, and bumping `issues:next-id` to `086`. Synced again when main advanced with #1300. CodeRabbit date thread already resolved. Bugbot: zero `cursor[bot]` findings. Required CI green (PR required SUCCESS). | merge-tree CLEAN; prettier + docs:check-links PASS; hosted Change scope/Static/PR required SUCCESS; no provider-backed checks. | | 2026-07-28 | PR #1291 / `claude/issues-upload-limit-sync-123366` | `af140d11d5ca23dee0d8705d9933db967fc8c404` | Babysit closeout tip | Supersedes prior #1291 row at `16075581` after appending the conflict/Bugbot ledger record. Product delta vs main unchanged: `#085` upload-limit capture only. merge-tree CLEAN; awaiting exact-head required checks. | ledger append + check:branch-review-ledger PASS; prior tip hosted PR required SUCCESS. | | 2026-07-28 | PR #1302 / `claude/maturity-ledger-entry` | `64da2c1b34ae101590b8676af12ec6b49c14f0ad` | CI/conflict babysit + Codex threads + Bugbot | FIXED. Real content conflict with main: `#085` already claimed by upload-limit rec (#1291). Merged origin/main; renumbered maturity backlog to `#086`, bumped `issues:next-id` to `087`, added recommended-queue order 29 with go-ahead/RAG/provider stop rules. X7/M1 work orders arrived via main #1299. Codex P2 threads replied + resolved. Bugbot: zero cursor[bot] findings. CircleCI stub from main clears prior "no configuration" status error. | merge-tree CLEAN; prettier + docs:check-links + docs:check-scripts PASS; awaiting exact-head hosted CI; no provider-backed checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | `cdde82e087ed3e7ad6be3662b1b96aa55462cae5` | CI babysit + Bugbot + main sync | FIXED. Required CI was already green on prior tip; branch was BEHIND (merge-tree CLEAN). Merged origin/main. Bugbot P2s fixed: wired shimmer-sweep into `.animate-skeleton-shimmer::after`, applied `.stagger-item` on AnswerSkeleton, knob uses `var(--ease-spring)`. ISSUE-02/05 reduced-motion halt retained. 0 review threads to resolve. | focused vitest 34/34; tsc --noEmit PASS; merge-tree CLEAN; no provider-backed checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | `9154d6ef` | CI Build flake fix | FIXED. Hosted Build failed when a runner reported 7.8 GiB via `os.totalmem` and hit the local Docker RAM floor in `guard-next-build.mjs`. Gate now skips under `CI`/`GITHUB_ACTIONS` (local protection retained). Prior tip motion/Bugbot fixes unchanged. | CI=true guard exit 0; awaiting exact-head hosted Build; no provider checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | tip after container RAM-gate fix | CI container Build flake | FIXED. `Container images / app-image` failed because Dockerfile `npm run build` hit the same local RAM floor (7.8 GiB, no CI env in the build stage). Guard now also skips for `CI=1` and `/.dockerenv`; Dockerfile sets `ENV CI=1`. | awaiting exact-head hosted container + PR required; no provider checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | `2ab54c8ac4a062d15051d604e04e585a446f363c` | CI container RAM-gate closeout | SUPERSEDES prior #1297 container row with placeholder tip. Tip includes motion wiring + CI/GITHUB_ACTIONS + `/.dockerenv` RAM-floor skips + Dockerfile `ENV CI=1`. | awaiting exact-head hosted Build/container/PR required; no provider checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | `b1318a4b80a3fa4b29e3de05150cf04d3aaf6525` | CI retrigger after prettier | Tip includes motion wiring + RAM-floor CI/container skips + prettier on guard-next-build. Prior Static failure was prettier-only on superseded tip `2a07c109`. Hosted pull_request CI failed to schedule on intermediate tips while a long Production UI job held the concurrency slot. | local focused vitest/tsc earlier PASS; awaiting exact-head hosted CI. | | 2026-07-28 | PR #1304 / `fix-test-run-lock` | `352eedfeb4bcec2665201188c1113fceab7d565d` | CI/conflict babysit + Bugbot | FIXED. Real content conflicts vs main (9 files). Merged origin/main; took main for superseded test-run-lock rewrite (lease/heartbeat already present), phone chrome CSS/tests, document-top-navigation mockups, ui-primitives forced-colors, and ultra-review prompts. Kept knip.json cleanup removing unused `ignoreDependencies: ["tailwindcss"]` (only unique product delta). Bugbot: zero `cursor[bot]` findings; 0 review threads. Local `verify:cheap` PASS (4114 tests). Hosted CI re-running on merge tip; mergeable=MERGEABLE. | merge-tree then manual resolve; verify:cheap PASS; Bugbot triage; no provider-backed checks. | | 2026-07-28 | PR #1304 / `fix-test-run-lock` | `6300b0218b2911fcdd6bc09c51db34dc9bed765b` | Babysit closeout tip | MERGE-READY for knip-only product delta. Supersedes prior #1304 row at `352eedfe` after ledger append. Hosted PR required SUCCESS on exact tip; mergeable=MERGEABLE; 0 review threads; Bugbot empty. Unique vs main: knip.json drops unused `ignoreDependencies: ["tailwindcss"]`. Original test-run-lock/phone-chrome work superseded by main during conflict resolve. | Hosted CI run 30328907503 PR required SUCCESS; verify:cheap PASS earlier; no provider-backed checks. | | 2026-07-28 | PR #1305 / `execute-audit-remediation-fixes` | `7662c94cf85fac19925debb567035c2e3717a20f` | pr-bugbot proactive (zero cursor[bot] Bugbot threads) | FIXED P1 phone-chrome regression: reverted ClinicalDashboard `@container`/`@max-sm:fixed`/`@md:` migration to viewport `sm:`/`md:`/`max-sm:` (search-chrome #20 + reserve contracts). FIXED P2 privacy notice stacking: restored `z-[5]` and allowed rung 5 in z-index ladder. Validated merge-sensitive: trustGatedAnswer clears `answer:""`; upload has no `canonicalAuthority`; SettingsStateProvider wired; no conflict markers. | Vitest chrome/clinical 31/31; eslint touched files clean; no provider-backed checks. | @@ -1153,8 +1158,13 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-07-28 | PR #1307 / `claude/rag-019-fallback-guard` | `2e5edfc6c9fcc0bb75674b179607888bbf872b91` | PR policy body synced + template removed | Sync PR policy body SUCCESS applied Clinical Governance Preflight (7/7). Deleted temporary `PR_POLICY_BODY.md`. Awaiting Build/PR policy/PR required on this tip. | Hosted Sync SUCCESS; no provider checks. | | 2026-07-28 | PR #1294 / `execute-typography-fixes-clean-2` | `a0df13f45cffb769b852e55bc44b6891b7fd80e7` | Main conflict resolve + CodeRabbit | FIXED. Merged #1307; took main ALLOW_LOW_RAM_BUILD RAM-guard (dropped DOCKER_BUILD approach). Tightened answer-evidence heading contract to component-scoped bodies (rejects sibling h2). Codex P2 already resolved. | Vitest heading+guard 4/4; merge-tree CLEAN; no provider-backed checks. | | 2026-07-28 | PR #1309 / `claude/gates-skill` | `7dfe103bfa408052c9e899211b8373c7ccb708d3` | Conflict sync + Codex/CodeRabbit + Bugbot | FIXED. GitHub CONFLICTING/DIRTY was main-staleness only (`merge-tree` clean); merged `origin/main`. Codex P2: skill wrongly claimed `verify:ui` exits 0 under heavy-lock contention — corrected to 15m queue then exit 1 via `run-playwright.mjs`; mirrored in AGENTS.md. CodeRabbit: marked `${PIPESTATUS[0]}` as Bash-specific. Bugbot: zero `cursor[bot]` findings; confirmed same P2. No CI failures on prior tip. | `prettier --check` PASS; `docs:check-links` 1287 PASS; no provider-backed checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | `c5a145ddbcf66ebcb4c32acf75c6b9588e253b2a` | CI babysit + Bugbot + conflict closeout | READY. Merged origin/main; resolved real Dockerfile/guard-next-build conflicts by adopting main's evaluateNextBuildRamGuard + ALLOW_LOW_RAM_BUILD. Motion fixes retained: ISSUE-02/05, IMP-01 spring token, IMP-04 shimmer ::after wiring, IMP-02 AnswerSkeleton stagger. Bugbot re-review: no remaining P0/P1/P2. 0 review threads. Hosted PR required SUCCESS. | focused vitest earlier; guard-next-build Vitest 3/3; hosted Build/Static/Unit/Production UI/PR required SUCCESS; no provider checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | `68b3d1de343ef1164d389925a9f45d4dc1106de2` | Review-thread disposition | RESOLVED Codex P2 (shimmer already wired) + CodeRabbit reduced-motion shimmer kill (explicit `animation: none` on `::after`). Threads replied + resolved. | prior tip PR required SUCCESS; awaiting exact-head recheck; no provider checks. | | 2026-07-28 | PR #1294 / `execute-typography-fixes-clean-2` | `62ddd24dc8ad223cde67373ea35e18aee6065057` | CI green closeout | APPROVE. Hosted Production UI + PR required PASS on product tip `e5543dc6`. Codex/CodeRabbit threads resolved (0 open). Unique delta: diagnosis-detail S: locator + heading hierarchy contract. RAM-guard owned by main #1307. | Hosted Static/Unit/Safety/Advisory/Production UI/PR required PASS; Build/Container skipped (unchanged); Bugbot clean; no provider-backed checks. | | 2026-07-28 | PR #1294 / `execute-typography-fixes-clean-2` | `f64fa84a8010008917619c16aac79e4f172a70ff` | Ledger dedupe after main sync | Removed 2 exact duplicate #1307 rows introduced by merge=union during main sync (kept first copies). Hosted required checks green on prior product tip `e5543dc6`; this tip is ledger hygiene + docs-only main sync. | `check:branch-review-ledger` PASS after dedupe; no provider-backed checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | tip-after-ledger-dedupe | Ledger hygiene | Dropped 1 exact-duplicate #1306 row introduced by ledger merge=union during main sync; first copy retained. | check:branch-review-ledger PASS; no provider checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | `9997ac9944e7e67135570cd86cc056da5d84aa0a` | Ledger hygiene closeout | SUPERSEDES prior placeholder tip row. Dropped 1 exact-duplicate #1306 record from merge=union; motion/a11y product tip unchanged (`68b3d1de` + main sync). | check:branch-review-ledger PASS; awaiting exact-head hosted CI; no provider checks. | +| 2026-07-28 | PR #1297 / `motion-audit-fixes-clean` | `8ef0c1b2d63451c51e8886e8ea076aad56498576` | CI babysit + Bugbot + review closeout | READY. Motion audit complete (ISSUE-02/05, IMP-01/02/04); reduced-motion shimmer kill; overlap gotoHome flake hardened; main synced; RAM-guard conflicts adopted main's ALLOW_LOW_RAM_BUILD. Codex + CodeRabbit threads resolved. Hosted PR required SUCCESS. | Hosted Build/Static/Unit/Advisory/Production UI/PR required SUCCESS; Bugbot clean; no provider checks. | | 2026-07-28 | PR #1305 / `execute-audit-remediation-fixes` | `4141ca3a737cdea51fe948f6e03599d3755930fa` | Ledger dedupe + CodeRabbit thread closeout | FIXED Static PR ledger guard: removed 1 exact-duplicate #1306 row from merge=union. CodeRabbit autofix threads (outstanding-issues row, z-index matcher, CardTitle ref, OverlayProvider deps) replied and resolved; OverlayProvider context value memoized. Adopted main RAM-guard. | check:branch-review-ledger PASS; Build/Unit green on prior tip; no provider-backed checks. | | 2026-07-28 | PR #1310 / claude/branch-review-ledger-fixes-42575f | 7c870c139211a419fe8b4dfacae3195a7a7caa2b | PR babysit: CI + Codex P2s + Bugbot-equivalent | Hosted PR required SUCCESS on 7c870c13. Fixed 3 Codex P2s (exact scope match, supersede mints distinct scope, verify full SHAs via git rev-parse) plus n/a-embedded hex and parenthetical ref-token false matches. 3 review threads replied+resolved. Mergeable; 0 behind main. Hosted Cursor Bugbot check not produced — bot-authored bugbot run/cursor review comments ignored; local Bugbot-style review done and defects fixed. | check:branch-review-ledger PASS; vitest repo-hygiene 25/25; lint; typecheck; full vitest 4133 pass; hosted Static/Unit/Build/Safety/PR-required SUCCESS. No provider-backed gates. | | 2026-07-28 | PR #1305 / execute-audit-remediation-fixes | 71ed5083616acff5b820069f2fd9835eab92ec88 | CI/conflict babysit + Bugbot + ledger merge residue | FIXED. Merged origin/main (#1310 ledger repair); converted 4 merge-residue heading records into table form (unique 2026-07-26 review kept as six-cell row). PR MERGEABLE + PR required PASS on this tip. Unresolved review threads 0. Bugbot-equivalent product-diff review: no P0/P1/P2; @cursor review requested. Trust gating + upload fail-closed + SettingsStateProvider + phone chrome viewport breakpoints retained. | check:branch-review-ledger PASS; focused vitest 41/41; hosted PR policy/Static/Build/Unit/Safety/Advisory/Production UI/PR required PASS; no provider-backed checks. | @@ -1162,6 +1172,7 @@ Records before 2026-07-28 were written by hand and had drifted: 146 lines carrie | 2026-07-28 | PR #1304 / `fix-test-run-lock` | `7cc32c053c752bef19f3de408a1376428e54af74` | CI babysit: sync main | FIXED. GitHub CONFLICTING/DIRTY was staleness only (`git merge-tree` CLEAN; 7 behind). Merged origin/main. Required CI was already SUCCESS on prior tip `e6b826ed`; no product conflicts. Unique vs main remains knip.json (+ ledger). Bugbot/review threads: none unresolved. | merge-tree CLEAN; merge origin/main; no provider-backed checks. | | 2026-07-28 | PR #1310 / `claude/branch-review-ledger-fixes-42575f` (merged) | 422e43d86a69c88368454065c2b117f5982a43d6 | prlanded | LANDED as squash 422e43d86. Ledger repair + lookup/append tooling + hardened guard all present on main; guard PASS at 1107 records and repo-hygiene 25/25. Review improved the branch before merge and main is ahead of the authoring branch: findReviews now compares scope exactly (the original substring match would have let a branch-cleanup-deletion-pending row satisfy a branch-cleanup lookup and skip a branch that still needed cleanup), refTokens no longer false-hits on bare parenthetical prose, headMatches accepts an annotated 'sha (squash)' cell and rejects 'n/a - see ', and resolveHead now verifies full-length hex so a mistyped 40-char string cannot become an unmatchable HEAD. Authoring branch was deleted at merge; its unpushed local ledger-record commit was superseded by this row rather than pushed. | npm run check:branch-review-ledger PASS (1107 records) and vitest tests/repo-hygiene.test.ts 25/25 PASS, both run against origin/main after the merge. Pre-merge npm run verify:pr-local PASS on the merged tree (405 files / 4126 tests, build 3.7min). No provider-backed checks run. | | 2026-07-28 | PR #1305 / execute-audit-remediation-fixes | b101b69631edfe51bcbbc8f6c07e47157fe2c4e8 | CI green closeout after main re-sync | APPROVE for merge by human. Hosted PR required + Production UI PASS on tip after merging origin/main (#1320). MERGEABLE. Unresolved review threads 0. Bugbot-equivalent: no P0/P1/P2 on unique product delta; @cursor review requested. Product delta retained: clinical-notes trust gating answer wipe, SettingsStateProvider wiring, z-index ladder, OverlayProvider/card fixes, phone chrome viewport breakpoints. | Hosted PR policy/Static/Build/Unit/Safety/Advisory/Production UI/PR required PASS on b101b696; check:branch-review-ledger PASS; no provider-backed checks. | +| 2026-07-28 | PR-1297 | 5c7c4ff8cc92a3af1cd0a65898067272f332809e | PR #1297 full diff vs origin/main | APPROVE after main sync; no high-confidence P0-P2 defects | Local diff review and merge-tree clean; prior exact-head PR required, build, unit coverage, and Production UI passed; new exact-head CI pending | | 2026-07-28 | PR #1304 / `fix-test-run-lock` | `2abe39506f9f0ceaf8cf638bfa2b6dc37dc0ed2c` | CI babysit: resync main | FIXED. After green PR required on `f7d86c27`, main advanced by 1 commit (#1305); GitHub DIRTY again but `git merge-tree` CLEAN. Merged origin/main. No product conflicts; unique delta still knip.json (+ ledger). | merge-tree CLEAN; hosted PR required SUCCESS on prior tip; no provider-backed checks. | | 2026-07-28 | PR #1304 / `fix-test-run-lock` | `463e5c0adc77fe722e20376666f5991db3e288d9` | CI babysit closeout | MERGE-READY. Hosted PR required SUCCESS on exact tip; mergeable=MERGEABLE; 0 behind main; merge-tree CLEAN. Unique product delta: knip.json removes unused tailwindcss ignoreDependencies. Prior GitHub DIRTY labels during babysit were main-churn only. | Hosted CI run success on 463e5c0a; no provider-backed checks. | | 2026-07-28 | PR #1304 / fix-test-run-lock | 7cc32c053c752bef19f3de408a1376428e54af74 | CI babysit: sync main | SUPERSEDED (documenting stale-CI ledger error). Prior row for this HEAD incorrectly treated hosted required-CI SUCCESS on earlier tip e6b826ed9150f312c2e7a957f715019e73a7f0be as verification of this later merge commit 7cc32c05. No hosted required-CI result exists for this exact SHA. This ref has since advanced; the later row at 463e5c0adc77fe722e20376666f5991db3e288d9 recorded exact-tip hosted CI SUCCESS, so this commit's status is historical/superseded. | No hosted CI run on this exact HEAD; prior row reused results from e6b826ed; corrective ledger entry only. | diff --git a/src/app/globals.css b/src/app/globals.css index 8b3690130c..bbcf0ca323 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2481,11 +2481,31 @@ html[data-motion="reduced"] .pwa-notice-card { right: -10px; } - /* Premium Skeleton Shimmer animation with custom easing and GPU compositing layer hints */ + /* Premium skeleton shimmer: GPU translateX sweep on ::after (IMP-04). + The highlight rides the compositor; the host keeps a solid fill so call sites + only need background colour + this class. Entrance stagger can still use the + host `animation` via .stagger-item without fighting the sweep. */ .animate-skeleton-shimmer { - will-change: transform, opacity; + position: relative; + overflow: hidden; + isolation: isolate; transform: translateZ(0); - animation: skeleton-pulse 2s cubic-bezier(0.4, 0, 0.2, 1) infinite; + } + + .animate-skeleton-shimmer::after { + content: ""; + position: absolute; + inset: 0; + transform: translateX(-100%); + background: linear-gradient( + 100deg, + transparent 20%, + color-mix(in srgb, var(--surface-highlight) 55%, transparent) 50%, + transparent 80% + ); + will-change: transform; + animation: shimmer-sweep 1.4s linear infinite; + pointer-events: none; } /* Premium Double-Ring Focus style */ @@ -2590,16 +2610,45 @@ html[data-motion="reduced"] .pwa-notice-card { } } -@keyframes skeleton-pulse { - 0%, +/* IMP-04: Compositor-thread sweep used by .animate-skeleton-shimmer::after. */ +@keyframes shimmer-sweep { 100% { + transform: translateX(100%); + } +} + +/* IMP-02: Staggered cascade entrance for search results and card grids. + Apply .stagger-item to each list child and set --stagger-index via inline style + or a :nth-child selector to sequence each card entrance at 35ms intervals. + Reduced-motion guard stops the animation while keeping items fully visible. */ +@keyframes cascade-fade-up { + from { + opacity: 0; + transform: translateY(6px); + } + to { opacity: 1; + transform: translateY(0); } - 50% { - opacity: 0.35; +} + +.stagger-item { + animation: cascade-fade-up 200ms var(--ease-out-soft) both; + animation-delay: calc(var(--stagger-index, 0) * 35ms); +} + +@media (prefers-reduced-motion: reduce) { + .animate-skeleton-shimmer::after, + .stagger-item { + animation: none !important; } } +html[data-motion="reduced"] .animate-skeleton-shimmer::after, +html[data-motion="reduced"] .stagger-item { + animation: none !important; +} + /* User preference and alternate output modes */ @media (prefers-reduced-motion: reduce) { *, diff --git a/src/components/clinical-dashboard/answer-status.tsx b/src/components/clinical-dashboard/answer-status.tsx index 2e616ac7b6..c318073b96 100644 --- a/src/components/clinical-dashboard/answer-status.tsx +++ b/src/components/clinical-dashboard/answer-status.tsx @@ -1,5 +1,6 @@ "use client"; +import type { CSSProperties } from "react"; import { Check, Circle, @@ -114,6 +115,15 @@ export function AnswerEmptyState({ ); } +function skeletonBar(className: string, staggerIndex: number) { + return ( +
+ ); +} + export function AnswerSkeleton() { // role=status (matching LoadingPanel) so the initial answer-pending window — // after submit but before the first progress event — is announced. Without it @@ -122,24 +132,24 @@ export function AnswerSkeleton() { return (
-
-
-
+ {skeletonBar("h-4 w-10/12", 0)} + {skeletonBar("h-4 w-full", 1)} + {skeletonBar("h-4 w-8/12", 2)}
-
-
+ {skeletonBar("h-3 w-24", 3)} + {skeletonBar("h-4 w-48 max-w-full", 4)}
-
+ {skeletonBar("h-tap w-20 rounded-lg", 5)}
-
-
+ {skeletonBar("h-tap w-48 rounded-lg", 6)} + {skeletonBar("h-tap w-40 rounded-lg", 7)}
-
-
+ {skeletonBar("h-28 rounded-lg", 8)} + {skeletonBar("hidden h-28 rounded-lg sm:block", 9)}
{answerLoading.ariaLabel}
diff --git a/src/components/therapy-compass/therapy-compass.css b/src/components/therapy-compass/therapy-compass.css index 0059856c7a..1a9dcd4257 100644 --- a/src/components/therapy-compass/therapy-compass.css +++ b/src/components/therapy-compass/therapy-compass.css @@ -889,9 +889,10 @@ box-shadow: var(--shadow-tight); /* Animate the horizontal move on the compositor (transform) rather than `left`, which forces layout each frame. The 18px offset matches the knob width so the - resting/active positions are visually identical to the old left-based values. */ + resting/active positions are visually identical to the old left-based values. + IMP-01: spring token for a tactile toggle without hardcoding the bezier. */ transform: translate(0, -50%); - transition: transform 150ms ease; + transition: transform 150ms var(--ease-spring); } .tc-clinician-knob.tc-is-active { @@ -930,8 +931,11 @@ } @media (prefers-reduced-motion: reduce) { + /* ISSUE-02: Halt rotational animation completely — slowing it still triggers + vestibular distress for motion-sensitive users. Replace with a static fade. */ .tc-root .tc-spin { - animation-duration: 2s; + animation: none !important; + opacity: 0.6; } .tc-root * { @@ -939,6 +943,20 @@ } } +/* ISSUE-05: Mirror the OS-level prefers-reduced-motion gate to the in-app + preference toggle (html[data-motion="reduced"]) so users who set "Reduce motion" + inside application settings get the same behaviour even when their OS motion + preference is enabled. globals.css already sets this attribute via the settings + dialog; therapy-compass.css must honour it in its own scope. */ +html[data-motion="reduced"] .tc-root .tc-spin { + animation: none !important; + opacity: 0.6; +} + +html[data-motion="reduced"] .tc-root * { + transition-duration: 0.001ms !important; +} + @media (max-width: 640px) { .tc-root .tc-main { padding: 20px 16px 32px !important; diff --git a/tests/ui-overlap.spec.ts b/tests/ui-overlap.spec.ts index 46a323e256..439ce50772 100644 --- a/tests/ui-overlap.spec.ts +++ b/tests/ui-overlap.spec.ts @@ -67,12 +67,15 @@ async function gotoHome(page: Page) { // shells (and two header#search nodes), which trips Playwright strict mode. await page.goto("/?mode=answer", { waitUntil: "domcontentloaded" }); // Wait until React settles on a single header. During client remount / - // hydration a second transient header#search can exist briefly and trip - // Playwright strict mode even though the stable tree has only one banner. - // Permanent double-render still fails toHaveCount(1). - const header = page.locator("header#search"); - await expect(header).toHaveCount(1, { timeout: 30_000 }); - await header.waitFor({ state: "visible", timeout: 30_000 }); + // hydration a second transient header#search can exist briefly; checking + // count then immediately calling waitFor races that flicker into a strict-mode + // violation. Retry count+visibility together so permanent double-render still + // fails while transient remounts can settle. + await expect(async () => { + const header = page.locator("header#search"); + await expect(header).toHaveCount(1); + await expect(header).toBeVisible(); + }).toPass({ timeout: 30_000 }); await page.getByRole("button", { name: "Open answer options" }).waitFor({ state: "visible", timeout: 30_000 }); }