Files
ttrpg_manager/docs/OLD_BUG_HUNT_REPORT.md
T
NilsBriggen 1a9e5e2c18 Build MVP: campaigns, characters, combat, dice, compendium
- Rules abstraction (5e + pf2e) behind one interface, fully tested
- Pure combat engine: turn-order safe across add/remove/reorder/init-change
- Dexie storage with real cascade deletes + Zod validation on write
- Seedable dice engine with notation parser
- Lazy SRD compendium (code-split), bestiary -> combat
- Strict TS, 54 unit tests, Playwright e2e smoke (all green)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 00:09:42 +02:00

265 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TTRPG Manager — Comprehensive Bug Hunt Report
**Date**: 2026-06-03
**Methodology**: 24 parallel AI agents searched 100+ source files across 20+ bug categories. Wave 1: 16 agents. Wave 2: 8 agents (addressing Oracle-identified gaps).
**Baseline**: 197 existing tests all pass (math engine only; 0 store/adapter/UI tests).
---
## 🔴 CRITICAL (19 bugs — data loss, crashes, security incidents)
### Infrastructure & Config
| ID | Finding | File |
|----|---------|------|
| C1 | **`tsconfig.app.json` missing `"strict": true`** — no strictNullChecks, noImplicitAny | `tsconfig.app.json` |
| C2 | **No React ErrorBoundary** — any uncaught render error white-screens entire app in React 19 | `App.tsx` |
| C3 | **CSP set to `null`** — all Content Security Policy protections disabled | `tauri.conf.json:23` |
### Tauri Integration
| ID | Finding | File |
|----|---------|------|
| C4 | **`capabilities.json` is `{}`** — no Tauri v2 permissions | `src-tauri/gen/schemas/capabilities.json` |
| C5 | **`import_campaign`/`export_campaign` are no-op stubs** | `commands.rs:1-9` |
| C6 | **Zero Tauri API usage in frontend** — no `@tauri-apps` imports | All `src/` |
| C7 | **SQLite configured but never queried** — no SQL adapter implemented | `tauri.conf.json:27-31`, `storage/instance.ts:8` |
### Storage & Data Integrity
| ID | Finding | File |
|----|---------|------|
| C8 | **Transaction completion not checked**`promisify` resolves on `request.onsuccess` BEFORE transaction commits. All 30+ writes can silently lose data. | `indexeddb.ts:62-67` |
| C9 | **Campaign deletion does NOT cascade** — 9 child entity types orphaned forever | `campaignStore.ts:80-86` |
| C10 | **Handout data split across localStorage AND IndexedDB** — export/import silently loses all handouts | `handoutStore.ts` / `types.ts:142-150` |
| C11 | **Combat encounter IDs inside `combatantsJson` not remapped on import** | `indexeddb.ts:628-629` |
| C12 | **`updateSettings` reads stale data across separate transactions** | `indexeddb.ts:518-525` |
| C13 | **No data migration strategy**`DB_VERSION = 1` hardcoded, `onupgradeneeded` only creates new stores | `indexeddb.ts:30, 651-723` |
### Security
| ID | Finding | File |
|----|---------|------|
| C14 | **No HTML sanitization library**`contentMd` stores raw HTML (not Markdown). DOMPurify missing. | All note components |
### State Management
| ID | Finding | File |
|----|---------|------|
| C15 | **Direct state mutation in handoutStore** — localStorage split-brain with IndexedDB; dual `Handout` type definitions | `handoutStore.ts:99-105` |
### Combat System
| ID | Finding | File |
|----|---------|------|
| C16 | **`removeCombatant` corrupts turn order** — doesn't decrement index when removing combatant before current turn | `combatStore.ts:165-175` |
| C17 | **`addCombatant`/`updateCombatant`/`reorderCombatants` don't adjust `currentTurnIndex`** | `combatStore.ts:143-150`, `152-163`, `222-229` |
| C18 | **`nextTurn()` calls async `nextRound()` without `await`** — race condition | `combatStore.ts:191-193` |
### Race Conditions (Wave 2)
| ID | Finding | File |
|----|---------|------|
| C19 | **Read-modify-write without locking in all stores** — two rapid mutations can overwrite each other (e.g., double `addCombatant`) | All store files |
---
## 🟠 HIGH (16 bugs — functional breakage under realistic conditions)
### Tauri & Storage
- **H1**: `init()` race — second caller returns promise resolving before `ensureSettings()` seeds defaults. `indexeddb.ts:97-104`
- **H2**: No `onblocked` handler on DB open — hangs indefinitely if another tab has connection. `indexeddb.ts:648-734`
- **H3**: DB connection not reset after unexpected close — `onclose` sets `db=null` but `initPromise` stays stale. `indexeddb.ts:725-730`
- **H4**: `handleClearAll` deletes campaigns but not related entities. `SettingsPage.tsx:148-156`
- **H5**: Full table scan on dice roll history — `getAll()` loads every roll. `indexeddb.ts:415-423`
- **H6**: Full table scan on note search — Fuse.js index rebuilt every keystroke. `indexeddb.ts:369-380`
### Math & Game Mechanics
- **H7**: Heavy armor incorrectly applies negative DEX modifier to AC — `Math.min(-1, 0) = -1`. `five-e/combat.ts:137-138`
- **H8**: `rollD20` / `recoveryCheck` use `Math.random()` despite seedable RNG available. `five-e/rolling.ts:35-36`
- **H9**: Recovery check returns `'success'` not `'criticalSuccess'` for nat 20. `pf2e/dying.ts:44-46`
### Combat System
- **H10**: No tie-breaking in initiative sort — unstable ordering. `combatStore.ts:130`
- **H11**: Reaction resets at end of turn instead of start — double-dipping on reactions. `CombatScreen.tsx:67-80`
- **H12**: Short Rest fully heals instead of requiring Hit Dice rolls. `CombatDetail.tsx:153-154`
### Character System
- **H13**: `ProficiencyRank` type conflict — numeric vs string across two modules. `shared.ts:20` vs `types.ts:12-18`
- **H14**: PF2e HP damage bypasses temporary HP entirely. `CharacterSheetPF2e.tsx:60-62`
### Data Integrity
- **H15**: NaN/Infinity accepted in ALL combat number inputs (7+ locations). `HPManager.tsx:83`, etc.
- **H16**: SettingsPage "Export All" produces non-importable format. `SettingsPage.tsx:129-134`
---
## 🟡 MEDIUM (16 bugs — degraded behavior, fragile states)
### Combat
- **M1**: Dead/unconscious combatants remain in initiative — no skip mechanism. `combatStore.ts:185-194`
- **M2**: HP clamped at 0 — no instant death / massive damage tracking. `HPManager.tsx:31-43`
- **M3**: Shield Block allowed when shield lowered/broken (PF2e rules). `ShieldManager.tsx:151`
- **M4**: PF2e custom condition value only added when > 1, presets use > 0 — inconsistency. `ConditionManager.tsx:74`
### Character System
- **M5**: Character sheet defaults rendered but NEVER persisted. `CharacterSheet5e.tsx:510-531`
- **M6**: Spell slot / hit dice "used" not clamped when "total" reduced. `CharacterSheet5e.tsx:326,385`
- **M7**: No auto-save — unsaved changes silently lost on navigation. `CharacterSheet5e.tsx:534-538`
- **M8**: `importDDBCharacter` does zero actual D&D Beyond parsing. `characterStore.ts:91-109`
### Storage
- **M9**: `Number("0") || 1` coalesces zero to one in ConditionManager. `ConditionManager.tsx:36`
- **M10**: `parseHp` returns HP object with potentially zero `max` causing NaN% bar. `CharacterList.tsx:18-26`
### React Patterns
- **M11**: `useMemo` used for side-effects — should be `useEffect`. `BestiarySearch.tsx:157-159`
- **M12**: `useCallback` misused as `useMemo` in QuestDetail. `QuestDetail.tsx:219-233`
- **M13**: Stale closures from `eslint-disable react-hooks/exhaustive-deps` — 9+ instances.
### Accessibility
- **M14**: No `aria-live` regions — screen readers get no dynamic announcements.
- **M15**: No focus trapping in any modal (4 modals affected).
- **M16**: Icon-only buttons missing `aria-label` — 8 instances.
---
## 🟢 LOW (17 bugs — maintenance concerns, minor UX)
### UI/UX
- **L1**: Fixed sidebar widths not responsive. Multiple components.
- **L2**: Delete buttons invisible until parent hover — undiscoverable for keyboard users.
- **L3**: Color contrast: `text-dnd-muted` (#8a8a9a) on `bg-dnd-bg` (#1a1a2e) ~3.8:1 — fails WCAG AA.
- **L4**: Version number not semantic. `App.tsx:102`
### Edge Cases
- **L5**: No level bounds on proficiency bonus — level 0→+1, level 1000→+251.
- **L6**: Fractional CR monsters corrupted to CR 0 — `Number("1/8")` = NaN.
- **L7**: `advanceDay()` crashes on invalid date — RangeError on `toISOString()`.
### Performance
- **L8**: Zero `React.memo` usage — entire tree rerenders on any change.
- **L9**: No code splitting — 10+ features eagerly imported including MB-scale JSON.
- **L10**: Synchronous `localStorage` on every handout/calendar mutation.
### Theme System (Wave 2)
- **L11**: **Light theme is a null-op**`AppSettings.theme` accepts `'light'` but `globals.css` defines only dark colors. Toggling theme silently does nothing.
### Python Scripts (Wave 2)
- **L12**: `fetch_data.py` contains hardcoded paths (`/home/nilsb/Downloads/`) — won't work on other machines.
- **L13**: `parse_mpmb.py` regex parsing has no error recovery on malformed input.
### PWA (Wave 2)
- **L14**: Service worker registered but no update prompt — old SW persists indefinitely until manual unregister.
### Dead Code (Wave 2)
- **L15**: `@tanstack/react-query` imported but barely used — 13KB gzipped dead weight.
- **L16**: `idMap` populated but never read in `importCampaign`. `indexeddb.ts:572-577`
- **L17**: `data-tab` attributes set but never consumed. `App.tsx:122`
---
## 📊 Summary by System Area
| System Area | Crit | High | Med | Low | Total |
|-------------|------|------|-----|-----|-------|
| Storage/IndexedDB | 6 | 6 | 2 | 0 | **14** |
| Combat System | 3 | 3 | 4 | 0 | **10** |
| Tauri Integration | 4 | 0 | 0 | 0 | **4** |
| Character System | 0 | 2 | 4 | 0 | **6** |
| State Management | 1 | 0 | 0 | 0 | **1** |
| Math/Game Mechanics | 0 | 3 | 0 | 2 | **5** |
| Security | 1 | 0 | 0 | 0 | **1** |
| React/Accessibility | 0 | 0 | 3 | 3 | **6** |
| Infrastructure | 3 | 0 | 0 | 0 | **3** |
| Data Integrity | 0 | 2 | 2 | 0 | **4** |
| Performance | 0 | 0 | 0 | 3 | **3** |
| Race Conditions | 1 | 0 | 0 | 0 | **1** |
| Theme/PWA/Python | 0 | 0 | 0 | 4 | **4** |
| Dead Code | 0 | 0 | 0 | 3 | **3** |
| **TOTAL** | **19** | **16** | **15** | **15** | **65** |
---
## 🎯 Fix Priority Roadmap
### Week 1 — Critical (19 bugs)
1. Enable `"strict": true` in tsconfig
2. Add React ErrorBoundary wrapping `<main>` in App.tsx
3. Add DOMPurify sanitization for all note content rendering
4. Fix `promisify` to await `transaction.oncomplete` (or use `transaction.done`)
5. Implement cascade delete for campaigns
6. Fix combatStore turn index on remove/add/reorder
7. Add form-level input validation (NaN/Infinity guards)
### Week 2 — High (16 bugs)
8. Fix heavy armor DEX modifier bug
9. Fix PF2e temp HP bypass
10. Replace `Math.random()` with seedable RNG for game rolls
11. Unify Handout types and migrate localStorage→IndexedDB
12. Fix stale closures in detail components
13. Fix short rest to use Hit Dice
14. Add `onblocked` handler to IndexedDB open
### Week 3 — Medium (15 bugs)
15. Add dead combatant skipping in initiative
16. Fix character sheet default persistence
17. Add `aria-live` regions and focus trapping
18. Replace `useMemo` side-effects with `useEffect`
19. Fix `Number("0") || 1` to use nullish coalescing
### Backlog — Low (15 bugs)
- Performance: add React.memo, code splitting
- Accessibility: fix color contrast, responsive widths
- Theme: implement light theme or remove the setting
- Python: fix hardcoded paths
- PWA: add update prompt
- Dead code: remove unused imports and `@tanstack/react-query`
---
---
## 🔴 Wave 3 — Oracle-Identified Gap Closures
### CombatMeta Data Loss (HIGH)
- **W3-1**: `combatMeta` stored in React `useState` — lost on tab switch. Temp HP, shield HP, used actions, MAP counts all reset. `CombatScreen.tsx:34`
### MapCanvas Touch Support (HIGH)
- **W3-2**: MapCanvas uses mouse events only (`onMouseDown/Up/Move/Wheel`) — no touch equivalents. Fog of war revelation impossible on tablets. `MapCanvas.tsx:430-434`
### Handout File Size Validation (MEDIUM)
- **W3-3**: Handout upload has no file size validation. Large files silently fail on localStorage quota. `handoutStore.ts:87`, `HandoutSection.tsx:36-42`. Compare with MapUpload.tsx which HAS 20MB validation.
### Memory/Resource Leaks (MEDIUM)
- **W3-4**: MapCanvas `requestAnimationFrame` not cancelled on unmount. `MapCanvas.tsx:200`
- **W3-5**: Unbounded async in useEffect without abort — 3 components. `CampaignDashboard.tsx:139-165`, `CampaignOrganizer.tsx:100-127`, `PlayerViewPage.tsx:175-190`
- **W3-6**: `setTimeout` without cleanup — 2 components. `LootGenerator.tsx:212-215`, `DiceRoller.tsx:53-55`
- **W3-7**: Handout data URLs in localStorage — quota exhaustion risk. `handoutStore.ts:44-57,82`
- **W3-8**: SRD reference data permanently resident in memory (~500KB+). `SRDViewer.tsx:3-8,419`
- **W3-9**: IndexedDB exportCampaign opens N+1 separate transactions. `indexeddb.ts:532-561`
### Test Coverage Gaps (MEDIUM)
- **W3-10**: 15 test files cover only math engine + 1 UI component. 0 tests for 9 stores, 747-line storage adapter, 30+ UI components. ~10% file coverage.
- **W3-11**: No integration tests — stores never tested with adapter. Zero data-flow verification.
- **W3-12**: E2E tests are smoke-only — verify page loads, no CRUD workflow testing.
- **W3-13**: No vitest coverage thresholds configured. No CI enforcement.
---
## Verification Notes
- **Oracle-reviewed**: Two false positives corrected (HandoutStore Zustang detection, Stored XSS via innerHTML)
- **Wave 2 additions**: Race conditions, input validation, Python scripts, PWA, theme system, dead code, memory leaks, schema migration, test coverage
- **Wave 3 additions**: CombatMeta data loss, MapCanvas touch support, handout size validation, 6 memory/resource leaks, 4 test coverage gaps
- **197 existing tests**: All pass, but cover only the math engine (0 store/adapter/UI tests)
- **Total agents deployed**: 30 across 4 waves
- **Total unique bugs**: 65 (Wave 1-2) + 13 (Wave 3) + 57 (Wave 4) ≈ **135 bugs**
### Wave 4 Additions (Oracle-identified coverage gaps)
- **Bundle bloat**: 15MB monolithic build, 12MB monsters-full.json imported 3× eagerly, zero code splitting
- **Calendar store**: `advanceDay` timezone/DST fragility (MEDIUM-HIGH)
- **Dice store**: `addRoll` can't associate with campaigns; `clearHistory` partial-failure inconsistency
- **Settings store**: `initialize` doesn't merge defaults; `resetSettings` skips init guard
- **UUID inconsistency**: 9 files use `uuidv4()`, 1 file uses `crypto.randomUUID()` — competing strategies
- **Tauri capabilities**: CRITICAL — no `capabilities/default.json`, all plugins dead at runtime
- **PWA triple-manifest**: static manifest, plugin manifest, HTML link — browser confusion
- **5e heavy armor DEX bug**: `Math.min(-1, 0) = -1` should be 0 (rules violation)
- **Encounter builder**: XP thresholds hardcoded for level 1; PF2e budget per-player vs party; PF2e uses 5e HP heuristics
- **Loot engine**: Individual treasure uses multiply instead of sum (wrong distribution); CR 0-4 art objects completely wrong; duplicate art objects CR 11-16 vs 17+
- **ActionTracker**: PF2e action diamond click logic inverted (used actions call onUseAction instead of onUndoAction)
- **BestiaryList**: monster name as React key (non-unique)
- **20+ component-level bugs** across InitiativeTracker, CombatantCard, CampaignForm, RestControls, EncounterBuilder