Files
ttrpg_manager/docs/OLD_BUG_HUNT_REPORT.md
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

14 KiB
Raw Permalink Blame History

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 checkedpromisify 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 strategyDB_VERSION = 1 hardcoded, onupgradeneeded only creates new stores indexeddb.ts:30, 651-723

Security

ID Finding File
C14 No HTML sanitization librarycontentMd 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-opAppSettings.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)

  1. Fix heavy armor DEX modifier bug
  2. Fix PF2e temp HP bypass
  3. Replace Math.random() with seedable RNG for game rolls
  4. Unify Handout types and migrate localStorage→IndexedDB
  5. Fix stale closures in detail components
  6. Fix short rest to use Hit Dice
  7. Add onblocked handler to IndexedDB open

Week 3 — Medium (15 bugs)

  1. Add dead combatant skipping in initiative
  2. Fix character sheet default persistence
  3. Add aria-live regions and focus trapping
  4. Replace useMemo side-effects with useEffect
  5. 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