# 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 `
` 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