Commit Graph
77 Commits
Author SHA1 Message Date
jakeandClaude Opus 4.6 724893196e Link explore hero title to its title detail page
Wraps the hero banner h2 in a Link with a subtle primary color
hover transition, matching the app's warm-accent interaction pattern.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:40:19 -05:00
jakeandClaude Opus 4.6 2c561a2bf4 Replace CSS overflow scroll with Embla Carousel + wheel gestures plugin
Title rows (TitleRow, ContinueWatchingList) now use the shadcn Carousel
component backed by Embla with embla-carousel-wheel-gestures, enabling
mouse wheel horizontal scrolling and drag-to-scroll on desktop while
preserving touch swipe, stagger animations, and existing layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:37:43 -05:00
jakeandClaude Opus 4.6 eb836eb2c7 Add LOG_LEVEL logger helper with createLogger() utility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 19:25:44 -05:00
jake 8d4628bb94 Move star-rating and status-button to title detail co-location, remove unused search components
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.
2026-03-03 19:13:00 -05:00
jakeandClaude Opus 4.6 6a9c9f22c6 Replace TitleInteractionProvider context with Jotai atoms
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>
2026-03-03 19:09:57 -05:00
jakeandClaude Opus 4.6 d9b408128b Remove unnecessary await/async from sync bun:sqlite db calls
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>
2026-03-03 19:04:40 -05:00
jakeandClaude Opus 4.6 0f345c8cd6 Replace custom setInterval scheduler with croner
Swap the hand-rolled Scheduler class (setInterval-based) for croner,
a battle-tested cron library with overlap protection and proper cron
expressions. Consolidate lib/jobs/ into a single lib/cron.ts and
remove the unused admin jobs API route.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 18:52:05 -05:00
jakeandClaude Opus 4.6 2ee20a6aff Tint title page accent colors to poster's vibrant color
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>
2026-03-03 17:42:02 -05:00
jakeandClaude Opus 4.6 5f45214d07 Move "Mark All Watched" button to Seasons heading
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:43:15 -05:00
jakeandClaude Opus 4.6 6bf68b332f Merge watchlist and watching into a single "Watching" status
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>
2026-03-03 16:38:17 -05:00
jakeandClaude Opus 4.6 01f6fa5f0e Fix sticky header by moving overflow-x-hidden to body
overflow-x-hidden on the pages wrapper div created a new scroll
context that prevented position: sticky from working on the header.
Moving it to <body> preserves the horizontal overflow clipping
without breaking sticky positioning.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:29:58 -05:00
jakeandClaude Opus 4.6 c9fc21e8db Add search trigger to mobile header, hide settings/sign-out on mobile
The search modal was inaccessible on mobile since the trigger was
desktop-only. Replace the settings and sign-out buttons (already
available in the mobile tab bar) with a full-width search bar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:26:36 -05:00
jakeandClaude Opus 4.6 e376367fe9 Add catch-up prompt when marking episodes with unwatched predecessors
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>
2026-03-03 16:19:09 -05:00
jakeandClaude Opus 4.6 a940e61d75 Derive TV show status from episode watch data instead of manual selection
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>
2026-03-03 16:14:21 -05:00
jakeandClaude Opus 4.6 adc0431c7f Add subtle ring and round image corners on continue watching cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:57:32 -05:00
jakeandClaude Opus 4.6 2fb329e0dc Switch from Node.js + pnpm to Bun runtime and package manager
Replace @libsql/client with bun:sqlite for zero-dependency SQLite access,
swap drizzle-orm/libsql adapter for drizzle-orm/bun-sqlite, and rewrite
Dockerfile to use oven/bun:1-alpine. The raw Database instance is no longer
exported via Proxy (native C++ methods lose `this` binding through
Reflect.get); instead, a closeDatabase() helper handles graceful shutdown
and the health check uses Drizzle's db.run() directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:17:57 -05:00
jakeandClaude Opus 4.6 ed6e061436 Introduce DATA_DIR env var for consistent data path configuration
Replace separate DATABASE_URL and IMAGE_CACHE_DIR env vars with a
single DATA_DIR root (default: ./data, Docker: /data). DATABASE_URL
and CACHE_DIR are derived from it but can still be overridden
individually.

Also:
- Pin pnpm to @10 for reproducible Docker builds
- Add --link to COPY instructions for better BuildKit cache reuse
- Add syntax directive for BuildKit features
- Simplify Dockerfile mkdir to just /data (ensureImageDirs handles subdirs)
- Remove redundant DATABASE_URL from docker-compose.yml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 14:26:08 -05:00
jakeandClaude Opus 4.6 fb785645f8 Add optional OIDC authentication via Better Auth genericOAuth plugin
Support self-hosted OIDC providers (Authentik, Authelia, Keycloak, etc.)
configured entirely via environment variables. Uses Better Auth's
hooks.before to gate email/password sign-up at the endpoint level, and
disables emailAndPassword entirely when DISABLE_PASSWORD_LOGIN is set.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:39:15 -05:00
jake e19ac78b39 Add robots.txt to disallow all crawlers 2026-03-03 13:28:12 -05:00
jakeandClaude Opus 4.6 0d272700f9 Lazy-init DB client to fix Docker build failure
The DB client was initialized at import time, which caused Next.js page
data collection to fail during Docker builds where no database exists.
Wrapping in Proxy defers createClient() until first actual use.

Also adds data/ to .dockerignore.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:21:00 -05:00
jakeandClaude Opus 4.6 64351cb896 Redirect authenticated users from landing page to dashboard
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 13:00:06 -05:00
jakeandClaude Opus 4.6 1a5a1e631c Use GIT_COMMIT_SHA env var instead of execSync for Docker compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:56:11 -05:00
jake ecc6a942a9 Move media server icons into settings and polish webhook card
Co-locate PlexIcon/JellyfinIcon under settings/_components/icons.tsx.
Update Plex icon to CoreUI Brands SVG with corrected viewBox. Add
animated height transitions to collapsible panels, fix open-state
padding, and turn the Plex Pass notice into a linked external reference
with an inline icon.
2026-03-03 12:49:23 -05:00
jakeandClaude Opus 4.6 03917e0a1d Add version and commit info footer to settings page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:17:36 -05:00
jakeandClaude Opus 4.6 472e5609e3 Add remove/clear buttons for search history in command palette
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:12:43 -05:00
jakeandClaude Opus 4.6 906e6f76ea Redesign title cards with proper padding and icon-based metadata
Replace text-based MOVIE/TV badges with Tabler icons (IconMovie, IconDeviceTv),
add proper padding to the metadata section below posters, use IconStarFilled for
ratings, and add subtle image hover zoom. Update skeleton and search autocomplete
to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 12:03:11 -05:00
jakeandClaude Opus 4.6 17c6b23e23 Fix nav bar flash of sign-in/register buttons on load
The pages layout already guards auth server-side, so the unauth
branch in NavBar was dead code causing a flash while useSession loaded.
Render nav links and controls unconditionally instead.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:55:27 -05:00
jakeandClaude Opus 4.6 6f415ad0b2 Fix episodes not showing as watched when marking title completed
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>
2026-03-03 11:51:46 -05:00
jakeandClaude Opus 4.6 ec256b7a96 Replace keyboard system with TanStack Hotkeys and jotai atoms
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>
2026-03-03 11:48:14 -05:00
jake d5e985efd9 Add auth guards to search and discover API routes 2026-03-03 11:13:14 -05:00
jakeandClaude Opus 4.6 f6ee32ca1e Add confirmation toasts to settings actions and fix webhook card spacing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 11:10:28 -05:00
jakeandClaude Opus 4.6 8487c3d7b1 Convert settings page to server component with server actions
Replace client-side data fetching with server-side queries and server
actions, eliminating 3 API route files. Extract page into granular
client components (account, integrations, server, webhook card) with
optimistic updates. Rename sections: Media Servers → Integrations,
Administration → Server. Add admin badge to account section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 09:32:18 -05:00
jakeandClaude Opus 4.6 83e2bc5143 Make media server cards collapsible with matching input/button heights
Wrap Plex and Jellyfin webhook cards in collapsible sections with chevron
indicators. Fix input/button height mismatch by using default button sizes
(h-7) instead of lg (h-8). Add Plex Pass requirement info box and move
the enable/disable switch into the collapsible body.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:55:00 -05:00
jakeandClaude Opus 4.6 f16ccc2f3f Replace title mutation API routes with server actions
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>
2026-03-02 15:47:04 -05:00
jakeandClaude Opus 4.6 c1867a4f23 Convert dashboard and title pages to server components with granular Suspense
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>
2026-03-02 15:42:42 -05:00
jakeandClaude Opus 4.6 081f53beac Add instant navigation from search results via TMDB IDs in URL
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>
2026-03-02 15:13:51 -05:00
jakeandClaude Opus 4.6 18385386d5 Add SQLite connectivity check to healthcheck endpoint
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:59:52 -05:00
jakeandClaude Opus 4.6 3455ff9c51 Replace custom CSS tooltips with shadcn Tooltip component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:50:27 -05:00
jakeandClaude Opus 4.6 e89397386c Add TMDB logo link to title page hero metadata
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:44:09 -05:00
jake 076108d82a Rename middleware.ts to proxy.ts and export as proxy function 2026-03-02 14:39:56 -05:00
jake f94ceaf233 Move auth protection to middleware and server layout
Replace client-side session checks (useSession + router.replace) with
a two-layer server-side approach: Next.js middleware performs a fast
cookie presence check for all protected routes, and the (pages) layout
does a full server-side session validation before rendering.

Move login/register into a dedicated (auth) route group with its own
minimal layout so they sit outside the authenticated shell. Lift the
Toaster to the root layout so it remains available across all route
groups.
2026-03-02 14:38:54 -05:00
jake c47fe97733 Shift theme base from cool indigo to warm charcoal
Replace the cool indigo hue (270) across all CSS custom properties with
a warm charcoal base (hue 55), giving the dark cinema palette a subtler
amber-tinted warmth. Adjust lightness and chroma values on borders,
inputs, and foreground tokens for improved contrast against the new
base.
2026-03-02 14:27:32 -05:00
jakeandClaude Opus 4.6 936c2e3c8a Apply full-bleed backdrop and fix mobile layout on title pages
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>
2026-03-02 14:19:28 -05:00
jakeandClaude Opus 4.6 978ebfbf3a Fix hero backdrop to span full viewport width on wide screens
Use full-bleed margin technique to break out of max-w-6xl container,
add explicit w-full to prevent aspect-ratio from shrinking the image
container when max-height kicks in, and add overflow-x-hidden to the
page wrapper to prevent scrollbar from 100vw units.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 14:06:48 -05:00
jakeandClaude Opus 4.6 5a2d3fa473 Await recommendations fetch during title import
Recommendations were fetched fire-and-forget, causing a race condition
where the title detail page would load before recommendations were
populated in the database, making the section appear intermittently.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:55:57 -05:00
jakeandClaude Opus 4.6 8c31784094 Replace Sofa text branding with sofa icon logo
Swap the "Sofa" wordmark for an SVG sofa icon across the nav bar,
auth forms, and landing page. Add SVG favicon replacing the old .ico.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:53:37 -05:00
jake 2f0fe02d26 Fix color extraction and image cache setup in development
Move `ensureImageDirs` outside the production-only block so the local
image cache directories are created in dev mode too. Update
`extractAndStoreColors` to download the poster if it isn't cached yet
instead of falling back to a direct TMDB CDN URL, keeping color
extraction consistent with the server-side image routing approach. Add
`/data` to `.gitignore` to avoid committing the local cache directory.
2026-03-02 13:49:57 -05:00
jakeandClaude Opus 4.6 8eb1007612 Fix stale TMDB poster paths on landing page
Replace 4 broken poster paths (The Dark Knight, Interstellar, Breaking
Bad, The Office) and correct the Forrest Gump path which was incorrectly
using Interstellar's poster.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 13:16:27 -05:00
jakeandClaude Opus 4.6 ee723e41d6 Add TMDB image caching pipeline and resolve image URLs server-side
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>
2026-03-02 13:10:17 -05:00
jakeandClaude Opus 4.6 cb0f366d44 Add dynamic color theming from poster art using node-vibrant
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>
2026-03-02 12:29:13 -05:00