Skip to content

Development

Bawgle is a small TypeScript monorepo with three moving parts:

Slice Runs as Entry point What it does
Client SPA Vite (port 5175 in dev) src/client/main.ts Svelte 5 UI — lobby, board input, results
Server tsx watch (port 3001) src/server/index.ts HTTP API, WebSocket gameplay, SQLite persistence
Admin bundle esbuild watcher src/admin-panel/assets/app.ts.js Admin dashboard front-end

pnpm dev runs all three concurrently with concurrently.

Prerequisites

  • Node 20
  • pnpm 9 (corepack enable if you don't have it yet)
  • Optional: Docker, for pnpm compose:dev

First-time setup

bash pnpm install cp .env.example .env # optional, only if you want admin enabled locally pnpm dev

Vite proxies /ws, /api, and /admin to the Node server so a single origin works for everything. The port is fixed at 5175 — change it in vite.config.js if needed.

Environment

All BAWGLE_* variables from .env.example apply in dev too. The noteworthy ones:

  • BAWGLE_ENVIRONMENT=development — set by pnpm dev. Relaxes the minimum round length from the 60s prod floor to 5s so you can end rounds quickly while iterating. Accepted by both the server (clamp logic) and the client (slider min).
  • BAWGLE_ADMIN_PASS — if empty the admin surface returns 401 for every request, so set something locally if you want to poke at it.
  • BAWGLE_ALLOWED_ORIGINS — leave unset in dev. The WebSocket origin check is disabled when the list is empty.

Scripts

Command Purpose
pnpm dev All three watchers in parallel
pnpm build Admin bundle + Vite production build to dist/
pnpm start Run the server against a prebuilt dist/
pnpm test Vitest, single run
pnpm test:watch Vitest in watch mode
pnpm test:coverage V8 coverage report
pnpm lint / pnpm lint:fix ESLint
pnpm typecheck tsc --noEmit across the whole tree
pnpm format / pnpm format:check Prettier
pnpm denylist:apply Rebuild data/dictionary/words.txt after editing denylist.txt
pnpm compose:dev Local docker-compose.dev.yml build + up
pnpm compose:dev:logs Tail the dev compose container

Repo layout

Everything source-controlled lives under src/, split by deployment target.

``` src/ server/ Node server (Hono + ws + better-sqlite3) index.ts Boot, route mount, static SPA fallback rooms.ts Room lifecycle, rounds, host transfer, ready-up netcode.ts WebSocket upgrade, origin check, per-IP caps, rate limiter storage.ts SQLite schema, room / round persistence dictionary.ts Trie-backed word lookup + Boggle solver metrics.ts Counters + event ring buffer + JSONL persistence

admin-panel/ Admin dashboard (host + plain-DOM client) index.ts Hono routes mounted by the server auth.ts HTTP Basic auth + per-IP fail throttle build.ts esbuild wrapper: assets/app.ts → app.js assets/ Static HTML, CSS, compiled dashboard client

shared/ Code used by both server and client board.ts Dice sets, rollBoard, wordPathExists types.ts RoomState, ClientMsg/ServerMsg, scoring

client/ Svelte 5 SPA built with Vite main.ts Mounts App.svelte into App.svelte Path-based router (/, /result, 404) style.css Entry CSS that imports lib/styles/* lib/ components/ Leaf components (Board, PlayerList, Timer, …) views/ Top-level views (Room, ResultPage, NotFound) stores/ Svelte stores (room, path, theme, audio, feedback) util/ Net, input, resolver, themes, share, client-id, … styles/ Split CSS partials imported from ../style.css

data/ dictionary/ words.txt, denylist.txt, definitions.json, inflections.json logs/ Daily JSONL event logs (runtime-generated) boggle.db SQLite DB (runtime-generated)

scripts/ One-off tools (dictionary build, solver verify, probes) tests/ Vitest specs mirroring src/ layout ```

Testing

pnpm test runs the whole Vitest suite. The tests cover:

  • Server — rooms, rounds, storage, netcode, dictionary, admin auth, metrics, countdown, multi-player flows.
  • Shared — board rolling, path validation, type invariants.
  • Client — game path store, input resolver, share-text, client-id utilities, DOM escapes.

A few patterns worth knowing:

  • src/server/rooms.ts exports __beginRoundForTests to skip the pre-round countdown from tests that predate it.
  • src/admin-panel/auth.ts exports __resetAdminThrottleForTests so the per-IP fail counter can be reset between cases.
  • Storage tests open SQLite in a tmp dir and call initStorage / closeStorage around each test.
  • tests/server/netcode.test.ts spins up the server on ephemeral ports so rate-limit + heartbeat behaviour can be exercised over real WebSockets.

Editing the dictionary

  1. Add strings you want to strip to data/dictionary/denylist.txt.
  2. Run pnpm denylist:apply. The script rewrites words.txt and preserves definitions/inflections that still have a valid base.
  3. Restart the server (it loads the dictionary once at boot).

Admin bundle

src/admin-panel/build.ts is a small esbuild wrapper that compiles src/admin-panel/assets/app.tsapp.js. In dev it runs with --watch; in CI it's invoked by pnpm build:admin before the Vite build so the published image ships a fresh bundle.