287 lines
8.9 KiB
Markdown
287 lines
8.9 KiB
Markdown
---
|
|
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
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@./.opencode/get-shit-done/workflows/execute-plan.md
|
|
@./.opencode/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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:**
|
|
```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
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>task 1: Create Wave 0 test infrastructure</name>
|
|
<files>vitest.config.ts, src/test/setup.ts, package.json</files>
|
|
<behavior>
|
|
- Test config extends Vite config
|
|
- Setup file provides test utilities
|
|
- Test command works: `npm test`
|
|
</behavior>
|
|
<action>
|
|
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
|
|
</action>
|
|
<verify>
|
|
<automated>npm test -- --run 2>&1 | head -20</automated>
|
|
</verify>
|
|
<done>Test infrastructure ready, `npm test` runs successfully</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>task 2: Create Zustand sensor store</name>
|
|
<files>src/app/stores/sensorStore.ts, src/app/stores/__tests__/sensorStore.test.ts</files>
|
|
<behavior>
|
|
- Store initializes with default/mock data
|
|
- setSensorData updates all fields
|
|
- Store can be subscribed to
|
|
- Selectors work for derived data
|
|
</behavior>
|
|
<action>
|
|
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
|
|
</action>
|
|
<verify>
|
|
<automated>npm test -- --run stores/sensorStore 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
|
</verify>
|
|
<done>Zustand store created with SensorData interface, tests pass</done>
|
|
</task>
|
|
|
|
<task type="auto" tdd="true">
|
|
<name>task 3: Create API client with mock fallback</name>
|
|
<files>src/app/api/client.ts, src/app/api/__tests__/client.test.ts</files>
|
|
<behavior>
|
|
- fetchSensorData returns SensorData
|
|
- On API failure, returns mock data
|
|
- Uses AbortController for cancellation
|
|
- Logs errors but doesn't throw
|
|
</behavior>
|
|
<action>
|
|
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
|
|
</action>
|
|
<verify>
|
|
<automated>npm test -- --run api/client 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
|
</verify>
|
|
<done>API client with mock fallback created, tests pass</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>task 4: Implement port-based mode detection</name>
|
|
<files>src/app/App.tsx, src/app/hooks/useDisplayMode.ts</files>
|
|
<behavior>
|
|
- Port 8080 → returns 'kiosk'
|
|
- Port 9090 → returns 'remote'
|
|
- Other ports → defaults to 'kiosk'
|
|
- Mode available via hook
|
|
</behavior>
|
|
<action>
|
|
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
|
|
</action>
|
|
<verify>
|
|
<automated>grep -n "useDisplayMode\|window.location.port\|displayMode" src/app/App.tsx src/app/hooks/useDisplayMode.ts 2>/dev/null | head -20</automated>
|
|
</verify>
|
|
<done>Mode detection implemented, App.tsx uses display mode hook</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>task 5: Set up data polling with cleanup</name>
|
|
<files>src/app/App.tsx, src/app/hooks/useSensorPolling.ts</files>
|
|
<behavior>
|
|
- Polls every 5 seconds by default
|
|
- Pauses when document.hidden
|
|
- Resumes when visible again
|
|
- Properly cleans up on unmount
|
|
</behavior>
|
|
<action>
|
|
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
|
|
</action>
|
|
<verify>
|
|
<automated>grep -n "useSensorPolling\|setInterval\|visibilitychange\|AbortController" src/app/App.tsx src/app/hooks/useSensorPolling.ts 2>/dev/null | head -20</automated>
|
|
</verify>
|
|
<done>Polling implemented with visibility awareness and cleanup</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
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"`
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/01-foundation-dashboard/01-01-SUMMARY.md`
|
|
</output>
|