Skip to content

Modern Player Web Client — Implementation Plan

Summary

This plan implements a full-featured player web client for the MAID engine, replacing the minimal inline HTML client embedded in net/web/server.py (lines 601-757) with a React 18 + TypeScript + Vite SPA served at /play/. The client provides ANSI color rendering via xterm.js, structured GMCP data panels (character stats, inventory, map, chat), AI-powered NPC dialogue UI, responsive mobile layout, and WCAG 2.1 AA accessibility.

The implementation spans two parallel work tracks — a Client Track (React/TypeScript frontend) and a Server Track (Python backend protocol and API) — across 6 phases over 14 weeks. Phase 0 establishes infrastructure (player frontend project, protocol extensions). Phases 1-3 build core functionality (auth, terminal, GMCP, map, chat, dialogue). Phase 4 adds mobile polish and security hardening. Phase 5 covers testing and launch.

Key architectural decisions: - Player frontend is a standalone Vite project; shared code extraction deferred until duplication is empirically proven - dispatchGMCP(pkg, data) typed dispatch function routes GMCP messages directly to Zustand store actions (no intermediate MessageBus layer) - GameWebSocket class (in player frontend) wraps native WebSocket with reconnect/backoff/queue/heartbeat - Existing /ws/game endpoint extended with optional envelope fields (seq, requestId) — no separate v2 endpoint or protocol adapter ABC - Full state snapshot on reconnect (no resume tokens or server-side message buffering) - 8 Zustand stores with direct GMCP subscription pattern - xterm.js as sole terminal renderer (no separate ANSI→HTML parser) - SafeHTML component as the ONLY place dangerouslySetInnerHTML is allowed

GMCP prerequisite: The client depends on GMCP packages that do not yet exist on the server (see §2.1). A GMCP schema audit and server-side extension work must be scoped before client GMCP integration begins. The current server GMCPHandler in net/telnet/protocols/gmcp.py returns Telnet-encoded bytes; a protocol-agnostic GMCPPayloadBuilder returning dict objects is needed so both Telnet and WebSocket transports can share GMCP data construction.

Content-pack boundary: Character creation/management APIs (§1.7) must use content-pack adapters or a pack-provided registration hook, since character models (classes, races, stats) are owned by content packs (e.g., maid-classic-rpg), not maid-engine. The engine should provide the REST routing infrastructure; packs provide the schemas and validation.

Design document: docs/designs/v3.1/05-web-client.md


Phase 0: Infrastructure (Week 0 — Pre-Sprint)

0.1 Player Frontend Bootstrap

Package: maid-engine (frontend infrastructure) Priority: P0 Dependencies: None

Note: Shared code extraction (@maid/shared workspace package) is deferred. The player frontend starts as a standalone project. Code from the admin frontend (e.g., apiRequest<T>(), auth patterns) is copied and adapted, not extracted. Extraction into a shared workspace package happens in a future milestone once duplication is empirically measured and CI/lockfile migration is planned.

  • [ ] Initialize packages/maid-engine/player_frontend/ as a standalone Vite + React + TypeScript project (see §1.1 for full setup)
  • [ ] Copy apiRequest<T>() utility from admin_frontend/src/api/client.ts into player_frontend/src/api/client.ts, adapt for player auth paths (/api/v1/)
  • [ ] Create SafeHTML component in player_frontend/src/components/SafeHTML.tsx using DOMPurify with allowlist (<span>, style attr only, CSS properties: color, background-color, font-weight, font-style, text-decoration)
  • [ ] Implement GameWebSocket class in player_frontend/src/lib/game-websocket.ts — wraps native WebSocket with reconnect (exponential backoff + jitter), offline queue, lifecycle hooks (onOpen, onClose, onError, onMessage)
  • [ ] Create base UI components (Button, Card, Input, Modal) in player_frontend/src/components/ui/, modeled after admin frontend equivalents

0.2 ESLint Security Configuration

Package: maid-engine (frontend infrastructure) Priority: P0 Dependencies: 0.1

  • [ ] Configure ESLint rule in shared/player .eslintrc.cjs banning dangerouslySetInnerHTML outside SafeHTML.tsx via no-restricted-syntax selector JSXAttribute[name.name='dangerouslySetInnerHTML']

0.3 WebSocket Protocol Extensions (Server — Python)

Package: maid-engine Priority: P0 Dependencies: None

Note: Instead of a separate v2 endpoint and protocol adapter abstraction, the existing /ws/game handler is extended incrementally with optional fields. Existing clients continue to work unchanged. No WebSocketProtocolAdapter ABC is needed.

  • [ ] Add optional requestId field to outbound ack/nack/completion messages in the existing /ws/game handler for request-response correlation
  • [ ] Add subscribe client message type — client sends {type: "subscribe", packages: ["Char.Vitals", ...]} to request GMCP data; server responds with current state for subscribed packages
  • [ ] Add gmcp_batch message type — server can send multiple GMCP updates in a single frame: {type: "gmcp_batch", messages: [{package, data}, ...]}
  • [ ] Implement full state snapshot on connect — after auth/character binding, server pushes all relevant GMCP state (vitals, room, inventory, channels) followed by {type: "sync_complete", domains: [...]}
  • [ ] Refactor GMCPHandler to separate payload construction from Telnet encoding — create protocol-agnostic methods returning dict objects usable by both Telnet and WebSocket transports

0.4 Origin Validation and Frame Limits (Server — Python)

Package: maid-engine Priority: P0 Dependencies: 0.3

  • [ ] Implement Origin header validation on all WebSocket upgrade requests in server.py — reject connections with non-allowed origins (close code 4003)
  • [ ] Add allowed_origins to WebSettings in packages/maid-engine/src/maid_engine/config/settings.py
  • [ ] Configure server-side max frame size: 256KB server→client, 8KB client→server (reject with close code 1009)
  • [ ] Add WEBSOCKET_MAX_MESSAGE_SIZE constant to server.py

0.5 CSP Headers (Server — Python)

Package: maid-engine Priority: P0 Dependencies: 0.3

  • [ ] Implement CSP nonce generation (cryptographic random) per request for /play/ route in server.py
  • [ ] Add CSP header to /play/ responses: default-src 'none'; script-src 'self'; style-src 'self' 'nonce-{random}'; connect-src 'self' wss://{host}; img-src 'self' data:; font-src 'self'; frame-src 'none'; base-uri 'none'; form-action 'self'
  • [ ] Inject nonce into <style> and <link> tags in the served HTML template

Phase 1: Foundation (Weeks 1-3)

1.1 Player Frontend Project Setup

Package: maid-engine/player_frontend Priority: P0 Dependencies: 0.1

  • [ ] Initialize Vite 5.4 + React 18 + TypeScript 5.6 project in packages/maid-engine/player_frontend/
  • [ ] Create package.json with dependencies: react, react-dom, react-router-dom@6, zustand@5, @tanstack/react-query@5, @xterm/xterm@5, dompurify@3, react-i18next@15, react-window@1.8
  • [ ] Create tsconfig.json with strict mode, path alias @src/
  • [ ] Create vite.config.ts with base /play/, React plugin, path alias, proxy config for dev
  • [ ] Create tailwind.config.js with game-specific colors (hp, mp, sp, xp, terminal) and dark theme defaults
  • [ ] Create index.html entry point
  • [ ] Create src/main.tsx entry point with React Router, React Query provider
  • [ ] Create src/App.tsx with route structure: / (login), /characters (select), /game (play)
  • [ ] Set up Vitest configuration in vite.config.ts for unit/component testing
  • [ ] Add @testing-library/react@16 and @testing-library/jest-dom as dev dependencies
  • [ ] Create src/test/setup.ts for test configuration
  • [ ] Use base components (Button, Card, Input, Modal) from src/components/ui/

1.2 Theme System and Global Styles

Package: maid-engine/player_frontend Priority: P1 Dependencies: 1.1

  • [ ] Create src/styles/globals.css with Tailwind layers (base, components, utilities)
  • [ ] Define CSS custom properties for dark theme on :root[data-theme="dark"] — bg-primary (#1a1a1a), bg-secondary (#252525), bg-tertiary (#2d2d2d), text-primary (#e0e0e0), text-secondary (#a0a0a0), text-muted (#666666), border (#3d3d3d), accent (#4a9eff), success (#4caf50), warning (#ff9800), danger (#f44336), hp (#e53935), mp (#2196f3), sp (#ffeb3b), xp (#9c27b0)
  • [ ] Define CSS custom properties for light theme on :root[data-theme="light"]
  • [ ] Define CSS custom properties for high-contrast theme on :root[data-theme="high-contrast"]
  • [ ] Add custom scrollbar styling, selection colors, terminal font CSS custom properties in base layer
  • [ ] Add button variants (.btn, .btn-primary, .btn-secondary, .btn-ghost), panel styling (.panel, .panel-header, .panel-body), vital bar components in components layer

1.3 TypeScript Type Definitions

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.1

  • [ ] Create src/types/messages.tsServerMessage, ClientMessage, all payload interfaces (TextPayload, SystemPayload, ErrorPayload, PromptPayload, GMCPPayload, GMCPBatchPayload, CompletionPayload, SyncCompletePayload, AckPayload, NackPayload, PongPayload, CommandPayload, CompletePayload, SubscribePayload, PingPayload), discriminated union types (ServerMessageType, ClientMessageType)
  • [ ] Create src/types/gmcp.tsGMCPMessage, GMCPCharVitals, GMCPCharStatus, GMCPStatusEffect, GMCPRoomInfo, GMCPExit, GMCPRoomPlayers, GMCPEntityInfo, GMCPCharItemsInv, GMCPItem, GMCPCommChannel, GMCPChannel, GMCPCommChannelText > Note: GMCP type names must be audited against actual server gmcp.py:supported_packages before implementation. Types for packages that don't exist yet (e.g., granular inventory events, dialogue, quest) should only be added once corresponding server GMCP work is complete.
  • [ ] Create src/types/game.tsCharacter, Vitals, StatusEffect, Room, Exit, EntityInfo, InventoryItem, ItemType, EquipmentSlot, EquipmentSlots, MapRoom, MapData, ChatChannel, ChatMessage, ActiveDialogue, DialogueMessage, DialogueNPC, QueuedCommand, ConnectionStatus
  • [ ] Create src/types/settings.tsThemeName, PanelType, LayoutConfig, PanelConfig, Macro, DefaultSettings
  • [ ] Create src/types/auth.ts — auth-related types for login response, character list, character details

1.4 Login Page and Auth UI

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.1, 1.3, 1.5 (server auth endpoints)

  • [ ] Create src/pages/LoginPage.tsx — full login page with form and server status
  • [ ] Create src/components/auth/LoginForm.tsx — username/password fields, remember me checkbox, submit button, error display (form fields are inline JSX, not separate components)
  • [ ] Create src/components/auth/ServerStatus.tsx — connection indicator and player count using GET /api/v1/health > Note: GET /api/v1/status does not currently exist in v1 routes. Either use existing /api/v1/health or add a task to create the status endpoint.
  • [ ] Implement REST auth integration using apiRequest<T>()POST /api/v1/auth/login, error handling, token storage in HttpOnly cookies
  • [ ] Create src/components/auth/AuthProvider.tsx — wraps app, checks session on mount via GET /api/v1/auth/me, manages auth state machine (INITIAL → CHECKING → LOGIN_FORM/CHARACTER_SELECT → IN_GAME)

1.5 REST Auth Endpoints (Server — Python)

Package: maid-engine Priority: P0 Dependencies: None

Note: The admin auth system (api/admin/auth.py) already implements JWT issuance, cookie management, and CSRF middleware. Extract shared auth primitives (token creation, cookie configuration, CSRF validation) into a common auth core module and reuse in the player auth router to avoid duplicating security-critical logic.

  • [ ] Create shared auth core module packages/maid-engine/src/maid_engine/auth/core.py — token issuance, cookie helpers, CSRF validation primitives reusable by both admin and player auth
  • [ ] Create packages/maid-engine/src/maid_engine/api/v1/auth.py router
  • [ ] Implement POST /api/v1/auth/login — validate credentials, return JWT in HttpOnly cookie (path /api/v1/), set WebSocket auth cookie (path /ws/), return character list in response body; access token 15min expiry, Secure, SameSite=Strict
  • [ ] Implement POST /api/v1/auth/refresh — refresh token rotation, 7-day expiry, family tracking for reuse detection, path /api/v1/auth/refresh
  • [ ] Implement POST /api/v1/auth/logout — invalidate session, revoke tokens, clear cookies
  • [ ] Implement GET /api/v1/auth/me — validate existing session from cookies, return player info and character list
  • [ ] Implement GET /api/v1/auth/csrf — return CSRF token in non-HttpOnly cookie (XSRF-TOKEN), client reads via JS and sends as X-CSRF-Token header (double-submit cookie pattern)
  • [ ] Register auth router with API v1 router in packages/maid-engine/src/maid_engine/api/v1/__init__.py

1.6 Character Select Page

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.4, 1.5, 1.7 (character API)

  • [ ] Create src/pages/CharacterSelectPage.tsx — character list, preview panel, create form
  • [ ] Create src/components/auth/CharacterList.tsx — scrollable list of character cards
  • [ ] Create src/components/auth/CharacterCard.tsx — renders avatar, name, level, last played time inline (not separate sub-components)
  • [ ] Create src/components/auth/CharacterPreview.tsx — stats summary, equipment preview, location preview (inline)
  • [ ] Create src/components/auth/CreateCharacterForm.tsx — name input, class selector, race selector, confirm button (form fields inline)
  • [ ] Implement error handling for character creation: name uniqueness validation, invalid class/race, max characters per account
  • [ ] Handle concurrent login prevention — display error if character is already logged in from another session
  • [ ] Integrate character list fetching via GET /api/v1/characters using React Query
  • [ ] Integrate character creation via POST /api/v1/characters
  • [ ] Integrate character details via GET /api/v1/characters/:id

1.7 Character and Settings API (Server — Python)

Package: maid-engine Priority: P0 Dependencies: 1.5

Note: Character models (classes, races, stats) are owned by content packs, not maid-engine. These endpoints should provide generic REST routing that delegates to content-pack-registered character adapters. When no content pack provides character management, return appropriate errors.

  • [ ] Create packages/maid-engine/src/maid_engine/api/v1/characters.py router
  • [ ] Implement GET /api/v1/characters — list authenticated player's characters (delegate to content-pack character manager)
  • [ ] Implement POST /api/v1/characters — create new character, validate name uniqueness, enforce max characters per account, reject if character class/race not provided by any loaded content pack
  • [ ] Implement GET /api/v1/characters/:id — character details (stats, equipment, location), verify ownership, return error if character is active in another session
  • [ ] Create packages/maid-engine/src/maid_engine/api/v1/settings.py router
  • [ ] Implement GET /api/v1/settings — retrieve saved user settings for authenticated player
  • [ ] Implement PUT /api/v1/settings — save user settings, validate against a server-side JSON Schema or Pydantic model (define max alias count, max macro count, valid font families, layout bounds — do not accept arbitrary JSON)
  • [ ] Register character and settings routers with API v1 router

1.8 GMCP Dispatch and Service Layer

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.1, 1.3

Note: Instead of a MessageBus + GMCPRouter indirection layer, GMCP messages are dispatched directly to store actions via a typed dispatchGMCP() function. This is ~30 lines, fully type-safe, and trivially testable. If plugin extensibility or additional decoupling is needed later, a MessageBus can be introduced.

  • [ ] Create src/lib/gmcp-dispatch.ts — typed dispatchGMCP(packageName: string, data: unknown) function that dispatches to store actions via a switch/map. Also dispatchGMCPBatch(messages) for atomic multi-update. Export for use in WebSocket hook and for test mocking.
  • [ ] Create src/lib/message-types.ts — constants for server message types (text, system, error, prompt, gmcp, gmcp_batch, completion, pong, ack, nack, sync_complete)

1.9 Zustand Stores (All 8)

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.3, 1.8

  • [ ] Create src/stores/connection-store.tsuseConnectionStore with state: status, sessionId, characterId, latency, reconnectAttempts, offlineQueue, lastError, lastConnectedAt; actions: connect, disconnect, send, setStatus, addToQueue, flushQueue, clearQueue, updateLatency, resetReconnectAttempts; queue constraints: max 20 commands, 30s TTL
  • [ ] Create src/stores/character-store.tsuseCharacterStore with state: character, statusEffects; actions: setCharacter, updateVitals, addStatusEffect, removeStatusEffect, updateStatusEffect, tickStatusEffects; GMCP subscriptions: Char.Vitals, Char.Status
  • [ ] Create src/stores/room-store.tsuseRoomStore with state: room; actions: setRoom, updateRoom; GMCP subscriptions: Room.Info, Room.Players > Note: Room.NPCs and Room.Items do not exist as separate GMCP packages on the server. Room content is currently delivered via Room.Info or Char.Items.Room. Add these subscriptions only after corresponding server GMCP packages are implemented.
  • [ ] Create src/stores/inventory-store.tsuseInventoryStore with state: inventory, equipment (10 slots: head, chest, hands, legs, feet, mainHand, offHand, ring1, ring2, amulet); actions: setInventory, addItem, removeItem, updateItem, equipItem, unequipItem; GMCP subscriptions: Char.Items.Inv > Note: Server currently has Char.Items.Inv (full list) and Char.Items.Room. Granular Char.Items.Add/Char.Items.Remove events do not exist yet; add when server implements them.
  • [ ] Create src/stores/map-store.tsuseMapStore with state: map (MapData with rooms: Record<string, MapRoom>, currentRoomId, viewportCenter, zoom), visitedRooms: Record<string, true>; actions: updateMap, addVisitedRoom, clearMap, setViewport; GMCP subscriptions: Room.Info (coordinates only)
  • [ ] Create src/stores/chat-store.tsuseChatStore with state: channels: Record<string, ChatChannel>, activeChannel, unreadCounts: Record<string, number>; actions: addMessage, setActiveChannel, markRead, createChannel, removeChannel, clearChannel, getUnreadTotal; GMCP subscriptions: Comm.Channel > Note: Server has a single Comm.Channel package, not split into Comm.Channel.Text and Comm.Channel.List.
  • [ ] Create src/stores/dialogue-store.tsuseDialogueStore with state: activeDialogue, history; actions: startDialogue, addResponse, setTyping, addPlayerMessage, endDialogue, clearHistory > Note: NPC dialogue operates through the standard command/response flow (talk, ask, greet, endconversation), not via dedicated GMCP channels. Dialogue.* GMCP packages do not exist. The dialogue store is populated by parsing server text responses to dialogue commands, not GMCP subscriptions.
  • [ ] Create src/stores/settings-store.tsuseSettingsStore with Zustand persist middleware (localStorage), state: theme, fontSize, fontFamily, compactMode, scrollbackLimit, showTimestamps, soundEnabled, notificationsEnabled, layout (LayoutConfig), aliases: Record<string, string>, macros: Macro[], keybindings: Record<string, string>; actions: updateSetting, resetDefaults, exportSettings, importSettings, addAlias, removeAlias, addMacro, removeMacro, setKeybinding; defaults: dark theme, 14px, Courier New, 10000 scrollback

1.10 Terminal Output Component

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.1, 1.8, 1.9

  • [ ] Create src/components/game/TerminalOutput.tsx — wraps xterm.js Terminal, configures scrollback (10000 lines, adjustable via settings store), receives text, system, error, prompt messages from WebSocket hook
  • [ ] Configure xterm.js with restrictive security settings: allowProposedApi: false, all windowOptions disabled, linkHandler: null
  • [ ] Apply theme colors from settings store to xterm.js theme options
  • [ ] Implement ARIA live region (aria-live="polite") for game output accessibility — mirror system messages and important game events (not all terminal output); use assertive for combat messages with aggregation (batch window 3-5s)
  • [ ] Enable xterm.js built-in accessibility mode (screenReaderMode: true) for screen reader users
  • [ ] Implement combat message aggregation for screen readers (batch every 3-5 seconds during rapid sequences, summarize as "You dealt X damage, took Y damage")

1.11 Command Input Component

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.8, 1.9, 1.10

  • [ ] Create src/components/game/CommandInput.tsx — single-line input with submit on Enter
  • [ ] Create src/components/game/InputField.tsx — the actual <input> element with focus management
  • [ ] Create src/components/game/HistoryNavigator.tsx (or hook useCommandHistory) — command history (500 entries), up/down arrow navigation
  • [ ] Create src/components/game/TabCompleter.tsx (or hook useTabCompletion) — sends complete request with requestId, 150ms debounce, 2s timeout, tracks latest pending requestId to discard stale responses, dropdown for multiple completions
  • [ ] Implement alias expansion before command execution (reads from settings store)
  • [ ] Implement macro trigger detection and expansion with loop protection: 5 recursion depth, 20 commands max per expansion, 10/sec throttle
  • [ ] Implement client-side rate limiting: 10 commands/second
  • [ ] Implement command length cap: 2000 characters
  • [ ] Implement paste protection: single-line paste allowed, multi-line (2-5 lines) shows preview modal, multi-line (>5) shows scrollable preview, strip non-printable characters except newlines

1.12 WebSocket Hook

Package: maid-engine/player_frontend Priority: P0 Dependencies: 0.1 (GameWebSocket), 1.8, 1.9

  • [ ] Create src/hooks/useGameWebSocket.ts — uses GameWebSocket from src/lib/game-websocket.ts, connects to /ws/game, configures reconnect (1s/2s/4s/8s/16s cap + jitter, max 10 attempts)
  • [ ] Implement onOpen handler: set status connected, reset parse error counter, start heartbeat, send subscribe message with GMCP packages and characterId
  • [ ] Implement onClose handler: set status reconnecting, stop heartbeat
  • [ ] Implement onError handler: log error, set status error
  • [ ] Implement onMessage handler: JSON.parse with try/catch, validateServerMessage() type guard, route by message type — text/system/error/prompt → terminal, gmcpdispatchGMCP(), gmcp_batchdispatchGMCPBatch(), ack/nack → request correlation, pong → latency, sync_complete → flushQueue
  • [ ] Implement heartbeat: ping every 30s, expect pong within 10s, set stale on miss, force reconnect on 2nd consecutive miss, derive latency from RTT
  • [ ] Implement page lifecycle handling: visibilitychange → pause/resume heartbeat, beforeunload → WebSocket close frame (code 4000)
  • [ ] Implement JSON parse error tracking: count errors in 10s window, force reconnect if >5
  • [ ] Create validateServerMessage() type guard accepting text, system, error, prompt, gmcp, gmcp_batch, completion, pong, ack, nack, sync_complete

1.13 WebSocket Provider and Game Page

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.10, 1.11, 1.12

  • [ ] Create src/providers/WebSocketProvider.tsx — wraps game page, initializes useGameWebSocket, provides send function via context
  • [ ] Create src/pages/GamePage.tsx — wraps content in WebSocketProvider
  • [ ] Create src/components/game/ReconnectionOverlay.tsx — shown during reconnect/sync, includes attempt counter, elapsed timer, manual retry button, return to login button (all inline, not separate sub-components)
  • [ ] Create src/components/layouts/GameLayout.tsx — single responsive CSS Grid layout: grid-cols-1 (mobile), md:grid-cols-[280px_1fr] (tablet), lg:grid-cols-[280px_1fr_280px] (desktop); initially renders MainColumn only
  • [ ] Integrate text, system, error message display in terminal
  • [ ] Add loading and error states for initial connection

1.14 Protocol Features (Server — Python)

Package: maid-engine Priority: P0 Dependencies: 0.3

  • [ ] Implement ping/pong heartbeat in WebSocket handler — respond to client ping with pong including echo timestamp
  • [ ] Implement ack/nack command acknowledgment — correlate via requestId, send nack with reason and optional retryAfter for rate-limited commands
  • [ ] Implement completion response — return tab-complete results correlated to complete request via requestId
  • [ ] Implement characterId binding in subscribe handler — validate characterId belongs to authenticated player, load character state, attach to session

1.15 Static File Serving for Player Client (Server — Python)

Package: maid-engine Priority: P1 Dependencies: 0.5

  • [ ] Update packages/maid-engine/src/maid_engine/net/web/server.py mount order: API → Admin → WebSocket → Player SPA → Root redirect
  • [ ] Mount player frontend build at /play/ via StaticFiles(directory=player_frontend_dist, html=True) for SPA routing
  • [ ] Implement fallback to inline client if player_frontend/dist/ does not exist
  • [ ] Add root / redirect to /play/

Phase 2: GMCP Integration (Weeks 4-6)

2.1 GMCP Negotiation and Router Wiring

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.8, 1.12

Prerequisite: Before implementing client GMCP integration, create a definitive GMCP schema document mapping each package to its exact field names, types, update semantics (full replace vs delta), and which content packs provide which packages. This prevents the client and server from implementing against different assumptions. Audit against gmcp.py:supported_packages.

  • [ ] Implement GMCP subscription in useGameWebSocket — send subscribe message after connection with desired package list
  • [ ] Define SUBSCRIBED_GMCP_PACKAGES constant: Core 1, Char.Vitals 1, Char.Items 1, Char.Status 1, Room.Info 1, Room.Players 1, Comm.Channel 1 > Note: Packages Room.NPCs, Room.Items (as separate packages), Comm.Channel.Text, Comm.Channel.List, Dialogue.*, and Quest.* do not exist on the server. Subscribe to these only after server-side implementation. The client should gracefully handle missing packages.
  • [ ] Wire dispatchGMCP() in useGameWebSocket message handler
  • [ ] Add GMCP debugging tools for development (log all GMCP messages to console with toggle)
  • [ ] Handle unrecognized GMCP packages from content packs — route through dispatchGMCP (no-op if no handler registered)

2.2 GMCP State Snapshot and Server Extensions (Server — Python)

Package: maid-engine Priority: P0 Dependencies: 0.3, 1.14

Note: Session resume with message buffering (resume tokens, seq-based replay, 200-message ring buffer) is deferred. On reconnect, the server always sends a full state snapshot. This is simpler and sufficient — MUD clients have operated this way for decades.

  • [ ] Implement gmcp_batch message type in WebSocket handler — atomic multi-GMCP-package delivery in single frame
  • [ ] Implement full state snapshot on every connect/reconnect — push Char.Vitals, Char.Status, Char.Items.Inv, Room.Info, Comm.Channel for the bound character, followed by {type: "sync_complete", domains: [...]}
  • [ ] Server GMCP extension work (prerequisite for full client GMCP integration):
  • [ ] Refactor GMCPHandler to produce dict payloads separate from Telnet IAC encoding
  • [ ] Audit existing supported_packages in gmcp.py — document exact field names, types, update semantics
  • [ ] Scope and prioritize server-side implementation of missing GMCP packages needed by client (granular inventory events, room content, channel sub-types) as separate tasks

2.3 Character Stats Panel

Package: maid-engine/player_frontend Priority: P1 Dependencies: 1.9 (character store), 2.1

  • [ ] Create src/components/panels/CharacterStatsPanel.tsx — container for vital bars, status effects, level/class display
  • [ ] Create src/components/panels/HPBar.tsx — HP progress bar with color coding, pulsing animation on low health (respects prefers-reduced-motion)
  • [ ] Create src/components/panels/MPBar.tsx — MP progress bar with color coding
  • [ ] Create src/components/panels/SPBar.tsx — SP progress bar with color coding
  • [ ] Create src/components/panels/XPBar.tsx — XP progress bar with percentage display
  • [ ] Create src/components/panels/StatusEffects.tsx — list of active effects with icons, duration timers, stacking indicators
  • [ ] Implement client-side status effect countdown via 1s setInterval (cosmetic only)
  • [ ] Implement status effect timer reconciliation: replace local remainingTime on every Char.Status update, request refresh on tab visibility change

2.4 Inventory Panel

Package: maid-engine/player_frontend Priority: P1 Dependencies: 1.9 (inventory store), 2.1

  • [ ] Create src/components/panels/InventoryPanel.tsx — container with tabs, grid, equipment slots
  • [ ] Create src/components/panels/InventoryTabs.tsx — tab view (All, Equipment, Consumables)
  • [ ] Create src/components/panels/ItemGrid.tsx — scrollable item list using react-window for large inventories, item context menus (Use, Equip, Drop)
  • [ ] Create src/components/panels/EquipmentSlots.tsx — visual equipment slot layout (10 slots), drag-and-drop equip/unequip
  • [ ] Implement item search/filter functionality
  • [ ] Implement quantity display for stacked items and weight tracking

2.5 Room Info Panel

Package: maid-engine/player_frontend Priority: P1 Dependencies: 1.9 (room store), 2.1

  • [ ] Create src/components/panels/RoomInfoPanel.tsx — container for room details
  • [ ] Create src/components/panels/RoomTitle.tsx — room name with area label
  • [ ] Create src/components/panels/RoomDescription.tsx — room description text (uses SafeHTML if HTML content)
  • [ ] Create src/components/panels/ExitButtons.tsx — clickable direction buttons for each exit, icons for locked/hidden exits
  • [ ] Create src/components/panels/PlayerList.tsx — list of players in room
  • [ ] Create src/components/panels/NPCList.tsx — list of NPCs in room
  • [ ] Create src/components/panels/ItemList.tsx — list of items in room

2.6 Sidebar Layout Integration

Package: maid-engine/player_frontend Priority: P1 Dependencies: 2.3, 2.4, 2.5

  • [ ] Update GameLayout.tsx — add LeftSidebar (MapPanel placeholder, RoomInfoPanel), RightSidebar (CharacterStatsPanel, InventoryPanel)
  • [ ] Implement collapsible sidebars on tablet (640-1024px)
  • [ ] Implement panel lazy loading via React Suspense with skeleton fallbacks
  • [ ] Add panel resize handles for desktop sidebar widths
  • [ ] Wire sidebar visibility to settings store (showLeftSidebar, showRightSidebar)

Phase 3: Map and Chat (Weeks 7-9)

3.1 Map Panel

Package: maid-engine/player_frontend Priority: P1 Dependencies: 1.9 (map store), 2.1

  • [ ] Create src/components/panels/MapPanel.tsx — container for SVG map, zoom controls, legend
  • [ ] Create src/components/panels/MapCanvas.tsx — SVG grid rendering, coordinate-based room positioning from Room.Info GMCP, current room highlighting with pulsing indicator, exit connections as lines, terrain-based color coding
  • [ ] Create src/components/panels/ZoomControls.tsx — zoom in/out/reset buttons, scroll wheel zoom
  • [ ] Create src/components/panels/MapLegend.tsx — terrain type legend
  • [ ] Implement fog of war for unvisited rooms (dimmed/hidden rooms not in visitedRooms)
  • [ ] Implement pan controls (click-drag on map)
  • [ ] Implement room pruning: discard rooms >100 units from current position to manage memory

3.2 Chat Panel

Package: maid-engine/player_frontend Priority: P1 Dependencies: 1.9 (chat store), 2.1

  • [ ] Create src/components/panels/ChatPanel.tsx — container for channel tabs, message list, quick reply
  • [ ] Create src/components/panels/ChannelTabs.tsx — tabs for Say, Tell, Guild, OOC, System with unread message count badges
  • [ ] Create src/components/panels/MessageList.tsx — scrollable message list using react-window, player name highlighting, timestamps, message type styling (player/npc/system)
  • [ ] Create src/components/panels/QuickReply.tsx — inline input for quick channel messages
  • [ ] Implement channel join/leave functionality
  • [ ] Implement message sanitization via SafeHTML component for any HTML in chat messages

3.3 NPC Dialogue Overlay

Package: maid-engine/player_frontend Priority: P1 Dependencies: 1.9 (dialogue store), 2.1

Note: NPC dialogue uses the standard command/response flow (talk, ask, greet, endconversation), not dedicated GMCP channels. Dialogue.* GMCP packages do not exist. The dialogue overlay is populated by detecting dialogue-related text patterns in server responses to dialogue commands. If server-side Dialogue.* GMCP support is added later, the store can be wired to those events.

  • [ ] Create src/components/dialogue/NPCDialogueOverlay.tsx — side panel overlay on desktop (slides from right), bottom sheet on mobile (<640px, lower 60%), replaces sidebar on tablet; lazy-loaded via React.lazy()
  • [ ] Create src/components/dialogue/DialogueBubble.tsx — chat bubble UI, NPC portrait and name display, sender differentiation (player vs NPC)
  • [ ] Create src/components/dialogue/TypingIndicator.tsx — animated dots during AI generation (respects prefers-reduced-motion)
  • [ ] Create src/components/dialogue/SuggestedResponses.tsx — clickable chips for quick reply suggestions
  • [ ] Implement conversation history scrollback
  • [ ] Implement close button and Escape key/click-outside dismissal (sends endconversation command)
  • [ ] Ensure terminal remains fully visible and interactive during active dialogue — dialogue does NOT capture keyboard focus unless clicked

Phase 4: Polish and Mobile (Weeks 10-11)

4.1 Responsive Layout Verification

Package: maid-engine/player_frontend Priority: P1 Dependencies: 2.6, 3.1, 3.2, 3.3

  • [ ] Verify GameLayout responsive behavior at all breakpoints: mobile (<640px), tablet (640-1024px), desktop (>1024px)
  • [ ] Verify 3-column desktop layout with resizable sidebars
  • [ ] Verify 2-column tablet layout with collapsible sidebar
  • [ ] Verify single-column mobile layout with bottom tab navigation
  • [ ] Test on real devices: iOS Safari 15+, Chrome Android 90+, desktop Chrome 90+, Firefox 90+, Safari 15+, Edge 90+

4.2 Mobile Tab Bar

Package: maid-engine/player_frontend Priority: P1 Dependencies: 4.1

  • [ ] Create src/components/game/MobileTabBar.tsx — visible <640px only, tabs: Game, Map, Stats, Quests (hidden if no quest GMCP), Chat
  • [ ] Implement swipe gestures for panel switching on mobile

4.3 Mobile Macro Bar

Package: maid-engine/player_frontend Priority: P1 Dependencies: 1.9 (settings store), 4.1

  • [ ] Create src/components/game/MacroBar.tsx — configurable row of swipeable soft-key buttons above command input on mobile
  • [ ] Create src/components/game/MacroButton.tsx — single macro button, sends predefined command on tap
  • [ ] Implement swipeable macro banks (Combat, Travel, Social) — reads from macros in settings store
  • [ ] Implement macro loop protection enforcement in macro button handler

4.4 Virtual D-Pad (Deferred)

Package: maid-engine/player_frontend Priority: P2 (post-launch enhancement) Dependencies: 4.1

Note: Deferred to post-launch. The ExitButtons in the Room Info panel already provide tap-to-move navigation on mobile. The D-Pad is a nice-to-have, not required for MVP.

  • [ ] Create src/components/game/VirtualDPad.tsx — visible <640px only, translucent overlay on bottom-right corner of terminal
  • [ ] 8 cardinal/ordinal direction buttons, up/down toggle, draggable repositioning

4.5 Quest Log Panel

Package: maid-engine/player_frontend Priority: P2 Dependencies: 2.1

  • [ ] Create src/components/panels/QuestLogPanel.tsx — container with active quests, quest detail, completed toggle
  • [ ] Create src/components/panels/ActiveQuests.tsx — collapsible list of active quests with progress indicators
  • [ ] Create src/components/panels/QuestDetail.tsx — objectives with completion checkmarks, progress bars
  • [ ] Create src/components/panels/CompletedQuestsToggle.tsx — toggle to show/hide completed quests
  • [ ] Subscribe to GMCP Quest.List and Quest.Update (if available — these packages do not exist yet; panel hides gracefully if not negotiated)
  • [ ] Gracefully hide panel and mobile tab if no quest GMCP packages are negotiated
  • [ ] Implement quest objective update highlighting (briefly flash on change)

4.6 PWA Configuration

Package: maid-engine/player_frontend Priority: P2 Dependencies: 1.1

Note: Service worker asset caching is deferred. A game that requires a live WebSocket connection gains minimal benefit from offline caching, and service workers add cache invalidation complexity. Only manifest.json is included for Add-to-Home-Screen support.

  • [ ] Create public/manifest.json — app name, icons, theme color, display standalone
  • [ ] Create public/favicon.ico
  • [ ] Enable Add-to-Home-Screen on mobile

4.7 Security Hardening

Package: maid-engine/player_frontend Priority: P0 Dependencies: 0.2, 1.10, 1.11

  • [ ] Verify <SafeHTML> component is used for ALL non-terminal HTML rendering (chat panels, tooltips, room descriptions)
  • [ ] Verify ESLint rule catches any dangerouslySetInnerHTML outside SafeHTML.tsx
  • [ ] Verify CSP headers on /play/ route include nonce-based styles, no unsafe-inline
  • [ ] Verify xterm.js security config: allowProposedApi: false, all windowOptions disabled, linkHandler: null
  • [ ] Verify CSRF double-submit cookie flow works end-to-end
  • [ ] Verify paste protection modal for multi-line paste (preview, confirmation, control character stripping)
  • [ ] Verify client-side rate limiting (10 cmd/sec)
  • [ ] Verify command length cap (2000 chars)
  • [ ] Verify macro loop protection (5 depth, 20 commands, 10/sec)
  • [ ] Verify WebSocket connects only to same origin (CSP connect-src)

4.8 Internationalization

Package: maid-engine/player_frontend Priority: P2 Dependencies: 1.1

  • [ ] Configure react-i18next with lazy-loaded locale bundles
  • [ ] Create src/locales/en/translation.json — all UI chrome strings (panel headers, buttons, tooltips, error messages, status text)
  • [ ] Create src/locales/es/translation.json — Spanish translation
  • [ ] Replace all hardcoded user-facing strings with t() translation keys
  • [ ] Game text from server is NOT translated (author-controlled content)

4.9 Sanitization Utility

Package: maid-engine/player_frontend Priority: P0 Dependencies: 1.1

  • [ ] Create src/lib/sanitize.ts — thin wrapper around SafeHTML config, DOMPurify configuration with allowlist for non-terminal HTML sanitization

Phase 5: Testing and Launch (Weeks 12-13)

5.1 Unit Tests

Package: maid-engine/player_frontend Priority: P0 Dependencies: All Phase 1-4

  • [ ] Test dispatchGMCP() — correct routing to store actions for each package name, unknown packages are no-ops
  • [ ] Test dispatchGMCPBatch() — dispatches all messages synchronously
  • [ ] Test GameWebSocket — reconnect logic, backoff/jitter, queue management, lifecycle hooks
  • [ ] Test validateServerMessage() type guard — accepts valid types, rejects unknown
  • [ ] Test useConnectionStore — all actions, queue constraints (max 20, 30s TTL), status transitions
  • [ ] Test useCharacterStoresetCharacter, updateVitals, status effect CRUD, tickStatusEffects
  • [ ] Test useRoomStoresetRoom, updateRoom, partial updates
  • [ ] Test useInventoryStoresetInventory, addItem, removeItem, equipItem, unequipItem, all 10 equipment slots
  • [ ] Test useMapStoreupdateMap, addVisitedRoom, clearMap, setViewport, Record<string, true> serialization
  • [ ] Test useChatStoreaddMessage, setActiveChannel, markRead, createChannel, removeChannel, unread counts
  • [ ] Test useDialogueStorestartDialogue, addResponse, setTyping, addPlayerMessage, endDialogue, clearHistory
  • [ ] Test useSettingsStoreupdateSetting, resetDefaults, exportSettings/importSettings round-trip, alias/macro/keybinding CRUD, localStorage persistence
  • [ ] Test command history — add, navigate up/down, 500 entry limit
  • [ ] Test alias expansion — simple alias, nested alias, missing alias
  • [ ] Test macro expansion — simple macro, loop protection (depth, count, throttle)
  • [ ] Test rate limiting — allow under limit, reject over 10/sec
  • [ ] Test tab completion — debounce, requestId tracking, stale response discard, timeout
  • [ ] Test paste protection — single-line passthrough, multi-line detection, control char stripping
  • [ ] Test sanitize utility — allowed tags, stripped tags, CSS property allowlist
  • [ ] Target: >80% coverage for business logic (stores, services, utilities)

5.2 Component Tests

Package: maid-engine/player_frontend Priority: P0 Dependencies: All Phase 1-4

  • [ ] Test LoginForm — render, field validation, submit, error display, loading state
  • [ ] Test CharacterSelectPage — character list rendering, selection, create form
  • [ ] Test TerminalOutput — xterm.js initialization, message display, scrollback, ARIA live region
  • [ ] Test CommandInput — input, submit, history navigation, tab completion UI
  • [ ] Test ReconnectionOverlay — display on reconnect, attempt count, elapsed timer, manual retry, return to login
  • [ ] Test CharacterStatsPanel — vital bars render correct percentages, color coding, low HP animation
  • [ ] Test StatusEffects — effect list, duration display, stack count
  • [ ] Test InventoryPanel — tab switching, item list, equipment slots, context menus
  • [ ] Test RoomInfoPanel — room name, description, exit buttons, entity lists
  • [ ] Test MapPanel — room rendering, current room highlight, fog of war
  • [ ] Test ChatPanel — channel tabs, message list, unread badges, quick reply
  • [ ] Test NPCDialogueOverlay — dialogue bubbles, typing indicator, suggested responses, dismiss
  • [ ] Test QuestLogPanel — quest list, detail view, completed toggle, hidden when no quests
  • [ ] Test SafeHTML — renders sanitized HTML, strips disallowed tags/attributes
  • [ ] Test MobileTabBar — tab rendering, active state, visibility at breakpoint
  • [ ] Test VirtualDPad — direction buttons, command sending, position handle
  • [ ] Test MacroBar — button rendering, command sending, bank switching
  • [ ] Test all components with injectable dispatchGMCP mock for test isolation
  • [ ] Target: >60% overall coverage

5.3 Integration Tests

Package: maid-engine/player_frontend Priority: P1 Dependencies: All Phase 1-4

  • [ ] Test GMCP data flow: mock WebSocket → dispatchGMCP() → Store → Component re-render
  • [ ] Test gmcp_batch flow: single message → dispatchGMCPBatch() → multiple store updates → single React render
  • [ ] Test WebSocket connection lifecycle: connect → subscribe → game messages → disconnect → reconnect → sync_complete → resume
  • [ ] Test authentication flow: login → character select → WebSocket connect → game
  • [ ] Test session persistence: cookie-based session → page reload → GET /api/v1/auth/me → resume
  • [ ] Test offline queue: disconnect → queue commands → reconnect → sync_complete → confirmation overlay → flush
  • [ ] Test settings persistence: change settings → localStorage → reload → restored

5.4 E2E Tests (Playwright)

Package: maid-engine/player_frontend Priority: P1 Dependencies: All Phase 1-4

  • [ ] Install and configure Playwright 1.48 with cross-browser config (Chromium, Firefox, WebKit)
  • [ ] Test full login flow: load /play/ → login form → submit credentials → character select → enter game
  • [ ] Test basic gameplay loop: connect → send command → receive text → see output in terminal
  • [ ] Test GMCP panel updates: move to room → room info panel updates → map panel updates → character stats update
  • [ ] Test reconnection: kill WS connection → overlay appears → auto-reconnect → sync → resume
  • [ ] Test mobile viewport: set viewport to 375px → verify tab bar visible → verify sidebar hidden → switch tabs
  • [ ] Test NPC dialogue: talk npc → dialogue overlay opens → typing indicator → response → suggested replies → close
  • [ ] Test settings persistence: change theme → reload page → theme persists

5.5 Accessibility Tests

Package: maid-engine/player_frontend Priority: P1 Dependencies: All Phase 1-4

  • [ ] Integrate axe-core with Vitest for automated accessibility checks
  • [ ] Run axe-core audit on LoginPage — verify WCAG 2.1 AA compliance
  • [ ] Run axe-core audit on CharacterSelectPage
  • [ ] Run axe-core audit on GamePage (full layout)
  • [ ] Verify keyboard navigation: Tab through all interactive elements, arrow keys in panels, Escape closes modals/overlays, Enter submits
  • [ ] Verify ARIA landmarks: navigation, main, complementary for sidebars
  • [ ] Verify ARIA live regions: terminal output (polite), combat messages (assertive with aggregation)
  • [ ] Verify screen reader support: meaningful labels on icon-only buttons, alt text for visuals
  • [ ] Verify high-contrast theme meets WCAG 2.1 AA color contrast ratios
  • [ ] Verify prefers-reduced-motion is respected (no pulsing animations, no typing indicator animation)
  • [ ] Verify focus management: modal focus trapping, focus return on close

5.6 Performance Validation

Package: maid-engine/player_frontend Priority: P1 Dependencies: All Phase 1-4

  • [ ] Run Vite bundle analyzer — verify initial bundle <250KB gzipped (<180KB target), total <330KB
  • [ ] Verify code splitting: panels chunk <40KB, dialogue chunk <20KB, locale chunks <10KB each
  • [ ] Run Lighthouse audit — verify performance score >90, time to interactive <2s on 3G
  • [ ] Verify First Contentful Paint <1.5s
  • [ ] Verify xterm.js scrollback at 10,000 lines: idle memory <50MB, 10K lines buffer <100MB
  • [ ] Verify ANSI parse performance: 1000 chars <5ms
  • [ ] Verify vitals debounce: coalesce within 100ms during rapid combat

5.7 Server Integration Tests (Python)

Package: maid-engine Priority: P0 Dependencies: All server tasks from Phases 0-2

  • [ ] Test WebSocket protocol extensions — subscribe message handling, requestId correlation, gmcp_batch delivery
  • [ ] Test GMCP payload builder — dict output matches expected schemas for all supported packages
  • [ ] Test full state snapshot on connect — correct GMCP packages pushed after character binding, sync_complete sent
  • [ ] Test /ws/game handler — backward compatibility with existing flat JSON protocol
  • [ ] Test POST /api/v1/auth/login — valid credentials, invalid credentials, JWT cookie properties (HttpOnly, Secure, SameSite, Path), WebSocket auth cookie
  • [ ] Test POST /api/v1/auth/refresh — token rotation, family tracking, expired token
  • [ ] Test POST /api/v1/auth/logout — cookie clearing, session invalidation
  • [ ] Test GET /api/v1/auth/me — valid session, expired session, no session
  • [ ] Test GET /api/v1/auth/csrf — CSRF token in non-HttpOnly cookie
  • [ ] Test GET /api/v1/characters — list characters for authenticated user
  • [ ] Test POST /api/v1/characters — create character, name uniqueness validation
  • [ ] Test GET /api/v1/characters/:id — character details, ownership verification
  • [ ] Test GET /api/v1/settings — retrieve settings for authenticated user
  • [ ] Test PUT /api/v1/settings — save settings, schema validation
  • [ ] Test ping/pong heartbeat — correct echo timestamp in pong
  • [ ] Test ack/nack — requestId correlation, rate limit nack with retryAfter
  • [ ] Test completion response — requestId correlation, completions array
  • [ ] Test gmcp_batch — multiple GMCP packages in single frame
  • [ ] Test state snapshot on reconnect — full state pushed, sync_complete sent with correct domains
  • [ ] Test Origin validation — allowed origin accepted, disallowed origin rejected (close 4003)
  • [ ] Test max frame size — oversized client message rejected (close 1009), server messages within 256KB
  • [ ] Test CSP headers — correct nonce in header and style tags, no unsafe-inline

5.8 Launch Preparation

Package: maid-engine Priority: P0 Dependencies: 5.1-5.7

  • [ ] Fix all bugs discovered during testing
  • [ ] Final production build optimization (tree shaking, compression, content-hashed filenames)
  • [ ] Configure long cache headers for hashed assets, no-cache for index.html
  • [ ] Update server deployment configuration to serve player frontend at /play/
  • [ ] Update CHANGELOG.md with new player web client feature
  • [ ] Update README.md with player client documentation (build instructions, configuration)
  • [ ] Document build instructions: cd packages/maid-engine/player_frontend && npm install && npm run build
  • [ ] Soft launch to beta testers

Dependencies Summary

Phase 0 (Infrastructure):
  0.1 Player Frontend Bootstrap (standalone)
  0.2 ESLint Security Config (depends on 0.1)
  0.3 WebSocket Protocol Extensions ──→ 0.4 Origin/Frames
                                     ──→ 0.5 CSP Headers

Phase 1 (Foundation):
  0.1 ──→ 1.1 Project Setup ──→ 1.2 Themes
                              ──→ 1.3 Types ──→ 1.4 Login Page
                              ──→ 1.8 GMCP Dispatch ──→ 1.9 Stores
  1.5 REST Auth (Server) ──→ 1.4 Login Page
                          ──→ 1.7 Character/Settings API (Server)
  1.6 Character Select ←── 1.4, 1.5, 1.7
  1.9 Stores + 1.8 ──→ 1.10 Terminal ──→ 1.11 Command Input
  0.1 GameWebSocket + 1.8 + 1.9 ──→ 1.12 WS Hook
  1.10 + 1.11 + 1.12 ──→ 1.13 Game Page
  0.3 ──→ 1.14 Protocol Features (Server)
  0.5 ──→ 1.15 Static Serving (Server)

Phase 2 (GMCP):
  1.8 + 1.12 ──→ 2.1 GMCP Negotiation
  0.3 + 1.14 ──→ 2.2 State Snapshot / GMCP Extensions (Server)
  1.9 + 2.1 ──→ 2.3 CharacterStatsPanel
            ──→ 2.4 InventoryPanel
            ──→ 2.5 RoomInfoPanel
  2.3 + 2.4 + 2.5 ──→ 2.6 Sidebar Layout

Phase 3 (Map/Chat/Dialogue):
  1.9 + 2.1 ──→ 3.1 MapPanel
            ──→ 3.2 ChatPanel
            ──→ 3.3 NPCDialogueOverlay

Phase 4 (Polish/Mobile):
  2.6 + 3.* ──→ 4.1 Responsive Verification
  4.1 ──→ 4.2 MobileTabBar
      ──→ 4.3 MacroBar
      ──→ 4.4 VirtualDPad (P2 — deferred)
  2.1 ──→ 4.5 QuestLogPanel (P2)
  All ──→ 4.7 Security Hardening

Phase 5 (Testing/Launch):
  All ──→ 5.1-5.7 Tests ──→ 5.8 Launch

Priority Legend

  • P0: Must have for MVP — client is not shippable without these
  • P1: Should have — delivers significant value, expected for launch
  • P2: Nice to have — can defer past initial release

Estimated Effort

Phase Weeks Priority Client Tasks Server Tasks
Phase 0: Infrastructure Week 0 P0 Player frontend bootstrap, ESLint Protocol extensions, Origin, CSP
Phase 1: Foundation Weeks 1-3 P0 Auth UI, terminal, input, stores, WebSocket REST auth (with shared auth core), characters, settings, ping/pong/ack
Phase 2: GMCP Integration Weeks 4-6 P0/P1 GMCP dispatch, stats/inventory/room panels Batch, state snapshot, GMCP handler refactor
Phase 3: Map and Chat Weeks 7-9 P1 Map, chat, NPC dialogue
Phase 4: Polish and Mobile Weeks 10-11 P1/P2 Mobile UI, macros, security, i18n
Phase 5: Testing and Launch Weeks 12-14 P0 Unit/component/integration/E2E/a11y tests Server integration tests
Total 14 weeks

Server track estimate: The server track (auth core, REST endpoints, WebSocket extensions, GMCP handler refactor, character API with content-pack adapters) is realistically 6-8 weeks of effort, not 4. Consider starting the server track 2 weeks before the client track, or running it in parallel with a dedicated engineer. The auth work alone (shared core extraction, player JWT scope, cookie paths, CSRF, refresh rotation) is 1-2 weeks.