| .claude | ||
| docs | ||
| openspec | ||
| public | ||
| src | ||
| Writerside | ||
| .env.example | ||
| .gitignore | ||
| CLAUDE.md | ||
| DEPLOYMENT.md | ||
| drizzle.config.ts | ||
| eslint.config.mjs | ||
| next.config.ts | ||
| package-lock.json | ||
| package.json | ||
| postcss.config.mjs | ||
| README.md | ||
| tsconfig.json | ||
Mausritter Companion
A private web app for managing a Mausritter tabletop RPG campaign. Built for our campaign group — not publicly accessible.
Goal
A digital companion that lives alongside our sessions. Players can manage their characters between sessions, and the GM has a master view of everything.
Users & Access
GM
- Full account via NextAuth (email or OAuth)
- Can see and edit all characters, NPCs, factions, campaigns, and the world map
- Can lock/unlock editing on individual characters
- Can approve or remove player usernames
Players
- No account required — players identify themselves with a username
- On first visit: enter a chosen username → record created in
userstable → identity persisted in browser - On return visit: enter username → looked up in DB → signed back in (no password)
- Anyone who knows a username can sign in as that player — acceptable trade-off for a private campaign tool
- Can create characters linked to their user account
- Can view and edit their own characters (unless GM has locked one)
Features
Character Sheets
- Core stats: STR, DEX, WIL, HP, Pips, background, birthsign, disposition, coat
- Inventory grid: 6-slot pack + 2 worn slots + main/off paw (matching physical sheet layout)
- Conditions, and XP advancement
- Avatar image upload
- GM lock/unlock to freeze editing
- Character sheet drawer accessible from the AppShell header on any page
Campaigns
- GM creates and manages campaigns (name, description, optional session link)
- One campaign can be active at a time
- Characters are assigned to campaigns
- Players see the active campaign name and join link on their character list
World Map
- Interactive hex map with pan and zoom
- Dynamic grid size (cols × rows, configurable per campaign)
- 3-state hex visibility: hidden / outline (explored, not described) / visible (full detail)
- Click any hex to view/edit: label, terrain type, GM notes, player-facing description
- GM-only floating panel with:
- Wilderness watch tracker: morning/afternoon/evening/night checkboxes, persisted per world map
- Encounter tables: d6 tables authored by GM, rollable in play with inline reaction roll
- World map setup wizard (auto-roll terrain or step through manually)
Combat Tracker
- Client-only, no persistence needed — combats are ephemeral
- Add combatants by name + max HP; initiative order with Up/Down reorder
- Per-combatant HP tracking (+/− buttons), dead badge when HP reaches 0
- Optional enemy image URL per combatant
- End Turn (wraps around), End Combat (clears all)
- Accessible from the AppShell header (⚔ button) on any page; active indicator when combat is in progress
- State persists while navigating between pages
NPC & Faction Tracker (stub — not yet implemented)
- Planned: record NPCs the party has met, track factions and relationships
Loot & Pips (stub — not yet implemented)
- Planned: shared party loot list, pips owed tracker
Tech Stack
Core
- Next.js (App Router, route group layout for shared AppShell)
- Neon +
@neondatabase/serverless— serverless Postgres - NextAuth +
@auth/drizzle-adapter— GM authentication - Mantine (
@mantine/core,@mantine/hooks) — UI component library - Drizzle ORM +
drizzle-kit— schema, queries, migrations - Zod — API and form validation
Hex Map
- honeycomb-grid — hex coordinate math
- react-zoom-pan-pinch — pan and zoom
- react-konva + konva — canvas rendering
Data Model
users — id, username, role (gm | player), created_at
characters — id, user_id, name, stats (JSON), inventory (JSON),
conditions, advancement, avatar_url, locked, created_at
campaigns — id, name, description, campaign_link, active, created_at
campaign_characters — id, campaign_id, character_id
world_maps — id, campaign_id, cols, rows, locked, created_at
hex_map — id, world_map_id, q, r, label, terrain_type,
gm_notes, player_description, visibility, disabled, created_at
encounter_tables — id, world_map_id, name, entries (JSON[6]), created_at
wilderness_state — id, world_map_id (unique), morning_done, afternoon_done,
evening_done, night_done, updated_at
TODO
Character Sheet v2 (stub — character-sheet-v2)
- Inventory layout rework: paws + body + pack left-to-right matching physical sheet
- Conditions as inventory cards occupying pack slots
- STR/DEX/WIL current/max values
- Pips capacity rules (250 in pockets, pip bag for overflow)
- Character description free text field
- Overflow/encumbered inventory
- Grit implementation
- Remove physical rolling instructions from character creation
NPC & Faction Tracker
- NPC list and detail view with Mausritter stat block (stub —
npc-tracker) - NPC creation with rollable fields from rulebook tables
- Spotlight modal (show image/description to players)
- Faction list with named resources and goal progress (stub —
faction-tracker) - Link NPCs to factions (many-to-many)
Loot & Pips
- Shared loot list
- Add/remove loot items
- Pips owed tracker
Adventure Map (specced — adventure-map)
- Per-campaign location maps (image background + draggable markers)
- Dungeon turn tracker (turn counter, checklist, light source countdown)
Adventure Map Drawing (stub — adventure-map-drawing)
- Freehand pen, line, rect, and text annotation tools on the map overlay
- Drawing layer persisted to DB; GM toggle for player visibility
Spell Stones & Magical Items (stub — spell-items)
- Spell stone and magical item tracking with charge counters on the character sheet
GM Character View (stub — gm-character-view)
- GM can view any player's character sheet from the AppShell drawer
Combat HP Sync (stub — combat-hp-sync)
- Link combat tracker combatants to character records; HP changes write back to the character sheet
Spell Stones & Magical Items (stub — spell-items)
- Usage mark dots on character sheet (current/max marks)
- Spell picker from rulebook list
- Casting rules reference collapsible
GM Enhancements
- GM can view any player's character sheet from drawer (stub —
gm-character-view) - Combat tracker HP syncs back to character sheet (stub —
combat-hp-sync)
Polish
- Printable/exportable character sheets
Manual Testing Checklist
Flows to verify in the browser after significant changes:
Campaigns
- GM flow: create campaign, edit name/description/link, activate, deactivate, assign characters, remove characters, delete campaign
- Player flow: active campaign banner + join link appear on character list; nothing shown when inactive or no link
- Cascade: deleting a campaign removes its character assignments; character records intact
World Map
/mapredirects to/world- World map creator (auto-roll terrain, step-through, skip)
- Hex visibility states (hidden/outline/visible) correct for GM vs player views
- Wilderness watch tracker persists across page reload
- Encounter tables: create, roll, reaction roll
Characters
- Character creation → sheet → edit → GM lock/unlock
- Inventory grid: add items, bulky items span 2 slots
- Character sheet drawer: loads correct character, HP/pips save on blur
Auth
- Player username creation and return login
- GM login via NextAuth
- GM-only routes redirect non-GM users
Getting Started
Copy .env.example to .env.local and fill in your values, then:
npm install
npm run dev
Open http://localhost:3000.
See DEPLOYMENT.md for deploy and hosting instructions.
Notes
⏺ Here's what's worth addressing, in order of impact:
High impact
- Auth check inconsistency in API routes
Some routes use the existing isGM() helper, others re-implement the session check manually with getServerSession() + role check. The inconsistency means some routes may behave differently in dev mode. Affects: creatures, hirelings, factions, world-maps routes.
Medium impact
-
Create-resource modal duplicated in list pages creatures/page, hirelings/page, factions/page, campaigns/page all have the same pattern: modal state + TextInput + error Alert + POST fetch + router.push. Same component, different labels.
-
Fetch-on-open pattern in drawers/effects CharacterSheetDrawer, HexDetailDrawer, InventoryLayout all repeat the same useEffect fetch → set state → finally setLoading pattern. A small useFetch hook or the existing fetcher.ts (already in the project) could handle this.
Lower impact (not worth touching now)
-
Polling pattern in DiceTray/SpotlightModal — only 2 callsites, low duplication
-
List filtering (search) — trivial one-liners, not worth abstracting
-
Slot grid rendering — purely layout repetition, no logic
Which of these do you want to tackle? The auth inconsistency (#1) is the most important to fix since it affects correctness. The CRUD boilerplate (#2) and create modal (#3) are purely quality-of-life.