Clean up settings section design: consolidate backup cards, add
hover-reveal actions with tooltips, improve icon sizing across
components, and replace custom timeAgo helper with date-fns
formatDistanceToNow in webhook card.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-locate `StarRating` and `StatusButton` under the title detail
`_components/` directory since they're only used there. Delete the
unused `SearchAutocomplete` and `SearchBar` components from the shared
`components/` root.
Replaces the 335-line React Context provider with a scoped Jotai store,
giving consumers granular subscriptions and stable handler callbacks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
drizzle-orm/bun-sqlite is fully synchronous — all queries return values
directly, not promises. Remove await from all db calls, drop async from
functions that no longer need it, simplify Promise.all patterns that
wrapped sync operations, and fix setSetting() which was missing .run()
(previously masked by await triggering execution via thenable).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Override CSS custom properties (--primary, --ring, --primary-foreground,
--status-watching) on the title detail page wrapper using the vibrant
color from the poster's node-vibrant palette. All Tailwind classes
referencing these variables automatically pick up the new color.
Also await lazy color extraction in getTitleWithChildren() so palettes
are available on first visit to never-before-opened titles.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The distinction between "Watchlist" and "Watching" added no value — the
progress bar already shows whether you've started. Now there are just two
visible states: Watching (amber) and Completed (green).
Adding a show via "+ Watchlist" now sets in_progress directly, and both
the old watchlist and in_progress DB values render as "Watching" for
backward compatibility.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When marking an episode as watched, if earlier episodes are unwatched,
the success toast now includes a "Catch up" action button that offers
to mark all previous episodes as watched in one click.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Status (watchlist/watching/completed) for TV shows is now automatically
determined by episode progress rather than requiring manual selection from
a dropdown. The only manual action is adding/removing from watchlist —
watching and completed states flow from episode data.
- Decouple markAllEpisodesWatched from setTitleStatus and export it
- Replace status dropdown with toggle button + read-only status badge
- Add "Mark All Watched" button with confirmation dialog for TV shows
- Fix optimistic transitions (watchlist → watching on first episode)
- Simplify W keyboard shortcut to toggle watchlist on/off
- Add optimistic completion check when marking entire seasons
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Optimistically update episodeWatches client state when setting status
to "completed" on a TV title, so episodes render as watched immediately
without requiring a page refresh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove react-hotkeys-hook, KeyboardProvider context, useRegisterShortcut
wrapper, and KeyboardHelpDialog component. Components now call TanStack
useHotkey/useHotkeySequence directly. Shared state uses jotai atoms in
lib/atoms/, shortcut metadata lives as a static const in lib/constants/.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move all title page mutations (status, rating, movie watch, episode
watch/unwatch, season watch/unwatch) from API route handlers to server
actions called directly from TitleInteractionProvider. Same optimistic
update pattern, no behavioral change — just eliminates the fetch()
round-trip through 5 API routes.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace fully client-side dashboard and title detail pages with server
component orchestrators that fetch data directly via service functions,
eliminating extra network round-trips through API routes. Each section
streams independently through its own Suspense boundary.
- Extract getUserStats(), getTitleWithChildren(), getRecommendationsForTitle()
into service layer; update getNewAvailableFeed() to include tmdbId/voteAverage
- Split dashboard into server sections (stats, continue watching, library,
recommendations) with client children for animations
- Split title page into server hero + client interaction provider with shared
context for optimistic mutations across actions and seasons
- Add generateMetadata with OG tags, server-side TMDB ID resolution via
redirect(), loading.tsx and not-found.tsx for both pages
- Add per-section skeleton components, fix ContinueWatchingSkeleton dimensions
- Remove 6 unused API routes (feed/*, titles/[id] GET, titles/[id]/recommendations)
- Remove components/stats-summary.tsx, add lib/types/title.ts for shared types
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Navigate instantly to /titles/tmdb-{id}-{type} and show a skeleton
while the new /api/titles/resolve endpoint handles the full import
(TMDB fetch, availability, recommendations, colors). Existing titles
resolve in ~5ms; the URL is replaced with the UUID once resolved.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove breadcrumb navigation, use full-viewport backdrop technique
matching explore page, and fix mobile hero layout: poster and title
info now sit side-by-side at all sizes with a smaller poster and
scaled-down typography on mobile.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Introduce a local disk cache for TMDB images served through a proxy API
route (/api/images/[...path]), eliminating direct client-side CDN
dependencies. Images are cached by category (posters, backdrops, stills,
logos) and served with immutable cache headers.
Move all tmdbImageUrl() calls from client components to API routes and
server components so clients receive ready-to-use URLs. This removes the
need to expose TMDB_IMAGE_BASE_URL and IMAGE_CACHE_ENABLED via
next.config.ts env block. The landing page is split into a server
wrapper (app/page.tsx) and client component (components/landing-page.tsx).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Extract dominant colors from movie/TV poster images server-side and use
them to create per-title atmospheric effects on detail pages — tinted
backdrop gradients, ambient glow orbs, and colored poster shadows.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Renames the env var for clarity since it holds a read access token, not an API
key. Adds optional TMDB_API_BASE_URL and TMDB_IMAGE_BASE_URL env vars for
advanced users. Centralizes image URL construction into a shared tmdbImageUrl()
helper, replacing hardcoded URLs across all components.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Continue Watching card uses viewport-relative width on mobile (calc(100vw-3rem))
with edge-to-edge scroll container using negative margin trick
- Nav bar hides user name on mobile (bottom tab bar shows it instead)
- Season rows show percentage text on mobile where progress bar is hidden
- Season 5 correctly shows "94%" instead of requiring the hidden progress bar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Episode rows now show still images (thumbnail, hidden on mobile)
- Episode descriptions shown as 2-line clamp below title on desktop
- Watched episodes dimmed with opacity for clear visual differentiation
- Provider logos get CSS hover tooltips showing the service name
- Backdrop gradient strengthened with extra overlay layer for text
readability on bright/colorful backdrop images
- Added stillPath to Episode interface
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Toggle episode watched state, bulk unwatch seasons via new DELETE endpoints,
and replace outer <button> with <div role="button"> in season accordion to
avoid invalid nested button HTML that caused hydration errors.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Move dashboard page to app/(pages)/dashboard, redirect / to /dashboard
for authenticated users, update all nav/breadcrumb/auth hrefs
- Mark all episodes watched when a TV show status is set to "completed"
(markAllEpisodesWatched skips already-watched episodes)
- Refactor KeyboardProvider to store shortcuts in a ref instead of
state, eliminating unnecessary re-renders; use react-hotkeys-hook for
Cmd+K so it works reliably in form inputs
- Fix useRegisterShortcut: re-register on every render (stable ref read)
with a separate cleanup effect, removing brittle key memoization
- Search page: silently navigate to title detail on import instead of
showing "Added to library" toast
Add premium "Late Night Screening Room" experience with warm indigo/amber
palette, framer-motion spring animations, Cmd+K command palette with TMDB
search, keyboard shortcuts (G H, G S, ?, W, M, 1-5), sonner toasts with
optimistic updates, skeleton loading states, stats dashboard, search
autocomplete with filter tabs, cinematic backdrop with film grain, season
progress bars, and staggered card reveal animations throughout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add all 10 milestones: Drizzle ORM + SQLite database with WAL mode,
Better Auth email/password authentication, TMDB API integration for
search and metadata import, TV season/episode caching, user tracking
(watchlist/status/watches/ratings with auto-transitions), discovery
feeds (continue watching, library, recommendations), US streaming
availability via TMDB providers, background job scheduler with
instrumentation hook, and dark cinema-themed frontend with DM Serif
Display + DM Sans typography and amber accent design system.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>