Files
sp80/.planning/phases/01-foundation-dashboard/01-01-PLAN.md

8.9 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
01-foundation-dashboard 01 execute 1
src/app/stores/sensorStore.ts
src/app/api/client.ts
src/app/App.tsx
vitest.config.ts
src/test/setup.ts
package.json
true
DASH-01
DASH-02
DASH-03
DASH-04
DASH-05
DASH-06
DASH-07
UI-04
truths artifacts key_links
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
path provides exports
src/app/stores/sensorStore.ts Zustand store for sensor data useSensorStore, SensorState
path provides exports
src/app/api/client.ts API client with mock fallback fetchSensorData, mockSensorData
path provides min_lines
src/app/App.tsx Mode detection and app root 30
path provides
vitest.config.ts Test configuration
from to via
sensorStore client.ts fetchSensorData updates store
from to via
App.tsx sensorStore 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.

<execution_context> @./.opencode/get-shit-done/workflows/execute-plan.md @./.opencode/get-shit-done/templates/summary.md </execution_context>

@.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:

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"`

<success_criteria>

  • 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 </success_criteria>
After completion, create `.planning/phases/01-foundation-dashboard/01-01-SUMMARY.md`