Architecture
Tome of Sessions is split into three active areas:
web/: Vite, React, TypeScript, React Router, and the Tome design system.server/: FastAPI, Pydantic v2, SQLAlchemy 2 async ORM, Alembic, and PostgreSQL with pgvector.docs-site/: Docusaurus developer documentation.
The old prototype/ directory is a visual reference only.
Runtime Shape
In local development, Vite serves the SPA and proxies /api plus /uploads to
FastAPI. In the one-process deployment, FastAPI serves API routes, uploaded
files, and the built SPA from web/dist.
Startup goes through server/app/run.py, which ensures the database exists,
applies Alembic migrations, and starts uvicorn. The app factory lives in
server/app/main.py.
Backend Boundaries
Backend routes live in server/app/routers/. They should be thin:
- Authenticate with dependencies from
server/app/deps.py. - Authorize campaign-scoped actions with helpers from
server/app/lib/access.py. - Validate bodies with Pydantic models in
server/app/schemas.py. - Return DTOs from
server/app/serializers.py, never raw ORM rows. - Raise helpers from
server/app/errors.pyso responses use the standard error envelope.
Business logic belongs in focused modules under server/app/lib/, such as
combat, sessions, uploads, campaign cloning, encounter math, and the RAG stack.
Data Model
server/app/models.py is the database source of truth. Edit models first, then
generate and apply an Alembic migration:
cd server
uv run alembic revision --autogenerate -m "describe change"
uv run alembic upgrade head
Keep server/seed.py useful when new models need sample data.
Frontend Boundaries
Network calls go through web/src/lib/api.ts. DTO types live in
web/src/types.ts and must match the backend serializers. Feature components
should not call fetch directly.
Shared UI lives under web/src/components/, theme and formatting helpers under
web/src/lib/, screens under web/src/screens/, and the visual system in
web/src/styles/tome.css.
Icons come from FontAwesome via web/src/components/Icon.tsx; feature
components should render the local <Icon /> wrapper instead of importing icon
packages directly.
Authorization Model
There are two distinct role concepts:
- Platform admin:
User.isAdmin, bootstrapped fromADMIN_EMAILS, used for/api/admin/*. - Campaign role: membership role such as DM or player, enforced with campaign-scoped helpers.
AI access is a separate user flag. Effective AI access is aiAccess OR isAdmin;
server-side guards enforce it for AI endpoints, while the frontend hides gated
controls for users without access.