Docs Decisions

Contents

ADR-0013: The web Studio runs on a host of its own, with sign-in by emailed code

  • Status: Accepted (2026-10-06: the owner said yes to Fly.io and Resend)
  • Date: 2026-10-05
  • Supersedes: ADR-0009's "Later: … Better Auth on Neon Postgres via Drizzle, Vercel Blob for bundles, Vercel Workflows for long runs, Vercel Sandbox with a Godot image" (its play origin, bundles and player stand).

Context

The owner wants gamewright.diy to have "signin for the web version of the app". The web Studio is the Studio's own Godot web export (ADR-0012: the same UI on the web as on the desktop). It needs a core to talk to: the session daemon (gamewright serve, S3) holding a WebSocket, one per game, with headless Godot for the checks. Vercel functions hold neither a WebSocket nor a process that lives for a session. ADR-0009 planned accounts on Better Auth and Neon and long runs on Vercel Workflows and Sandbox before the Studio and its daemon existed.

Reelwright, the owner's other studio, shipped the same thing on 2026-10-04 and it is live: reelwright host on Fly.io, about 8,700 lines in core/src/host/ (research: docs/design/site.md).

Decision

  • A host of our own: gamewright host, in the core, on one Fly.io machine with a volume, ported from Reelwright's host. It serves the web Studio at gamewright.diy/app (a Vercel rewrite to the Fly app), a JSON API the Studio calls same-origin, and the daemon's WebSocket directly at the Fly domain, reached with a one-time ticket the API hands out (Vercel does not proxy WebSockets).
  • Accounts: invite-only at first. Sign-in by a six-digit code sent by email (Resend), valid ten minutes, once, five guesses; only hashes of codes and sessions are stored; rate limits per address and per IP; an HttpOnly; Secure; SameSite=Lax session cookie scoped to /app.
  • Data: one SQLite file (node:sqlite) on the volume for users, codes, sessions, invites, settings and the audit log; each person's games as folders on the volume.
  • The person's own key: checked with Anthropic before it is kept, sealed with AES-256-GCM under a host secret with the user id as additional data, shown only as sk-ant-…WXYZ, with monthly caps.
  • Games run where they always run: in the browser on the play origin (ADR-0009). The host never runs a person's game in a page of ours.
  • An admin console for ADMIN_EMAILS only (invites, settings, operations, audit).
  • Releases are published to a public repository of builds only, Xaxis/gamewright-releases (the owner's choice, 2026-10-06; the source repo stays private), not on the host's volume.

Consequences

  • One Fly app (Reelwright's sizing is about $65 a month; smaller until checks run there) and a Resend domain: the owner's accounts and DNS records at Namecheap. Nothing is spent before the owner says yes; the host is built and tested locally first.
  • .env.example replaces BETTER_AUTH_SECRET and DATABASE_URL with RESEND_API_KEY, ADMIN_EMAILS and KEY_ENCRYPTION_KEY.
  • The host's code is Reelwright's, renamed and listed in core/UPSTREAM.md, so its fixes can be ported.

From docs/adr/0013-web-studio-host.md in the Gamewright repository.