docs: create roadmap (7 phases)
This commit is contained in:
@@ -152,20 +152,87 @@ Verschoben — anerkannt, aber nicht in dieser Roadmap.
|
||||
|
||||
| Requirement | Phase | Status |
|
||||
|-------------|-------|--------|
|
||||
| LVL-01 — LVL-15 | Phase ? | Pending |
|
||||
| PWA-01 — PWA-10 | Phase ? | Pending |
|
||||
| PUSH-01 — PUSH-08 | Phase ? | Pending |
|
||||
| BAT-01 — BAT-11 | Phase ? | Pending |
|
||||
| DICE-01 — DICE-07 | Phase ? | Pending |
|
||||
| CHAT-01 — CHAT-06 | Phase ? | Pending |
|
||||
| GM-01 — GM-08 | Phase ? | Pending |
|
||||
| VAULT-01 — VAULT-10 | Phase ? | Pending |
|
||||
| LVL-01 | Phase 1 | Pending |
|
||||
| LVL-02 | Phase 1 | Pending |
|
||||
| LVL-03 | Phase 1 | Pending |
|
||||
| LVL-04 | Phase 1 | Pending |
|
||||
| LVL-05 | Phase 1 | Pending |
|
||||
| LVL-06 | Phase 1 | Pending |
|
||||
| LVL-07 | Phase 1 | Pending |
|
||||
| LVL-08 | Phase 1 | Pending |
|
||||
| LVL-09 | Phase 1 | Pending |
|
||||
| LVL-10 | Phase 1 | Pending |
|
||||
| LVL-11 | Phase 1 | Pending |
|
||||
| LVL-12 | Phase 1 | Pending |
|
||||
| LVL-13 | Phase 1 | Pending |
|
||||
| LVL-14 | Phase 1 | Pending |
|
||||
| LVL-15 | Phase 1 | Pending |
|
||||
| PWA-01 | Phase 2 | Pending |
|
||||
| PWA-02 | Phase 2 | Pending |
|
||||
| PWA-03 | Phase 2 | Pending |
|
||||
| PWA-04 | Phase 2 | Pending |
|
||||
| PWA-05 | Phase 2 | Pending |
|
||||
| PWA-06 | Phase 2 | Pending |
|
||||
| PWA-07 | Phase 2 | Pending |
|
||||
| PWA-08 | Phase 2 | Pending |
|
||||
| PWA-09 | Phase 2 | Pending |
|
||||
| PWA-10 | Phase 2 | Pending |
|
||||
| PUSH-01 | Phase 3 | Pending |
|
||||
| PUSH-02 | Phase 3 | Pending |
|
||||
| PUSH-03 | Phase 3 | Pending |
|
||||
| PUSH-04 | Phase 3 | Pending |
|
||||
| PUSH-05 | Phase 3 | Pending |
|
||||
| PUSH-06 | Phase 3 | Pending |
|
||||
| PUSH-07 | Phase 3 | Pending |
|
||||
| PUSH-08 | Phase 3 | Pending |
|
||||
| DICE-01 | Phase 4 | Pending |
|
||||
| DICE-02 | Phase 4 | Pending |
|
||||
| DICE-03 | Phase 4 | Pending |
|
||||
| DICE-04 | Phase 4 | Pending |
|
||||
| DICE-05 | Phase 4 | Pending |
|
||||
| DICE-06 | Phase 4 | Pending |
|
||||
| DICE-07 | Phase 4 | Pending |
|
||||
| CHAT-01 | Phase 4 | Pending |
|
||||
| CHAT-02 | Phase 4 | Pending |
|
||||
| CHAT-03 | Phase 4 | Pending |
|
||||
| CHAT-04 | Phase 4 | Pending |
|
||||
| CHAT-05 | Phase 4 | Pending |
|
||||
| CHAT-06 | Phase 4 | Pending |
|
||||
| BAT-01 | Phase 5 | Pending |
|
||||
| BAT-02 | Phase 5 | Pending |
|
||||
| BAT-03 | Phase 5 | Pending |
|
||||
| BAT-04 | Phase 5 | Pending |
|
||||
| BAT-05 | Phase 5 | Pending |
|
||||
| BAT-06 | Phase 5 | Pending |
|
||||
| BAT-07 | Phase 5 | Pending |
|
||||
| BAT-08 | Phase 5 | Pending |
|
||||
| BAT-09 | Phase 5 | Pending |
|
||||
| BAT-10 | Phase 5 | Pending |
|
||||
| BAT-11 | Phase 5 | Pending |
|
||||
| GM-01 | Phase 6 | Pending |
|
||||
| GM-02 | Phase 6 | Pending |
|
||||
| GM-03 | Phase 6 | Pending |
|
||||
| GM-04 | Phase 6 | Pending |
|
||||
| GM-05 | Phase 6 | Pending |
|
||||
| GM-06 | Phase 6 | Pending |
|
||||
| GM-07 | Phase 6 | Pending |
|
||||
| GM-08 | Phase 6 | Pending |
|
||||
| VAULT-01 | Phase 7 | Pending |
|
||||
| VAULT-02 | Phase 7 | Pending |
|
||||
| VAULT-03 | Phase 7 | Pending |
|
||||
| VAULT-04 | Phase 7 | Pending |
|
||||
| VAULT-05 | Phase 7 | Pending |
|
||||
| VAULT-06 | Phase 7 | Pending |
|
||||
| VAULT-07 | Phase 7 | Pending |
|
||||
| VAULT-08 | Phase 7 | Pending |
|
||||
| VAULT-09 | Phase 7 | Pending |
|
||||
| VAULT-10 | Phase 7 | Pending |
|
||||
|
||||
**Coverage:**
|
||||
- v1 requirements: 75 total
|
||||
- Mapped to phases: 0 (wird beim Roadmap-Erstellen befüllt)
|
||||
- Unmapped: 75 ⚠️ (initial — wird durch gsd-roadmapper aufgelöst)
|
||||
- Mapped to phases: 75 (Phase 1: 15, Phase 2: 10, Phase 3: 8, Phase 4: 13, Phase 5: 11, Phase 6: 8, Phase 7: 10)
|
||||
- Unmapped: 0 ✓
|
||||
|
||||
---
|
||||
*Requirements defined: 2026-04-27*
|
||||
*Last updated: 2026-04-27 after initial definition*
|
||||
*Last updated: 2026-04-27 after roadmap mapping*
|
||||
|
||||
141
.planning/ROADMAP.md
Normal file
141
.planning/ROADMAP.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# Roadmap: Dimension47 — Milestone "PWA-Companion"
|
||||
|
||||
## Overview
|
||||
|
||||
Diese Roadmap baut auf der bestehenden Dimension47-Codebase auf (Auth, Kampagnen, Charakterbogen mit HP/Zuständen/Inventar/Talenten/Aktionen/Alchemie/HTML-Export, Equipment-DB mit 5.482 Items, WebSocket-Sync, Battle-MVP, GM-Library — alles bereits geliefert) und liefert die nächste Ausbaustufe: regelkonformes PF2e-Level-Up, installierbare PWA mit Offline-Read, Web-Push für GM-Pings, getrennter Battle-Display-Mode für den eingelassenen Tisch-Bildschirm, In-App-Würfler mit Chat, GM-Live-Cockpit und Obsidian-Vault-Browser. Sieben Phasen in Forschungs-empfohlener Reihenfolge: Level-Up beginnt unabhängig und etabliert Test-Disziplin und WebSocket-Kanal-Trennung als Querschnittsmuster, danach baut PWA-Foundation die Service-Worker-Basis für Push und Vault-Offline-Cache, anschließend laufen Push, Würfeln/Chat und Battle-Ausbau parallel-möglich, und am Ende konsolidieren GM-Live-Tools und Vault.
|
||||
|
||||
## Phases
|
||||
|
||||
**Phase Numbering:**
|
||||
- Integer phases (1, 2, 3): Planned milestone work
|
||||
- Decimal phases (2.1, 2.2): Urgent insertions (marked with INSERTED)
|
||||
|
||||
Decimal phases appear between their surrounding integers in numeric order.
|
||||
|
||||
- [ ] **Phase 1: Level-Up (PF2e regelkonform)** - Charakter steigt im Wizard regelkonform mit allen Wahlen auf, Werte stimmen automatisch
|
||||
- [ ] **Phase 2: PWA Foundation** - App ist installierbar, der eigene Charakterbogen ist offline lesbar, Updates blockieren keine Battle-Session
|
||||
- [ ] **Phase 3: Web Push** - Spieler erhalten gezielte Push-Benachrichtigungen vom GM auf installierten Geräten
|
||||
- [ ] **Phase 4: Würfeln & Chat** - Server-autoritatives PF2e-Würfeln und Echtzeit-Chat mit Roll-Embeds pro Kampagne und Battle
|
||||
- [ ] **Phase 5: Battle Multi-Screen Ausbau** - Eigener Display-Mode fürs Tisch-Display, Initiative-Tracker, Token-Effekte, sicher getrennt von GM-Sicht
|
||||
- [ ] **Phase 6: GM Live-Tools** - GM steuert HP/Zustände/Items/Geld der Spieler-Charaktere live aus einem Cockpit-Panel und sendet gezielte Pings
|
||||
- [ ] **Phase 7: Obsidian Vault (Read-only)** - Vault-Browser mit Markdown, Wikilinks, Bildern, Suche und Offline-Cache der zuletzt gelesenen Notes
|
||||
|
||||
## Phase Details
|
||||
|
||||
### Phase 1: Level-Up (PF2e regelkonform)
|
||||
**Goal**: Spieler und GM heben Charaktere regelkonform und nachvollziehbar auf das nächste Level — alle PF2e-Wahlachsen werden im Wizard angeboten, Voraussetzungen werden geprüft, abgeleitete Werte werden automatisch neu berechnet, und ein bestätigter Aufstieg ist atomar persistiert.
|
||||
**Depends on**: Nothing (first phase)
|
||||
**Requirements**: LVL-01, LVL-02, LVL-03, LVL-04, LVL-05, LVL-06, LVL-07, LVL-08, LVL-09, LVL-10, LVL-11, LVL-12, LVL-13, LVL-14, LVL-15
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. Spieler startet auf seinem Charakter "Stufe steigen", durchläuft einen Wizard mit allen für sein Level relevanten Wahlpunkten (Boost, Klassentalent, Fertigkeitstalent, Allgemein-Talent, Skill-Increase, Ancestry-Talent, Klassenmerkmal — je nach Level) und sieht in jedem Schritt nur Optionen, deren Voraussetzungen erfüllt sind, und Free-Archetype-Slots sind sichtbar, wenn aktiviert.
|
||||
2. Spieler kann den Wizard jederzeit abbrechen oder zurückgehen, ohne dass der Charakter bisher mutiert wurde — Charakterbogen außerhalb des Wizards zeigt unveränderten Vorher-Stand bis zur ausdrücklichen Bestätigung.
|
||||
3. Nach Bestätigung sind HP-Max, Save-Boni, AC, Klassen-DC und Wahrnehmung gemäß PF2e neu berechnet (Boost-Cap-bei-18 wird respektiert: bestehender Wert ≥18 → +1, sonst +2), und alle anderen Mitspieler sehen den neuen Level + neuen Stat-Block in Echtzeit über WebSocket.
|
||||
4. Spellcaster-Charaktere sehen nach dem Aufstieg korrekte Slot- und Cantrip-Progression gemäß ihrer Klasse/Tradition; spontane Caster sehen zusätzlich erhöhtes Repertoire-Limit.
|
||||
5. Bestätigtes Level-Up erzeugt einen nachvollziehbaren Historieneintrag (Snapshot-Vorher in JSON) und ist atomar persistiert — kein halbes Level-Up bei Fehler mitten im Commit.
|
||||
**Plans**: TBD
|
||||
**UI hint**: yes
|
||||
|
||||
**Spike Required**: Voraussetzungs-DSL-Scope (LVL-09) und Free-Archetype-Slot-Restriktionen müssen vor der Implementierung geklärt werden. Die Discuss-Phase soll fragen: (a) Welche Voraussetzungs-Muster sind mechanisch evaluierbar (Skill-Rang, Talent-Besitz, Level, Klasse) versus Escape-Hatch-Warnung? Empfehlung: Warnung statt Hard-Block für nicht-evaluierbare Prereqs. (b) Welche Archetyp-Talent-Quellen sind nach der Dedication zulässig — strikt nur der gewählte Archetyp oder beliebige Archetyp-Talente (Pathbuilder-Verhalten)?
|
||||
|
||||
**First-Phase Note**: Phase 1 ist die ERSTE Phase des neuen Milestones. Hier werden zwei Querschnittsmuster etabliert, die alle nachfolgenden Phasen erben:
|
||||
- **Test-Disziplin**: Reine Funktionen (Boost-Cap-Logik, Skill-Increase-Cap, Voraussetzungs-Evaluator, Recompute-Formeln) werden mit Unit-Tests gesichert. Die existierende Codebase hat null Tests; Pitfall-Mapping zeigt Boost-Cap-bei-18 und Skill-Increase-History als typische "schleichende Korruption"-Bugs. Test-Net wird in dieser Phase aufgesetzt und in allen Folge-Phasen für reine Funktionen weitergeführt.
|
||||
- **WebSocket-Channel-Trennung**: `level_up_committed` ergänzt das bestehende `CharacterUpdatePayload['type']`-Union als sauberer neuer Kanal-Typ. Atomare Transaktion + einzelner Broadcast (statt mehrerer kleiner Updates während Recompute) ist das Muster, das in Phase 4 (Dice-Gateway), Phase 5 (Battle-Sub-Rooms) und Phase 6 (GM-Live-Tools auditiert bestehende Char-Events) angewendet wird.
|
||||
|
||||
### Phase 2: PWA Foundation
|
||||
**Goal**: Die App ist als installierbare PWA verfügbar, der eigene Charakterbogen mit allen Tabs ist auch ohne Internet lesbar, Service-Worker-Updates passieren kontrolliert per User-Bestätigung und niemals während einer aktiven Battle-Session, und es gibt keinen Cache-Leak zwischen User-Logins auf dem gleichen Gerät.
|
||||
**Depends on**: Phase 1 (Level-Up touch-points an `CharactersService`/`CharactersGateway` sind abgeschlossen, bevor Caching-Layer drüberkommt)
|
||||
**Requirements**: PWA-01, PWA-02, PWA-03, PWA-04, PWA-05, PWA-06, PWA-07, PWA-08, PWA-09, PWA-10
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. Spieler installiert die App auf seinem Android-Handy via "Zum Startbildschirm" (automatischer Prompt) oder auf seinem iPhone via geführter Anleitung im App-Overlay (Safari-Schritte) und sieht danach das Dimension47-Icon auf dem Home-Screen mit korrektem Splash beim Öffnen.
|
||||
2. Spieler startet die App im Flugmodus oder ohne WLAN und sieht den zuletzt geladenen Charakterbogen vollständig — Inventar, Talente, Aktionen, Alchemie sind lesbar, ohne Login-Fehler oder kaputte Seite.
|
||||
3. Wenn ein zweiter User auf dem gleichen Browser/Gerät einloggt, sieht er nur seine eigenen Daten — keine Charakter- oder Kampagnen-Daten des Vorgänger-Users sind aus dem Cache abrufbar.
|
||||
4. Wenn während einer aktiven Battle-Session ein neues App-Update auf dem Server liegt, lädt der Tab nicht automatisch neu — der Update-Hinweis erscheint erst, nachdem die Battle-Session geschlossen ist, und der Spieler bestätigt das Update manuell.
|
||||
5. Wenn ein Charakter live über WebSocket aktualisiert wird (z.B. HP-Schaden), zeigt die App nach Reconnect/Reload sofort den frischen Wert — der gecachte alte Wert wird über `postMessage`-Invalidation aus dem Service-Worker-Cache entfernt.
|
||||
**Plans**: TBD
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 3: Web Push
|
||||
**Goal**: Eingeloggte Spieler abonnieren auf Wunsch Push-Benachrichtigungen pro Gerät, der GM kann gezielt einzelne Spieler oder die ganze Kampagne anpingen, das System sendet automatisch einen Battle-Turn-Alert, wenn ein Spieler dran ist, und abgelaufene Subscriptions werden serverseitig automatisch aufgeräumt.
|
||||
**Depends on**: Phase 2 (Service Worker muss existieren, damit der Push-Handler darin laufen kann)
|
||||
**Requirements**: PUSH-01, PUSH-02, PUSH-03, PUSH-04, PUSH-05, PUSH-06, PUSH-07, PUSH-08
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. Spieler aktiviert in den Einstellungen "Benachrichtigungen aktivieren", erteilt im OS-Prompt die Erlaubnis, und das Gerät erscheint in seiner Subscription-Liste — er kann dieselbe Aktion auf einem zweiten Gerät wiederholen und beide Geräte werden separat abonniert.
|
||||
2. Wenn der GM "Ping an Spieler X" oder "Ping an Kampagne" auslöst, erhält der Spieler eine sichtbare Notification (auch wenn die App im Hintergrund oder geschlossen ist), und ein Tap auf die Notification öffnet die App auf dem Battle-, Charakter- oder Chat-Screen je nach Payload.
|
||||
3. Wenn die Initiative im Battle auf den Charakter eines Spielers wechselt, erhält dieser Spieler automatisch einen Push "Du bist dran" auf allen abonnierten Geräten, ohne dass der GM manuell auslösen muss.
|
||||
4. Wenn ein Spieler eine Subscription auf einem Gerät widerruft (Browser-Settings) oder das Gerät die Subscription rotiert, wird der zugehörige DB-Eintrag beim nächsten fehlgeschlagenen Send (410 Gone) automatisch gelöscht, ohne dass die Push-Pipeline für andere Subscriptions kaputtgeht.
|
||||
5. VAPID-Keys liegen persistiert in `.env` (`VAPID_PUBLIC_KEY`, `VAPID_PRIVATE_KEY`, `VAPID_SUBJECT`) und sind in `.env.example` als persistente App-Secrets dokumentiert mit klar formuliertem Rotations-Verfahren — bei Re-Deploy gehen Subscriptions nicht verloren.
|
||||
**Plans**: TBD
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 4: Würfeln & Chat
|
||||
**Goal**: Spieler und GM würfeln in PF2e-Notation server-autoritativ (kein Client-Tampering möglich), das Ergebnis ist live für alle Teilnehmer einer Kampagne und Battle sichtbar, PF2e-spezifische Crit-Mechaniken (Schaden-Würfel verdoppeln, Modifikatoren NICHT) und Degree-of-Success werden korrekt berechnet, und ein Echtzeit-Chat mit Markdown und Roll-Embeds läuft pro Kampagne und Battle.
|
||||
**Depends on**: Phase 2 (Offline-Read des Roll-Logs nutzt Service-Worker-Cache)
|
||||
**Requirements**: DICE-01, DICE-02, DICE-03, DICE-04, DICE-05, DICE-06, DICE-07, CHAT-01, CHAT-02, CHAT-03, CHAT-04, CHAT-05, CHAT-06
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. Spieler tippt `1d20+7` (oder nutzt Quick-Buttons), das Ergebnis erscheint sofort in der Roll-Liste mit Wer/Was/Wann/optionaler DC, ist server-seitig persistiert und wird live an alle Mitspieler in derselben Kampagne und Battle-Session gepusht — eine manipulierte Client-Payload wird vom Server abgewiesen.
|
||||
2. Bei einem Schaden-Roll mit Crit (`2d6+4` Crit) zeigt die App `4d6+4` als Berechnung — Würfel-Anzahl verdoppelt, Modifikator unverändert; bei einem Check gegen DC mit ±10-Regel zeigt die App den korrekten Degree-of-Success (Crit Success / Success / Failure / Crit Failure).
|
||||
3. Spieler postet eine Markdown-Nachricht in den Kampagnen- oder Battle-Chat, alle Mitspieler sehen sie sofort, und ein injizierter `<img onerror=...>` oder `[click](javascript:...)` wird vom Renderer neutralisiert (kein Skript-Auslösen, keine `dangerouslySetInnerHTML`-Lecks).
|
||||
4. Spieler postet einen Roll in den Chat, das Ergebnis erscheint inline als Roll-Card im Chat-Verlauf (FK `embedRollId` von ChatMessage auf DiceRoll), und der Chat-Verlauf lädt paginiert beim Hochscrollen via Cursor.
|
||||
5. Wenn ein Spieler 30 Sekunden Verbindungsverlust hat und in dieser Zeit Rolls/Nachrichten passieren, sieht er nach Reconnect die fehlenden Einträge über REST-Refetch (nicht nur live-WebSocket) — Roll-Log und Chat-History sind in Postgres mit Composite-Index `(campaignId, createdAt)` und `(battleSessionId, createdAt)` performant abrufbar.
|
||||
**Plans**: TBD
|
||||
**UI hint**: yes
|
||||
|
||||
### Phase 5: Battle Multi-Screen Ausbau
|
||||
**Goal**: Der Battle hat zwei sicher getrennte Sichten: ein GM-Steuerpanel auf dem Laptop und ein read-only Display-Mode für den eingelassenen Tisch-Bildschirm, wobei GM-only-Daten serverseitig gefiltert werden (nicht clientseitig versteckt). Initiative-Tracker zeigt sortiert "wer ist dran", Token-Effekte sind als Datenmodell modelliert und auf beiden Sichten sichtbar, und Token-Add/Remove laufen sauber als WebSocket-Events.
|
||||
**Depends on**: Phase 4 (Dice-Anbindung in Initiative-Card und Roll-Embeds in Chat sind nutzbar; Sub-Room-Pattern profitiert vom Test-Net aus Phase 1)
|
||||
**Requirements**: BAT-01, BAT-02, BAT-03, BAT-04, BAT-05, BAT-06, BAT-07, BAT-08, BAT-09, BAT-10, BAT-11
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. GM klickt "Display starten", die Display-Route `/battle/:id/display` öffnet sich (auf zweitem Monitor / Tisch-Display) mit großschriftigem Querformat-Layout — keine Drag-Handles, keine GM-Sidebar, keine GM-Notizen sichtbar; bei `socket.onAny(console.log)` in den DevTools des Display-Clients erscheinen NULL GM-only-Felder im WebSocket-Frame.
|
||||
2. Display-Route ist mit einem kurzlebigen Display-Token authentifiziert, das vom GM ausgestellt wurde und an die Battle-Session gebunden ist — kopierte URL aus einem anderen Browser-Profil sieht nichts, abgelaufenes Token wird nach Battle-Ende vom Server abgewiesen.
|
||||
3. Initiative-Tracker zeigt eine sortierte Liste mit deutlichem "Wer ist dran"-Highlight (auch aus Tisch-Distanz lesbar), GM-"Nächster Zug"-Button rückt die Initiative weiter, und das Event broadcastet binnen ~1s an GM-Sicht, Display-Sicht und alle Spieler — keine inkonsistenten Zwischenzustände.
|
||||
4. GM kann pro Token Effekte hinzufügen, entfernen und aktualisieren (Name, Typ Buff/Debuff/Aura/Marker, optionale Dauer in Runden), und sowohl GM- als auch Display-Sicht zeigen sie als Pille/Badge am Token sowie in der Initiative-Karte; Effekte sind mit FK auf Token persistiert.
|
||||
5. GM fügt einen neuen Token hinzu oder entfernt einen — beide Aktionen broadcasten als saubere `token:added`/`token:removed`-WebSocket-Events (nicht als Query-Invalidate-Workaround), und alle Clients sehen das Update sofort.
|
||||
**Plans**: TBD
|
||||
**UI hint**: yes
|
||||
|
||||
**Spike Required**: Display-Token-Modell (BAT-03) muss vor Implementierung geklärt werden. Die Discuss-Phase soll fragen: (a) JWT-Variante mit `battle:id`-Claim, kurzem TTL und server-validierter Display-Route — wer stellt aus (GM-Klick "Display starten"), wo wird das Token transportiert (Query-Param, Header, Cookie), wann erlischt es (Battle-Ende-Hook, TTL-Default)? Empfehlung gemäß SUMMARY.md: Server stellt One-Shot-URL aus, GM klickt zum Generieren. (b) Tisch-Display-Hardware-Specs (Aspect-Ratio, Auflösung) müssen vor Phase-Ende vom User vorliegen, damit das Display-Layout korrekt skaliert (Pitfall #24).
|
||||
|
||||
### Phase 6: GM Live-Tools
|
||||
**Goal**: Der GM hat ein zentrales Cockpit-Panel, von dem aus er HP, Zustände, Items und Geld jedes Spieler-Charakters live setzen kann (über die bestehenden Char-WebSocket-Events, aber serverseitig auditiert auf GM-Rolle), und kann gezielt Push-/Chat-Nachrichten an einen Spieler oder alle Spieler senden. Destruktive Aktionen (HP=0, Item löschen) sind durch zweistufige Bestätigung abgesichert.
|
||||
**Depends on**: Phase 3 (Push für GM-Ping) + Phase 4 (Chat für gezielte Nachrichten)
|
||||
**Requirements**: GM-01, GM-02, GM-03, GM-04, GM-05, GM-06, GM-07, GM-08
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. GM öffnet das Live-Tools-Panel im Kampagnen-Detail und sieht alle Spieler-Charaktere mit Quick-Actions — er klickt "Schaden 5" auf Charakter A, der HP-Wert geht live runter, der Spieler von Charakter A sieht den neuen Wert in Echtzeit auf seinem Charakterbogen.
|
||||
2. GM kann eine Condition (z.B. Frightened 2) auf einem Spieler-Charakter setzen, entfernen oder updaten — der Spieler sieht sie sofort im eigenen Conditions-Tab; gleicher Pfad gilt für Item-Übergabe (Suche aus Equipment-DB → Auswahl → live im Inventar des Spielers) und Geld-Anpassung (Credits live).
|
||||
3. GM klickt "Ping an Spieler X mit Nachricht 'Wirf Reflex DC 25'" — Spieler X sieht innerhalb von ~1s die Push-Notification (auch wenn App im Hintergrund) UND eine Chat-Nachricht im Kampagnen-Chat; Ping an "alle Spieler" erreicht alle gleichzeitig.
|
||||
4. Wenn GM eine destruktive Aktion auslöst (HP auf 0 setzen, Item löschen), erscheint ein Bestätigungs-Dialog, der den betroffenen Charakter und die Aktion explizit nennt — versehentlicher Klick ohne Bestätigung mutiert nichts.
|
||||
5. Wenn ein Nicht-GM-User versucht, einen GM-Live-Tool-Event über manuelle WebSocket-Payloads abzufeuern (z.B. fremden Charakter modifizieren), lehnt der Server ab — die bestehenden Character-WebSocket-Events sind serverseitig auditiert auf "Aktor ist GM dieser Kampagne" und Lücken (falls vorhanden) sind geschlossen.
|
||||
**Plans**: TBD
|
||||
**UI hint**: yes
|
||||
|
||||
**Spike Required**: Authorization-Audit der bestehenden Character-WebSocket-Events (GM-06) muss vor Implementierung der GM-UI passieren. Die Discuss-Phase soll fragen: Welche der existierenden Char-Events (`character_update`-Familie in `characters.gateway.ts`) prüfen heute schon "Aktor ist GM dieser Kampagne", welche prüfen nur "Aktor ist Owner"? FEATURES.md flaggt Lücken; vor dem Bauen einer GM-UI, die diese Events massenhaft auslöst, muss die Server-Seite konsistent sein.
|
||||
|
||||
### Phase 7: Obsidian Vault (Read-only)
|
||||
**Goal**: GM und Spieler lesen Markdown-Notes aus dem selbst-gehosteten Vault über einen In-App-Browser — Ordner-Navigation, Wikilinks zwischen Notes, Bild-Embeds, Volltextsuche, alles über die NestJS-Server-API geleitet (nie direkter Browser-Zugriff auf Vault-Credentials), und zuletzt gelesene Notes sind offline verfügbar.
|
||||
**Depends on**: Phase 2 (Service Worker für Offline-Note-Cache)
|
||||
**Requirements**: VAULT-01, VAULT-02, VAULT-03, VAULT-04, VAULT-05, VAULT-06, VAULT-07, VAULT-08, VAULT-09, VAULT-10
|
||||
**Success Criteria** (what must be TRUE):
|
||||
1. Spieler navigiert im Vault-Browser durch eine Ordner-Tree-Struktur, öffnet eine Markdown-Datei und sieht sie gerendert mit GFM-Tabellen, Codeblöcken, Bildern (`![[image.png]]`) und Wikilinks (`[[Note]]` und `[[Note|Alias]]`); ein Klick auf einen Wikilink öffnet die verlinkte Note.
|
||||
2. Wenn zwei Notes denselben Basenamen haben, wird der mehrdeutige Wikilink dem User explizit als "mehrdeutig" angezeigt mit Auswahl-Liste — der Renderer sucht NICHT stillschweigend einen Treffer aus; ein zyklischer Embed (A → B → A) rendert einen Platzhalter "(Zyklus)" statt Stack-Overflow.
|
||||
3. Spieler nutzt die Volltextsuche und findet eine bekannte Note in einem Vault mit zumindest einem realistischen Inhalts-Volumen — Such-Endpoint ist server-seitig (Postgres FTS) und respektiert die JWT-Auth.
|
||||
4. Spieler liest eine Note online, geht offline (Flugmodus), öffnet die App erneut und kann dieselbe Note (und andere kürzlich gelesene Notes) weiterhin lesen — der Service-Worker-Cache (StaleWhileRevalidate, LRU) hat sie vorgehalten.
|
||||
5. Wenn ein User versucht, einen Pfad mit `../../etc/passwd` über die Vault-API zu lesen, lehnt der Server ab (Path-Traversal-Guard); Vault-Credentials liegen ausschließlich in `.env` am NestJS-Server und sind im Browser-DevTools-Network-Tab nicht sichtbar.
|
||||
**Plans**: TBD
|
||||
**UI hint**: yes
|
||||
|
||||
**Spike Required**: Vault-Transport-Entscheidung (VAULT-01) muss vor Implementierung getroffen werden. Die Discuss-Phase soll fragen: Liegt der Obsidian-Vault auf derselben Maschine wie der NestJS-Server (Filesystem-Mount via `fs/promises` mit `FsVaultProvider`, einfacher und niedriger Latenz) oder auf einer separaten Maschine (Synology/Nextcloud, dann WebDAV-Proxy via `webdav` lib mit `WebDAVVaultProvider`)? Empfehlung gemäß SUMMARY.md: `VaultProvider`-DI-Interface, Default `FsVaultProvider`, `WebDAVVaultProvider` als swappable Alternative.
|
||||
|
||||
## Progress
|
||||
|
||||
**Execution Order:**
|
||||
Phasen werden in numerischer Reihenfolge ausgeführt: 1 → 2 → 3 → 4 → 5 → 6 → 7. Negotiable parallelization (mit Bandwidth zur Hand): Phase 4 darf direkt nach Phase 2 starten (ohne Phase 3 abzuwarten); Phase 7 darf direkt nach Phase 2 starten (ohne Phase 5/6 abzuwarten). Hard dependencies bleiben: 2 vor 3, 2 vor 7, 3+4 vor 6.
|
||||
|
||||
| Phase | Plans Complete | Status | Completed |
|
||||
|-------|----------------|--------|-----------|
|
||||
| 1. Level-Up (PF2e regelkonform) | 0/TBD | Not started | - |
|
||||
| 2. PWA Foundation | 0/TBD | Not started | - |
|
||||
| 3. Web Push | 0/TBD | Not started | - |
|
||||
| 4. Würfeln & Chat | 0/TBD | Not started | - |
|
||||
| 5. Battle Multi-Screen Ausbau | 0/TBD | Not started | - |
|
||||
| 6. GM Live-Tools | 0/TBD | Not started | - |
|
||||
| 7. Obsidian Vault (Read-only) | 0/TBD | Not started | - |
|
||||
80
.planning/STATE.md
Normal file
80
.planning/STATE.md
Normal file
@@ -0,0 +1,80 @@
|
||||
# Project State
|
||||
|
||||
## Project Reference
|
||||
|
||||
See: .planning/PROJECT.md (updated 2026-04-27)
|
||||
|
||||
**Core value:** Am Tisch funktioniert alles in Echtzeit und regelkonform — Charakterbogen am Handy, Battle-Steuerung am Laptop, Tisch-Display als read-only Spielsicht, ohne Reibung, ohne falsche Werte, ohne Reload.
|
||||
**Current focus:** Phase 1 — Level-Up (PF2e regelkonform)
|
||||
|
||||
## Current Position
|
||||
|
||||
Phase: 1 of 7 (Level-Up (PF2e regelkonform))
|
||||
Plan: 0 of TBD in current phase
|
||||
Status: Ready to plan
|
||||
Last activity: 2026-04-27 — Roadmap created from research-recommended 7-phase build order
|
||||
|
||||
Progress: [░░░░░░░░░░] 0%
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
**Velocity:**
|
||||
- Total plans completed: 0
|
||||
- Average duration: —
|
||||
- Total execution time: 0 hours
|
||||
|
||||
**By Phase:**
|
||||
|
||||
| Phase | Plans | Total | Avg/Plan |
|
||||
|-------|-------|-------|----------|
|
||||
| — | — | — | — |
|
||||
|
||||
**Recent Trend:**
|
||||
- Last 5 plans: —
|
||||
- Trend: —
|
||||
|
||||
*Updated after each plan completion*
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
### Decisions
|
||||
|
||||
Decisions are logged in PROJECT.md Key Decisions table.
|
||||
Recent decisions affecting current work:
|
||||
|
||||
- Milestone init: Level-Up wird voll regelkonform implementiert (statt Re-Import-only)
|
||||
- Milestone init: PWA mit Web Push, kein Native-Wrapper (PWA-Install ersetzt App-Store)
|
||||
- Milestone init: Battle-Screen bleibt Laptop+Tisch-Display, kein Pflicht-Mobile
|
||||
- Milestone init: Obsidian-Anbindung ist v1 nur lesend
|
||||
- Milestone init: Vault wird selbst-gehostet (Transport-Protokoll wird in Phase 7 entschieden)
|
||||
- Roadmap: 7-Phasen-Reihenfolge gemäß Research-Empfehlung (Level-Up zuerst, dann PWA als Foundation für Push/Vault)
|
||||
|
||||
### Pending Todos
|
||||
|
||||
None yet.
|
||||
|
||||
### Blockers/Concerns
|
||||
|
||||
Open spike questions documented per phase in ROADMAP.md (Spike Required notes):
|
||||
- Phase 1: Voraussetzungs-DSL-Scope, Free-Archetype-Slot-Restriktionen
|
||||
- Phase 5: Display-Token-Modell, Tisch-Display-Hardware-Specs
|
||||
- Phase 6: Authorization-Audit der bestehenden Character-WebSocket-Events
|
||||
- Phase 7: Vault-Transport-Entscheidung (Filesystem-Mount vs WebDAV)
|
||||
|
||||
Cross-cutting from research (PITFALLS.md):
|
||||
- Codebase hat null Tests heute — Phase 1 etabliert Test-Disziplin für reine Funktionen
|
||||
- WebSocket-Channel-Trennung (separate Sub-Rooms vs ein Broadcast) wird in Phase 1 als Muster etabliert und in Phase 5 für GM/Display ausgebaut
|
||||
|
||||
## Deferred Items
|
||||
|
||||
Items acknowledged and carried forward from previous milestone close:
|
||||
|
||||
| Category | Item | Status | Deferred At |
|
||||
|----------|------|--------|-------------|
|
||||
| *(none)* | | | |
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-04-27 (Initialisierung)
|
||||
Stopped at: Roadmap erstellt, alle 75 v1-Requirements gemappt, bereit für `/gsd-plan-phase 1`
|
||||
Resume file: None
|
||||
371
CLAUDE.md
371
CLAUDE.md
@@ -219,3 +219,374 @@ cd server && npm run db:generate # Prisma Client generieren
|
||||
cd server && npm run db:seed # Basis-Seed-Daten laden
|
||||
cd server && npm run db:seed:equipment # Equipment-Datenbank laden
|
||||
```
|
||||
|
||||
<!-- GSD:project-start source:PROJECT.md -->
|
||||
## Project
|
||||
|
||||
**Dimension47**
|
||||
|
||||
Dimension47 ist eine selbst gehostete Web-App für Pathfinder-2e-Tischrunden auf Deutsch. Sie verwaltet Kampagnen, Charakterbögen mit komplettem PF2e-Regelumfang (HP, Zustände, Inventar, Talente, Aktionen, Alchemie) und stellt einen GM-Battle-Screen mit 3D-Druck-Tisch-Display bereit — alles in Echtzeit synchronisiert. Zielgruppe ist die eigene Spielgruppe, nicht die breite Öffentlichkeit.
|
||||
|
||||
**Core Value:** Am Tisch funktioniert alles in Echtzeit und regelkonform: Spieler lesen ihren Charakterbogen am Handy, der GM steuert vom Laptop aus den Battle-Screen, der eingelassene Tisch-Display zeigt die Spielsicht — ohne Reibung, ohne falsche Werte, ohne Reload.
|
||||
|
||||
### Constraints
|
||||
|
||||
- **Tech-Stack**: NestJS 11 + React 19 + Prisma 7 + PostgreSQL + Socket.io + Tailwind v4 — gesetzt durch Bestand, kein Stack-Wechsel sinnvoll
|
||||
- **Sprache**: Deutsche UI durchgehend — alle neuen Texte ebenfalls Deutsch
|
||||
- **Design**: Mobile-First für alle nutzerseitigen Screens (Charakterbogen, Vault-Browser, Würfler, Chat); Battle-GM-Screen darf desktop-fokussiert bleiben; Tisch-Display ist eigener Layout-Modus
|
||||
- **Daten-Persistenz**: Alle PF2e-Daten gehören in die DB (Prisma-Seeds aus JSON), nichts wird zur Laufzeit aus JSON-Dateien gelesen
|
||||
- **Migrationen**: Schema-Änderungen ausschließlich über `prisma migrate dev`, niemals `db push`
|
||||
- **Code-Qualität**: TypeScript strict, keine `any`-Types, kein Quick-Fix der später wehtut
|
||||
- **Hosting-Modell**: Self-hosted für eigene Spielgruppe — keine Multi-Tenant-/SaaS-Anforderungen
|
||||
- **Push-Plattform**: Web Push (Service-Worker-basiert), kein FCM/APNs-Native-Wrapper
|
||||
- **Vault-Endpoint**: Selbst-gehosteter Endpoint (vermutlich auf eigenem Server) — konkretes Protokoll wird in der Vault-Phase entschieden, nicht jetzt
|
||||
<!-- GSD:project-end -->
|
||||
|
||||
<!-- GSD:stack-start source:codebase/STACK.md -->
|
||||
## Technology Stack
|
||||
|
||||
## Languages
|
||||
- TypeScript 5.9.3 (server), 5.7.3 (client) - Strict mode enabled, full type safety across codebase
|
||||
- JavaScript (build outputs, scripts)
|
||||
- HTML/CSS (frontend templates)
|
||||
## Runtime
|
||||
- Node.js (version not pinned, assumed LTS) - Used for both server and client dev/build
|
||||
- ES2023 target (server), ES2022 target (client)
|
||||
- npm - Lockfiles present for both client and server
|
||||
## Frameworks
|
||||
- React 19.2.0 - UI framework
|
||||
- React Router DOM 7.12.0 - Client-side routing
|
||||
- Vite 7.2.4 - Build tool and dev server
|
||||
- NestJS 11.0.1 - Web framework with TypeScript-first design
|
||||
- Express (via `@nestjs/platform-express`) - Underlying HTTP server
|
||||
- Tailwind CSS 4.1.18 - Utility-first CSS framework
|
||||
- `@tailwindcss/vite` plugin for Vite integration
|
||||
- Jest 30.0.0 - Test runner (server-side only, configured in package.json)
|
||||
- ts-jest - TypeScript support for Jest
|
||||
- Supertest 7.0.0 - HTTP assertion library (server integration tests)
|
||||
- @vitejs/plugin-react - React fast refresh for Vite
|
||||
## Key Dependencies
|
||||
- `@prisma/client` 7.2.0 - ORM and database abstraction layer
|
||||
- `prisma` 7.2.0 - CLI and schema management tool
|
||||
- `@anthropic-ai/sdk` 0.71.2 - Claude API for on-demand German translations
|
||||
- `socket.io` 4.8.3 - WebSocket library for real-time communication
|
||||
- `@nestjs/websockets` 11.1.12 - NestJS WebSocket integration
|
||||
- `@nestjs/platform-socket.io` 11.1.12 - Socket.io adapter for NestJS
|
||||
- `@nestjs/jwt` 11.0.2 - JWT authentication provider
|
||||
- `@nestjs/passport` 11.0.5 - Passport.js integration for authentication
|
||||
- `passport-jwt` 4.0.1 - JWT strategy for Passport
|
||||
- `bcrypt` 6.0.0 - Password hashing
|
||||
- `class-validator` 0.14.3 - Request DTO validation
|
||||
- `class-transformer` 0.5.1 - DTO transformation
|
||||
- `@nestjs/swagger` 11.2.5 - OpenAPI/Swagger documentation
|
||||
- `@nestjs/config` 4.0.2 - Environment configuration management
|
||||
- `dotenv` 17.2.3 - .env file loading
|
||||
- `axios` 1.13.2 - HTTP client library for API requests
|
||||
- `socket.io-client` 4.8.3 - WebSocket client for real-time updates
|
||||
- `zustand` 5.0.10 - Client state management (auth store)
|
||||
- `@tanstack/react-query` 5.90.19 - Server state and data fetching (caching, synchronization)
|
||||
- `react-router-dom` 7.12.0 - Client-side routing
|
||||
- `framer-motion` 12.26.2 - Animation library
|
||||
- `lucide-react` 0.562.0 - Icon library (SVG icons)
|
||||
- `clsx` 2.1.1 - Conditional CSS class utilities
|
||||
- `tailwind-merge` 3.4.0 - Tailwind class merging utility
|
||||
- `@nestjs/cli` 11.0.0 - NestJS code generation and project scaffolding
|
||||
- `@nestjs/schematics` 11.0.0 - Code generators for NestJS
|
||||
- `prettier` 3.4.2 - Code formatter
|
||||
- `eslint` 9.18.0 - Linting
|
||||
- `typescript-eslint` 8.20.0 - TypeScript ESLint support
|
||||
- `tsx` 4.21.0 - TypeScript execution (used for seed scripts)
|
||||
- `tsconfig-paths` 4.2.0 - TypeScript path alias resolution
|
||||
- `ts-node` 10.9.2 - TypeScript REPL and script runner
|
||||
- `ts-loader` 9.5.2 - TypeScript webpack loader
|
||||
- `eslint` 9.39.1 - Linting
|
||||
- `typescript-eslint` 8.46.4 - TypeScript ESLint support
|
||||
- `eslint-plugin-react-hooks` 7.0.1 - React Hooks linting rules
|
||||
- `eslint-plugin-react-refresh` 0.4.24 - Vite React refresh linting
|
||||
## Configuration
|
||||
- Server loads from `.env` file (see `.env.example`):
|
||||
- Client loads from `.env` or `.env.local`:
|
||||
- Server: `server/tsconfig.json` with target ES2023, decorators enabled
|
||||
- Client: `client/tsconfig.app.json` with target ES2022, strict mode
|
||||
- Client Vite config: `client/vite.config.ts`
|
||||
- Server NestJS config: `server/nest-cli.json` (if exists)
|
||||
## Platform Requirements
|
||||
- Node.js LTS (tested with v22)
|
||||
- npm (lockfiles version management)
|
||||
- PostgreSQL 16+ (via Docker Compose)
|
||||
- Docker & Docker Compose (for database)
|
||||
- Git
|
||||
- Node.js LTS
|
||||
- PostgreSQL 16+ (managed service or self-hosted)
|
||||
- Anthropic API key for translations (optional but recommended)
|
||||
- File storage: Local filesystem or cloud storage (currently local via `./uploads`)
|
||||
## Database
|
||||
- Port 5432 (internal), 5433 (exposed for dev)
|
||||
- Database name: `dimension47`
|
||||
- Default credentials in docker-compose (dev only)
|
||||
- pgAdmin 4 included for database management (port 5050)
|
||||
- Schema: `server/prisma/schema.prisma`
|
||||
- Generator: Prisma Client with CommonJS module format
|
||||
- Adapter: `@prisma/adapter-pg` for optimized PostgreSQL queries
|
||||
- Output: `src/generated/prisma/`
|
||||
<!-- GSD:stack-end -->
|
||||
|
||||
<!-- GSD:conventions-start source:CONVENTIONS.md -->
|
||||
## Conventions
|
||||
|
||||
## Naming Patterns
|
||||
- Components: kebab-case (e.g., `character-sheet-page.tsx`, `add-condition-modal.tsx`, `hp-control.tsx`)
|
||||
- Utilities/Services: kebab-case (e.g., `use-character-socket.ts`, `export-character-html.ts`)
|
||||
- Directories: kebab-case (e.g., `features/`, `shared/`, `components/`)
|
||||
- camelCase for all function names
|
||||
- Hooks prefixed with `use` (e.g., `useAuthStore`, `useCharacterSocket`, `useMemo`, `useState`)
|
||||
- Event handlers prefixed with `handle` (e.g., `handleApply`, `handleConnection`, `handleDisconnect`)
|
||||
- Getter/setter methods follow camelCase (e.g., `getToken()`, `setToken()`)
|
||||
- camelCase for all variable names
|
||||
- Constants in UPPER_SNAKE_CASE (e.g., `PROFICIENCY_BONUS`, `SKILL_DATA`, `TABS`)
|
||||
- Type unions with capitalized names (e.g., `CharacterType`, `AbilityType`, `TabType`)
|
||||
- Boolean prefixes: `is`, `has`, `should`, `can` (e.g., `isLoading`, `isOwner`, `hasAccess`)
|
||||
- PascalCase for all types, interfaces, and enums
|
||||
- Domain types in shared: `Character`, `Campaign`, `User`, `CharacterItem`, `CharacterFeat`
|
||||
- Props interfaces suffixed with `Props` (e.g., `HpControlProps`, `AddConditionModalProps`)
|
||||
- DTO interfaces suffixed with `Dto` (e.g., `CreateCharacterDto`, `LoginDto`, `RegisterDto`)
|
||||
- State types in Zustand: domain-specific (e.g., `AuthState`)
|
||||
## Code Style
|
||||
- Prettier configured in `server/.prettierrc` with:
|
||||
- Line length: implicit (Prettier default ~80 chars but allows overflow)
|
||||
- Indentation: 2 spaces
|
||||
- Client: ESLint 9 with flat config (`eslint.config.js`)
|
||||
- Server: ESLint 9 with flat config (`eslint.config.mjs`)
|
||||
- **Strict Mode Enforced** on client (`client/tsconfig.app.json`):
|
||||
- Server TypeScript (`server/tsconfig.json`):
|
||||
## Import Organization
|
||||
- Client: `@/*` → `./src/*` (defined in `vite.config.ts` and `tsconfig.app.json`)
|
||||
- Server: No path alias configured; uses relative paths
|
||||
- Always use `@/` prefix on client to avoid relative path hell
|
||||
- Barrel files used: `features/*/index.ts`, `shared/components/ui/index.ts`
|
||||
- Default exports on pages: `export default App`
|
||||
- Named exports for utilities, types, hooks: `export { HpControl }`, `export const api = new ApiClient()`
|
||||
## Error Handling
|
||||
- Use NestJS exception classes: `NotFoundException`, `ForbiddenException`, `UnauthorizedException`, `ConflictException`
|
||||
- Access checks before operations (e.g., `checkCampaignAccess()`, `checkCharacterAccess()` in `characters.service.ts`)
|
||||
- Return meaningful error messages: "Campaign not found", "No access to this campaign"
|
||||
- Prisma operations wrapped in try-catch only when necessary for data validation
|
||||
- JWT verification throws exceptions through guard: `JwtAuthGuard` handles 401s
|
||||
- Axios interceptors for request/response handling (in `api.ts`)
|
||||
- 401 errors trigger logout and redirect to `/login` (except auth endpoints)
|
||||
- Error state in components: `isLoading`, `error`, `pendingChange` states
|
||||
- Modal operations: try/catch with `setIsLoading(false)` in finally block
|
||||
- Error logging: `console.error()` for debugging, no silent failures
|
||||
- User feedback: Navigation on error (`navigate()`) or via error state
|
||||
- Token validation on connection (throws `client.disconnect()`)
|
||||
- Logger for all connection/disconnection events
|
||||
- Errors logged but don't crash server
|
||||
## Logging
|
||||
- Server: NestJS `Logger` (in modules/gateways)
|
||||
- Client: `console.error()` for error debugging
|
||||
- Server logs connection events with user context
|
||||
- Client silently catches most errors, logs critical ones
|
||||
- No debug logging infrastructure in place
|
||||
## Comments
|
||||
- Constants with unclear meaning (e.g., `PROFICIENCY_BONUS` values, skill-to-ability mappings)
|
||||
- Complex calculations (e.g., HP percentage calculations)
|
||||
- Business logic that isn't immediately obvious (e.g., Pathfinder 2e rules)
|
||||
- NOT used for obvious code ("increment counter")
|
||||
- Minimal usage observed
|
||||
- API methods documented with Swagger decorators on server: `@ApiOperation()`, `@ApiResponse()`
|
||||
- No runtime JSDoc comments in source
|
||||
## Function Design
|
||||
- Modal components: 500-1700 lines (large, but feature-complete)
|
||||
- Service methods: 10-50 lines (concise, focused)
|
||||
- Utility functions: 5-20 lines
|
||||
- Interface-based: Pass objects instead of multiple params
|
||||
- Optional params with defaults: `remember: boolean = false`
|
||||
- Async functions return typed Promises: `async getCharacter(): Promise<Character>`
|
||||
- Component callbacks: callbacks are async promises (`onHpChange: (newHp: number) => Promise<void>`)
|
||||
- Services return full domain objects with relations included
|
||||
## Module Design
|
||||
- Components export as named: `export function HpControl() { ... }`
|
||||
- Utilities export as named: `export const api = new ApiClient()`
|
||||
- One export per file (generally)
|
||||
- Used in features: `features/auth/index.ts` re-exports `useAuthStore`, `LoginPage`, `RegisterPage`
|
||||
- Used for UI components: `shared/components/ui/index.ts` re-exports all buttons, cards, inputs
|
||||
- Reduces import complexity
|
||||
- NestJS @Injectable() decorator for services
|
||||
- Constructor injection: `constructor(private prisma: PrismaService)`
|
||||
- Circular dependencies resolved with @Inject(forwardRef()): seen in `CharactersService`
|
||||
## State Management
|
||||
- Single store pattern: `create<AuthState>()`
|
||||
- Persisted state with middleware: `persist()`
|
||||
- Partialize: stores only non-sensitive fields (`user`, `isAuthenticated`)
|
||||
- Actions as methods in store: `login()`, `logout()`, `checkAuth()`
|
||||
- Error state in store: `error: string | null`
|
||||
- No client state management needed
|
||||
- WebSocket gateway maintains connection map: `connectedClients = new Map()`
|
||||
## Design Tokens (UI)
|
||||
- Primary: Magenta `#c26dbc` (in Tailwind as `primary-500`, `primary-600`, etc.)
|
||||
- Secondary: Dark grays for dark mode (`secondary-800`, `secondary-700`)
|
||||
- Text: Primary (light), secondary (dimmer), tertiary (dimmest)
|
||||
- Background: Primary, secondary, tertiary layers
|
||||
- Error: Red (`error-500`, `error-600`)
|
||||
- Success: Green (`green-500`)
|
||||
- Warning: Yellow (`yellow-500`)
|
||||
- Font sizes: sm, base, lg (Tailwind defaults)
|
||||
- Weights: normal, medium (600), semibold
|
||||
- All UI text in German except code/technical terms
|
||||
- Lucide React exclusively (e.g., `Heart`, `Swords`, `BookOpen`, `Package`)
|
||||
- No emoji anywhere
|
||||
- Icon sizes: `h-4 w-4` (standard), `h-6 w-6` (large)
|
||||
- Minimum 44px (mobile-first, accessible)
|
||||
- Buttons: `h-11` (44px), `h-9` (36px for small), `h-12` (48px for large)
|
||||
- Icon buttons: `h-11 w-11` (44x44px)
|
||||
- Tailwind v4 defaults: `p-4`, `gap-2`, `rounded-lg`, `rounded-2xl`
|
||||
- Modals: `rounded-t-2xl sm:rounded-2xl` (bottom sheet on mobile, centered on desktop)
|
||||
- Mobile-first breakpoints: `sm:` prefix for tablet+ (e.g., `sm:items-center`, `sm:rounded-2xl`)
|
||||
- Flexbox for layouts: `flex`, `flex-col`, `items-center`, `justify-between`
|
||||
- Grid rarely used
|
||||
<!-- GSD:conventions-end -->
|
||||
|
||||
<!-- GSD:architecture-start source:ARCHITECTURE.md -->
|
||||
## Architecture
|
||||
|
||||
## Pattern Overview
|
||||
- Modular NestJS architecture with feature-based modules (Auth, Characters, Campaigns, Equipment, Battle)
|
||||
- Feature-first React organization with shared components and hooks
|
||||
- WebSocket Gateway real-time sync for character and battle state
|
||||
- Role-based access control (ADMIN, GM, PLAYER) enforced globally
|
||||
- Prisma ORM for PostgreSQL data persistence
|
||||
- JWT-based stateless authentication
|
||||
## Layers
|
||||
- Purpose: HTTP request handling and routing
|
||||
- Location: `server/src/modules/*/[feature].controller.ts`
|
||||
- Contains: REST endpoints with decorators (@Post, @Get, @Put, @Patch, @Delete)
|
||||
- Depends on: Services, DTOs, Guards, Decorators
|
||||
- Used by: HTTP clients (React frontend)
|
||||
- Examples:
|
||||
- Purpose: Business logic, data processing, validation
|
||||
- Location: `server/src/modules/*/[feature].service.ts`
|
||||
- Contains: Methods for creating, updating, deleting entities; complex calculations
|
||||
- Depends on: PrismaService, other Services, external APIs (Claude)
|
||||
- Used by: Controllers, Gateways, other Services
|
||||
- Examples:
|
||||
- Purpose: Real-time bidirectional communication for live updates
|
||||
- Location: `server/src/modules/*/[feature].gateway.ts`
|
||||
- Contains: Socket.io event handlers, authentication, room management
|
||||
- Depends on: JwtService, PrismaService, Services
|
||||
- Used by: React WebSocket hooks
|
||||
- Examples:
|
||||
- Purpose: Database abstraction and ORM
|
||||
- Location: `server/src/prisma/prisma.service.ts`
|
||||
- Contains: Prisma client wrapper, query interface
|
||||
- Depends on: PostgreSQL database
|
||||
- Used by: All Services
|
||||
- Data Models defined in: `server/prisma/schema.prisma`
|
||||
- Purpose: Isolated feature domains with components, hooks, types
|
||||
- Location: `client/src/features/[feature]/`
|
||||
- Contains: Components, hooks, index.ts barrel exports
|
||||
- Examples:
|
||||
- Purpose: Reusable components, hooks, utilities, types across features
|
||||
- Location: `client/src/shared/`
|
||||
- Contains:
|
||||
- Used by: All features
|
||||
- Purpose: Navigation and route protection
|
||||
- Location: `client/src/App.tsx`
|
||||
- Contains: React Router v6 routes, protected route wrapper
|
||||
- Depends on: React Router, Feature components
|
||||
- Entry point: `client/src/main.tsx`
|
||||
- Purpose: Client-side state persistence
|
||||
- Location: `client/src/features/auth/hooks/use-auth-store.ts`
|
||||
- Contains: Zustand store for authentication state
|
||||
- Used by: Auth-related components, ProtectedRoute
|
||||
## Data Flow
|
||||
### Character Update (Real-Time WebSocket Sync)
|
||||
### Equipment Database Search
|
||||
### Character Creation (Pathbuilder Import)
|
||||
### Battle Session Synchronization
|
||||
### Alchemy System State
|
||||
## Key Abstractions
|
||||
- Purpose: Database abstraction, query builder
|
||||
- Location: `server/src/prisma/prisma.service.ts`
|
||||
- Pattern: Singleton service injected into all modules
|
||||
- Used for: All CRUD operations, complex queries with relations
|
||||
- Purpose: Authentication and authorization
|
||||
- Location:
|
||||
- Pattern: NestJS guards executed globally on every request
|
||||
- Metadata: `@Roles()` and `@Public()` decorators control per-endpoint behavior
|
||||
- Purpose: HTTP communication abstraction
|
||||
- Location: `client/src/shared/lib/api.ts`
|
||||
- Pattern: Singleton class with axios instance
|
||||
- Features: Token management, auto-retry, auth interceptors, 401 handling
|
||||
- Purpose: Prevent duplicate connections, manage subscriptions
|
||||
- Location: `client/src/shared/hooks/use-character-socket.ts`
|
||||
- Pattern: Global socket singleton with ref counting
|
||||
- Features: Auto-reconnect, polling fallback, room subscription
|
||||
- Purpose: Type-safe event dispatch
|
||||
- Location:
|
||||
- Pattern: Union types for event kind discrimination
|
||||
## Entry Points
|
||||
- Location: `server/src/main.ts`
|
||||
- Triggers: `npm run start:dev` or deployed container startup
|
||||
- Responsibilities:
|
||||
- Location: `client/src/main.tsx`
|
||||
- Triggers: Browser page load or `npm run dev`
|
||||
- Responsibilities:
|
||||
- Location: `client/src/App.tsx`
|
||||
- Pattern: React Router v6 with protected routes
|
||||
- Flow:
|
||||
## Error Handling
|
||||
- `NotFoundException` - 404 when entity not found
|
||||
- `ForbiddenException` - 403 when user lacks permission
|
||||
- `BadRequestException` - 400 for invalid input
|
||||
- `UnauthorizedException` - 401 for auth failures
|
||||
- Global error filter could be added for consistent formatting
|
||||
- Service methods validate access before querying: `checkCampaignAccess()`, `checkCharacterAccess()`
|
||||
- API client `response.interceptors` catches 401 → redirects to /login
|
||||
- Components wrapped in error boundaries (future enhancement)
|
||||
- Failed requests return rejected promises to component
|
||||
- Token verification on connection → disconnect if invalid
|
||||
- No structured error responses; silent failures with console logging
|
||||
- Clients auto-reconnect via socket.io configuration
|
||||
## Cross-Cutting Concerns
|
||||
- Backend: NestJS Logger class used in services/gateways
|
||||
- Frontend: Console.log for development (socket.io events log connection state)
|
||||
- Backend: Global ValidationPipe with DTOs (class-validator)
|
||||
- Frontend: Form validation in components (manual checks in modals)
|
||||
- Prisma schema enforces constraints (NOT NULL, unique, enums)
|
||||
- Backend: JwtAuthGuard applied globally in app.module.ts
|
||||
- Endpoints opt-out via @Public() decorator
|
||||
- WebSocket: Token verified in gateway.handleConnection()
|
||||
- Frontend: Token stored in localStorage (persistent) or sessionStorage (session-only)
|
||||
- Backend: RolesGuard checks @Roles() metadata
|
||||
- Service methods verify campaign/character ownership before allowing operations
|
||||
- Pattern: Check campaign membership → check character ownership → allow operation
|
||||
- WebSocket Gateway manages rooms (one room per character/session)
|
||||
- Clients join room on component mount, leave on unmount
|
||||
- Broadcasts to all clients in room except sender (optional)
|
||||
<!-- GSD:architecture-end -->
|
||||
|
||||
<!-- GSD:skills-start source:skills/ -->
|
||||
## Project Skills
|
||||
|
||||
No project skills found. Add skills to any of: `.claude/skills/`, `.agents/skills/`, `.cursor/skills/`, or `.github/skills/` with a `SKILL.md` index file.
|
||||
<!-- GSD:skills-end -->
|
||||
|
||||
<!-- GSD:workflow-start source:GSD defaults -->
|
||||
## GSD Workflow Enforcement
|
||||
|
||||
Before using Edit, Write, or other file-changing tools, start work through a GSD command so planning artifacts and execution context stay in sync.
|
||||
|
||||
Use these entry points:
|
||||
- `/gsd-quick` for small fixes, doc updates, and ad-hoc tasks
|
||||
- `/gsd-debug` for investigation and bug fixing
|
||||
- `/gsd-execute-phase` for planned phase work
|
||||
|
||||
Do not make direct repo edits outside a GSD workflow unless the user explicitly asks to bypass it.
|
||||
<!-- GSD:workflow-end -->
|
||||
|
||||
<!-- GSD:profile-start -->
|
||||
## Developer Profile
|
||||
|
||||
> Profile not yet configured. Run `/gsd-profile-user` to generate your developer profile.
|
||||
> This section is managed by `generate-claude-profile` -- do not edit manually.
|
||||
<!-- GSD:profile-end -->
|
||||
|
||||
Reference in New Issue
Block a user