Thanks for considering it. Shortlist is a small, safety-critical codebase — it modifies other people's Plex views — so the bar for write-path changes is deliberately high.
# Backend (the lock first, so you get the versions the image ships)
pip install -r requirements.lock && pip install -e ".[dev]"
pytest # unit + integration, parallel (no network, ever)
pytest tests/unit/test_foo.py # while editing: just the file you're working on
pytest -m e2e # Playwright against an in-process app + fake Plex
ruff check . --fix && ruff format .
# Frontend
pnpm -C web install
pnpm -C web dev # Vite on :5173, proxies /api to :5959 (or SHORTLIST_API_PROXY)
pnpm -C web test && pnpm -C web build
# Run the app against a throwaway config in safe mode, on port 5960
bash scripts/devrun.shRun the whole suite once, when the work has settled, rather than after every edit. If you change
pyproject.toml dependencies, regenerate requirements.lock (the command is in .claude/CLAUDE.md).
shortlist/engine/never imports fromshortlist/server/. The engine is a pure library.- Read
.claude/rules/plex-safety.mdbefore touching any code path that writes to Plex or plex.tv. Highlights: snapshot before restriction writes; share filters are read-modify-write merges, never rebuilt; onlyshortlist_*-labeled collections may be touched; every write path takesdry_run; tokens never in logs or exceptions. - Tests are required. No test may touch the network — use the conftest fixtures,
recorded fixtures in
tests/fixtures/, ortests/fakes/fake_plex.py. Privacy/merge code changes need property tests. - Conventional Commits (
feat:,fix:,docs:,test:,chore:). - Docs ship with the feature — README/docs updated in the same PR.
devis the default branch. All work lands here, so open PRs againstdev. Every push todevruns the full CI suite and, once it's green, publishes theghcr.io/stevezau/shortlist:devimage. That's the bleeding-edge build.masteris the stable branch. It moves only by promotingdev→mastervia PR when cutting a release. Pushing tomasterbuilds nothing on its own.- Releases are cut by pushing a semver tag (
vX.Y.Z, orvX.Y.Z-beta.Nfor pre-releases). CI builds:latest+:X.Y.Zfrom the tag. The release commit bumpsshortlist/__init__.py(and the OpenAPI snapshot) first. - Publish gate: the image is only pushed after lint, tests (Python 3.12), the web build, and the Playwright e2e suite all pass in the same run — a red suite never ships an image.
Image tags: :dev (latest dev build) · :latest (latest stable release) · :X.Y.Z (pinned).
Use the issue templates, or the Have an issue? page in the app, which opens a pre-filled bug report with a secrets-free diagnostic. For anything privacy-related (a user saw a row that wasn't theirs), please mark it clearly — those get fixed first, always.