Includes development setup, Docker deployment, API reference, WebSocket events, troubleshooting guide, and curl-based test script. Updates App plan.md with implementation completion status. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
8.7 KiB
Here’s a concrete, handoff-ready plan you can give to another AI to implement a first version (v1) of a Spelljammer Ship Tracker with multi-ship CRUD, constrained editing, SQLite persistence, and <1s live updates.
0) Product goal (v1)
A small phone-friendly web app where your group can:
- Create/select/delete ships (delete requires confirm modal)
- View & edit core ship stats + current weapons on the ship
- See changes propagate to all connected clients within ~1s
- Persist “real data” server-side in SQLite (ship data + weapons + optional reference content)
- Show a read-only Rules/Reference screen for battle lookups
No authentication. Anyone can edit anything.
1) Recommended stack (simple + robust)
Option A (recommended): Node.js + Fastify + Socket.IO + SQLite (better-sqlite3) + React (Vite)
- Fast to implement, great websocket support, easy Docker build.
- Socket.IO simplifies reconnects & room-based updates (per ship).
- better-sqlite3 is reliable and fast for low-concurrency apps.
Option B: Node.js + Express + ws + SQLite (also fine, but more manual reconnection & rooms logic)
Either way: single Docker Compose with one container; SQLite is a mounted file.
2) Architecture
Backend
-
REST API for CRUD and initial state fetch.
-
WebSocket (Socket.IO) for real-time updates:
-
Clients join a “ship room” when viewing a ship.
-
When any field changes, server:
- validates & persists to SQLite
- broadcasts an update event to that room
-
Frontend
Phone-first layout, 3–4 screens:
- Ship List (create/select/delete)
- Ship Dashboard (core stats + weapons list)
- Edit Weapon (or inline editing)
- Rules/Reference (read-only)
Use constrained controls:
- numeric steppers/sliders for numbers
- dropdowns for enums (size category, maneuverability class, etc.)
- toggles for booleans
- “Add weapon” form with select + numeric inputs
3) Data model (SQLite)
Implement migrations on startup.
Tables
ships
-
idTEXT (uuid, PK) -
nameTEXT (unique-ish, not required but recommended) -
Core stats (examples; adjust to your desired fields):
hull_currentINTEGER NOT NULLhull_maxINTEGER NOT NULLarmor_currentINTEGER NOT NULLarmor_maxINTEGER NOT NULLacINTEGER NOT NULLcon_saveINTEGER NULL (orcon_score)speedINTEGER NULLmaneuver_classTEXT NULL (enum: A/B/C/D/E/F/S per your rules)size_categoryTEXT NULL
-
notesTEXT NULL -
updated_atINTEGER (unix ms)
weapons
idTEXT (uuid, PK)ship_idTEXT (FK -> ships.id, indexed)nameTEXT NOT NULLtypeTEXT NULL (optional enum: siege/arcane/etc.)attack_modINTEGER NULLdamageTEXT NULL (freeform like “3d10”)rangeTEXT NULLammo_currentINTEGER NULLammo_maxINTEGER NULLstatusTEXT NULL (enum: ok/damaged/disabled)notesTEXT NULLsort_orderINTEGER NOT NULL default 0updated_atINTEGER
rules_pages (optional; for read-only reference)
idTEXT PK (e.g., “battle”, “ship-stats”)titleTEXTcontent_markdownTEXTupdated_atINTEGER
Keep rules content as markdown stored in DB or ship it as static markdown files in the frontend. For v1, static markdown is simplest.
4) API design (REST)
Ships
GET /api/ships→ list ships{id, name, updated_at}POST /api/ships→ create{name, initialStats...}→ returns full shipGET /api/ships/:id→ full ship + weaponsPATCH /api/ships/:id→ update core stats (validated + constrained)DELETE /api/ships/:id→ delete ship + weapons
Weapons
POST /api/ships/:id/weapons→ add weaponPATCH /api/weapons/:weaponId→ update weapon fieldsDELETE /api/weapons/:weaponId→ delete weapon
Validation rules (server-enforced):
- current values must be
0 <= current <= max - max values must be
>= 0 - enums must be in allowed set
- name length limits
5) WebSocket events (Socket.IO)
Client → Server
ship:join{shipId}ship:leave{shipId}ship:update{shipId, patch}// core stats patchweapon:create{shipId, weapon}weapon:update{weaponId, patch}weapon:delete{weaponId}
Server → Client (broadcast to ship room)
ship:state{ship, weapons}// on join, or after major changesship:patched{shipId, patch, updated_at}weapon:created{shipId, weapon}weapon:patched{shipId, weaponId, patch, updated_at}weapon:deleted{shipId, weaponId}
Implementation approach:
- On any mutation, server writes to DB then emits event.
- On join, server emits current
ship:state. - Clients apply patches locally; on reconnect, re-join and refresh state.
Conflict model:
- Last write wins is fine (your requirement). Keep it simple.
6) Frontend UI details (phone-first)
Screen 1: Ship List
-
List cards: name + “last updated”
-
Buttons:
- “Create ship” (modal with name + optional initial stats)
- Tap card to open
- Delete icon → confirm modal (“Delete ship X?”)
Screen 2: Ship Dashboard
Sections:
-
Vitals
- Hull: current/max (stepper)
- Armor: current/max
- AC (stepper)
-
Mobility
- Speed (stepper)
- Maneuver class (dropdown)
- Size category (dropdown)
-
Weapons
- List weapons with quick fields (ammo, status)
- Tap weapon to open “Weapon details”
- “Add weapon” button
-
Notes
- Multi-line text
Include a top bar:
- Ship name
- “Rules” link/button
- “Back to ships”
Screen 3: Weapon Details (or modal)
Constrained inputs for ammo/status/attack_mod. Freeform for damage/range/notes.
Screen 4: Rules/Reference
- Static markdown viewer with headings + quick links
- Optional “search within rules” (client-side filter)
7) Persistence + backups
- SQLite file at
/data/spelljammer.sqlite - Docker volume mount that path.
- Optional: periodic backup script/cron outside app (copy DB file nightly).
8) Docker Compose (single service)
-
Build a single image that serves both:
- backend API + websockets
- frontend static files
-
Expose one port (e.g., 3000) to Caddy reverse proxy.
Example structure (for the implementing AI):
/serverFastify app/webReact app build output served by server (or Caddy, but keep simple)
9) Implementation steps (what the AI should do in order)
-
Create repo scaffold with
/serverand/web -
Implement SQLite schema + migrations
-
Implement REST endpoints + validation
-
Add Socket.IO with rooms:
- join/leave
- mutation events that persist + broadcast
-
Build React UI:
- routing for screens
- state store (zustand or simple context)
- socket connection + reconnection handling
-
Wire CRUD flows:
- list → select → join room → render state
- edit fields → emit update → optimistic UI
-
Add delete confirmation modal
-
Add rules markdown page (static)
-
Dockerize + compose + test behind Caddy
10) Acceptance criteria checklist
- ✅ Create ship, appears in list, persists after restart
- ✅ Select ship, see dashboard with core stats + weapons
- ✅ Delete ship requires confirm, removes weapons too
- ✅ All edits propagate to another browser within <1s
- ✅ App is usable on phone (single column layout, large touch targets)
- ✅ SQLite file persists via mounted volume
- ✅ WebSocket reconnect works (refresh state on reconnect)
- ✅ No auth, no user accounts
11) “Reference guide” integration (v1)
Since you want read-only rules “for during battle”: Start with a curated markdown page that copies only the snippets you frequently need (e.g., maneuverability classes, hull/armor notes, weapon usage, combat steps). You can refine later.
(If you want, I can extract a concise “Battle Reference” page from your PDF and format it as markdown for the AI to drop into /web/src/rules/battle.md.)
12) v1 Implementation Status
Status: v1 COMPLETE (implemented 2026-02-19)
All 14 commits have been implemented:
- ✅ Project scaffold (Fastify + Vite + TypeScript)
- ✅ SQLite connection + migration system
- ✅ Ship REST API with Zod validation
- ✅ Weapon REST API with validation
- ✅ Socket.IO with rooms + real-time events
- ✅ React routing + layout (mobile-first dark theme)
- ✅ Zustand stores + Socket.IO client
- ✅ Ship List page with create modal
- ✅ Ship Dashboard (vitals + mobility)
- ✅ Weapons section (add/edit/detail modals + notes)
- ✅ Delete confirmation polish
- ✅ Rules/Reference page (collapsible battle reference)
- ✅ Dockerfile + docker-compose
- ✅ README with testing + deployment docs
See README.md for full setup, testing, and deployment instructions.