Lazy-paint slides thumbnails + show async image backgrounds#271
Conversation
Three UX bugs in the slides thumbnail panel, all visible on decks with master-level image backgrounds: 1. Image backgrounds never showed in thumbnails on first mount. The renderer's onAssetLoad callback fired into a discarded SlideRenderer (renderThumbnail constructed one per call and threw it away), so when the <img> finally loaded the canvas was never repainted. Now renderThumbnail takes an onAssetLoad and calls drawSlide directly; the panel routes the callback through ThumbnailScheduler so concurrent loads coalesce. 2. Initial-mount block on large decks. Building N items (div + canvas + getContext + observe) synchronously stalled the main thread for ~hundreds of ms on 50+ slide decks. Switched to IntersectionObserver-driven lazy paint (rootMargin 200px) plus chunked DOM construction via requestAnimationFrame so the first viewport's worth of thumbs paint in the first frame and the rest stream in one chunk per frame. 3. Whole-panel flicker on every slide edit. store.onChange in slides-view.tsx was calling thumbHandle.refresh() (full DOM wipe) for every drag, color tweak, or text edit. Split the panel API into refresh() (structural — rebuild DOM), refreshContent() (content-only — re-snapshot ThumbState.slide/doc and schedule repaints of painted thumbs), and syncCurrentHighlight() (toggle .current class only). refreshContent also detects when the slide order diverges from the store (remote peer reorder with the same count) and falls back to render() so the DOM doesn't end up pointing at the wrong slides. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughThis PR implements async background-image thumbnail repainting and lazy rendering for the slides editor. It adds an optional ChangesSlides Thumbnail Async Repaint & Lazy Rendering
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes The PR introduces substantial architectural changes across the thumbnail rendering pipeline: a new state-tracking system with Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Verification: verify:selfResult: ✅ PASS in 210.8s
Verification: verify:integrationResult: ✅ PASS |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 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/tasks/active/20260521-slides-thumbnail-async-bg-todo.md`:
- Around line 75-76: The design notes currently document ThumbnailScheduler
debounce as two different values; update the text so both references to
ThumbnailScheduler (including the description in the mountThumbnailPanel context
and the later mention around onFlush(ids)) use the same debounce duration
(choose either 100ms or 200ms) to avoid mismatch — ensure the single chosen
value replaces both occurrences (references: mountThumbnailPanel,
ThumbnailScheduler, onFlush(ids)).
- Around line 274-277: Update the stale limitation text that claims "Thumbnails
do not auto-refresh on canvas content edits" so it accurately reflects that the
panel can be refreshed via the existing refresh handle pattern and the
documented refreshContent() integration; replace the absolute claim with a note
that thumbnails only auto-refresh when consumers call refreshContent() (or wire
their store changes into the refresh handle) and clarify that the panel
subscribes to onCurrentSlideChange by default but will update for content edits
when refreshContent()/refresh handle is used; mirror this clarification in both
the section describing refreshContent() and the earlier limitation text to
remove the contradiction.
- Line 209: The review section has inconsistent completion state: the summary
claims all 12 plan items are done but the V2 checklist item is left unchecked
and the manual verification note is marked pending; update the review checklist
so states are consistent by either checking the V2 item and marking manual
verification as completed (if actually done) or changing the summary to reflect
the remaining items, and ensure the "V2" checklist entry and the "manual
verification" line match the final status and the total count of completed plan
items.
In `@packages/slides/src/view/editor/thumbnail-panel.ts`:
- Around line 537-572: The per-chunk restore unconditionally sets
scrollParent.scrollTop inside buildChunk which can snap the user's scroll during
multi-rAF builds; change it to only restore when the browser has actually
clamped (i.e., compare current scrollTop and only set if current <
savedScrollTop) and ensure scrollParent is non-null before comparing; update the
assignment inside buildChunk (where savedScrollTop and scrollParent are used) to
a conditional guard so user-initiated scrolls aren't overwritten across chunks.
In `@packages/slides/test/view/editor/thumbnail-panel.test.ts`:
- Around line 642-648: The test title is inaccurate: it claims to exercise
"called before first render" but the fixture helper mountThumbnailPanel runs
render synchronously, then the test calls dispose() and tests refreshContent()
after dispose; update the test name to reflect the actual behavior (e.g.
"refreshContent is a safe no-op after dispose") so it matches the exercised
path; locate the spec that uses mountThumbnailPanel(...), calls
handle.dispose(), and then handle.refreshContent(), and change the it(...)
description accordingly (referencing mountThumbnailPanel, dispose, and
refreshContent).
🪄 Autofix (Beta)
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: defaults
Review profile: CHILL
Plan: Pro
Run ID: eaa1a50d-6582-4e3f-b839-8fdca2bc7c06
📒 Files selected for processing (6)
docs/tasks/active/20260521-slides-thumbnail-async-bg-todo.mdpackages/frontend/src/app/slides/slides-view.tsxpackages/slides/src/view/canvas/thumbnail.tspackages/slides/src/view/editor/thumbnail-panel.tspackages/slides/test/view/canvas/thumbnail.test.tspackages/slides/test/view/editor/thumbnail-panel.test.ts
Code: per-chunk scrollTop restore now only fires when the browser clamped (scrollTop < savedScrollTop). On a 100-slide deck the rAF build runs across multiple frames; if the user scrolled past savedScrollTop during a between-chunk gap, the unconditional restore would snap them back to the saved position on every frame. Plan: reconcile two CodeRabbit-flagged inconsistencies — the ThumbnailScheduler debounce was documented as both 100ms and 200ms (actual value is 100ms = ASSET_LOAD_DEBOUNCE_MS), and the review section claimed all 12 items done while V2 (manual visual) was still unchecked. Also clarify the "no auto-refresh" limitation to reflect the post-refreshContent() caller contract. Test: rename "refreshContent is a no-op when state is empty (called before first render)" — mount runs render synchronously, so the test exercises the post-dispose path, not pre-render. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
These tasks' features merged to main (PRs #267 #271 #277 #284 #285) but their plan checkboxes were never ticked, so the todo files lingered in tasks/active. Added a status note citing each merge commit, marked the checkboxes complete, and ran tasks:archive to move them under archive/2026/05 with regenerated indexes. Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
Summary
Three UX bugs in the slides thumbnail panel, all most visible on decks with master-level image backgrounds:
renderThumbnailconstructed a throwawaySlideRenderer, so theonAssetLoadcallback that fires when an image finishes loading had no effect (the renderer was already GC'd). ThreadedonAssetLoadthroughrenderThumbnail→drawSlide; the panel routes it viaThumbnailSchedulerso concurrent loads coalesce.rootMargin: 200px) plus chunked DOM construction viarequestAnimationFrame— first viewport paints in the first frame, the rest stream in 20 items per frame.store.onChangewas callingthumbHandle.refresh()(full DOM wipe) for every drag / color / text edit. Split the panel API:refresh()— structural (rebuild DOM)refreshContent()— content-only, re-snapshotsThumbState.slide/docand schedules repaints of painted thumbs; falls back torender()when the slide order diverges from the store (catches remote peer reorder)syncCurrentHighlight()— toggles.currentclass + border color only, used byonCurrentSlideChangeTest plan
pnpm verify:fast— 792/792 greenrenderThumbnailonAssetLoadpropagation, IO lazy paint, async background image repaint via scheduler, dispose during async load, current-slide switch without canvas repaint,refreshContentdebounced repaint,refreshContentorder-divergence fallback, chunked render with rAF, refresh-during-chunking cancels stale items, dispose-during-chunking cancels pending chunks (44 panel tests, 5 thumbnail tests)pnpm devagainst a deck with master-level image background + ~30 slides — confirm (a) visible thumbs show the image on initial mount, (b) scrolling reveals lower thumbs which paint with the image, (c) clicking a thumb only updates the highlight (no flicker across the panel), (d) editing a shape only repaints the affected thumbPlan + review notes in
docs/tasks/active/20260521-slides-thumbnail-async-bg-todo.md.🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes
New Features
Bug Fixes
Tests