Skip to content

fix(viewer): Phase 0 gesture INP, preview a11y, image decode - #1660

Merged
BigSimmo merged 20 commits into
mainfrom
cursor/viewer-phase0-gesture-a11y-1db8
Aug 7, 2026
Merged

fix(viewer): Phase 0 gesture INP, preview a11y, image decode#1660
BigSimmo merged 20 commits into
mainfrom
cursor/viewer-phase0-gesture-a11y-1db8

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Summary

Verification

  • npx vitest run on Phase 0 files — 21 passed
  • npm run test5450 passed | 1 skipped (515 files)
  • npm run typecheck — exit 0
  • npm run verify:pr-local -- --files <phase0 paths> — exit 0 (format, docs guards, lint, typecheck, build/client-bundle scan, RAG fixtures)
  • UI verification not run: no phone-chrome ownership / composer / reserve changes; gesture/a11y unit+DOM proof is the Phase 0 gate. Manual INP spot-check still recommended on a real device/Chrome Performance panel.
  • Release verification not run: not a release handoff

Risk and rollout

  • Risk: Clinical document preview surface (source rendering a11y + gesture path). Behaviour change is conservative: scroll stays native; zoom still works; failures become audible.
  • Rollback: Revert the branch commits.
  • Provider or production effects: None

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed — no CDS behaviour change; preview chrome/a11y/gesture only

Notes

  • Next: Phase 1 shell extraction from DocumentViewer.tsx, then Phase 2 unified DocumentFrame / photo chrome.
  • Flightplan evidence: .local/workflow-evidence/2026-08-06T17-33-14-225Z-flightplan.json (classified ui; treated as clinical document surface + privacy-adjacent for governance).
Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Added priority loading for important images to display them faster.
    • Improved PDF and image preview error announcements for assistive technologies.
    • Enhanced document viewer zoom behavior for smoother trackpad and modified-wheel interactions.
  • Bug Fixes

    • Improved image decoding and preview failure handling.
    • Prevented hidden document-viewer elements from interfering with visible content.
  • Documentation

    • Updated the recommended execution queue and archived resolved items.

cursoragent and others added 4 commits August 6, 2026 17:37
Make PDF wheel listeners passive by default and attach non-passive only
for unmodified wheel-zoom or active Ctrl/⌘ pinch (#214). Announce
canvas/non-PDF preview failures with role=alert and LiveAnnouncer (#219).
Add lightbox decoding=async and SignedImage priority for above-fold loads
(partial #215).

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Archive gesture INP and preview-announce ledger rows; note partial #215
progress for lightbox decode and SignedImage priority.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Close the order gap so the recommended execution queue no longer points
at resolved viewer gesture/a11y work.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Avoid assigning read-only observer fields in the jsdom mock so
typecheck stays green under the Phase 0 priority coverage.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
@supabase

supabase Bot commented Aug 6, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy because there are no changes detected in supabase directory. You can change this behaviour in Project Integrations Settings ↗︎.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 12 minutes

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 73a31bc5-76a5-43c9-a469-7829dbeae2ad

📥 Commits

Reviewing files that changed from the base of the PR and between c814d03 and 810cfc9.

📒 Files selected for processing (1)
  • docs/outstanding-issues.md
📝 Walkthrough

Walkthrough

The pull request updates image loading, document preview accessibility, wheel gesture listeners, related tests, smoke-test locators, and the outstanding-issues ledger.

Changes

Viewer updates

Layer / File(s) Summary
Image loading behavior
src/components/clinical-dashboard/image-lightbox.tsx, src/components/clinical-dashboard/signed-image.tsx, tests/signed-image.dom.test.tsx
Lightbox images use asynchronous decoding. SignedImage supports priority loading and bypasses IntersectionObserver deferral when enabled.
Preview error announcements
src/components/document-viewer/non-pdf-source-preview.tsx, src/components/document-viewer/pdf-canvas-viewer.tsx, tests/document-frame-contract.test.ts, tests/document-viewer-non-pdf-preview.dom.test.tsx
Preview failures expose data-preview-error="true". PDF errors use assertive announcements deduplicated by URL and error text. Tests verify the markers, visible fallback, and announcement behavior.
Wheel gesture listener handling
src/components/document-viewer/use-viewer-gestures.ts, tests/use-viewer-gestures.dom.test.tsx
Wheel listeners are passive by default and become non-passive for modified zoom gestures. Cleanup occurs after inactivity, key release, and window blur.
Viewer locator validation
tests/ui-smoke.spec.ts
Smoke-test locators select visible document frames when hidden streaming clones are present.
Issue ledger updates
docs/outstanding-issues.md
The execution queue, item #215, and resolved records for items #214 and #219 are updated.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant useViewerGestures
  participant PassiveWheelListener
  participant NonPassiveWheelListener
  User->>PassiveWheelListener: Send ordinary wheel event
  PassiveWheelListener->>useViewerGestures: Handle unmodified scrolling
  User->>useViewerGestures: Send Ctrl/⌘ wheel or pinch event
  useViewerGestures->>NonPassiveWheelListener: Promote listener for blocking zoom
  NonPassiveWheelListener->>useViewerGestures: Prevent default and calculate zoom
Loading

Possibly related PRs

Suggested reviewers: claude

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: gesture INP, preview accessibility, and image decoding improvements.
Description check ✅ Passed The description includes the required summary, verification, risk and rollout, governance, and notes sections with clear completion or non-applicability details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/viewer-phase0-gesture-a11y-1db8

Comment @coderabbitai help to get the list of available commands.

@BigSimmo
BigSimmo marked this pull request as ready for review August 6, 2026 18:07
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

Comment thread src/components/document-viewer/use-viewer-gestures.ts Outdated
@BigSimmo
BigSimmo enabled auto-merge (squash) August 6, 2026 18:12

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/clinical-dashboard/signed-image.tsx (1)

35-61: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update shouldLoad when priority changes.

A mounted SignedImage does not start loading when priority changes from false to true. The current consumer derives priority from answer state, so this transition can occur during rerenders. Add a rerender test and make the load gate respond to the current prop, or document mount-only behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/clinical-dashboard/signed-image.tsx` around lines 35 - 61, The
shouldLoad state in SignedImage is initialized from priority but does not react
when that prop changes. Update the SignedImage load gate so a false-to-true
priority transition sets shouldLoad to true, and add a rerender test in
tests/signed-image.dom.test.tsx covering that transition.

Source: MCP tools

🧹 Nitpick comments (5)
src/components/clinical-dashboard/signed-image.tsx (1)

166-166: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Map priority to the supported Next.js 16 image API.

The repository uses Next.js 16.2.12. Next.js 16 deprecates the next/image priority prop in favor of preload; this line still passes the deprecated prop. Keep SignedImage.priority as the public contract if needed, but map it to the supported loading API that fits a client-resolved signed URL. Verify the installed Next.js documentation before merge. (nextjs.org)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/clinical-dashboard/signed-image.tsx` at line 166, Update the
Image props in SignedImage to map the public SignedImage.priority value to
Next.js 16’s supported preload API, removing the deprecated priority prop.
Preserve the existing behavior for client-resolved signed URLs and keep the
public priority contract unchanged.

Source: MCP tools

tests/signed-image.dom.test.tsx (2)

90-90: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the priority prop update path.

The new test covers only priority={true} on the initial render. It does not catch the false-to-true update failure in SignedImage at Lines 35-61. Start without priority, rerender with priority, and assert that no observer is used and loading starts immediately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/signed-image.dom.test.tsx` at line 90, Add a test case covering the
false-to-true priority update path in SignedImage: render without priority,
rerender with priority enabled, then assert loading begins immediately and
IntersectionObserver is not used. Keep the existing initial-render priority test
unchanged.

111-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the priority test independent of the signed-URL cache.

SignedImage seeds its initial load state from getCachedSignedUrl(endpoint) as well as priority. If an earlier test has cached the shared ENDPOINT, observe can remain unused even if the priority wiring regresses. Use a unique endpoint or reset the signed-URL cache in test setup.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/signed-image.dom.test.tsx` around lines 111 - 118, Update the priority
test around SignedImage so it cannot reuse a signed URL cached by earlier tests:
use a unique endpoint for this test or clear the signed-URL cache during setup.
Preserve the assertions verifying that priority skips observe while still
fetching and rendering the expected image.
tests/document-viewer-non-pdf-preview.dom.test.tsx (1)

34-34: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Do not require two announcement channels unless they are intentional.

The new assertion adds role="alert" while the test already verifies the assertive LiveAnnouncer region at Line 33. This locks in both channels for one failure. After selecting one production path, assert that path and add a no-duplicate regression check if both remain.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/document-viewer-non-pdf-preview.dom.test.tsx` at line 34, Update the
failure assertion in the document preview test to use only the intentionally
selected announcement channel, rather than requiring both the LiveAnnouncer
region and the role="alert" element. If both production channels remain, retain
the existing primary assertion and add a check that prevents duplicate
announcements.
tests/document-frame-contract.test.ts (1)

49-56: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Keep the contract test aligned with one announcement contract.

This test requires both role="alert" and announce(error) for the PDF owner. If one path is removed to avoid duplicate speech, these assertions will preserve the old dual-channel contract. Assert the selected announcement path instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/document-frame-contract.test.ts` around lines 49 - 56, Update the test
case “announces canvas-level PDF preview failures after the frame is already
ready” to assert only the chosen single PDF error announcement mechanism,
removing the conflicting requirement for both role="alert" and announce(error).
Keep the assertions for the other owners unchanged unless they use the same
consolidated contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/outstanding-issues.md`:
- Around line 137-172: Add the missing open ledger rows `#256` and `#257` to the
recommended queue using the established /issues flow, preserving their existing
priorities and details so the queue remains complete. If the intended queue is a
subset instead, revise its contract and enforce that scope consistently rather
than leaving the omissions implicit.

In `@src/components/document-viewer/pdf-canvas-viewer.tsx`:
- Around line 107-117: Use a single announcement contract for preview failures:
in src/components/document-viewer/pdf-canvas-viewer.tsx lines 107-117, remove
either the LiveAnnouncer path or the visible role="alert" path; in
src/components/document-viewer/non-pdf-source-preview.tsx line 123, retain one
image announcement path and scope the alert to the message text. Update
tests/document-frame-contract.test.ts lines 49-56 and
tests/document-viewer-non-pdf-preview.dom.test.tsx line 34 to assert the
selected paths and prevent duplicate announcements.
- Around line 107-117: Update the error announcement effect in the PDF canvas
viewer to associate each error with the URL that produced it, then announce only
when that error URL matches the current url. Preserve the existing assertive
priority and eventId behavior for current-URL errors, preventing stale messages
from being announced after navigation.

In `@src/components/document-viewer/use-viewer-gestures.ts`:
- Around line 114-135: The wheel blocking logic in onWheelPassive must prevent
browser page zoom on every modifier-wheel event, including the first event and
subsequent events while Ctrl/Meta remains pressed. Keep blockingHandler attached
until the corresponding keyup instead of detaching after 180 ms, explicitly
define the behavior for unkeyed trackpad pinch, and update
tests/use-viewer-gestures.dom.test.tsx lines 94-106 with Chromium coverage for
both modifier-wheel and unkeyed pinch behavior; the existing
listener-registration assertion should be expanded to verify event cancellation.

---

Outside diff comments:
In `@src/components/clinical-dashboard/signed-image.tsx`:
- Around line 35-61: The shouldLoad state in SignedImage is initialized from
priority but does not react when that prop changes. Update the SignedImage load
gate so a false-to-true priority transition sets shouldLoad to true, and add a
rerender test in tests/signed-image.dom.test.tsx covering that transition.

---

Nitpick comments:
In `@src/components/clinical-dashboard/signed-image.tsx`:
- Line 166: Update the Image props in SignedImage to map the public
SignedImage.priority value to Next.js 16’s supported preload API, removing the
deprecated priority prop. Preserve the existing behavior for client-resolved
signed URLs and keep the public priority contract unchanged.

In `@tests/document-frame-contract.test.ts`:
- Around line 49-56: Update the test case “announces canvas-level PDF preview
failures after the frame is already ready” to assert only the chosen single PDF
error announcement mechanism, removing the conflicting requirement for both
role="alert" and announce(error). Keep the assertions for the other owners
unchanged unless they use the same consolidated contract.

In `@tests/document-viewer-non-pdf-preview.dom.test.tsx`:
- Line 34: Update the failure assertion in the document preview test to use only
the intentionally selected announcement channel, rather than requiring both the
LiveAnnouncer region and the role="alert" element. If both production channels
remain, retain the existing primary assertion and add a check that prevents
duplicate announcements.

In `@tests/signed-image.dom.test.tsx`:
- Line 90: Add a test case covering the false-to-true priority update path in
SignedImage: render without priority, rerender with priority enabled, then
assert loading begins immediately and IntersectionObserver is not used. Keep the
existing initial-render priority test unchanged.
- Around line 111-118: Update the priority test around SignedImage so it cannot
reuse a signed URL cached by earlier tests: use a unique endpoint for this test
or clear the signed-URL cache during setup. Preserve the assertions verifying
that priority skips observe while still fetching and rendering the expected
image.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cfdede50-430d-4dc2-8a32-3e5bd2c45988

📥 Commits

Reviewing files that changed from the base of the PR and between d922138 and 278dcc1.

📒 Files selected for processing (11)
  • docs/outstanding-issues.md
  • src/components/clinical-dashboard/image-lightbox.tsx
  • src/components/clinical-dashboard/signed-image.tsx
  • src/components/document-viewer/non-pdf-source-preview.tsx
  • src/components/document-viewer/pdf-canvas-viewer.tsx
  • src/components/document-viewer/use-viewer-gestures.ts
  • tests/document-frame-contract.test.ts
  • tests/document-viewer-non-pdf-preview.dom.test.tsx
  • tests/signed-image.dom.test.tsx
  • tests/ui-smoke.spec.ts
  • tests/use-viewer-gestures.dom.test.tsx

Comment thread docs/outstanding-issues.md Outdated
Comment thread src/components/document-viewer/pdf-canvas-viewer.tsx
Comment thread src/components/document-viewer/use-viewer-gestures.ts Outdated
cursoragent and others added 5 commits August 7, 2026 03:16
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Resolve outstanding-issues by taking main and re-queueing #256/#257.
Take main's non-passive wheel listener (Sentry double-zoom fix) over the
branch's promote-to-blocking approach.

Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
@BigSimmo
BigSimmo merged commit 097dfd2 into main Aug 7, 2026
32 of 33 checks passed
@BigSimmo
BigSimmo deleted the cursor/viewer-phase0-gesture-a11y-1db8 branch August 7, 2026 04:07
cursor Bot pushed a commit that referenced this pull request Aug 7, 2026
Co-authored-by: BigSimmo <BigSimmo@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants