Keep the last good panel data when a refresh fails - #53
Conversation
usePolled documents that "a failed refresh keeps the last good data on screen", but it could only honour that for transport failures. An upstream declining arrives as a *successful* 200 carrying `available: false`, so setData() overwrote the last good payload with it and PanelBody fell straight through to PanelEmpty. The result was that one failed poll blanked a panel until the server's TTL lapsed — up to 60s for the calendar, 30s for requests and activity — and memoize would go on serving that cached failure for the rest of the TTL even after the upstream recovered. PanelBody now remembers the most recent available payload and keeps rendering it when a later response is unavailable, above a marker that names the reason and carries the same hint PanelEmpty would. Staleness is stated rather than hidden: showing stale numbers silently would be worse than showing none. Left memoize alone deliberately. It is generic over T and knows nothing about Result, and caching failures is what stops a dead upstream being hit every five seconds — the display decision belongs on the client, where Result is already understood. Verified end to end against a live Seerr behind a toggleable proxy: good data renders, breaking the upstream keeps the rows and adds "Showing last known data — HTTP 503", and healing it clears the marker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughPanel rendering retains the latest available result and displays it with a “Showing last known data” warning when a response is unavailable. Panels without prior data still show ChangesPanel stale-data rendering
Estimated code review effort: 3 (Moderate) | ~20 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@dashboard/web/src/components/Panel.tsx`:
- Around line 118-121: Update useLastAvailable so the lastAvailable ref is
assigned only inside a useLayoutEffect dependent on data, recording data when it
is available after the render commits. Remove the render-time assignment while
preserving the existing T | null return behavior.
🪄 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 Plus
Run ID: 24e4ee1c-9279-467d-8433-a4f5b97bec6b
📒 Files selected for processing (1)
dashboard/web/src/components/Panel.tsx
|
@claude fix issues |
There was a problem hiding this comment.
Pull request overview
This PR improves the dashboard panel UX by ensuring a temporary upstream “unavailable” response doesn’t blank out a panel that previously had good data, while clearly indicating when the displayed content is stale.
Changes:
- Added a
PanelStalewarning banner to indicate the newest refresh is unavailable while continuing to render the last known available payload. - Introduced
useLastAvailableto retain the most recentavailable: truepayload and updatedPanelBodyto render stale + retained data when appropriate.
Suppressed comments (1)
dashboard/web/src/components/Panel.tsx:122
useLastAvailablecurrently mutateslastAvailable.currentduring render. In React 18, renders can be restarted/aborted; updating refs during render can leavelastAvailablereflecting a value from a render that never committed. Prefer committing this ref update in auseEffecttied todata.
function useLastAvailable<T extends object>(data: Result<T> | null): T | null {
const lastAvailable = useRef<T | null>(null);
if (data?.available) lastAvailable.current = data;
return lastAvailable.current;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Follows up the
memoizefinding from the #51 review. Investigating it turned up something larger than the finding described.The bug
usePolled's docstring promises "a failed refresh keeps the last good data on screen." It can only honour that for transport failures. An upstream declining arrives as a successful200carryingavailable: false, sosetData(json)replaced the last good payload with it andPanelBodyfell through toPanelEmpty.Probing the real
memoizeshows the server side compounding it:Step 4 is the part the review didn't mention: the cached failure is served for the whole TTL even after the upstream is healthy. Combined, a single failed poll blanked a panel for up to 60s (Upcoming) or 30s (Requests, Activity) — the exact outcome the invariant exists to prevent.
The fix
PanelBodyremembers the most recentavailablepayload and keeps rendering it when a later response is unavailable, above a marker naming the reason and carrying the same hintPanelEmptywould.Staleness is stated, not hidden — silently showing stale numbers would be worse than showing none, and the hint keeps the fix discoverable without the panel going blank.
Why not fix
memoizeThe review proposed making
memoizeretain the last good value. I deliberately didn't:Tand knows nothing aboutResult; teaching it the discriminant couples the cache to the payload type.The display decision belongs where
Resultis already understood.Verification
End to end against a live Seerr behind a toggleable proxy, without reloading between steps (a reload would reset the retained value and prove nothing):
Showing last known data — HTTP 503Also confirmed the first-load case is untouched: with no prior good payload, an unavailable response still renders
PanelEmpty.Typecheck, lint, 42/42 tests and build all clean.
Not covered
GaugesandVpnCarddon't usePanelBodyand still drop upstream reasons — the other half of that review finding. Left for a separate change.🤖 Generated with Claude Code
Summary by CodeRabbit