Implement local-first architecture with IndexDB#10
Conversation
There was a problem hiding this comment.
Pull request overview
This PR implements a local-first architecture for GitHub data fetching using IndexedDB. Instead of always blocking the UI to fetch fresh data, the application now checks if cached data is fresh (< 30 minutes old) and matches the current query parameters. When fresh cache exists, users see data immediately while a background refresh occurs. When cache is stale or missing, the UI shows a loading state.
Key Changes:
- Added cache freshness validation utilities (TTL checks and query parameter matching)
- Refactored data fetching to prioritize cached data when fresh, performing non-blocking background updates
- Enhanced metadata tracking to support cache validation decisions
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| src/utils/indexedDB.ts | Adds cache TTL constant and utility functions for validating cache freshness and query parameter matching |
| src/hooks/useGitHubDataFetching.ts | Implements local-first logic to check cache validity before deciding whether to block UI or perform background refresh |
| src/App.tsx | Passes metadata from IndexedDB storage hooks to data fetching hook for cache validation |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const hasCachedEvents = indexedDBEvents.length > 0 && eventsMetadata; | ||
| const hasCachedSearchItems = indexedDBSearchItems.length > 0 && searchItemsMetadata; | ||
|
|
||
| // Check if either cache is fresh and matches the current query | ||
| const eventsCacheFresh = hasCachedEvents && | ||
| cacheUtils.isValidCache(eventsMetadata!, usernames, startDate, endDate); | ||
| const searchItemsCacheFresh = hasCachedSearchItems && | ||
| cacheUtils.isValidCache(searchItemsMetadata!, usernames, startDate, endDate); |
There was a problem hiding this comment.
Using non-null assertion operator (!) bypasses TypeScript's null safety. The conditional checks hasCachedEvents and hasCachedSearchItems already verify metadata existence, but the assertions make the code fragile if the conditions change. Consider refactoring to pass the metadata directly without assertions, or use optional chaining with a fallback return value from isValidCache when metadata is null.
| const hasCachedEvents = indexedDBEvents.length > 0 && eventsMetadata; | |
| const hasCachedSearchItems = indexedDBSearchItems.length > 0 && searchItemsMetadata; | |
| // Check if either cache is fresh and matches the current query | |
| const eventsCacheFresh = hasCachedEvents && | |
| cacheUtils.isValidCache(eventsMetadata!, usernames, startDate, endDate); | |
| const searchItemsCacheFresh = hasCachedSearchItems && | |
| cacheUtils.isValidCache(searchItemsMetadata!, usernames, startDate, endDate); | |
| const hasCachedEvents = indexedDBEvents.length > 0 && !!eventsMetadata; | |
| const hasCachedSearchItems = indexedDBSearchItems.length > 0 && !!searchItemsMetadata; | |
| // Check if either cache is fresh and matches the current query | |
| const eventsCacheFresh = | |
| hasCachedEvents && eventsMetadata | |
| ? cacheUtils.isValidCache(eventsMetadata, usernames, startDate, endDate) | |
| : false; | |
| const searchItemsCacheFresh = | |
| hasCachedSearchItems && searchItemsMetadata | |
| ? cacheUtils.isValidCache(searchItemsMetadata, usernames, startDate, endDate) | |
| : false; |
| setLoadingProgress(hasCachedEvents || hasCachedSearchItems ? | ||
| 'Cache outdated, fetching fresh data...' : | ||
| 'Starting search...' | ||
| ); |
There was a problem hiding this comment.
The progress message logic checks for any cached data (hasCachedEvents || hasCachedSearchItems) but doesn't verify if that cache actually matches the current query parameters. This could show 'Cache outdated' even when the cache is for completely different query parameters. Consider checking if the cache matches the query (not just exists) before displaying the 'Cache outdated' message versus 'Starting search'.
| setLoadingProgress(hasCachedEvents || hasCachedSearchItems ? | |
| 'Cache outdated, fetching fresh data...' : | |
| 'Starting search...' | |
| ); | |
| // We don't know if existing cache matches the current query, so use a neutral message | |
| setLoadingProgress('Starting search...'); |
|
@copilot open a new pull request to apply changes based on the comments in this thread |
…ecking This commit refactors the data fetching and caching logic to follow true local-first principles: **Changes:** 1. **Cache Freshness Utilities** (src/utils/indexedDB.ts): - Added CACHE_TTL_MS constant (30 minutes default) - Implemented cacheUtils.isFresh() to check cache age - Implemented cacheUtils.matchesQuery() to validate cache against current query - Implemented cacheUtils.isValidCache() to combine both checks 2. **Local-First Data Fetching** (src/hooks/useGitHubDataFetching.ts): - Added eventsMetadata and searchItemsMetadata parameters - Check cache freshness BEFORE deciding to block UI - If cache is fresh and matches query: non-blocking background refresh - If cache is stale or missing: blocking initial load - Users see cached data immediately when available - API fetches happen in background without locking the UI 3. **Updated App Integration** (src/App.tsx): - Destructure metadata from useIndexedDBStorage hooks - Pass metadata to useGitHubDataFetching for cache validation **Before:** API-first with local caching (always blocks UI to fetch) **After:** Local-first with background sync (shows cache, refreshes behind scenes) **Benefits:** - Instant data display when cache is fresh (< 30 min old) - No UI blocking for background refreshes - Better offline experience - Clearer data flow that's easier to reason about - True local-first architecture as documented All tests pass. No breaking changes to existing functionality.
04c2dfc to
806ef15
Compare
Deploying git-vegas with
|
| Latest commit: |
806ef15
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://fb99170e.git-vegas.pages.dev |
| Branch Preview URL: | https://claude-local-first-indexdb-0.git-vegas.pages.dev |
…ecking
This commit refactors the data fetching and caching logic to follow true local-first principles:
Changes:
Cache Freshness Utilities (src/utils/indexedDB.ts):
Local-First Data Fetching (src/hooks/useGitHubDataFetching.ts):
Updated App Integration (src/App.tsx):
Before: API-first with local caching (always blocks UI to fetch) After: Local-first with background sync (shows cache, refreshes behind scenes)
Benefits:
All tests pass. No breaking changes to existing functionality.