Imported from andrewthetechie/jelly-swipe (
AGENTS.md). Install upstream withnpx skills add andrewthetechie/jelly-swipe. Copyright stays with the author.
Jellyswipe — Agent Instructions
Jellyswipe is a Jellyfin-based media swiping app. Users join or create a room, swipe left/right on movie and TV show cards fetched from a Jellyfin server, and a match fires when both participants swipe right on the same item. The app supports solo sessions and hosted two-user sessions.
Command Policy
- Treat this as a
uv-managed Python project. Do not assume globalpython,pip,pytest,ruff, oralembic. - Always prefer
uv run ...for project commands anduv syncfor first-time setup. - Default test command:
uv run pytest tests/ - Single test file:
uv run pytest tests/test_routes_room.py -v - Lint:
uv run ruff check . - Format:
uv run ruff format . - Run the app locally:
uv run python -m jellyswipe.bootstrap - Do not use bare
pytest, bareruff, barealembic, orpython -m pytestunless the user explicitly asks for it.
Architecture
frontend/ React + Vite frontend (source, tests, build config)
jellyswipe/
├── routers/ FastAPI route handlers
├── services/ Business logic
├── repositories/ SQLAlchemy data access
├── models/ SQLAlchemy ORM models
├── utils/frontend.py Resolves Vite dist output path
├── jellyfin/ Split-by-role Jellyfin integration (issue #299)
│ ├── client.py Async httpx transport (JellyfinClient)
│ ├── vault.py Delegate token + delegate user resolution (JellyfinVault)
│ ├── library.py DeckProvider adapter: deck, genres, items, images (JellyfinLibrary)
│ └── watchlist.py Add-to-favorites writer (JellyfinWatchlistWriter)
├── config.py
├── db.py
├── db_runtime.py
├── db_uow.py
├── frontend_dist/ Built Vite output (production/Docker; populated by Dockerfile)
└── static/ PWA assets only (manifest.json, sw.js, favicon.ico, icons)
- Entry point:
jellyswipe/__init__.pybuilds the FastAPI app and registers routers. - The app is a package, not a loose script. Use
uv run python -m jellyswipe.bootstrap. - Frontend changes normally live in
frontend/(React + Vite). Build output is emitted tofrontend/dist/(local dev) orjellyswipe/frontend_dist/(production/Docker); FastAPI serves/from itsindex.htmland/assets/*from itsassets/dir. Seejellyswipe/utils/frontend.py(find_frontend_dist_path) for the resolution order.
Database Rules
- This repo uses SQLAlchemy 2.x plus Alembic. Do not treat it as a raw
sqlite3project. - Application/runtime database access should use SQLAlchemy async sessions from
jellyswipe.db_runtimeand theDatabaseUnitOfWorkinjellyswipe.db_uow. - All DB writes go through
DatabaseUnitOfWork. Do not instantiate repositories directly outside a managed session/UoW boundary. - Transaction completion is owned by the
get_db_uowrequest boundary (see ADR-0004): it commits on success, rolls back on error/abort, and wakes SSE subscribers after commit. Routes never callsession.commit()or commit/notify helpers; declare wake intent viauow.wake_on_commit(code)and discard an error-return route's writes viauow.abort(). - Do not add new raw
sqlite3application code. The existing directsqlite3helpers are test-only utilities intests/conftest.py. - If schema changes are needed, update the SQLAlchemy models and create a new Alembic revision in
alembic/versions/. - Create migrations with
uv run alembic revision --autogenerate -m "short description". - Apply migrations with
uv run alembic upgrade head. - Never modify
alembic/versions/0001_phase36_baseline.py. Add a new numbered revision instead. - Tests bootstrap schema through Alembic. Do not hand-roll tables in tests when the migration path should own the schema.
Example write pattern:
async with runtime_sessionmaker() as session:
uow = DatabaseUnitOfWork(session)
result = await service.create_room(session_dict, user_id, provider, uow)
await session.commit()
API and Service Conventions
- Always use
XSSSafeJSONResponsefor JSON API responses. Do not useJSONResponsedirectly. - Authenticated routes should use
require_auth. - Routes that only need the caller's identity/room may declare
actor: SessionActor = Depends(get_session_actor)instead —get_session_actorcomposesrequire_auth(401 on missing auth), so auth is still enforced and the route does not declarerequire_authagain. Session key names live only in the session adapter independencies.py; never readrequest.sessionkeys directly in routers or services. DeckProvideris defined injellyswipe/services/room_lifecycle.py. Production usesJellyfinLibrary(jellyswipe/jellyfin/library.py); tests usually useFakeProvider.fetch_decktakesmedia_types: list[str]containing"movie"and/or"tv_show".- Public API payloads use
media_id, notmovie_id. POST /roomexpects JSON booleans formovies,tv_shows, andsolo. String booleans should be rejected.
Domain Notes
Room.pairing_codeis the 4-digit join code.Room.solo_modedistinguishes solo from hosted sessions.Room.include_moviesandRoom.include_tv_showsare immutable after room creation.Room.readymeans the room accepts swipes.Room.current_genreuses"All"to mean no filter.Room.deck_position_jsonstores per-user cursors.Room.movie_data_jsonstores the current deck card payloads.
Card dict shape:
{
"id": str,
"title": str,
"summary": str,
"thumb": str,
"year": int | None,
"media_type": str,
"rating": float | None,
"duration": str | None,
"season_count": int | None,
}
Testing Conventions
- Use
@pytest.mark.anyiofor async tests. tests/conftest.pyprovides important fixtures includingruntime_sessionmakerandclient_real_auth.- Mock Jellyfin with
FakeProviderormocker.patch. - Prefer targeted runs while iterating, but when reporting verification use the exact command you ran.
- If you say you ran tests, include whether it was
uv run pytest tests/or a narroweruv run pytest tests/.... - Test names should describe behavior, for example
test_create_room_with_tv_shows_sets_include_tv_shows.
Environment Variables
- Required:
JELLYFIN_URL,JELLYFIN_API_KEY,TMDB_ACCESS_TOKEN,SESSION_SECRET - Optional:
DB_PATH,JELLYFIN_DEVICE_ID,ALLOW_PRIVATE_JELLYFIN - Use
SESSION_SECRET, notFLASK_SECRET.
Git and Commit Hygiene
- Use explicit
git add <file>paths. Never usegit add .orgit add -A. - Do not commit orchestration or local-index state:
.orchestra/opencode.json,.opencode/.serena/ORCH_DISPATCH_*.md.gitnexus/
GitNexus — Code Intelligence
This project is indexed by GitNexus as jelly-swipe. Use GitNexus to understand code, assess impact, and navigate safely.
If any GitNexus tool warns the index is stale, run
npx gitnexus analyzefirst.
Always Do
- Before modifying a function, class, or method, run impact analysis on the target symbol and report the blast radius to the user.
- Run change detection before committing to verify only expected symbols and execution flows changed.
- Warn the user before proceeding if impact analysis reports HIGH or CRITICAL risk.
- When exploring unfamiliar code, prefer GitNexus execution-flow queries over blind grepping.
Never Do
- Never edit a function, class, or method without first running impact analysis on that symbol.
- Never ignore HIGH or CRITICAL impact warnings.
- Never rename symbols with find-and-replace when GitNexus rename support is available.
- Never commit without checking the affected scope with GitNexus change detection.
Resources
gitnexus://repo/jelly-swipe/contextgitnexus://repo/jelly-swipe/clustersgitnexus://repo/jelly-swipe/processesgitnexus://repo/jelly-swipe/process/{name}
Skill Pointers
- Architecture and execution flow:
gitnexus-exploring - Impact analysis:
gitnexus-impact-analysis - Debugging:
gitnexus-debugging - Refactoring:
gitnexus-refactoring - Tooling reference:
gitnexus-guide - CLI workflows:
gitnexus-cli