# DnD Campaign Hub - Agent Guidelines ## Project Overview Monorepo with a Node.js/Express API + Discord bot and React/Vite frontend. **Stack:** - Server: Node.js + TypeScript + Express + SQLite + Discord.js - Web: React 19 + TypeScript + Vite + React Router - Testing: Vitest - Build: npm workspaces ## Commands ### Root (Monorepo) ```bash npm install # Install all dependencies npm run dev # Run both server and web in dev mode npm run build # Build server and web npm run test # Run all tests ``` ### Server (`apps/server`) ```bash npm run dev -w @dnd-hub/server # Dev mode with hot reload (tsx) npm run build -w @dnd-hub/server # Compile TypeScript npm run start -w @dnd-hub/server # Run compiled server npm run test -w @dnd-hub/server # Run server tests npm run migrate -w @dnd-hub/server # Run DB migrations ``` ### Web (`apps/web`) ```bash npm run dev -w @dnd-hub/web # Vite dev server (port 5173) npm run build -w @dnd-hub/web # Type check + Vite build npm run preview -w @dnd-hub/web # Preview production build npm run test -w @dnd-hub/web # Run web tests ``` ### Running a Single Test ```bash # Run specific test file npm run test -w @dnd-hub/server -- test/sessionService.test.ts npm run test -w @dnd-hub/web -- src/smoke.test.ts # Run with filter pattern npm run test -w @dnd-hub/server -- --run --reporter=verbose ``` ## Code Style ### TypeScript - **Strict mode enabled** in both apps (`strict: true`) - Target ES2022, use ESNext modules - Server: `module: NodeNext`, `moduleResolution: NodeNext` - Web: `module: ESNext`, `moduleResolution: Bundler` - Always use `.ts`/`.tsx` extensions (no `.js` in src) ### Imports - Use ES modules (`import`/`export`) - Include `.js` extension for relative imports in server code - Group imports: React/hooks first, then libraries, then local imports - Example: ```typescript import { useEffect, useState } from "react"; import { Link, Route, Routes } from "react-router-dom"; import { api } from "./api/client"; ``` ### Naming Conventions - **Files**: camelCase for components/services (e.g., `CharacterSheetDrawer.tsx`, `sessionService.ts`) - **Components**: PascalCase (e.g., `CharacterPage`, `ToastProvider`) - **Functions/variables**: camelCase - **Constants**: UPPER_SNAKE_CASE for config values - **Types/Interfaces**: PascalCase (e.g., `Me`, `Campaign`) - **Test files**: `*.test.ts` or `*.test.tsx` alongside or near tested code ### Formatting - 2-space indentation - Semicolons required - Double quotes for strings (single in JSX when needed) - Trailing commas in multi-line objects/arrays - Max line length: ~100 chars (flexible) ### React Conventions - Functional components with hooks only (no class components) - Custom hooks prefixed with `use` (e.g., `useToast`, `useDebugMode`) - Context providers for global state (Toast, Debug, CharacterSheet) - Arrow functions for event handlers - TypeScript interfaces for props ### Error Handling - Server: Use try/catch with proper HTTP status codes - Validate inputs with Zod schemas - Web: Use Toast context for user-facing errors - Log errors server-side, show friendly messages client-side - Never expose stack traces to clients ### API Design - RESTful endpoints under `/routes/` - Use Express middleware for auth/validation - CORS configured per environment - Health check at `GET /health` ### Database - SQLite via better-sqlite3 (synchronous) - Migrations in `src/db/migrate.ts` - Path: `./data/dnd_hub.db` ### Environment Variables - Load via dotenv from monorepo root `.env` - Use `config.ts` wrapper with defaults for dev - Required vars throw on startup if missing - Never commit `.env` (use `.env.example`) ### Testing - Framework: Vitest - Pattern: `describe`/`it`/`expect` - Import from `vitest` - Tests should be isolated and deterministic - Smoke tests for basic functionality ## Project Structure ``` dnd-hub/ ├── apps/ │ ├── server/ │ │ ├── src/ │ │ │ ├── routes/ # Express route handlers │ │ │ ├── services/ # Business logic │ │ │ ├── db/ # Database schema/migrations │ │ │ ├── discord/ # Discord bot commands │ │ │ ├── jobs/ # Scheduled cron jobs │ │ │ └── main.ts # Entry point │ │ └── test/ # Test files │ └── web/ │ ├── src/ │ │ ├── components/ # React components │ │ ├── contexts/ # React context providers │ │ ├── pages/ # Route page components │ │ ├── api/ # API client │ │ └── App.tsx # Root component │ └── vite.config.ts ├── infra/ # Docker, nginx config ├── scripts/ # Utility scripts ├── .env.example # Environment template └── package.json # Workspace root ``` ## Key Features ### Transcript Sessions - Discord commands: `/session start`, `/session stop`, `/session status` - API endpoints: `POST /sessions/*`, `GET /sessions/:id/transcript` - Cloud sync via `CLOUD_API_BASE_URL` with HMAC signatures ### Discord Integration - OAuth2 authentication flow - Role-based permissions (admin/dm/player) - Game night notifications via node-cron ### Recap System - Configurable providers: ollama (local) or claude (cloud) - Set via `RECAP_MODE` or `RECAP_PROVIDERS` env vars ## Common Tasks ### Adding a New Route 1. Create handler in `apps/server/src/routes/` 2. Register in `app.ts` 3. Add TypeScript types if needed ### Adding a React Component 1. Create in `apps/web/src/components/` 2. Use TypeScript with typed props 3. Follow existing component patterns ### Database Changes 1. Update schema in `src/db/migrate.ts` 2. Run `npm run migrate -w @dnd-hub/server` ### Debug Mode - Web app has debug toggle to simulate player role - Controlled via DebugContext ## Gotchas - `.env` is at monorepo root, loaded relative to workspace - Server uses `.js` extensions in imports (NodeNext module resolution) - Web uses JSX transform (`jsx: react-jsx`) - Both apps share Vitest but run independently - Clean up both `.js` and `.ts` files in web/src (legacy JS exists)