Physical View
Deployment and infrastructure as defined in application dev at 01c423c
(11 October 2026).
Self-Hosted Runtime
Dockerfile builds with Node 24, installs using npm ci --legacy-peer-deps,
and copies the Nitro .output directory into the runtime image. The container
starts node .output/server/index.mjs and exposes port 3000.
Dockerfile.yjs installs the independent yjs/package.json dependencies and
starts yjs-server.mjs on port 1234. The local deployment workflow runs this
container with host networking; the app container maps 3000:3000 and adds
host.docker.internal pointing to the host gateway.
Collaboration Network Path
- Development defaults to
ws://localhost:1234;npm run devstarts Vite and the standalone Yjs server concurrently. - Production clients default to
ws(s)://<app-origin>/yjs-ws, with the file ID appended byWebsocketProvideras the room name. VITE_YJS_WS_URLoverrides the browser endpoint at build time.- The Node listener plugin forwards raw upgrade headers and bytes to
YJS_WS_UPSTREAM, defaulting tohost.docker.internal:1234. - The dedicated server uses the URL's room name. Preserve the same room naming across proxy configurations; adding/removing URL prefixes can change the PostgreSQL persistence key.
- The application checks owner/contributor access before connecting its normal editor client. The standalone WebSocket handler itself does not validate Better Auth sessions; do not describe the client pre-check as server-side room authorization.
The server records non-replay updates in yjs_updates, replays a room's rows
ordered by ID when opened, and compacts the loaded history when it exceeds
YJS_COMPACT_AFTER (default 1000). Persistence failures are logged. Room
persistence is separate from saving a versioned JSON document.
Build and Deployment Workflows
All three workflows target main; the coverage workflow also runs for pull
requests targeting main. Working on dev alone does not trigger these
deployment workflows.
The Vercel workflow sets NITRO_PRESET=vercel. It does not deploy the dedicated
Yjs container. Vercel is therefore an alternative application target, not
evidence of a functioning same-origin Node WebSocket listener there; configure
a separately reachable collaboration service when that listener is unavailable.
Configuration Contract
Names below come from source, not secret values. Keep credentials out of docs,
source control, and browser-exposed VITE_ variables.
| Variable | Consumer and purpose |
|---|---|
DATABASE_URL | App database pool, Better Auth pool, and Yjs persistence pool |
BETTER_AUTH_URL, BETTER_AUTH_SECRET | Auth origin and session/signing configuration |
GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET | GitHub OAuth in lib/auth.ts |
GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | Google OAuth in lib/auth.ts |
FILE_SERVER_URL, INTERNAL_SECRET | Document-byte storage URL and X-Internal-Key |
NITRO_PRESET | Build target, e.g. node_server or vercel |
VITE_YJS_WS_URL | Optional browser collaboration URL, embedded at build time |
YJS_WS_UPSTREAM | Node app's TCP upstream host/port |
YJS_WS_PORT | Dedicated server port, default 1234 |
YJS_COMPACT_AFTER | Loaded room-history compaction threshold, default 1000 |
DAILY_TIMEZONE | Daily date boundary, default Africa/Johannesburg |
GROQ_API_KEY | Optional server key for AI assistance; the API can also receive x-groq-key |
Configuration Gaps in dev
These are repository observations, not fixes made by this documentation update:
- The Docker deployment workflow exports
HUB_CLIENT_IDandHUB_CLIENT_SECRET, but auth code readsGITHUB_CLIENT_IDandGITHUB_CLIENT_SECRET. Align the deployment environment with the names actually consumed by the application. - The workflow does not explicitly pass the optional Groq, timezone, upstream, or browser URL settings. Defaults/user-provided keys may apply; provisioning custom values needs a deployment change.
- Comments reference
sql/collab.sqlandsql/problems.sql, but the checked tracked tree contains no SQL migration files. Existing database provisioning must provide the tables/constraints in Database View; do not assume startup creates them. - The Yjs subproject lockfile is behind its manifest. Its Dockerfile currently
uses
npm install --omit=dev, rather than a lockfile-only reproducible install.
Persistence and Scaling Boundaries
PostgreSQL holds identity, file metadata, invites, problem/daily state, and Yjs update history. External storage holds saved document bytes. Browser preferences and temporary editing state are not database backups. The AI endpoint's credit log is in-process, so it is not a distributed limit across replicas.
PostgreSQL update history enables room restoration, but it does not coordinate live broadcasts among multiple independent Yjs servers. Scaling the application should retain consistent room routing or introduce an explicitly coordinated collaboration backend. No Redis, PgBouncer, replica, or cluster is configured in this repository.