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
GMCPHandlerinnet/telnet/protocols/gmcp.pyreturns Telnet-encoded bytes; a protocol-agnosticGMCPPayloadBuilderreturningdictobjects 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), notmaid-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/sharedworkspace 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 fromadmin_frontend/src/api/client.tsintoplayer_frontend/src/api/client.ts, adapt for player auth paths (/api/v1/) - [ ] Create
SafeHTMLcomponent inplayer_frontend/src/components/SafeHTML.tsxusing DOMPurify with allowlist (<span>,styleattr only, CSS properties:color,background-color,font-weight,font-style,text-decoration) - [ ] Implement
GameWebSocketclass inplayer_frontend/src/lib/game-websocket.ts— wraps nativeWebSocketwith 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.cjsbanningdangerouslySetInnerHTMLoutsideSafeHTML.tsxviano-restricted-syntaxselectorJSXAttribute[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/gamehandler is extended incrementally with optional fields. Existing clients continue to work unchanged. NoWebSocketProtocolAdapterABC is needed.
- [ ] Add optional
requestIdfield to outboundack/nack/completionmessages in the existing/ws/gamehandler for request-response correlation - [ ] Add
subscribeclient message type — client sends{type: "subscribe", packages: ["Char.Vitals", ...]}to request GMCP data; server responds with current state for subscribed packages - [ ] Add
gmcp_batchmessage 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
GMCPHandlerto separate payload construction from Telnet encoding — create protocol-agnostic methods returningdictobjects 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 code4003) - [ ] Add
allowed_originstoWebSettingsinpackages/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_SIZEconstant toserver.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 inserver.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.jsonwith 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.jsonwith strict mode, path alias@→src/ - [ ] Create
vite.config.tswith base/play/, React plugin, path alias, proxy config for dev - [ ] Create
tailwind.config.jswith game-specific colors (hp, mp, sp, xp, terminal) and dark theme defaults - [ ] Create
index.htmlentry point - [ ] Create
src/main.tsxentry point with React Router, React Query provider - [ ] Create
src/App.tsxwith route structure:/(login),/characters(select),/game(play) - [ ] Set up Vitest configuration in
vite.config.tsfor unit/component testing - [ ] Add
@testing-library/react@16and@testing-library/jest-domas dev dependencies - [ ] Create
src/test/setup.tsfor 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.csswith 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.ts—ServerMessage,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.ts—GMCPMessage,GMCPCharVitals,GMCPCharStatus,GMCPStatusEffect,GMCPRoomInfo,GMCPExit,GMCPRoomPlayers,GMCPEntityInfo,GMCPCharItemsInv,GMCPItem,GMCPCommChannel,GMCPChannel,GMCPCommChannelText> Note: GMCP type names must be audited against actual servergmcp.py:supported_packagesbefore 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.ts—Character,Vitals,StatusEffect,Room,Exit,EntityInfo,InventoryItem,ItemType,EquipmentSlot,EquipmentSlots,MapRoom,MapData,ChatChannel,ChatMessage,ActiveDialogue,DialogueMessage,DialogueNPC,QueuedCommand,ConnectionStatus - [ ] Create
src/types/settings.ts—ThemeName,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 usingGET /api/v1/health> Note:GET /api/v1/statusdoes not currently exist in v1 routes. Either use existing/api/v1/healthor 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 viaGET /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.pyrouter - [ ] 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 asX-CSRF-Tokenheader (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/charactersusing 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.pyrouter - [ ] 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.pyrouter - [ ] 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— typeddispatchGMCP(packageName: string, data: unknown)function that dispatches to store actions via a switch/map. AlsodispatchGMCPBatch(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.ts—useConnectionStorewith 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.ts—useCharacterStorewith state:character,statusEffects; actions:setCharacter,updateVitals,addStatusEffect,removeStatusEffect,updateStatusEffect,tickStatusEffects; GMCP subscriptions:Char.Vitals,Char.Status - [ ] Create
src/stores/room-store.ts—useRoomStorewith state:room; actions:setRoom,updateRoom; GMCP subscriptions:Room.Info,Room.Players> Note:Room.NPCsandRoom.Itemsdo not exist as separate GMCP packages on the server. Room content is currently delivered viaRoom.InfoorChar.Items.Room. Add these subscriptions only after corresponding server GMCP packages are implemented. - [ ] Create
src/stores/inventory-store.ts—useInventoryStorewith 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 hasChar.Items.Inv(full list) andChar.Items.Room. GranularChar.Items.Add/Char.Items.Removeevents do not exist yet; add when server implements them. - [ ] Create
src/stores/map-store.ts—useMapStorewith state:map(MapDatawithrooms: 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.ts—useChatStorewith 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 singleComm.Channelpackage, not split intoComm.Channel.TextandComm.Channel.List. - [ ] Create
src/stores/dialogue-store.ts—useDialogueStorewith 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.ts—useSettingsStorewith Zustandpersistmiddleware (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.jsTerminal, configures scrollback (10000 lines, adjustable via settings store), receivestext,system,error,promptmessages from WebSocket hook - [ ] Configure xterm.js with restrictive security settings:
allowProposedApi: false, allwindowOptionsdisabled,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); useassertivefor 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 hookuseCommandHistory) — command history (500 entries), up/down arrow navigation - [ ] Create
src/components/game/TabCompleter.tsx(or hookuseTabCompletion) — sendscompleterequest withrequestId, 150ms debounce, 2s timeout, tracks latest pendingrequestIdto 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— usesGameWebSocketfromsrc/lib/game-websocket.ts, connects to/ws/game, configures reconnect (1s/2s/4s/8s/16s cap + jitter, max 10 attempts) - [ ] Implement
onOpenhandler: set statusconnected, reset parse error counter, start heartbeat, sendsubscribemessage with GMCP packages andcharacterId - [ ] Implement
onClosehandler: set statusreconnecting, stop heartbeat - [ ] Implement
onErrorhandler: log error, set statuserror - [ ] Implement
onMessagehandler: JSON.parse with try/catch,validateServerMessage()type guard, route by message type —text/system/error/prompt→ terminal,gmcp→dispatchGMCP(),gmcp_batch→dispatchGMCPBatch(),ack/nack→ request correlation,pong→ latency,sync_complete→ flushQueue - [ ] Implement heartbeat:
pingevery 30s, expectpongwithin 10s, setstaleon miss, force reconnect on 2nd consecutive miss, derive latency from RTT - [ ] Implement page lifecycle handling:
visibilitychange→ pause/resume heartbeat,beforeunload→ WebSocket close frame (code4000) - [ ] Implement JSON parse error tracking: count errors in 10s window, force reconnect if >5
- [ ] Create
validateServerMessage()type guard acceptingtext,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, initializesuseGameWebSocket, provides send function via context - [ ] Create
src/pages/GamePage.tsx— wraps content inWebSocketProvider - [ ] 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/pongheartbeat in WebSocket handler — respond to clientpingwithpongincluding echo timestamp - [ ] Implement
ack/nackcommand acknowledgment — correlate viarequestId, sendnackwithreasonand optionalretryAfterfor rate-limited commands - [ ] Implement
completionresponse — return tab-complete results correlated tocompleterequest viarequestId - [ ] Implement
characterIdbinding insubscribehandler — validatecharacterIdbelongs 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.pymount order: API → Admin → WebSocket → Player SPA → Root redirect - [ ] Mount player frontend build at
/play/viaStaticFiles(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— sendsubscribemessage after connection with desired package list - [ ] Define
SUBSCRIBED_GMCP_PACKAGESconstant:Core 1,Char.Vitals 1,Char.Items 1,Char.Status 1,Room.Info 1,Room.Players 1,Comm.Channel 1> Note: PackagesRoom.NPCs,Room.Items(as separate packages),Comm.Channel.Text,Comm.Channel.List,Dialogue.*, andQuest.*do not exist on the server. Subscribe to these only after server-side implementation. The client should gracefully handle missing packages. - [ ] Wire
dispatchGMCP()inuseGameWebSocketmessage 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_batchmessage 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.Channelfor the bound character, followed by{type: "sync_complete", domains: [...]} - [ ] Server GMCP extension work (prerequisite for full client GMCP integration):
- [ ] Refactor
GMCPHandlerto producedictpayloads separate from Telnet IAC encoding - [ ] Audit existing
supported_packagesingmcp.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 (respectsprefers-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
remainingTimeon everyChar.Statusupdate, 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 usingreact-windowfor 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 (usesSafeHTMLif 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
Suspensewith 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 fromRoom.InfoGMCP, 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 usingreact-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
SafeHTMLcomponent 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-sideDialogue.*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 viaReact.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 (respectsprefers-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
endconversationcommand) - [ ] 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
GameLayoutresponsive 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
macrosin 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.ListandQuest.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.jsonis 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
dangerouslySetInnerHTMLoutsideSafeHTML.tsx - [ ] Verify CSP headers on
/play/route include nonce-based styles, nounsafe-inline - [ ] Verify xterm.js security config:
allowProposedApi: false, allwindowOptionsdisabled,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-i18nextwith 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 aroundSafeHTMLconfig, 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
useCharacterStore—setCharacter,updateVitals, status effect CRUD,tickStatusEffects - [ ] Test
useRoomStore—setRoom,updateRoom, partial updates - [ ] Test
useInventoryStore—setInventory,addItem,removeItem,equipItem,unequipItem, all 10 equipment slots - [ ] Test
useMapStore—updateMap,addVisitedRoom,clearMap,setViewport,Record<string, true>serialization - [ ] Test
useChatStore—addMessage,setActiveChannel,markRead,createChannel,removeChannel, unread counts - [ ] Test
useDialogueStore—startDialogue,addResponse,setTyping,addPlayerMessage,endDialogue,clearHistory - [ ] Test
useSettingsStore—updateSetting,resetDefaults,exportSettings/importSettingsround-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
dispatchGMCPmock 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_batchflow: 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-corewith 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-motionis 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 —
subscribemessage handling,requestIdcorrelation,gmcp_batchdelivery - [ ] 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_completesent - [ ] Test
/ws/gamehandler — 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.