Drawspell (drawspell.space) is a multiplayer tabletop for Magic: The Gathering cards, with realtime shared state and deck import powered by Scryfall card data. The web client renders the board and handles client-side state, while the backend runs a PartyServer/Cloudflare Durable Object that hosts the Yjs document and per-room tokens. Both apps live under apps/ and communicate over PartyKit/PartyServer WebSockets.
flowchart LR
Web["apps/web<br/>TanStack React Start (Vite)"] -->|WebSocket: Yjs sync + intents| Server["apps/server<br/>PartyServer (Durable Object)"]
Discord["apps/discord<br/>Cloudflare Worker (Discord Interactions)"] -->|Service Binding `SERVER`| Server
Web -->|HTTPS| Scryfall["Scryfall API"]
Discord -->|Discord REST API| DiscordApi["Discord API"]
Server --> DO[(Durable Object storage)]
- Web app (
apps/web): UI, routing, and client-side state; connects to PartyServer for sync; fetches Scryfall card data. - Realtime server (
apps/server): PartyServer runtime; applies intents to the Yjs document; manages hidden state and room tokens in Durable Object storage. - Discord worker (
apps/discord): verifies slash-command interactions, provisions rooms via server binding, and sends DM invite links. - Shared types: server imports types from
apps/web/src/types(seeapps/server/src/domain/types.ts). - External services: Scryfall API for card metadata; Discord API for interactions/DMs; Cloudflare Workers + Durable Objects for hosting.
| Path | Description |
|---|---|
apps/web |
Web client (TanStack React Start + Vite). See apps/web/README.md. |
apps/server |
PartyServer backend (Cloudflare Workers + Durable Objects). See apps/server/README.md. |
apps/discord |
Discord Interactions worker for /drawspell create. See apps/discord/README.md. |
- Bun
1.2.21(seepackage.json#packageManager). - Node.js 24+ for Portless local dev URLs.
bun install| Task | Command | Notes |
|---|---|---|
| Dev: web app | bun run dev |
Runs the web app through Portless at https://ds.localhost. |
| Dev: server | bun run dev:server |
Runs the realtime server through Portless at https://server.ds.localhost. |
| Dev: Discord worker | bun run dev:discord |
Runs wrangler dev --env development in apps/discord. |
| Build | bun run build |
Builds the web app. |
| Preview | bun run preview |
Builds + previews the web app. |
| Test | bun run test |
Runs tests for all workspaces. |
| Typecheck | bun run typecheck |
Runs typechecks for all workspaces. |
| Deploy: staging (server + web) | bun run deploy:staging |
Deploys dedicated staging workers. |
| Deploy: staging server | bun run deploy:server:staging |
Deploys drawspell-server-staging. |
| Deploy: staging web | bun run deploy:web:staging |
Builds with CLOUDFLARE_ENV=staging and deploys drawspell-staging. |
| Deploy: web | bun run deploy:web |
Deploys apps/web. |
| Deploy: server | bun run deploy:server |
Deploys apps/server. |
| Deploy: Discord worker | bun run deploy:discord |
Deploys apps/discord. |
| Register Discord commands (guild) | bun run register:discord:commands -- --guild-id <GUILD_ID> |
Fast propagation for testing. |
| Register Discord commands (global) | bun run register:discord:commands:global |
Use after guild validation. |
Smoke test: Discord /drawspell create |
bun run test:discord:smoke |
Runs TO-01 integration expectation used post-registration. |
| Cloudflare types (web) | bun run cf-typegen |
Generates Workers types for the web app. |
| Cloudflare types (discord) | bun run --cwd apps/discord cf:typegen |
Regenerates Discord worker Env types from wrangler.jsonc + .dev.vars. |
| Lint | TBD | No lint script is defined in package.json. |
| Name | Used by | Description | Source |
|---|---|---|---|
VITE_PUBLIC_POSTHOG_KEY |
apps/web |
Public PostHog API key used by the web client. | apps/web/.env |
VITE_PUBLIC_POSTHOG_HOST |
apps/web |
Public PostHog host used by the web client. | apps/web/.env |
JOIN_TOKEN_SECRET |
apps/web, apps/server |
HMAC secret for join tokens; must match across web + server workers. | Cloudflare secret or local .dev.vars |
DISCORD_PUBLIC_KEY |
apps/discord |
Discord public key for interaction signature validation. | Cloudflare secret or apps/discord/.dev.vars |
DISCORD_BOT_TOKEN |
apps/discord |
Bot token for DM fanout and command registration. | Cloudflare secret or apps/discord/.dev.vars |
DISCORD_SERVICE_AUTH_SECRET |
apps/discord, apps/server |
Shared secret for internal provisioning auth. | Cloudflare secret or .dev.vars in each app |
DISCORD_APPLICATION_ID |
apps/discord registration script |
Discord application ID used for slash-command registration. | Cloudflare secret/env var or apps/discord/.dev.vars |
DISCORD_COMMAND_GUILD_ID |
apps/discord registration script |
Optional default guild for faster command registration rollout. | Local env or shell export |
DISCORD_API_BASE_URL |
apps/discord registration script |
Optional Discord API base URL override for command registration. | Local env |
VITE_ENV |
apps/web |
Build/runtime environment selector used by the web app to resolve Drawspell hosts. | wrangler.jsonc vars or Vite define |
NODE_ENV |
apps/server, apps/discord |
Runtime environment selector used for server host resolution and Discord-to-server routing. | wrangler.jsonc vars |
- Web: Vite loads
.env*files fromapps/webduringvite buildandvite dev. - Web: public
VITE_*values belong inapps/web/.env*; runtime-only values and secrets belong inapps/web/wrangler.jsoncpluswrangler secret. - Server: Durable Object binding
roomsis configured inapps/server/wrangler.jsonc; secrets usewrangler secretorapps/server/.dev.vars. - Drawspell web/server origins are centralized in
packages/shared/src/constants/hosts.ts. apps/webselects hosts withVITE_ENV;apps/serverandapps/discordselect them withNODE_ENV.- Discord: service binding
SERVERis configured inapps/discord/wrangler.jsonc; default/prod binds todrawspell-server, andenv.developmentbinds todrawspell-server-developmentfor localwrangler dev --env development. - Discord command registration script reads exported shell env values only (
DISCORD_BOT_TOKEN,DISCORD_APPLICATION_ID, and optionalDISCORD_COMMAND_GUILD_ID/DISCORD_API_BASE_URL). - Discord worker
Envtype declarations are generated intoapps/discord/worker-configuration.d.ts; rerunbun run --cwd apps/discord cf:typegenwhen Discord worker vars/bindings or.dev.varskeys change.
- In one terminal:
bun run dev:server - In another terminal:
bun run dev - Open
https://ds.localhostand create a game.
Portless runs the proxy for stable .localhost names and may prompt to trust
its local certificate authority on first run.
- Web only:
bun run --cwd apps/web dev - Server only:
bun run --cwd apps/server dev - Discord only:
bun run --cwd apps/discord dev
- Staging deploy target is
devbranch by convention. - Staging deploy command:
bun run deploy:staging. - Web deploy:
bun run deploy:web(config:apps/web/wrangler.jsonc). - Server deploy:
bun run deploy:server(config:apps/server/wrangler.jsonc,apps/server/partykit.json). - Discord deploy:
bun run deploy:discord(config:apps/discord/wrangler.jsonc). - Slash-command registration workflow: see apps/discord/README.md for guild-first rollout and global rollout commands.
- Durable Object migrations are defined in
apps/server/wrangler.jsonc. - Durable Object duration checks after server deploys are documented in apps/server/README.md.
- CI: TBD (no CI config found in the repo; add one under
.github/or similar).
See docs/performance-report.md for the latest benchmarks, load scripts, and measurement notes.
- Use Bun for scripts and dependency management.
- Run
bun run testandbun run typecheckbefore opening a PR. - Keep changes scoped and update docs/tests when behavior changes.
- Formatting/linting: TBD (follow existing file style until a formatter is added).