12 KiB
PMOControl WebUI - Design Recommendations & Implementation Plan
Executive Summary
Based on my analysis of the existing codebase, I'm providing comprehensive recommendations for implementing a Vue.js WebUI for PMOControl. The system already has:
- A complete REST API with OpenAPI documentation (
/api/control/*) - SSE endpoints for real-time updates (
/api/control/events/*) - Vue 3 + TypeScript + Vite setup
- Existing components (GenericMusicPlayer, UpnpExplorer, Cache Managers, LogView)
Design Decisions & Recommendations
1. State Management: Use Pinia
Recommendation: Pinia (Vue 3's official state management)
Rationale:
- Centralized real-time state: Essential for managing SSE updates from multiple sources (renderers, media servers)
- Multi-client synchronization: Single source of truth for renderer states, volumes, playback positions
- TypeScript native: Better type inference than Vuex
- DevTools integration: Built-in debugging for SSE event flows
- Composition API friendly: Matches existing Vue 3 patterns in codebase
- Performance: Lightweight (~1KB), modular stores
- Official Vue 3 recommendation: Future-proof choice
Store Architecture:
// stores/renderers.ts - Renderer state (SSE updates)
// stores/mediaServers.ts - Media server state (SSE updates)
// stores/playback.ts - Current playback session
// stores/ui.ts - UI state (selected renderer, view preferences)
Benefits for your use case:
- Handle 20+ concurrent clients with shared state
- Real-time SSE event synchronization across all views
- Easy to scale with multi-renderer, multi-server, multi-session architecture
2. UI Component Library: Headless UI + Custom Components
Recommendation: Hybrid approach - Headless UI components + custom styling
Component Library: Shadcn-vue (Headless UI primitives)
Rationale:
- Lightweight & performant: Only import what you need
- Full style control: Match "carte uniforme, responsive, colorée selon statut" spec exactly
- TypeScript-first: Perfect type safety
- Accessibility built-in: ARIA compliance out of the box
- No theme lock-in: Complete CSS freedom
- Composable primitives: Card, Dialog, Dropdown, Slider components
Why NOT a full framework (Vuetify, Element Plus)?
- Heavy bundle size (100-500KB vs ~10KB for headless)
- Theme customization overhead
- Your spec requires custom status-based coloring
- Performance critical with 20 concurrent clients
Alternative if you prefer pre-styled: PrimeVue
- Good performance
- Customizable themes
- Strong TypeScript support
- But: 150KB+ bundle size
Custom Components to Build:
RendererCard- Status-colored cards for each rendererTransportControls- Play/Pause/Stop/Next buttonsVolumeControl- Slider with mute toggleQueueViewer- Playlist display with drag-dropMediaServerBrowser- Container navigation
3. Existing Components: Reorganize into Debug Section
Recommendation: Keep existing components, create new PMOControl home
Structure:
/app (root) → PMOControl Dashboard (NEW)
/app/debug → Dropdown menu
├─ /logs → LogView
├─ /upnp → UpnpExplorer
├─ /covers-cache → CoverCacheManager
├─ /audio-cache → AudioCacheManager
├─ /api-dashboard → APIDashboard
└─ /radio-paradise → RadioParadiseExplorer
Rationale:
- Existing components are valuable for development/debugging
- Don't break existing functionality
- PMOControl becomes primary interface as specified
- Debug tools remain accessible but not prominent
- Matches current App.vue dropdown pattern
Home Screen (/) - PMOControl Dashboard:
- Grid of renderer cards (status-colored)
- Active playback session viewer
- Quick controls (play/pause/volume)
- Media server browser panel
4. Responsive Design: Mobile-first with 3 breakpoints
Recommendation: Follow existing 768px pattern + add tablet/desktop
Breakpoints:
/* Mobile: < 768px (existing pattern) */
- Single column layout
- Stacked renderer cards
- Bottom-fixed playback controls
- Collapsible media browser
/* Tablet: 768px - 1024px */
- Two column layout
- Grid of renderer cards (2 columns)
- Side panel for media browser
- Floating playback controls
/* Desktop: > 1024px */
- Three column layout
- Renderer cards grid (3-4 columns)
- Persistent media browser sidebar
- Always-visible playback controls
Target Devices:
- Primary: Desktop browsers (control station)
- Secondary: Tablets (remote control)
- Tertiary: Mobile phones (quick controls)
Performance considerations:
- Virtualized lists for 20+ renderers (use vue-virtual-scroller)
- Lazy load album art
- Throttle SSE position updates (max 1/sec per renderer)
5. Icons: Lucide Icons (SVG library)
Recommendation: Lucide Icons (NOT emoji)
Rationale:
- Professional appearance: Emojis inconsistent across platforms
- Customizable: Size, color, stroke width
- Lightweight: Tree-shakeable SVG imports (~1KB per icon)
- Status coloring: Icons can match card status colors
- Accessibility: Proper ARIA labels
- Vue components:
lucide-vue-nextpackage
Icon mapping:
Play → PlayCircle
Pause → PauseCircle
Stop → StopCircle
Next → SkipForward
Volume → Volume2 / VolumeX (muted)
Renderer → Speaker / MonitorSpeaker
Server → Server / Database
Queue → ListMusic
Alternative if you prefer minimal bundle: Heroicons
- Smaller set (fewer icons)
- Tailwind CSS integration
- But: less comprehensive for music player needs
Why NOT emoji:
- Platform inconsistencies (iOS ≠ Android ≠ Windows)
- No color control
- Accessibility issues
- Unprofessional for production UI
Technical Architecture
Real-time SSE Integration
SSE Event Handling:
// services/controlPointSSE.ts
class ControlPointSSE {
private eventSource: EventSource
private renderersStore: ReturnType<typeof useRenderersStore>
connect() {
this.eventSource = new EventSource('/api/control/events')
this.eventSource.addEventListener('control', (e) => {
const event = JSON.parse(e.data)
if (event.category === 'renderer') {
this.handleRendererEvent(event)
} else if (event.category === 'media_server') {
this.handleServerEvent(event)
}
})
}
handleRendererEvent(event: RendererEventPayload) {
switch (event.type) {
case 'state_changed':
this.renderersStore.updateState(event.renderer_id, event.state)
break
case 'volume_changed':
this.renderersStore.updateVolume(event.renderer_id, event.volume)
break
// ... handle all event types
}
}
}
Store Integration:
// stores/renderers.ts
export const useRenderersStore = defineStore('renderers', () => {
const renderers = ref<Map<string, RendererState>>(new Map())
// SSE updates
function updateState(id: string, state: string) {
const renderer = renderers.value.get(id)
if (renderer) {
renderer.transport_state = state
}
}
// REST API calls
async function play(id: string) {
await fetch(`/api/control/renderers/${id}/play`, { method: 'POST' })
// SSE will update state automatically
}
return { renderers, updateState, play }
})
Performance Optimizations
For 20+ concurrent clients:
-
Throttle position updates:
const throttledPositionUpdate = throttle((id, pos) => { store.updatePosition(id, pos) }, 1000) // Max 1 update/second -
Virtual scrolling for renderer lists:
npm install vue-virtual-scroller -
Lazy load album art:
<img :src="albumArt" loading="lazy" /> -
Debounce volume sliders:
const debouncedVolumeChange = debounce((id, vol) => { api.setVolume(id, vol) }, 300) -
Memoize computed properties:
const activeRenderers = computed(() => renderers.value.filter(r => r.online) )
Implementation Roadmap
Phase 1: Core Infrastructure (Week 1)
- Install Pinia + configure stores
- Install Lucide Icons
- Create SSE service layer
- Setup store structure (renderers, servers, playback, ui)
- Connect SSE events to stores
Phase 2: UI Components (Week 2)
- Build RendererCard component (status-colored)
- Build TransportControls component
- Build VolumeControl component
- Build QueueViewer component
- Create responsive grid layouts
Phase 3: Dashboard Assembly (Week 3)
- Create PMOControl home view
- Integrate all components
- Add media server browser panel
- Implement responsive breakpoints
- Add loading states & error handling
Phase 4: Polish & Testing (Week 4)
- Test with 20+ concurrent clients
- Performance profiling & optimization
- Accessibility audit (ARIA, keyboard nav)
- Cross-browser testing
- Mobile/tablet testing
- Documentation
Dependencies to Install
{
"dependencies": {
"pinia": "^2.2.8",
"lucide-vue-next": "^0.470.0",
"vue-virtual-scroller": "^2.0.0-beta.8"
},
"devDependencies": {
// Already installed: vue, vue-router, typescript, vite
}
}
Total bundle size estimate: +15KB gzipped (Pinia + Lucide + Virtual Scroller)
Status-based Coloring Scheme
Based on "carte uniforme, responsive, colorée selon statut" spec:
/* Renderer Card Status Colors */
.renderer-card.playing {
border-color: #22c55e; /* green */
background: linear-gradient(135deg, #22c55e10, transparent);
}
.renderer-card.paused {
border-color: #f59e0b; /* amber */
background: linear-gradient(135deg, #f59e0b10, transparent);
}
.renderer-card.stopped {
border-color: #6b7280; /* gray */
background: linear-gradient(135deg, #6b728010, transparent);
}
.renderer-card.offline {
border-color: #ef4444; /* red */
background: linear-gradient(135deg, #ef444410, transparent);
opacity: 0.6;
}
.renderer-card.transitioning {
border-color: #3b82f6; /* blue */
background: linear-gradient(135deg, #3b82f610, transparent);
animation: pulse 2s infinite;
}
Answers to Your Specific Questions
1. State Management?
Answer: Pinia - Vue 3 official, perfect for SSE real-time updates, TypeScript native, lightweight
2. UI Component Library?
Answer: Headless UI (Shadcn-vue) + Custom Components - Full control over status-based styling, lightweight, no theme lock-in
3. Keep existing components?
Answer: Yes, reorganize into Debug section - Keep valuable dev tools, make PMOControl the new home screen
4. Responsive breakpoints?
Answer: Mobile-first with 3 breakpoints - <768px (mobile), 768-1024px (tablet), >1024px (desktop)
5. Icons?
Answer: Lucide Icons (SVG library) - Professional, customizable, status-colored, NOT emoji
Risk Mitigation
Potential challenges:
-
SSE connection management across tabs
- Solution: Use BroadcastChannel API for cross-tab sync
- Fallback: LocalStorage events
-
20+ renderers performance
- Solution: Virtual scrolling + throttled updates
- Monitor: Chrome DevTools Performance profiler
-
Network reliability (SSE reconnection)
- Solution: Exponential backoff reconnection
- UI indicator for connection status
-
Album art loading (CORS, 404s)
- Solution: Proxy through backend
- Fallback: Default placeholder image
-
Browser compatibility (SSE support)
- Chrome/Edge: Native support ✅
- Firefox: Native support ✅
- Safari: Native support ✅
- IE11: Use EventSource polyfill
Success Metrics
Performance targets:
- Initial load: <2s (FCP)
- SSE event latency: <100ms
- UI interaction: <16ms (60fps)
- Memory usage: <50MB with 20 renderers
- Bundle size: <250KB gzipped
Functionality checklist:
- Display all discovered renderers in real-time
- Show accurate playback state (play/pause/stop)
- Volume control works across all renderer types
- Queue display syncs with server
- Media server browsing functional
- Playlist attachment working
- Responsive on mobile/tablet/desktop
- Accessible (WCAG AA compliance)
- 20+ concurrent clients supported
Next Steps
- Review & approve this plan with stakeholders
- Clarify any ambiguities in requirements
- Set up development environment (install dependencies)
- Begin Phase 1 (Core Infrastructure)
Would you like me to proceed with implementation, or do you have questions about any of these recommendations?