--- phase: 01-foundation-dashboard plan: 01 type: execute wave: 1 depends_on: [] files_modified: - src/app/stores/sensorStore.ts - src/app/api/client.ts - src/app/App.tsx - vitest.config.ts - src/test/setup.ts - package.json autonomous: true requirements: - DASH-01 - DASH-02 - DASH-03 - DASH-04 - DASH-05 - DASH-06 - DASH-07 - UI-04 must_haves: truths: - Zustand store exists with sensor data types - API client with mock fallback works - Port-based mode detection works (8080=kiosk, 9090=remote) - Test infrastructure is ready artifacts: - path: src/app/stores/sensorStore.ts provides: Zustand store for sensor data exports: useSensorStore, SensorState - path: src/app/api/client.ts provides: API client with mock fallback exports: fetchSensorData, mockSensorData - path: src/app/App.tsx provides: Mode detection and app root min_lines: 30 - path: vitest.config.ts provides: Test configuration key_links: - from: sensorStore to: client.ts via: fetchSensorData updates store - from: App.tsx to: sensorStore via: Initializes data polling --- Create the foundational infrastructure for the RTU dashboard: Zustand state management, API client with mock fallback, port-based mode detection, and test infrastructure (Wave 0). Purpose: This infrastructure enables all subsequent dashboard features. Without these foundations, individual components cannot share data or communicate with the backend. Output: Working store, API client, mode detection, and test setup that subsequent plans build upon. @./.opencode/get-shit-done/workflows/execute-plan.md @./.opencode/get-shit-done/templates/summary.md @.planning/phases/01-foundation-dashboard/01-CONTEXT.md @.planning/phases/01-foundation-dashboard/01-RESEARCH.md @.planning/ROADMAP.md @.planning/REQUIREMENTS.md @sample_interface/src/app/App.tsx ## Key Implementation Details **Mode Detection:** - Port 8080 → kiosk mode (fixed 1024x600) - Port 9090 → remote mode (responsive Full HD) - Use `window.location.port` for detection **Sensor Data Structure:** ```typescript interface SensorData { rainfall: { today: number; hourly: number; monthlyAcc: number; yearlyAcc: number; }; voltage: { solar: number; battery: number; batteryStatus: 'HIGH' | 'LOW'; }; station: { id: string; version: string; }; communication: { asu: number; dBm: number; percentage: number; }; timestamp: string; } ``` **Polling Strategy:** - Default 5 second interval - Configurable via settings - Pause when document.hidden - Use AbortController for cleanup task 1: Create Wave 0 test infrastructure vitest.config.ts, src/test/setup.ts, package.json - Test config extends Vite config - Setup file provides test utilities - Test command works: `npm test` 1. Create vitest.config.ts extending vite.config.ts: - Use @vitejs/plugin-react - Configure test environment as jsdom - Set setupFiles to src/test/setup.ts 2. Create src/test/setup.ts: - Import @testing-library/jest-dom matchers - Configure cleanup after each test - Add mock for window.matchMedia if needed 3. Update package.json scripts: - "test": "vitest run" - "test:watch": "vitest" - Add devDependencies: vitest, @testing-library/react, @testing-library/jest-dom, jsdom 4. Run `pnpm install` to add dependencies npm test -- --run 2>&1 | head -20 Test infrastructure ready, `npm test` runs successfully task 2: Create Zustand sensor store src/app/stores/sensorStore.ts, src/app/stores/__tests__/sensorStore.test.ts - Store initializes with default/mock data - setSensorData updates all fields - Store can be subscribed to - Selectors work for derived data 1. Install zustand: `pnpm add zustand` 2. Create src/app/stores/sensorStore.ts: - Define SensorData interface per context - Create store with initial state - Export useSensorStore hook - Add actions: setSensorData, updatePollingInterval - Include selector for last update timestamp 3. Create tests verifying: - Store initializes with defaults - setSensorData updates state - Subscribers receive updates npm test -- --run stores/sensorStore 2>&1 | grep -E "(PASS|FAIL|✓|✗)" Zustand store created with SensorData interface, tests pass task 3: Create API client with mock fallback src/app/api/client.ts, src/app/api/__tests__/client.test.ts - fetchSensorData returns SensorData - On API failure, returns mock data - Uses AbortController for cancellation - Logs errors but doesn't throw 1. Create src/app/api/client.ts: - Define API endpoint URL (configurable) - Create fetchSensorData function with AbortSignal - Create mockSensorData generator with realistic values - Implement fallback: try API → catch → return mock - Add request timeout (5s) 2. Create tests verifying: - Returns data on successful fetch - Returns mock on network error - Respects AbortController - Mock data has valid structure npm test -- --run api/client 2>&1 | grep -E "(PASS|FAIL|✓|✗)" API client with mock fallback created, tests pass task 4: Implement port-based mode detection src/app/App.tsx, src/app/hooks/useDisplayMode.ts - Port 8080 → returns 'kiosk' - Port 9090 → returns 'remote' - Other ports → defaults to 'kiosk' - Mode available via hook 1. Create src/app/hooks/useDisplayMode.ts: - Read window.location.port - Return 'kiosk' for 8080, 'remote' for 9090 - Default to 'kiosk' for other ports - Memoize result (port won't change) 2. Update src/app/App.tsx: - Import useDisplayMode hook - Pass mode to DashboardLayout - Initialize data polling on mount - Clean up polling on unmount (useEffect cleanup) - Add data-attr or class based on mode for CSS targeting grep -n "useDisplayMode\|window.location.port\|displayMode" src/app/App.tsx src/app/hooks/useDisplayMode.ts 2>/dev/null | head -20 Mode detection implemented, App.tsx uses display mode hook task 5: Set up data polling with cleanup src/app/App.tsx, src/app/hooks/useSensorPolling.ts - Polls every 5 seconds by default - Pauses when document.hidden - Resumes when visible again - Properly cleans up on unmount 1. Create src/app/hooks/useSensorPolling.ts: - Accept polling interval (ms) parameter, default 5000 - Use setInterval for polling - Listen for visibilitychange event - Pause interval when document.hidden - Resume when visible - Use AbortController per fetch - Return cleanup function 2. Update App.tsx: - Call useSensorPolling in component - Pass polling interval from settings (or default) - Ensure cleanup happens on unmount grep -n "useSensorPolling\|setInterval\|visibilitychange\|AbortController" src/app/App.tsx src/app/hooks/useSensorPolling.ts 2>/dev/null | head -20 Polling implemented with visibility awareness and cleanup After completing all tasks: 1. Run `npm test` — all tests should pass 2. Check store exists: `ls src/app/stores/sensorStore.ts` 3. Check API client exists: `ls src/app/api/client.ts` 4. Check mode detection: `grep "useDisplayMode" src/app/App.tsx` 5. Verify no console errors on build: `npm run build 2>&1 | grep -i error || echo "Build clean"` - Zustand store with SensorData interface exists and is tested - API client with mock fallback exists and is tested - Port-based mode detection works (8080=kiosk, 9090=remote) - Data polling with cleanup and visibility awareness implemented - Test infrastructure ready (`npm test` runs) - All files committed After completion, create `.planning/phases/01-foundation-dashboard/01-01-SUMMARY.md`