Commit Graph
26 Commits
Author SHA1 Message Date
jake 1798107afc feat: add public-api app and route update checks through it
- Add `apps/public-api` (`@sofa/public-api`) — minimal Hono app
  deployable to Vercel; `GET /v1/version` fetches the latest GitHub
  release and returns `{ version, releaseUrl }` with a 15-minute
  CDN cache (`s-maxage=900, stale-while-revalidate=3600`)
- Update `packages/core/src/update-check.ts` to call
  `PUBLIC_API_URL/v1/version` instead of the GitHub API directly;
  `PUBLIC_API_URL` defaults to `https://public-api.sofa.watch`
2026-03-13 19:53:51 -04:00
jake e7ffceb278 refactor(native): replace custom toast implementation with burnt
Swap the hand-rolled `ToastView`/`ToastProvider`/queue system for the
`burnt` library, which delegates to native iOS/Android toast APIs.

- Add `burnt@0.13.0` dependency
- Delete `toast.tsx` and `toast-provider.tsx`; remove `<ToastProvider />`
  from `_layout.tsx`
- Rewrite `lib/toast.ts` to wrap `burnt` — maps success/error/info/warning
  to burnt presets and haptic types; duration converted from ms to seconds
2026-03-13 19:09:48 -04:00
jake b4c2a1d343 feat(native): add recently viewed history to search screen
Track titles and people visited in the native app and surface them
in the search tab when the query is empty, replacing the static
empty-state icon.

- Add `lib/recently-viewed.ts` — MMKV-backed store (max 20 items)
  with `addRecentlyViewed`, `removeItem`, `clearAll`, and a
  `useRecentlyViewed` hook
- Track visits via `useEffect` in `title/[id].tsx` and
  `person/[id].tsx` once data resolves
- `RecentlyViewedList` — platform-split component:
  - iOS (`.ios.tsx`): native SwiftUI List via `@expo/ui` with
    system SF Symbols, swipe-to-delete, and a "Clear" button
  - Android/fallback: FlatList with `SwipeableRow` for delete
- Add `SwipeableRow` component using `ReanimatedSwipeable`
- Clear recently viewed history on sign-out (both `HeaderAvatar`
  and Settings screen)
- Add `@expo/ui@55.0.2` dependency
2026-03-13 19:08:13 -04:00
jake 35bdfc171e refactor(native): replace runOnJS with scheduleOnRN and custom timeAgo with date-fns
- Swap `runOnJS` for `scheduleOnRN` from `react-native-worklets` in
  gesture handler `.onEnd()` callbacks (HeroBanner, PosterCard)
- Delete custom `timeAgo` util; use `date-fns` `formatDistanceToNow`
  in integration status strings
- Enhance person detail screen: formatted birth date with age/death
  calculation, place of birth with MapPin icon, department label
  mapping, CalendarIcon for dates
- Fix `ExpandableText` truncation detection by measuring line count
  on a hidden full-text node rather than the clamped one; add
  `actionColor` prop so title accent color can theme the toggle
- Polish PosterCard metadata row: remove icon opacity, tighten gaps
2026-03-13 18:11:05 -04:00
jake b6eb53e9b8 fix(deps): mismatched/multiple react versions 2026-03-13 16:28:13 -04:00
jake c021b8157b chore: bump deps 2026-03-13 15:55:25 -04:00
jake 912b2766ff feat(native): use App Tracking Transparency to gate PostHog analytics (#10) 2026-03-13 12:30:38 -04:00
jake c5d12482e2 feat(native): port title page color tinting from web to mobile (#11) 2026-03-13 11:19:24 -04:00
jake 83839a0cd7 Add @sofa/native Expo React Native app (#7) 2026-03-12 19:39:54 -04:00
jakeandClaude Opus 4.6 a326c968b7 Migrate web app from Next.js to Vite + TanStack Router SPA (#6)
* Convert to Turborepo monorepo with shared API contract package

Restructure the repository as a monorepo in preparation for adding
future clients (mobile app, CLI). Extract the oRPC contract and Zod
schemas into `@sofa/api` (packages/api/) as a JIT internal package,
and relocate the Next.js app to `@sofa/web` (apps/web/).

- Add Turborepo with Bun workspaces for task orchestration and caching
- Extract `contract.ts` and `schemas.ts` into `@sofa/api` package
- Move all app code, configs, tests, and migrations to `apps/web/`
- Update 17 import paths from `@/lib/orpc/schemas` to `@sofa/api/schemas`
- Add `outputFileTracingRoot` and `transpilePackages` to next.config.ts
- Rewrite Dockerfile with `turbo prune --docker` for efficient builds
- Update CI workflows to use `turbo run` for lint/check-types/test
- Update CLAUDE.md with monorepo structure and commands

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Extract standalone Hono API server and split shared packages

Separate all server-side concerns from the Next.js frontend into a new
`apps/server/` Hono app and dedicated shared packages, making `@sofa/web`
a frontend-only app with no direct DB or service access.

- Add `@sofa/server` (`apps/server/`) — Hono API on port 3001 hosting
  oRPC procedures, Better Auth, cron jobs, and non-RPC routes
- Add `@sofa/core` (`packages/core/`) — All 15 business logic services
  moved from `apps/web/lib/services/`; tests moved to `packages/core/test/`
- Add `@sofa/db` (`packages/db/`) — DB client, schema, migrations,
  constants, and logger extracted from `apps/web/lib/db/` and `lib/`
- Add `@sofa/tmdb` (`packages/tmdb/`) — TMDB client and image helpers
  moved from `apps/web/lib/tmdb/`
- Add `@sofa/auth` (`packages/auth/`) — Better Auth server config moved
  from `apps/web/lib/auth/`
- Move oRPC procedures, handler, router, middleware to `apps/server/src/orpc/`
- Move Hono route handlers (avatars, backups, images, lists, webhooks,
  health) to `apps/server/src/routes/`; delete equivalent Next.js API routes
- Strip `apps/web` to frontend-only: no DB imports, no service imports,
  all data via oRPC client calls to the API server
- Add `entrypoint.sh` to start API server, wait for health, then Next.js
- Update `next.config.ts` rewrites to proxy `/rpc/*` and `/api/*` to
  `INTERNAL_API_URL` (default `http://localhost:3001`)
- Update Dockerfile and CLAUDE.md for the new structure

* Migrate web app from Next.js to Vite + TanStack Router SPA and add workspace catalog

Replace Next.js with a pure Vite SPA using TanStack Router for file-based routing,
removing all SSR complexity. The API server (Hono) now serves both API routes and
SPA static files in production, simplifying Docker to a single-process container.

Key changes:
- Vite 7 + @tanstack/react-router with file-based routing via plugin
- Route guards via beforeLoad + authClient.getSession() (replaces server-side auth)
- Route loaders with queryClient.ensureQueryData() (replaces SSR data fetching)
- Self-hosted fonts via @fontsource (replaces next/font/google)
- Tailwind v4 via @tailwindcss/vite (replaces @tailwindcss/postcss)
- Single oRPC client (removed SSR client and server-side session helper)
- Hono serves SPA static files in production (single port 3000)
- Single-process Dockerfile (removed entrypoint.sh)
- Bun workspace catalog for centralized dependency version management

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Extract @sofa/logger and @sofa/config shared packages

- Add `@sofa/logger` (`packages/logger/`) — standalone logger package
  extracted from `@sofa/db/logger`; update all imports across server,
  core, auth, db, and tmdb packages
- Add `@sofa/config` (`packages/config/`) — standalone config/constants
  package extracted from `@sofa/db/constants`; exports `DATA_DIR`,
  `DATABASE_URL`, `CACHE_DIR`, `AVATAR_DIR`, `BACKUP_DIR`
- Move `.env.example` from `apps/web/` to repo root; update server dev
  scripts to load it via `--env-file=../../.env`
- Move image serving from `/api/images` to `/images`; add `serveStatic`
  fast path in `index.ts` for cached files before falling back to the
  TMDB fetch route; add `/images` proxy to Vite dev config
- Fix `Sparkline` component: replace `ResponsiveContainer` with
  `ResizeObserver` to avoid SSR/hydration issues with recharts
- Replace `VITE_SERVER_URL` env var with `window.location.origin` in
  the oRPC client (always same-origin in both dev and production)

* Fix asset caching, SPA 404 fallback, and DATA_DIR resolution

- Add `Cache-Control: immutable` header for hashed `/assets/*` files;
  return 404 for missing asset paths instead of falling back to
  `index.html` (prevents serving stale chunks after deploy)
- Wrap `query.invalidate` in an arrow function in the oRPC QueryClient
  error handler to avoid illegal invocation errors
- Resolve `DATA_DIR` to an absolute path via `path.resolve()` so
  relative paths work regardless of the process working directory

* Migrate @sofa/logger to pino for structured logging

- Replace custom logger implementation in `packages/logger/` with pino
  + pino-pretty; add both as workspace catalog dependencies
- Add `pino` and `pino-pretty` to the workspace catalog in `package.json`
- Fix `log.error()` calls in oRPC and OpenAPI handlers to pass the
  error directly instead of wrapping it in `{ error }` to match pino's
  serializer expectations

* Rename discoverProcedure/statsProcedure exports to discover/stats

* Add TanStackDevtools unified panel and VS Code workspace config

- Replace separate Router/Query devtools with unified `TanStackDevtools`
  from `@tanstack/react-devtools` + `@tanstack/devtools-vite` plugin
- Wrap app in `<StrictMode>` in `main.tsx`
- Add `.vscode/settings.json` (Biome formatter, format-on-save, readonly
  `routeTree.gen.ts`) and `.vscode/extensions.json` (recommended extensions)

* Move test DB helpers to @sofa/db/test-utils and add root bunfig.toml

Extract in-memory SQLite setup and fixture helpers (insertUser, insertTitle,
etc.) from packages/core/test/sqlite.ts into packages/db/src/test-utils.ts
so DB test utilities live alongside the schema they depend on. Use
import.meta.dir for CWD-independent migration path resolution.

Add root bunfig.toml so `bun test` works from the repo root in addition
to `bun run test` (turbo).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Fix devtools plugin order and whitespace-only TMDB token check

Move devtools() to first position in Vite plugins array per TanStack
docs, and trim TMDB_API_READ_ACCESS_TOKEN before boolean coercion so
whitespace-only values are treated as unconfigured.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 16:50:34 -04:00
jakeandClaude Opus 4.6 26558b29e4 Migrate from server actions & REST APIs to oRPC for type-safe RPC (#5)
* Replace server actions with API routes and migrate SWR to TanStack Query

- Add 33 new REST API routes covering titles, episodes, seasons, people,
  dashboard, explore, integrations, admin, backups, and account operations
- Replace SWR with @tanstack/react-query: add QueryProvider, api-client
  helper, query-client singleton, and TanStack Query hooks for titles
- Migrate all 26 server actions to fetch calls against the new API routes
  in 10 client components (use-title-actions, title-card, command-palette,
  hero-banner, integration-card, account/backup/registration/update-check
  settings sections)
- Delete lib/actions/ directory (titles.ts, watchlist.ts, people.ts,
  settings.ts) and lib/swr/fetcher.ts

https://claude.ai/code/session_01L6gF2sqqe6gw5bemZUnRBb

* Convert pages to TanStack Query client-side data fetching

Migrate dashboard, explore, settings, and people pages from server
component data fetching to client-side TanStack Query hooks. Create
query hook files for each domain (dashboard, explore, people, admin,
integrations). Add GET routes for admin/registration and admin/update-check.
Update CLAUDE.md architecture docs to reflect TanStack Query patterns.

https://claude.ai/code/session_01L6gF2sqqe6gw5bemZUnRBb

* Migrate API layer from REST routes to oRPC with TanStack Query

Replace ~35 hand-written REST API routes and manual TanStack Query hooks
with a contract-first oRPC setup that provides end-to-end type safety
from contract → server procedures → client → TanStack Query.

- Add oRPC foundation: contract, context, auth/admin middleware, RPCHandler,
  client (browser + SSR via globalThis), TanStack Query utils
- Implement ~30 procedures across 14 files (titles, episodes, seasons,
  people, dashboard, explore, search, discover, stats, status,
  integrations, admin, account, watchlist)
- Migrate all client components from api()/lib/queries/ to
  orpc.*.queryOptions() and client.*.method() calls
- Delete all replaced REST API routes, lib/queries/, lib/api-client.ts,
  and old hooks (use-discover, use-search, use-stats, use-system-health)
- Wire up SSR optimization via instrumentation.ts
- Keep non-RPC routes: auth, images, avatars, backup restore/download,
  webhooks, lists, health

https://claude.ai/code/session_01L6gF2sqqe6gw5bemZUnRBb

* Add strict Zod output schemas to all oRPC contract procedures

Replaces the permissive z.any() output schemas with precise Zod schemas
for every procedure that returns data. This makes the contract fully
self-documenting and enables runtime output validation.

- Define ~30 output schemas in schemas.ts covering titles, people,
  dashboard, explore, search, discover, stats, integrations, admin, etc.
- Wire all output schemas into contract.ts
- Fix procedure return type mismatches (nullable fields, enum narrowing)
- Align consumer types (SearchResult, IntegrationConnection) with contract

https://claude.ai/code/session_01L6gF2sqqe6gw5bemZUnRBb

* Delete lib/types.ts — infer all types from Zod schemas

The oRPC Zod schemas are now the single source of truth for domain types.
All 17 consumer files (services, components, atoms, utils) now import
inferred types (Episode, Season, ResolvedTitle, CastMember, etc.) from
lib/orpc/schemas instead of hand-written interfaces in lib/types.ts.

https://claude.ai/code/session_01L6gF2sqqe6gw5bemZUnRBb

* Migrate all mutations to TanStack Query useMutation

Replace raw oRPC client calls with useMutation/mutateAsync via
orpc.*.mutationOptions() for consistent mutation tracking across
all components: title-card, command-palette, account, system-health,
integrations, backups, backup-schedule, and title-actions.

https://claude.ai/code/session_01L6gF2sqqe6gw5bemZUnRBb

* Fix bugs in settings/explore and bump dependencies

- Deduplicate backup list on create (filter by filename before prepend)
- Add isError state to backup-schedule section with fallback UI
- Add error check for avatar DELETE response before clearing state
- Strengthen backup restore file guard with `instanceof File`
- Change explore trending default type from "movie" to "all"
- Pin all oRPC and TanStack Query packages to exact versions
- Bump jotai 2.18.0→2.18.1, motion 12.35.1→12.35.2,
  shadcn 4.0.0→4.0.2, @types/node 25.3.5→25.4.0,
  semver 6.3.1→7.7.4

* Migrate title recommendations to client-side TanStack Query

- Convert TitleRecommendations from server component with `"use cache"`
  to a client component using `orpc.titles.recommendations.queryOptions()`
  with an inline skeleton loading state
- Delete recommendations-grid.tsx — merged into title-recommendations.tsx
- Remove RecommendationsSkeleton from skeletons.tsx (now inline)
- Drop Suspense wrapper in title detail page (client component handles
  its own loading state)
- Include userStatuses in recommendations procedure output so the
  component no longer needs a separate session/tracking lookup
- Remove `revalidate` option and all `updateTag`/`cacheTag`/`cacheLife`
  calls from refreshCredits and refreshRecommendations — no longer
  needed without "use cache" server components
- Remove `cacheComponents: true` from next.config.ts

* Replace Jotai title atoms with React Context + TanStack Query cache

- Add title-context.tsx with TitleContext and useTitleContext /
  useTitleUserInfo hooks; useTitleUserInfo reads from
  orpc.titles.userInfo cache instead of atoms
- Rewrite TitleProvider to seed the userInfo cache via
  queryClient.setQueryData on mount (unconditional overwrite
  prevents stale data on revisit / account switch) and provide
  titleId, titleType, titleName, seasons, and watchingEp via
  context
- Rewrite use-title-actions to read/write userInfo via
  queryClient.getQueryData / setQueryData helpers instead of
  useStore + individual atoms; optimistic updates and rollbacks
  now operate on a single UserInfo object in the query cache
- Migrate title-actions, title-keyboard-shortcuts, and
  title-seasons off useAtomValue / useSetAtom to the new
  context hooks
- Delete per-title atoms from lib/atoms/title.ts

* Migrate avatar and backup restore routes to oRPC procedures

- Add `account.uploadAvatar` and `account.removeAvatar` procedures
  (FormData file input, auth middleware) replacing `PUT`/`DELETE`
  `/api/account/avatar` route
- Add `admin.backups.restore` procedure (File input, admin middleware)
  replacing `POST /api/admin/backups/restore` route
- Delete both REST route files
- Migrate account-section and backup-restore-section off
  `useTransition` + raw `fetch` to `useMutation` via
  `orpc.*.mutationOptions()`
- Rename `lib/utils/title-theme.ts` → `lib/theme.ts` and
  `getTitleThemeStyle` → `getThemeCssProperties`; update title
  detail page import accordingly

* Add OpenAPI v1 REST API endpoint via @orpc/openapi

- Add `@orpc/openapi` and `@orpc/json-schema` dependencies
- Add `lib/orpc/openapi-handler.ts` with OpenAPIHandler instance
- Add `app/api/v1/[[...rest]]/route.ts` catch-all serving GET/POST/PUT/DELETE
  at `/api/v1` with request-headers context
- Annotate contract procedures with OpenAPI metadata (tags, summaries,
  descriptions, and successStatus codes) across all ~30 procedures

* Fix 8 bugs: memory safety, data races, and correctness issues

- Move queryClient.setQueryData from useState initializer to useEffect
  to avoid mutating the query cache during React render phase
- Add SQL LIMIT to per-integration event queries instead of loading all
  events and trimming in memory
- Lower backup restore upload limit from 500 MB to 100 MB and stream
  upload to disk instead of buffering entirely in memory
- Reorder avatar upload to write new file before deleting old one so a
  failed upload doesn't remove the current avatar
- Make optimistic rollback field-specific so a failed rating update
  doesn't erase concurrent status or episode-watch changes
- Gate titles.userInfo query behind session state to prevent failing
  RPC calls for anonymous users
- Derive OpenAPI session cookie name from BETTER_AUTH_URL so HTTPS
  deployments use the correct __Secure- prefixed cookie

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* Add explicit z.void() output schemas to all 17 void procedures

Ensures the OpenAPI spec correctly generates 204 No Content responses
for mutation endpoints that don't return data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-10 10:26:26 -04:00
jake 5fdc7ebd97 Revert "Persist dashboard periods and explore genre filters in URL via nuqs"
This reverts commit 2bb5af042b.
2026-03-08 15:14:03 -04:00
jake 2bb5af042b Persist dashboard periods and explore genre filters in URL via nuqs
- Add `nuqs` dependency; define `dashboardSearchParams` and
  `exploreSearchParams` loaders in new `search-params.ts` files
- Thread `moviePeriod`/`episodePeriod` from URL into `StatsSection`
  and `StatsDisplay`, replacing local `useState` with `useQueryStates`
- Rename `getUserStats` fields `moviesThisMonth`/`episodesThisWeek` →
  `movieCount`/`episodeCount` and accept period args so SSR stats
  match the active URL params
- Pre-fetch genre-filtered results server-side on the explore page
  when `movieGenre`/`tvGenre` params are present; pass as
  `initialGenreItems` to `FilterableTitleRow` so first paint is
  already populated
- Render genre chips as `<Link>` elements (with click-handler
  fallback) so genre selections produce shareable, bookmarkable URLs
2026-03-08 14:32:09 -04:00
jake 0838e5fb74 Replace hand-written TMDB types with openapi-fetch + generated schema
- Add `openapi-fetch` dependency; generate `lib/tmdb/schema.d.ts` from
  TMDB's OpenAPI spec and delete `lib/tmdb/types.ts`
- Rewrite `lib/tmdb/client.ts` to use the typed fetch client against
  the generated schema, exporting `TmdbMovieDetails`, `TmdbTvDetails`,
  `TmdbVideo`, and `TmdbGenre` directly
- Fix null-safety across services (`credits`, `availability`,
  `metadata`, `person`, `webhooks`) and call sites (explore page,
  search route, explore actions) to handle optional fields produced
  by the stricter generated types
- Update `metadata.ts` and `metadata.test.ts` to import shared types
  from `@/lib/tmdb/client` instead of the removed `types.ts`
- Exclude `lib/tmdb/schema.d.ts` from Biome linting
2026-03-08 14:07:32 -04:00
jake 02466e94f9 Replace Carousel with ScrollArea, remove embla-carousel dependency
- Delete carousel.tsx and replace all Carousel/CarouselContent/CarouselItem
  usage across title rows, cast carousel, continue watching, and explore
  sections with ScrollArea + scrollFade horizontal scroll
- Update ScrollArea to accept a `scrollFade` prop that renders a
  gradient edge overlay instead of the old per-site absolute div hack
- Remove "use client" directive from title-row and cast-carousel now
  that they have no client-side hooks
- Delete unused resizable.tsx; inline TypeBadge icon into TitleHero
  and delete the standalone type-badge.tsx file
- Add TitleTheme component for per-title accent color CSS injection
- Swap IconSparkles → IconThumbUp in recommendations section and grid
- Simplify ambient glow to a single size (remove mobile breakpoint override)
2026-03-08 09:53:12 -04:00
jake 7ed172d675 Replace Jotai atoms with local state, add Suspense for PPR compatibility
- Delete filterable-row, backup-schedule, integrations, and system-health
  atom files; replace with useState/useTransition + server action calls
- Replace /api/explore/discover route with discoverByGenre server action;
  refactor FilterableTitleRow to call it directly via useTransition
- Wrap PagesLayout and AuthLayout children in Suspense to fix dynamic
  rendering errors during PPR static generation; remove StoreProvider
- Sequence ExplorePage session fetch before TMDB calls to prevent
  build-time requests during static generation
- Drop unnecessary "use client" directives from dashboard and person
  components that have no client-side hooks or browser API usage
- Improve Dockerfile: add bun install layer cache mount, copy
  .next/cache from builder, reorder ENV declarations
2026-03-07 16:00:19 -05:00
jake ccd482db4e Update StarRating animation to simplify scale transition logic 2026-03-07 13:30:58 -05:00
jake c61d04f700 Bump drizzle-orm and drizzle-kit to beta.16 2026-03-05 16:40:03 -05:00
jakeandClaude Opus 4.6 9ecf7bbe3c Add watch trailer button with cinematic YouTube player dialog
Fetch trailer video keys from TMDB videos API during title import/refresh,
store in DB, and display a "Trailer" button on title pages that opens a
media-chrome Sutro-themed YouTube player in a fullwidth dialog.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 13:36:38 -05:00
jakeandClaude Opus 4.6 59e9dd6775 Add SWR for system health and command palette search fetching
Replace manual fetch/useState/useEffect patterns with SWR hooks in
system health section and command palette search, gaining automatic
caching, request deduplication, and stale-while-revalidate.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 12:24:23 -05:00
jake dfc0f67527 Migrate component state to Jotai atoms across settings, explore, and dashboard
Replace ad-hoc useState/useEffect fetch patterns with Jotai atoms and
loadables in StatsDisplay, FilterableTitleRow, BackupScheduleSection,
IntegrationsSection, and CommandPalette. Each component now gets a
scoped Jotai Provider with a pre-initialized store so server-rendered
initial values hydrate correctly. Async data fetching moves into
atom-level loadables, eliminating manual loading flags and cancellation
logic throughout.
2026-03-05 10:09:47 -05:00
jakeandClaude 83456c6a21 Migrate from Node.js built-ins to Bun-native APIs (#1)
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-04 12:49:38 -05:00
jakeandClaude Opus 4.6 36afbcb8c0 Add sparkline charts to stats cards, use rolling periods, upgrade recharts to v3
Background sparkline area charts on Movies and Episodes cards show watch
activity distribution over the selected period. Periods now use rolling
windows (past 24h/7d/30d/365d) instead of calendar boundaries. Bumped
stats grid breakpoint to lg. Upgraded recharts 2.15.4 → 3.7.0 and fixed
chart.tsx types for v3 (TooltipContentProps, LegendPayload).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 21:13:43 -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 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 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