UI Improvements - March 2026
Last Updated: March 26, 2026Related PRs: #1093, #1092, #1089, #1088
Overview
March 2026 saw a comprehensive UI polish pass focusing on consistency, reusability, and user experience. Key improvements include unified skilling panels, redesigned NPC dialogue system, combat panel enhancements, and critical bug fixes.Skilling Panel Unification (PR #1093)
Problem
Each skilling panel (Fletching, Cooking, Smelting, Smithing, Crafting, Tanning) had its own duplicated styling code:- ~500 lines of duplicated CSS-in-JS across 5 panels
- Inconsistent visual treatment (colors, borders, shadows)
- Duplicated quantity selector logic
- Maintenance burden (changes required updating 5+ files)
Solution
Extracted shared components and style helpers intoSkillingPanelShared.tsx:
SkillingPanelBody
Wrapper component for panel content with intro text and empty state support.SkillingSection
Themed section card for grouping recipes.- Background:
theme.colors.background.panelSecondary - Border:
theme.colors.border.default - Inset highlight:
rgba(255, 255, 255, 0.03) - Rounded corners:
rounded-xl
SkillingQuantitySelector
Reusable quantity selector with preset buttons and custom input mode.- Preset buttons: 1, 5, 10, All, X (custom)
- Custom input mode with Enter/Escape keyboard shortcuts
- Remembers last custom quantity
- Responsive layout (2 columns mobile, 5 columns desktop)
Style Helpers
Consistent visual treatment for selectable items and badges.- Selected: Accent-tinted background with glow border
- Unselected: Dark semi-transparent background
- Disabled: 48% opacity
- Dark background with subtle border
- Secondary text color
- Consistent across all panels
Impact
- Code Reduction: ~500 lines of duplicated styling eliminated
- Consistency: All skilling panels now have identical visual language
- Maintainability: Single source of truth for skilling UI patterns
- Reusability: New skilling panels can use shared components immediately
- Mobile: Responsive layouts with proper touch targets
Migration
Before (duplicated in each panel):NPC Dialogue Redesign (PR #1093)
Problem
Old dialogue system had several issues:- Generic modal shell (not dialogue-specific)
- No character portraits (less immersive)
- Service handoffs (bank, store, tanner) left orphaned dialogue panels
- Inconsistent focus management
Solution
DialoguePopupShell
Dedicated modal shell for NPC dialogue with proper focus management.- Auto-focus on open
- Escape key to close
- Click outside to close
- Prevents event bubbling to game world
- Gold accent bar at top (dialogue-specific styling)
- Responsive sizing (mobile-friendly)
- Width: 700px
- Max width:
min(86vw, 700px)(responsive) - Max height:
min(40vh, 400px)(prevents overflow)
DialogueCharacterPortrait
Live 3D VRM portrait rendering in dialogue panels.- Renders NPC’s VRM model in real-time
- Isolated Three.js scene (doesn’t affect main game)
- Automatic camera positioning
- Memoized for performance
- Fallback to placeholder if VRM not loaded
- Creates dedicated
WebGPURendererinstance - Clones NPC’s VRM model (shares textures, independent materials)
- Renders to canvas element in dialogue panel
- Cleanup on unmount (disposes renderer, scene, materials)
Service Handoff Fix
Opening bank/store/tanner now properly closes dialogue: Before:packages/client/src/hooks/useModalPanels.ts):
Impact
- Immersion: Live NPC portraits make dialogue feel more engaging
- Consistency: Dedicated dialogue shell with dialogue-specific styling
- UX: Service handoffs no longer leave orphaned panels
- Accessibility: Proper focus management and keyboard navigation
Combat Panel Enhancements (PR #1088)
Combat Style Banners
Features:- Drag-to-action-bar support for combat styles
- Fixed click handling on drag overlays
- Stabilized banner width (4-column calc width instead of flex:1)
- Banners stay same size whether 3 or 4 styles are shown
Auto-Retaliate Toggle
Fix: Auto-retaliate toggle was overridden by stale entity read onuseEffect re-run.
Problem:
Equipment Panel Cross-Player Leak Fix (PR #1089)
Problem
Equipment panel showed stale data from previously inspected players:- User inspects Player A’s equipment
- Panel opens with Player A’s data
- User closes panel
- User inspects Player B’s equipment
- Panel opens with Player A’s data (stale!)
renderPanel function was created, it closed over the initial data value. Subsequent data changes didn’t recreate the function.
Solution
Include panel data inuseMemo dependencies to recreate renderPanel when data changes:
Before:
Impact
- Equipment panel always shows current player’s data
- No cross-contamination between inspected players
- Fixes confusing UX where wrong player’s stats appeared
Arrow Key Capture Fix (PR #1092)
Problem
When a combined panel tab retained focus, pressing an arrow key would switch tabs instead of moving the camera. This broke camera controls during gameplay. Root Cause: Tab component’sonKeyDown handler consumed arrow key events without checking if they should be reserved for game controls.
Solution
AddedreserveArrowKeys prop to Tab component:
Impact
- Arrow keys control camera movement even when panel tabs have focus
- Enter/Space still activate tabs for keyboard accessibility
- Better separation between game controls and UI navigation
Missing Packet Handlers (PR #1091)
Problem
Server was sending 8 packet types that client had no handlers for, causing console errors:Solution
Added 8 missing handler methods inClientNetwork.ts:
Impact
- Eliminates “No handler for packet” console errors
- UI systems can now react to skill completion events
- Combat end notifications work correctly
- Quest start notifications work correctly
Prayer Login Sync Fix (PR #1090)
Problem
Prayer state (points, active prayers) wasn’t syncing correctly on player login:- Prayer points reset to max on login
- Active prayers cleared on login
- Inconsistent state between sessions
Solution
Fixed prayer state synchronization in login flow:- Prayer drain persistence
- Prayer point restoration on level-up
- Active prayer sync on reconnect
Impact
- Prayer points persist correctly between sessions
- Active prayers remain active after reconnect
- Consistent prayer state across client and server
Component API Reference
SkillingPanelBody
Props:theme: Theme- Current theme objectchildren?: ReactNode- Panel content (recipe grid, etc.)emptyMessage?: string- Message shown when no content availableintro?: string- Introductory text at top of panel
- Intro text:
text-xs, secondary color, relaxed line height - Empty message: Centered, rounded border, secondary background
SkillingSection
Props:theme: Theme- Current theme objectchildren: ReactNode- Section contentclassName?: string- Additional CSS classesstyle?: CSSProperties- Additional inline styles
- Background:
panelSecondary - Border:
border.default - Padding:
p-3(12px) - Border radius:
rounded-xl - Inset highlight:
rgba(255, 255, 255, 0.03)
SkillingQuantitySelector
Props:theme: Theme- Current theme objectshowCustomInput: boolean- Whether custom input mode is activecustomQuantity: string- Current custom quantity valuelastCustomQuantity: number- Last submitted custom quantity (for placeholder)onCustomQuantityChange: (value: string) => void- Custom input change handleronCustomSubmit: () => void- Custom input submit handleronCancelCustomInput: () => void- Custom input cancel handleronPresetQuantity: (quantity: number) => void- Preset button click handlerallQuantity: number- Maximum quantity for “All” buttononShowCustomInput: () => void- Show custom input mode handler
- Buttons: 1, 5, 10, All, X
- Grid layout: 2 columns (mobile), 5 columns (desktop)
- Click preset → immediately process quantity
- Number input with placeholder showing last custom quantity
- OK/Cancel buttons
- Enter key → submit
- Escape key → cancel
- Auto-focus on input
DialoguePopupShell
Props:visible: boolean- Whether dialogue is visibletitle: string- Dialogue title (NPC name)children: ReactNode- Dialogue content (text, responses, portrait)onClose: () => void- Close handlerwidth?: number | string- Panel width (default: 700)maxWidth?: number | string- Max width (default:min(86vw, 700px))maxHeight?: number | string- Max height (default:min(40vh, 400px))contentStyle?: CSSProperties- Additional content area styles
- Auto-focus on open
- Escape key to close
- Click outside to close
- Prevents event bubbling to game world
- Gold accent bar at top
- Responsive sizing
DialogueCharacterPortrait
Props:world: World- Game world instancenpcEntityId: string- NPC entity IDnpcName: string- NPC name (for fallback)className?: string- Additional CSS classes
- Creates isolated Three.js scene
- Clones NPC’s VRM model
- Renders to 200×200 canvas
- Auto-cleanup on unmount
Style Helpers
getSkillingSelectableStyle
Generate consistent style for selectable items (recipe cards, material options).getSkillingBadgeStyle
Generate consistent style for badges (level requirements, quantities).Migration Guide
Updating Existing Skilling Panels
Step 1: Import shared componentsCreating New Skilling Panels
Use shared components from the start:Best Practices
Skilling Panels
- Always use shared components - Don’t duplicate styling
- Consistent intro text - Explain what materials are needed
- Empty states - Show helpful message when no recipes available
- Responsive grids - Use
grid-cols-2(mobile) andsm:grid-cols-3(desktop) - Proper touch targets - Minimum 44×44px for mobile
Dialogue Panels
- Use DialoguePopupShell - Don’t use generic modal
- Include character portrait - More immersive than text-only
- Close on service handoff - Bank/store/tanner should close dialogue
- Keyboard navigation - Escape to close, Enter to select response
- Prevent event bubbling - Mark events as
isCoreUIto prevent game interaction
Combat Panels
- Fixed banner width - Use calc width, not flex:1
- Drag overlay - Handle both click and drag events
- Optimistic updates - Update UI immediately, sync with server
- Auto-retaliate - Don’t override with stale entity reads
Performance Considerations
Skilling Panels
- Shared components: Reduce bundle size (single implementation)
- Memoization: Use
React.memofor expensive recipe cards - Virtual scrolling: For panels with 100+ recipes (future)
Dialogue Panels
- Portrait rendering: Isolated Three.js scene (doesn’t affect main game)
- Memoization:
DialogueCharacterPortraitis memoized - Cleanup: Dispose renderer/scene/materials on unmount
- Texture sharing: Cloned VRM shares textures (memory efficient)
Combat Panels
- Optimistic updates: Reduce perceived latency
- Debounced sync: Batch style changes to server
- Fixed layouts: Prevent reflow on style count change
Accessibility
Keyboard Navigation
- Tab: Focus next interactive element
- Enter/Space: Activate focused element
- Escape: Close modal/cancel input
- Arrow keys: Reserved for game controls (camera movement)
Screen Readers
- ARIA labels: All buttons have
aria-label - Role attributes: Modals use
role="dialog"andaria-modal="true" - Focus management: Auto-focus on modal open, restore on close
- Semantic HTML: Use
<button>,<input>,<h2>instead of<div>
Touch Targets
- Minimum size: 44×44px for mobile
- Spacing: 8px gap between interactive elements
- Visual feedback: Hover/active states for all buttons
Future Improvements
Skilling Panels
- Virtual scrolling for large recipe lists
- Recipe search/filter
- Favorite recipes
- Batch crafting queue
- Material requirement tooltips
Dialogue Panels
- Dialogue history (scroll back)
- Voice acting integration
- Animated portraits (lip sync, expressions)
- Dialogue choices with icons
- Multi-NPC dialogues (group conversations)
Combat Panels
- Combat style presets (save/load)
- Damage calculator
- DPS meter
- Combat log
- Auto-retaliate delay slider
References
- PR #1093: Dialogue and skilling panel polish
- PR #1092: Arrow key capture fix
- PR #1091: Missing packet handlers
- PR #1090: Prayer login sync
- PR #1089: Equipment panel cross-player leak
- PR #1088: Comprehensive UI panel upgrade
- SkillingPanelShared.tsx: Shared skilling components
- DialoguePopupShell.tsx: Dialogue modal shell
- DialogueCharacterPortrait.tsx: Live NPC portrait rendering