feat(ui): persistent timeline cache with stale-while-revalidate#427
feat(ui): persistent timeline cache with stale-while-revalidate#427
Conversation
Remove the 30s TTL from the timeline cache so cached data never expires on its own. When switching back to a project, show cached timeline data immediately and display a "Looking for changes..." spinner row at the bottom while revalidating in the background. - Remove TIMELINE_CACHE_MAX_AGE_MS; cache entries persist until explicitly invalidated - Add getBranchTimelineWithRevalidation() that returns cached data and kicks off a background re-fetch - Add invalidateProjectBranchTimelines() for batch cache eviction - Update BranchCard.loadTimeline() to use stale-while-revalidate on initial load with a version counter to discard stale results - Add revalidating prop to BranchTimeline with a pending-commit style "Looking for changes..." row - Invalidate cache entries on project and branch deletion Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set loading=false in cached path so timeline data isn't hidden behind the loading spinner when switching back to a project - Add !revalidating to all isLast calculations so the vertical connector line extends to the "Looking for changes..." row - Move cache invalidation to success path in branch deletion to avoid triggering revalidation for a just-deleted branch - Remove dead fetchedAt field from timeline cache entries Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a 'revalidating' type to TimelineRow that renders a grey spinner (default icon styling) instead of green commit styling. The row always hides the vertical connector line and uses reduced vertical padding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the icon box background/border and use --text-faint for the spinner, icon, and title text so the "Looking for changes..." row blends into the background rather than competing with actual timeline content. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keep the icon background box but apply opacity: 0.5 so it fades into the background rather than disappearing entirely. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace opacity hack with --bg-hover for the icon box background and border, making it barely visible against the page background while still present. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bump compact row padding from 4px to 6px vertical to give the "Looking for changes..." row a bit more breathing room. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wrap the "Looking for changes..." row in a slide transition so it smoothly animates in and out, matching the other dynamic timeline rows. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When background revalidation fails and we have cached timeline data, keep showing the stale data with a subtle error below instead of replacing it with a full error state. Also cancel in-flight revalidations when loadTimeline is called from any path to prevent stale responses from overwriting fresher data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a1d7ae2f0b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| .catch((e) => { | ||
| if (version !== revalidationVersion) return; | ||
| error = e instanceof Error ? e.message : String(e); |
There was a problem hiding this comment.
Keep stale timeline visible when background refresh fails
In the stale-while-revalidate path, a transient failure now assigns error even though timeline still holds the cached data. Because this component renders {:else if error} before {:else if timeline} later in BranchCard.svelte, opening a previously cached branch while offline (or during any backend hiccup) replaces the usable stale timeline with an error panel instead of preserving the fallback content.
Useful? React with 👍 / 👎.
| return { | ||
| cached: entry?.timeline ?? null, | ||
| fresh: getBranchTimeline(branchId, { force: true }), |
There was a problem hiding this comment.
Avoid reusing mount-time refresh for later forced timeline loads
getBranchTimelineWithRevalidation() starts a forced fetch immediately, and getBranchTimeline() still deduplicates by branch ID against any in-flight request. That means if the user starts a note/commit while this initial refresh is still running, the later loadTimeline() calls that are meant to show the new pending stub immediately (see the session-status-changed handler in BranchCard.svelte) collapse onto the older request that was issued before the new session existed. On slower timelines, the card can therefore stay stuck on the pre-action state until some later refresh happens.
Useful? React with 👍 / 👎.
Summary
Test plan
🤖 Generated with Claude Code