Flip the seasonal master switch (SEASON_ACTIVE=false in useMidsummer) so the
Sma Grodorna theme is fully dormant — no rain/frogs/maypole/banner/palette,
regardless of any stored toggle preference — and remove the 🐸 toggle from the
sidebar. All theme code is kept; to bring it back next Midsummer, flip
SEASON_ACTIVE to true and re-add <FrogToggle /> in SidebarWindowButtons.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per request: remove the WebAudio jingle (+ its 🔊 toggle and sound state);
replace the one-shot confetti with a continuous rain of 🌼🌸🐸🇸🇪🌿 over the
screen (MidsummerRain, gated by the theme, reduced-motion aware, leak-free);
and replace player-dot markers with frogs themselves (override the inline
dot color/border) instead of a flower-crown on top. Still toggled by the
🐸 Midsommar switch. Includes rebuilt static bundle.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Logout: new sidebar link 'Log out (username)' that POSTs /api/logout
(clears session cookie) and navigates to /login. Visible to everyone.
Replaces 'no logout functionality' state where users could only get
out by deleting cookies manually.
Admin window: new 'Admin · Users' window (only shown when current
user.is_admin) lists all users in a table with:
- Add user (username + password + admin checkbox)
- Reset password inline per row
- Toggle admin per row
- Delete user per row (blocked for self)
Wraps the existing /api-admin/users CRUD endpoints in main.py.
Plumbing: useCurrentUser hook fetches /me on mount; apiPatch+apiDelete
helpers added to api/client.ts; new endpoint wrappers exported from
api/endpoints.ts; AdminUsersWindow.tsx registered in WindowRenderer
under id prefix 'adminusers'; CSS for admin table/form/buttons and
the muted-red logout link.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The floating version badge scrolled awkwardly and wasn't necessary
now that the bind-mount/deploy issue is fixed. The existing ml-version
inside the Sidebar is sufficient.
Also removed the temporary [INV_DEBUG] console logs from useLiveData
and InventoryWindow — the inventory live-update bug is confirmed fixed.
Kept the per-character inventoryVersions fix and the cache-buster on
the refetch URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Temporary instrumentation to diagnose why InventoryWindow doesn't refresh
on inventory_delta. Three log points:
- useLiveData: logs when inventory_delta arrives and version bump
- InventoryWindow effect: logs every run with state
- InventoryWindow fetch: logs when debounce fires and result count
Also added cache-buster (_t=timestamp) to the refetch URL in case HTTP
caching is masking fresh data.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The inventoryVersion counter in useLiveData was a single global value that
bumped on every inventory_delta for any character. With 60+ active chars
all generating deltas, the global counter advanced multiple times per
second.
InventoryWindow's debounce effect watched this global counter, so every
bump reset its 2-second fetch timer. Since bumps arrived faster than 2s,
the fetch never fired — the window appeared frozen until the user closed
and reopened it (which triggered the initial-fetch effect).
Fix: make inventoryVersions a Map<string, number> keyed by character name.
Each inventory_delta now only bumps its own character's counter, so an
open window's debounce correctly fires 2s after its character's last
delta, ignoring unrelated traffic.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cleanup:
- Removed 109 stale asset files from static/assets/ (was 122, now 13)
- Removed static/v2/ entirely (was duplicate of root assets)
- Removed dead dashboard code: DashboardView, Layout, GlobalStats,
CharacterCard, CharacterGrid, VitalBar, TabContainer, CombatTab,
RaresTab, MapTab, InventoryTab, global.css, MapTransformContext
- Removed recharts dependency (425KB chunk eliminated)
- CSS reduced from 17KB to 10KB
- Added deploy-frontend.sh script for one-command build+deploy
- Updated CLAUDE.md with combat_stats, share_*, dungeon_map events
and React frontend architecture
Death alerts (frontend + backend):
- Frontend: DeathNotification component with red banner + sawtooth
sound when vitae goes from 0 to >0
- Backend: detects vitae transition in vitals handler, sends Discord
webhook to #aclog with "☠️ CHARACTER died! (vitae: X%)"
- Rate-limited: max 1 Discord alert per character per 5 minutes
Idle detection (backend):
- Background task runs every 60 seconds
- Detects: vt_state "default"/"idle" OR kph=0 while in combat/hunt
- Sends Discord webhook: "⚠️ CHARACTER appears idle (state: X, KPH: 0)"
- Auto-clears alert when character becomes active again
- No duplicate alerts for same idle period
Discord integration:
- DISCORD_ACLOG_WEBHOOK env var for webhook URL
- Used by both death alerts and idle detection
- Graceful fallback when not configured
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The CharacterWindow only fetched once from API on mount and never
updated. Now:
- character_stats WS messages are tracked in useLiveData via ref
- Passed through WindowRenderer to CharacterWindow as liveStats prop
- Window uses live WS data when available, falls back to API fetch
- Attributes, skills, vitals base values, properties (augmentations,
ratings, etc.), allegiance all update in real-time
Also: vitals bars in the character window use live WS vitals data
(health_percentage etc.) for real-time HP/Stamina/Mana display.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Functional:
1. Chat: "▼ New messages below" indicator when scrolled up, click to jump
2. Combat stats: "Clear Session" button (red, with confirm dialog)
3. Inventory: live updates via inventory_delta WS (re-fetches on change)
4. Inventory: real mana time from equipment_cantrip_state WS (live
countdown with state dot: green=active, red=inactive, yellow=unknown)
Visual:
5. Thin separator line between tool links and sort buttons
6. Selected player row highlighted with darker background (#2a3344)
7. Scroll-to-top button (▲) appears when scrolled past 200px in player list
UX:
8. Double-click player dot on map opens their chat window
9. Right-click player dot shows context menu (Chat/Stats/Inv/Char/Combat/Radar)
10. Ctrl+D keyboard shortcut toggles between map and dashboard views
11. Sound notification on rare drops (880Hz sine beep via Web Audio API)
Backend:
12. Deep-merge lifetime offense/defense per element — accumulates
total_attacks, failed_attacks, crits, damage per AttackType×Element
instead of overwriting with latest session data
13. Startup cleanup: deletes stale combat_stats records from before
the lifetime fix (pre-2026-04-14T09:00Z)
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>
1. Player click → zoom: clicking a player in sidebar or on map dot
zooms to their position at 3x zoom, centered on screen. Click
again to deselect. Uses direct DOM transform (no React state).
2. Selected dot blink: selected player dot gets 10px size + blink
animation (0.6s step-end infinite) matching v1's .dot.highlight.
3. Version display: fetches /api-version on mount, shows "vX.Y.Z"
in small text positioned just right of sidebar (fixed, top: 6px).
4. Missing sidebar buttons: added Combat Stats (⚔️) alongside
existing Issues (📋) and Vital Sharing (🤝) in SidebarWindowButtons.
5. Rare notification: added 🎆 emojis to "LEGENDARY RARE!" title
matching v1's notification text.
6. Dungeon map in radar — verbatim port from v1 lines 3596-3930:
- loadDungeonTiles(): fetches dungeon_tiles.json, processes each
tile image (color remap: UB source colors → display colors,
white → transparent, black → semi-transparent)
- cellRotation(): maps rotation values to radians (v1's exact logic)
- Dungeon rendering: sorts z_levels (current floor on top at 85%
opacity, others at 12%), draws each cell with per-cell rotation,
uses processed tile canvases or colored rectangle fallback
- Requests dungeon map via WebSocket when radar detects dungeon
- Caches dungeon maps on window.__dungeonMapCache
- Overworld map: fixed srcSize calculation to use range * pixPerCoord
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Radar:
- nearby_objects WebSocket messages now tracked in useLiveData state
- Passed through MapLayout → WindowRenderer → RadarWindow
- Objects list updates live as radar data streams in
Inventory:
- Items now render actual game icons via /icons/{hexId}.png
using the portal.dat offset formula (iconRaw + 0x06000000)
- Hover tooltip shows: name, material, AL, damage, workmanship,
tinks, set, imbue (multi-line)
- Equipment grid slots show item icons instead of text names
- Pack item grid shows item icons with proper tooltips
- Fallback icon (06000133.png) on load error
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rebuilds the v1 map-centric experience in React:
Layout:
- 400px sidebar on left, interactive map on right (flex, 100vh)
- Exact same proportions and dark theme as v1
Sidebar (top→bottom):
- Header with active player count + Dashboard toggle button
- Server status dot (Coldeve online/offline with pulse)
- Aggregate counters: Rares (gold), Server KPH (blue glow), Kills (red)
- 6 sort buttons (Name, KPH, S.Kills, S.Rares, T.Kills, KPR)
- Player name filter
- Scrollable player list with per-row:
- Name + coordinates
- HP/Stamina/Mana vital bars (red/orange/blue gradients)
- Session kills, total kills, KPH
- Session rares, total rares, VTank meta state pill
- Online time, deaths, prismatic tapers
- Color-coded left border per player
Map:
- dereth.png with CSS transform pan (drag) + zoom (wheel, 1.1x factor, max 20x)
- Player dots (6px circles, color-matched to sidebar)
- Hover tooltip (name, coords, kph, kills)
- World coordinate display at cursor position
- Fit-to-window on first load
View toggle: Map View ↔ Dashboard with localStorage persistence.
All v1 CSS ported under ml-* prefix, scoped via map-layout.css.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New modern dashboard at /v2 running alongside the existing UI at /.
Same backend, same APIs, same WebSocket — zero backend changes.
Stack: React 19 + Vite + TypeScript + Recharts
Source: frontend/ — build output: static/v2/
Phase 1 delivers:
- Character overview cards in a responsive CSS Grid
- Live HP/Stamina/Mana bars via WebSocket vitals
- Kills/hr, total kills, deaths, session uptime
- VTank state badge (Combat/Nav/Idle)
- Location coordinates
- Click to expand: combat stats, prismatic count, CPU/RAM
- Global stats header: active chars, total kills, total rares, server health
- WebSocket hook with auto-reconnect
- HTTP poll fallback for initial load + server health
- Mobile responsive (single column on narrow screens)
- Dark theme matching the MosswartOverlord palette
Build: cd frontend && npm run build
Access: /v2 (served by existing NoCacheStaticFiles mount)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>