Overview
Film Fanatic implements a sophisticated hybrid storage system for watchlists that works both with and without user authentication. The system synchronizes between local browser storage (for anonymous users) and cloud storage via Convex (for authenticated users).Storage Architecture
Hybrid Storage Model
Local Storage
Anonymous Users
- Data stored in browser localStorage
- Uses Zustand for state management
- Persists across browser sessions
- No account required
Cloud Storage
Authenticated Users
- Synced via Convex backend
- Available across all devices
- Real-time synchronization
- Optimistic UI updates
Implementation Details
Location:src/hooks/usewatchlist.ts
Zustand Store (Local)
Zustand Store (Local)
The
useWatchlistStore provides:- Persists to
localStorageunder keywatchlist-storage - Fallback to in-memory storage for server-side rendering
- Automatic JSON serialization/deserialization
Convex Backend (Cloud)
Convex Backend (Cloud)
Backend schema:
convex/schema.ts and convex/watchlist.tsKey mutations:setWatchlistMembership- Add/remove from watchlistsetProgressStatus- Update watch statussetReaction- Set mood/reactionupdateProgress- Track percentage watchedmarkEpisodeWatched- Episode-level tracking
Watchlist Data Structure
Each watchlist item contains:src/hooks/usewatchlist.ts:38-52
Watch Statuses
Progress Status
Users can mark items with one of four progress statuses:Plan to Watch
Content the user intends to watch but hasn’t started.Value:
"want-to-watch"Watching
Currently in progress, actively watching.Value:
"watching"Completed
Finished watching entirely.Value:
"finished"Dropped
Started but chose not to continue.Value:
"dropped"Reaction Status
After watching, users can optionally add a mood/reaction:- Loved
- Liked
- Mixed
- Not For Me
Absolutely enjoyed the content.Value:
"loved" | Icon: Heart ❤️Reactions are independent of progress status. A user can love something they dropped or have mixed feelings about something they completed.
Watchlist Page UI
Location:src/routes/watchlist.tsx
Filter System
- Progress Filters
- Secondary Filters
Primary filter tabs showing counts:
- All - Everything except dropped items
- Plan to Watch - Unwatched content
- Watching - In progress
- Completed - Finished items
- Dropped - Discontinued content
Active Filter Display
The “Filters” button shows a badge with the number of active secondary filters. Users can:- Toggle filters panel
- Reset all secondary filters at once
- See real-time count updates as they apply filters
src/routes/watchlist.tsx:298-316
Empty States
No Items
Shown when the watchlist is completely empty.Message: “No items in your watchlist”
No Matches
Shown when filters exclude all items.Message: “No items match your filters”
Import/Export
Location:src/hooks/usewatchlistimportexport.ts
Export Watchlist
JSON File Generated
Browser downloads a JSON file containing all watchlist data:
- All items with metadata
- Progress statuses
- Reactions
- Timestamps
Import Watchlist
Validation
System validates the file structure:
- Checks for required fields
- Identifies invalid entries
- Shows warning if some items can’t be imported
Watchlist Button Component
Location:src/components/watchlist-button.tsx
The watchlist button appears throughout the app:
Placement
- Homepage media cards
- Search result cards
- Media detail pages (movie/TV)
- Collection pages
Visual States
- Not in Watchlist
- In Watchlist
- On Watchlist Page
Shows outline bookmark icon.Label: “Add to watchlist”
Toggle Behavior
Implementation:src/hooks/usewatchlist.ts:405-531
- Automatically detects current membership
- Adds if not in watchlist, removes if already present
- Syncs to appropriate storage (local or cloud)
- Provides optimistic UI update
Real-Time Sync for Authenticated Users
Optimistic Updates
All Convex mutations use optimistic updates:- User Action - Clicks watchlist button
- Immediate UI Update - Button state changes instantly
- Backend Mutation - Sent to Convex server
- Success - UI remains updated
- Failure - UI reverts to previous state
src/hooks/usewatchlist.ts:407-483
Multi-Device Sync
For authenticated users:- Changes on one device appear on all devices
- Real-time subscription via Convex queries
- Automatic conflict resolution (last-write-wins)
- No manual refresh needed
Watchlist Card Display
Location:src/routes/watchlist.tsx:434-552
Each card shows:
Visual Info
- Poster thumbnail
- Media type badge (MOVIE/TV)
- Star rating
- Release date
Status Info
- Title (truncated to 2 lines)
- Overview (truncated to 2 lines)
- Progress status badge
- Reaction/mood badge
Card Actions
- Click Poster/Title - Navigate to media detail page
- Click Remove - Remove from watchlist with confirmation
Progress Calculation
For TV shows, progress is calculated based on episode watch tracking:- 0% - Want to watch
- 1-99% - Watching (partial playback)
- 100% - Finished
src/hooks/useWatchProgress.ts
Migration and Legacy Support
The system includes migration logic for legacy statuses: Old status → New status mapping:"plan-to-watch"→progressStatus: "want-to-watch",reaction: null"watching"→progressStatus: "watching",reaction: null"completed"→progressStatus: "finished",reaction: null"liked"→progressStatus: "finished",reaction: "liked""dropped"→progressStatus: "dropped",reaction: null
src/hooks/usewatchlist.ts:132-157
