# Product ## Platform web ## Users The Plex **server owner** (a homelab admin) is the only person who uses the Shortlist web UI. They sit at a desk, mostly on a desktop browser: once during setup, then occasionally to check what last night's run did, fix a connection, add or edit a row, or press "Run now". Phone use is occasional, not the primary scene (confirmed 2026-10-03). The owner's **shared users** and **managed users** never see this UI. They experience Shortlist only as a "Picked for You" row on their own Plex Home. Everything the admin does here is on their behalf, and the admin cannot see what those people see without switching to a non-owner account. ## Product Purpose Shortlist gives every person on a Plex server a private, personal recommendation row on Plex Home, built from their own watch history and refreshed nightly. One Docker container: FastAPI backend, React SPA, SQLite, and a pure-Python engine. Success for the admin: the nightly run completed, every enabled person got a row, nobody can see anyone else's row, and when something went wrong the UI says exactly what, for whom, and what to do. ## Positioning The only maintained open-source tool with genuinely private per-user rows. Comparable tools (Curatarr, Immaculaterr) are dormant or closed, or build one global row. Shortlist's privacy is structural, not a setting: rows are hidden from everyone else via Plex share-filter excludes BEFORE they are promoted to Home, so a row is never briefly visible to the wrong person. This became possible only after Plex fixed label-restriction behaviour in PMS 1.43.2 (May 2026). ## Operating Context - Runs as one container on the admin's server; the engine runs on its own nightly schedule (APScheduler). - Admin signs in once with Plex (PIN flow, no passwords), completes a 7-step setup wizard, picks which users get rows, and leaves it. - Each run: read each person's watch history from the PMS, fetch candidates (TMDB similar/recommendations, Trakt, optional web search), optionally ask an AI to suggest titles through web search, write a per-user Plex collection, merge privacy excludes, then promote. Optional: auto-request great picks not in the library via Radarr/Sonarr/Seerr. - Coexists with Kometa, Agregarr and other tools that manage collections on the same server; it must touch only collections it created. - Admin checks results in the UI: Dashboard, Runs (per-user timeline, diffs, warnings), Activity (live log and jobs), Privacy (share filters, snapshots and enforcement check), Requests queue. - Writes to Plex are the risky part; everything supports dry-run and uninstall restores snapshots. ## Capabilities and Constraints - Candidate sources: TMDB, Trakt, optional web search (Exa/SearXNG) or offline. - Curation: Anthropic, OpenAI, Google (admin's own key), local Ollama/llama.cpp, or heuristic-only with no AI key. - Seasonal rows: built-in holidays plus custom dates, hidden between seasons. - Cold start: people with little watch history get a "Popular on " row. - Plex limits that shape the UI and copy: the owner can see every user's row in the library's Collections tab (the owner cannot be restricted), though none lands on the owner's own Home; no per-user row position control; rows update in place; a TV library write can take ~16s per collection, so a run is long by design. - Every state change is audited (structured events) and must be answerable from the UI. - Settings live in the DB; the UI is the configuration surface. - Frontend stack is fixed: React 19, Vite, TypeScript strict, Tailwind, shadcn/ui, TanStack Query, API types generated from OpenAPI. See `.claude/rules/frontend.md`. ### Terminology **row** (a collection on Home) · **pick** (a title in a row) · **run** (one nightly engine execution) · **job** (a scheduled or manual run request) · **share** (an invited user with their own token) · **managed user** (a Plex Home profile without its own token) · **owner** (the server admin; never restricted) · **share filter / restriction** (Plex's hide-by-label mechanism) · **snapshot** (a person's pre-Shortlist filter state, restored on uninstall) · **dry run** (a run that logs would-be writes). ### Decided - **Scope of the 2026-10 design update** (owner, 2026-10-03): the whole audit, under direction A "amber, tightened" for the app and "show the row" for the website. Built and shipped on `dev` 2026-10-03/04. ## Brand Commitments - Name: **Shortlist**. Fixed. - Service glyphs (Plex, Claude etc.) are hand-drawn in `web/src/components/brand-glyphs.tsx`. - **The mark is decided (owner, 2026-10-03):** the Plex-gold rounded square with two drawn sparkles, flat (no gradient, no glow). The same drawing is the app's rail mark, the browser-tab icon and the website logo. - Voice (from `.claude/docs/shortlist-design.md`): plain English; controls say exactly what happens; errors say what went wrong and how to fix it, never raw codes; constraints are stated up front; every pick carries a "Because you watched X" reason. ## Evidence on Hand - In production on the maintainer's own server (40+ users, PMS 1.43.x); v1.12.2 released 2026-10-21. - Public docs site (docs/ in repo, 15+ pages) and MIT GitHub repo with CI (ruff, pytest, Playwright e2e). - A real Plex Home screenshot in README.md. - `tests/fakes/fake_plex.py` lets the whole app run with no real Plex server, so any screen can be rendered and screenshotted locally (`bash scripts/devrun.sh` on :5960, `pnpm -C web dev` on :5173). - No testimonials, user counts beyond the maintainer's server, or benchmarks. Do not invent any. ## Product Principles 1. **Privacy is the product.** Any screen that touches who can see what must make the consequence legible before the write happens. 2. **The admin acts on behalf of people they cannot see as.** Surface what each person will experience, and say plainly when the owner's own view is not representative. 3. **A run is the unit of trust.** Last night's result, per person, with what changed and why, is the question the UI exists to answer. 4. **Say the constraint, not the apology.** Plex limits are facts; state them where the admin meets them. 5. **Operate, not persuade.** This is a tool used a few minutes at a time; scanability and consistency outrank expression. Brand lives in details. ## Accessibility & Inclusion Project rule (`.claude/rules/frontend.md`): real `