Developer guide
Repository layout, the local toolchain, tests and lint, and the conventions the code follows.
Repository layout
webstation_broker/ FastAPI app (pip installable, console script webstation-broker)
api.py activate / join / exit / status / state and archive REST endpoints
room.py collab websocket (chat, webcam fanout, resolution, input passing)
session.py single-session state, room broadcast, gamepad/MK assignment
selkies.py token pushes to the selkies control plane
outbox.py per-connection outbound queue, so a slow room socket delays only itself
saves.py save archive restore on activate, delta dump on exit
memcard.py whole memory card capture and hydrate
imports.py declared save/state/card imports, placed or refused per emulator in preflight
screenshot.py save-state thumbnail capture over the pixelflux computer-use endpoint
callback.py exit-time save archive upload to the parent
settings.py environment-driven configuration
main.py console-script entrypoint; refuses to start without BROKER_SECRET or dev mode
app.py application factory, SUBFOLDER mount, orphan reaping on start
emulators/ one launcher per emulator, all subclassing emulators.base.Emulator
frontend/ vite vanilla-JS room interface
scripts/ check_broker.py (monitoring check), sync_core_info.py (core catalog upkeep, dev only)
tests/ pytest suite; launchers are stubbed, nothing needs a display
docs/ this site (Fumadocs, static export to GitHub Pages)Toolchain
Python 3.11 or newer. The repository uses uv for
environments but plain pip works the same:
uv venv
uv pip install -e . pytest pytest-asyncio "ruff==0.16.1"
.venv/bin/pytest -q
.venv/bin/ruff check webstation_broker testsCI runs the same lint, builds the app factory to catch import errors, runs
the tests, and runs the frontend's npm test. Dependencies are deliberately
few: FastAPI, uvicorn, httpx, Pillow (for save-state screenshots) and pyfatx
(for xemu's FATX images).
Conventions
- Google-style docstrings everywhere. Every module, class, function and
test carries one, with
Args:,Returns:andRaises:sections. The Python reference is generated from them, so a docstring is documentation, not decoration. Module constants and class attributes get attribute docstrings (a string literal on the line after the assignment) so they show up too. - Type hints on every signature, kept Python 3.9-safe:
Optional/Unionfromtyping, not PEP 604 unions (str | None), even though the package itself targets 3.11+. Builtin generics anddict[str, Any]for loosely shaped payloads are fine. - ruff enforces both.
pyproject.tomlenables theDrules with the Google convention and theANNrules alongsideE,F,WandI, and CI fails on any of them. - Keep the rationale. This codebase explains why in docstrings and comments: why a slot is parked, why a card is wiped before a launch, why a pid is recorded. Preserve that when you touch code; it is what keeps the next person from undoing a fix.
- No em dashes in prose, docs or docstrings.
- Conventional commits.
feat:andfix:subjects drive the release bump; see Releases.
The PR workflow itself (branching, linking issues, what to run before opening one) is in CONTRIBUTING.md at the repository root.
Running against a real container
Launching an emulator needs the container's display and binaries, so the
tests stub the launchers. For end-to-end work run the image in
dev mode with the checkout mounted at /broker;
uvicorn reloads on every edit and the room page is served by vite with hot
reload.