Skip to main content

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.py so 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 from ADMIN_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.