- v1 vanilla JS frontend moved to /classic (static/classic/)
- v2 React app now serves at / (root)
- Vite base changed from /v2/ to /
- Assets at /assets/, service worker at /sw.js
- /classic still works — all v1 files preserved with relative paths
- /v2 still works as before (build output unchanged)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. React.memo on WindowRenderer — prevents re-renders when parent
state changes but no windows are affected
2. Coordinate display via direct DOM ref — no React state updates
on mouse move (was triggering re-renders on every pixel)
3. useDeferredValue for sidebar vitals + player list — React
prioritizes map interactions over stat text updates
4. Chat messages in ref — stores in useRef instead of useState,
only bumps a version counter for re-render. Eliminates a
new Map() allocation on every chat message.
5. Lazy-load 8 window components — InventoryWindow, CharacterWindow,
RadarWindow, CombatStatsWindow, IssuesWindow, VitalSharingWindow,
StatsWindow, CombatPickerWindow all loaded on first open.
Main bundle dropped from 278KB to 211KB (24% reduction).
6. Preload critical assets — dereth.png, backpack icon, dungeon_tiles.json
via <link rel="preload"> in index.html for instant map render.
7. Bundle splitting — React runtime extracted to separate 12KB chunk
(cached independently). Window components split into 8 chunks.
Total: 13 chunks vs previous 2.
8. Service worker — caches map images, icon sprites, and dungeon tiles.
Icon images cached on first fetch. Repeat page loads serve from
cache instantly. Auto-cleans old cache versions.
Net result:
- Initial load: 211KB main + 17KB CSS (was 278KB + 17KB)
- React cached separately: 12KB
- Windows load on demand: 1-15KB each
- Dashboard with Recharts: 425KB (unchanged, still lazy)
- Map images/icons: cached by service worker after first load
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>