perf(viewer): isolate PDF subtree, decouple page-nav + jsdom interaction tests#651
Conversation
Three render-cost reductions on the document viewer, all frontend/offline: - Page/chunk navigation no longer triggers a full document reload. The main load effect now distinguishes a full (re)load — new documentId or explicit retry — from navigation on the already-loaded document via a loadedKeyRef. Navigation skips the loading reset, keeps the mounted PDF/content on screen (no flash), preserves current content on a transient detail failure, and does not re-issue signed URLs. - The app-shell scroll container (#main-content) is resolved synchronously and the MutationObserver only watches until it appears, then disconnects — rather than reacting to every app-wide DOM mutation for the viewer's lifetime. - The heaviest subtree (PdfCanvasViewer, holding the pdf.js document and the canvas raster) plus NativePdfEmbed and NonPdfSourcePreview are wrapped in memo, and the two PDF callbacks (onUrlExpired/onLoadSuccess) are stabilised with useCallback. Unrelated parent re-renders — source-search keystrokes, composer focus, connectivity changes — no longer re-render or re-rasterise the viewer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DqMTk5fDNgi5GwEK5peRmj
…r jsdom Add jsdom coverage for the effect-driven viewer paths the node/SSR suite can't reach: - signed-image.dom.test.tsx: drives the SignedImage fetch → failed → retry → recover flow (jsdom omits IntersectionObserver, so the image loads immediately) and the click-to-open lightbox that closes on Escape. The useAuthSession mock returns stable references so the fetch effect isn't re-run on every render. - use-viewer-gestures.dom.test.tsx: asserts the native non-passive wheel branch (Ctrl/⌘ gating, lightbox no-modifier mode, wheelZoom off) and the pointer pan/pinch maths (frame-delta panning, distance-ratio zoom, touchPan opt-out). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DqMTk5fDNgi5GwEK5peRmj
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughDocumentViewer now separates full reloads from navigation, preserves state and signed URLs during navigation, and observes DOM mutations for scroll-container setup. Viewer components are memoized, while signed-image and gesture interactions gain jsdom coverage. ChangesDocument viewer behavior
Memoized viewer components
DOM interaction tests
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/components/DocumentViewer.tsx (1)
1901-1970: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy liftNo targeted test for the new full-reload vs navigation branching.
This effect's behavior change (
isFullReload/loadedKeyRefgating resets, signed-URL refetch, and error propagation) is exactly the kind of logic a regression like the one flagged above would slip through untested — the PR's new tests coverSignedImageanduseViewerGesturesonly, not this branching. As per coding guidelines, "When behavior changes, add or update the smallest relevant targeted test and run the narrowest relevant validation before broader suites."🤖 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/DocumentViewer.tsx` around lines 1901 - 1970, Add a focused test for the DocumentViewer loading effect covering both full reload and navigation behavior: verify full reload failures clear viewer content and set the appropriate error, while transient navigation detail failures preserve existing content; also cover signed-URL application and stale/aborted request handling as relevant. Use the existing DocumentViewer test setup and run the narrowest targeted test command.Source: Coding guidelines
🤖 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 `@src/components/DocumentViewer.tsx`:
- Around line 1849-1850: Update the settle-handler around the detail-load result
and loadedKeyRef.current so the key is stamped only when the detail load
succeeds, specifically when detailResult.status indicates success. Keep failed
detail loads and signed-URL failures treated as full reloads so subsequent
navigation re-fetches URLs and updates viewer errors.
---
Nitpick comments:
In `@src/components/DocumentViewer.tsx`:
- Around line 1901-1970: Add a focused test for the DocumentViewer loading
effect covering both full reload and navigation behavior: verify full reload
failures clear viewer content and set the appropriate error, while transient
navigation detail failures preserve existing content; also cover signed-URL
application and stale/aborted request handling as relevant. Use the existing
DocumentViewer test setup and run the narrowest targeted test command.
🪄 Autofix (Beta)
❌ Autofix failed (check again to retry)
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 Plus
Run ID: 379b9f9e-2c44-40c2-9d8e-82cc2581fabf
📒 Files selected for processing (5)
src/components/DocumentViewer.tsxsrc/components/document-viewer/non-pdf-source-preview.tsxsrc/components/document-viewer/pdf-canvas-viewer.tsxtests/signed-image.dom.test.tsxtests/use-viewer-gestures.dom.test.tsx
A failed full load stamped loadedKeyRef unconditionally, so the next page/chunk navigation on the same document computed isFullReload=false and wrongly skipped the signed-URL re-fetch and the viewer-error refresh — a document that never loaded could never recover except via an explicit "Retry preview". (fetchSignedUrlPair uses allSettled and never rejects, so this path is reached, not caught.) Advance the loaded key only when the detail request actually succeeds; a failed load keeps the previous key so subsequent navigation is still treated as a full reload and retries the recovery work. Extract the reload-key decision into pure documentLoadKey / isFullDocumentReload / nextLoadedDocumentKey helpers in document-viewer-navigation.ts and cover the full-reload-vs-navigation gating (incl. the failed-load regression guard) in the node suite — the branching was previously untested. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DqMTk5fDNgi5GwEK5peRmj
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Autofix skipped. No unresolved CodeRabbit review comments with fix instructions found. |
Summary
Follow-up to the document/image viewer review (#619, #643): three render-cost reductions on
DocumentViewerplus the jsdom coverage the node/SSR suite can't reach. All changes are frontend/offline — no route, API, CSP, or fetch-contract change.documentIdor an explicit retry (previewAttempt) — from navigation on the already-loaded document, via aloadedKeyRef. On navigation it skips the loading reset (no flash), keeps the mounted PDF and current content visible, preserves current content on a transient detail failure, and does not re-issue signed URLs. A full load still resets, re-issues signed URLs, and surfaces errors exactly as before.MutationObserver.#main-content(the app-shell scroll container) is resolved synchronously; the observer only watches the DOM until it appears, then disconnects — instead of reacting to every app-wide mutation for the viewer's lifetime.PdfCanvasViewer(holds the pdf.js document + canvas raster),NativePdfEmbed, andNonPdfSourcePrevieware wrapped inmemo, and the two PDF callbacks (onUrlExpired/onLoadSuccess) are stabilised withuseCallback. Unrelated parent re-renders — source-search keystrokes, composer focus, connectivity changes — no longer re-render or re-rasterise the viewer.SignedImage(fetch → failed → retry → recover, plus click-to-open lightbox that closes on Escape) anduseViewerGestures(Ctrl/⌘-wheel gating, lightbox no-modifier mode,wheelZoomoff; pointer pan frame-deltas, pinch distance-ratio zoom,touchPanopt-out).Verification
npm run lint(repo,--max-warnings 0) — cleannpm run typecheck(tsc --noEmit) — cleannpx vitest run— 2295 passed / 3 skipped (incl. the 2 new dom specs, 8 tests)npx prettier --checkon all touched files — cleannpm run verify:pr-local— not run this session (heavier gate); the constituent lint/typecheck/format/vitest gates above all passnpm run verify:ui— not run; changes are render-cost/isolation only (memoisation + effect gating), no visual/DOM output change. The viewer's visual behaviour was driven in real Chromium during the parent PRs (Optimise and extend the document & image viewers #619/Signed-URL-only refresh for expired PDFs (shared fetch, abortable) #643).No retrieval/ranking/selection/chunking/citation or answer-contract behaviour changed, so
eval:*gates are not applicable.Clinical Governance Preflight
The page-nav decouple touches the document-detail load + signed-URL re-issue pipeline (document access), so completed conservatively:
Clinical KB Database(sjrfecxgysukkwxsowpy) — no env/config changeNotes
InlineImagePreview(insideNonPdfSourcePreview) stays keyed bysignedUrl, so a freshly issued URL still remounts with a clean failure state under the newmemoboundary.🤖 Generated with Claude Code
https://claude.ai/code/session_01DqMTk5fDNgi5GwEK5peRmj
Generated by Claude Code
Summary by CodeRabbit