This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
At the start of every session, read the files in /memory/ to restore context:
memory/user.md— who the user is, their background and working stylememory/preferences.md— coding and workflow preferences for this projectmemory/decisions.md— past architectural and technical decisionsmemory/people.md— collaborators and stakeholders
At the end of every session (when the user says goodbye, wraps up, or asks to end), update the relevant memory files with anything learned during the session: new decisions made, preferences expressed, feedback given, or new information about people involved.
Questarr is a video game management app inspired by the *Arr ecosystem (Sonarr, Radarr). Users discover, track, and download games via automated indexer search and download client integration. Dark-themed UI built around visual game covers.
Questarr is for self-hosting game collectors and power users who manage discovery, tracking, and downloads from a browser-based dashboard. On mobile, they are often checking status, searching for a title, reviewing availability, or triggering a quick action while away from their desk. The primary mobile job to be done is fast, confident control of the library from a phone in portrait orientation, with tablet support as a secondary context.
Questarr should feel focused, capable, and trustworthy. The current product language already points to a privacy-conscious, dark-first, cover-led interface built for enthusiasts; on mobile that should translate into a fast and utility-first experience rather than a decorative or playful one. The emotional goal is confidence through clarity: users should feel that core actions are always within reach and never fragile on small screens.
Keep the dark-first, media-rich Questarr identity, but adapt it into a denser thumb-friendly control surface for phones. Preserve strong cover art, blue primary accents, and clear status color cues, while reducing visual chrome, avoiding cramped multi-column layouts, and prioritizing stacked flows, sticky context, and touchable surfaces. Mobile should feel like a deliberate field console for managing a game library, not a scaled-down desktop admin panel.
- Thumb-first navigation: primary actions and key navigation must stay reachable and comfortable with one-handed use.
- Speed over ornament: mobile layouts should surface the next useful action immediately and avoid decorative complexity that slows scanning.
- Preserve capability: core desktop functionality must remain available on mobile, but reorganized through progressive disclosure instead of compression.
- Cover-led clarity: game art and status signals should remain the visual anchor, with metadata condensed into scannable layers.
- Touch-safe density: interfaces can stay information-rich, but all controls need forgiving spacing and touch targets sized for real phones.
npm run dev # Dev server with hot reload (port 5000)
npm run build # Production build: Vite (client) + tsc (server/shared)
npm start # Run production server from dist/
npm run check # TypeScript type checking (no emit)
npm run lint # ESLint
npm run lint:fix # ESLint with auto-fix
npm run format # Prettier format all files
npm run format:check # Prettier check only
npm test # Vitest watch mode
npm run test:run # Run all tests once
npm run test:coverage # Coverage report (v8, HTML output)
npm run test:e2e # Playwright E2E tests (requires dev:test running)
npm run dev:test # Dev server with test DB on port 5100
# Run a single test file
npx vitest run server/__tests__/api_routes.test.ts
# Run tests matching a name pattern
npx vitest -t "pattern"
npm run db:generate # Generate Drizzle migration from schema changes
npm run db:migrate # Run pending migrations
npm run db:push # Push schema directly (dev only)Three-layer TypeScript app with a single package.json (not a monorepo):
/client/src— React 18 SPA. Wouter routing, TanStack React Query for server state, shadcn/ui + Radix primitives, Tailwind CSS 4. Pages are code-split withReact.lazy. Pages: library, discover, search, wishlist, calendar, downloads, indexers, downloaders, rss, xrel-releases, stats, settings./server— Express REST API + Socket.io WebSockets. JWT auth (bcryptjs), express-validator for input validation, Pino logging, SSRF-protected fetch./shared— Drizzle ORM schema (schema.ts), Zod validation schemas (derived from Drizzle), game title normalization (title-utils.ts), download categorization.
| File | Purpose |
|---|---|
routes.ts |
All API endpoints (~3360 lines, organized by domain) |
storage.ts |
Database access layer (Drizzle queries) |
downloaders.ts |
Multi-client download management (qBittorrent, Transmission, rTorrent, sabnzbd, nzbget) |
igdb.ts |
IGDB API client with in-memory cache |
search.ts |
Aggregated Torznab/Newznab indexer search |
cron.ts |
Scheduled jobs (auto-search, download checks, xREL monitoring, game updates) |
middleware.ts |
Rate limiters, validators, sanitizers |
ssrf.ts |
SSRF URL validation (DNS rebinding, cloud metadata filtering) |
nexusmods.ts |
NexusMods API client — mod search and trending mods per game |
steam.ts |
Steam wishlist import and Steam App ID resolution |
steam-routes.ts |
Express router for Steam endpoints |
pcgamingwiki-router.ts |
PCGamingWiki URL lookup via Steam App ID (CargoQuery API, 24h cache) |
config.ts |
System-wide configuration access layer |
- Frontend uses React Query to call Express REST endpoints
- Routes validate input (express-validator + Zod), call storage/service layers
- Storage layer uses Drizzle ORM against SQLite (better-sqlite3)
- Real-time updates pushed via Socket.io (download progress, notifications)
SQLite with Drizzle ORM. Schema defined in shared/schema.ts. Migrations in /migrations/. Key tables: users, userSettings, systemConfig, games, indexers, downloaders, gameDownloads, notifications, rssFeeds, rssFeedItems, xrelNotifiedReleases, releaseBlacklist.
Notable game fields added: steamAppId, hidden (boolean), userRating (0.5–10 scale), source ("manual" | "steam" | "api"). User fields: steamId64. UserSettings fields: preferredReleaseGroups, steamSyncFailures.
- TypeScript strict mode, no
any. Unused params prefixed with_. - Path aliases:
@/*→client/src/*,@shared/*→shared/* - ES modules throughout (
"type": "module"in package.json) - Prettier: 100 char width, 2 spaces, trailing comma ES5
- Pre-commit hooks: Husky + lint-staged runs ESLint + Prettier on staged files
- Commit messages: Start with a verb ("Add", "Fix", "Update"), reference issues when applicable
- Frontend styling: Tailwind CSS utility classes, dark-first theme with CSS variables. Colors: primary blue
#3B82F6, secondary emerald#10B981, background dark slate#1F2937. - Components: Functional, TypeScript interfaces for props, Radix UI for interactive primitives
- Wrap expensive computations in
useMemo; wrap stable callbacks passed as props or used in dependency arrays inuseCallback. - Prefer deriving state via
useMemoover storing redundant state that can be computed from existing state/props.
- All interactive elements must have an accessible label: use
aria-labelon icon-only buttons/controls,htmlFor+idpairs on form fields, andaria-labelledbywhen a visible heading already describes the region. - Use semantic HTML elements (
<button>,<nav>,<main>,<section>) rather than<div>with click handlers. - Radix UI primitives handle most ARIA roles automatically — avoid duplicating role attributes they already set.
- Unit tests: Vitest with
@testing-library/react(client) and supertest (server). Tests use in-memory SQLite. - E2E tests: Playwright. Run against
dev:testserver on port 5100. - Test files:
server/__tests__/andclient/__tests__/ - Setup:
tests/setup.tsprovides ResizeObserver mocks and test env vars
Key vars (see .env.example):
IGDB_CLIENT_ID,IGDB_CLIENT_SECRET— IGDB/Twitch API credentialsSQLITE_DB_PATH— Database file path (default:sqlite.db)JWT_SECRET— JWT signing secret (auto-generated if unset)PORT— Server port (default: 5000)NODE_ENV—development|production|test
Skill: .claude/sofa-skill.md. Credentials: .sofa/credentials.json (gitignored). Read the skill before interacting with SOFA.