docs(01-foundation-dashboard): create phase 1 plans
This commit is contained in:
286
.planning/phases/01-foundation-dashboard/01-01-PLAN.md
Normal file
286
.planning/phases/01-foundation-dashboard/01-01-PLAN.md
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
---
|
||||||
|
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>
|
||||||
271
.planning/phases/01-foundation-dashboard/01-02-PLAN.md
Normal file
271
.planning/phases/01-foundation-dashboard/01-02-PLAN.md
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
---
|
||||||
|
phase: 01-foundation-dashboard
|
||||||
|
plan: 02
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on:
|
||||||
|
- 01-01
|
||||||
|
files_modified:
|
||||||
|
- src/app/components/Header.tsx
|
||||||
|
- src/app/components/VoltageDisplay.tsx
|
||||||
|
- src/app/components/BatteryStatus.tsx
|
||||||
|
- src/app/components/__tests__/Header.test.tsx
|
||||||
|
- src/app/components/__tests__/VoltageDisplay.test.tsx
|
||||||
|
- src/app/components/__tests__/BatteryStatus.test.tsx
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- DASH-02
|
||||||
|
- DASH-03
|
||||||
|
- DASH-04
|
||||||
|
- DASH-07
|
||||||
|
- UI-01
|
||||||
|
- UI-02
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- Header displays all status information
|
||||||
|
- Voltage displays show solar and battery readings
|
||||||
|
- Battery status shows HIGH/LOW indicator with color
|
||||||
|
- All touch targets are minimum 44px
|
||||||
|
- Header is compact for 1024x600 display
|
||||||
|
artifacts:
|
||||||
|
- path: src/app/components/Header.tsx
|
||||||
|
provides: Main header with status info
|
||||||
|
exports: Header
|
||||||
|
min_lines: 80
|
||||||
|
- path: src/app/components/VoltageDisplay.tsx
|
||||||
|
provides: Solar/battery voltage display
|
||||||
|
exports: VoltageDisplay
|
||||||
|
- path: src/app/components/BatteryStatus.tsx
|
||||||
|
provides: Battery status indicator
|
||||||
|
exports: BatteryStatus
|
||||||
|
key_links:
|
||||||
|
- from: Header
|
||||||
|
to: sensorStore
|
||||||
|
via: useSensorStore hook
|
||||||
|
- from: BatteryStatus
|
||||||
|
to: VoltageDisplay
|
||||||
|
via: Status color based on voltage level
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Create the Header component and voltage monitoring components with modern styling, touch-friendly sizing, and status indicators.
|
||||||
|
|
||||||
|
Purpose: The header is the primary information display area, showing station status, voltage, and login state. These components are used across all views.
|
||||||
|
|
||||||
|
Output: Header.tsx, VoltageDisplay.tsx, BatteryStatus.tsx components with tests, all meeting touch target requirements.
|
||||||
|
</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-01-SUMMARY.md
|
||||||
|
@sample_interface/src/app/components/Header.tsx
|
||||||
|
|
||||||
|
## Key Implementation Details
|
||||||
|
|
||||||
|
**Header Layout (compact for 1024x600):**
|
||||||
|
```
|
||||||
|
[TCK Logo] [Station ID] [Version] [Solar: XX.XV] [Battery: XX.XV] [Status]
|
||||||
|
[HH:MM:SS] [YYYY-MM-DD] [ASU: XX] [Login: ●]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Voltage Display:**
|
||||||
|
- Format: XX.XV (one decimal place)
|
||||||
|
- Solar voltage: Always shown
|
||||||
|
- Battery voltage: With status indicator
|
||||||
|
|
||||||
|
**Battery Status:**
|
||||||
|
- HIGH (≥12.0V): Green indicator
|
||||||
|
- LOW (<12.0V): Red/orange indicator
|
||||||
|
- Threshold: 12.0V configurable
|
||||||
|
|
||||||
|
**Touch Targets:**
|
||||||
|
- All interactive elements: minimum 44px height
|
||||||
|
- Prefer 56px for primary actions
|
||||||
|
- Use padding not margin for hit areas
|
||||||
|
|
||||||
|
**Design Tokens:**
|
||||||
|
- Use existing Tailwind classes
|
||||||
|
- shadcn/ui Badge for status indicators
|
||||||
|
- Lucide icons for visual elements
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>task 1: Create VoltageDisplay component</name>
|
||||||
|
<files>src/app/components/VoltageDisplay.tsx, src/app/components/__tests__/VoltageDisplay.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Displays voltage with one decimal place
|
||||||
|
- Shows label (Solar/Battery)
|
||||||
|
- Updates when sensor data changes
|
||||||
|
- Touch-friendly container (44px min)
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Create src/app/components/VoltageDisplay.tsx:
|
||||||
|
- Props: label (string), voltage (number), className (optional)
|
||||||
|
- Format voltage to XX.XV
|
||||||
|
- Use flex layout with gap-2
|
||||||
|
- Min-height 44px for touch
|
||||||
|
- Use text-sm for compact display
|
||||||
|
- Add voltage icon from Lucide (Zap or Battery)
|
||||||
|
|
||||||
|
2. Create tests:
|
||||||
|
- Renders label and formatted voltage
|
||||||
|
- Updates on prop change
|
||||||
|
- Has minimum touch target size
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test -- --run VoltageDisplay 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>VoltageDisplay component created with tests passing</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>task 2: Create BatteryStatus component</name>
|
||||||
|
<files>src/app/components/BatteryStatus.tsx, src/app/components/__tests__/BatteryStatus.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Shows battery voltage with status badge
|
||||||
|
- HIGH status (≥12V): green badge
|
||||||
|
- LOW status (<12V): red/orange badge
|
||||||
|
- Status text visible: HIGH or LOW
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Create src/app/components/BatteryStatus.tsx:
|
||||||
|
- Props: voltage (number), threshold (optional, default 12.0)
|
||||||
|
- Calculate status based on threshold
|
||||||
|
- Use shadcn/ui Badge component
|
||||||
|
- Green variant for HIGH, destructive for LOW
|
||||||
|
- Display "XX.XV - HIGH/LOW" format
|
||||||
|
- Touch-friendly (44px min)
|
||||||
|
|
||||||
|
2. Create tests:
|
||||||
|
- Shows HIGH for voltage ≥12.0
|
||||||
|
- Shows LOW for voltage <12.0
|
||||||
|
- Uses custom threshold if provided
|
||||||
|
- Updates when voltage changes
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test -- --run BatteryStatus 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>BatteryStatus component created with color-coded indicators</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>task 3: Modernize Header component</name>
|
||||||
|
<files>src/app/components/Header.tsx, src/app/components/__tests__/Header.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Displays TCK logo from /logo/
|
||||||
|
- Shows station ID and version
|
||||||
|
- Shows current time (HH:MM:SS) updating every second
|
||||||
|
- Shows current date (YYYY-MM-DD)
|
||||||
|
- Shows solar voltage
|
||||||
|
- Shows battery voltage with status
|
||||||
|
- Shows communication ASU
|
||||||
|
- Shows login status indicator
|
||||||
|
- All in compact layout for 1024x600
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Update/modernize src/app/components/Header.tsx:
|
||||||
|
- Import useSensorStore from stores
|
||||||
|
- Import VoltageDisplay and BatteryStatus
|
||||||
|
- Use useEffect for clock updates (1s interval)
|
||||||
|
- Layout: Logo | Station Info | Time/Date | Voltages | Comm | Login
|
||||||
|
- Use flexbox with justify-between
|
||||||
|
- Compact padding (py-2 px-4)
|
||||||
|
- Use text-sm throughout
|
||||||
|
- Login indicator: green dot when logged in
|
||||||
|
- Cleanup clock interval on unmount
|
||||||
|
|
||||||
|
2. Create tests:
|
||||||
|
- Renders all required elements
|
||||||
|
- Time updates every second
|
||||||
|
- Uses sensor store data
|
||||||
|
- Has proper structure
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test -- --run Header 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Header component displays all status info, tests pass</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>task 4: Verify touch target sizing</name>
|
||||||
|
<files>src/app/components/Header.tsx, src/app/components/VoltageDisplay.tsx, src/app/components/BatteryStatus.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- All interactive elements ≥44px
|
||||||
|
- Verified via CSS inspection
|
||||||
|
- Document any exceptions
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Review all components for touch target compliance:
|
||||||
|
- Check min-height on containers
|
||||||
|
- Check padding on clickable elements
|
||||||
|
- Ensure no margins-only hit areas
|
||||||
|
|
||||||
|
2. Add explicit classes where needed:
|
||||||
|
- `min-h-[44px]` for touch targets
|
||||||
|
- `flex items-center` for vertical centering
|
||||||
|
|
||||||
|
3. Run visual verification:
|
||||||
|
- DevTools element picker
|
||||||
|
- Verify minimum dimensions
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>grep -n "min-h-\[44px\]\|min-h-11\|h-11\|h-\[44px\]" src/app/components/Header.tsx src/app/components/VoltageDisplay.tsx src/app/components/BatteryStatus.tsx 2>/dev/null | wc -l</automated>
|
||||||
|
</verify>
|
||||||
|
<done>All components have minimum 44px touch targets</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>task 5: Update DashboardLayout to use new Header</name>
|
||||||
|
<files>src/app/components/DashboardLayout.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- DashboardLayout imports and renders new Header
|
||||||
|
- Layout works with Sidebar
|
||||||
|
- Content area scrollable
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Update src/app/components/DashboardLayout.tsx:
|
||||||
|
- Import new Header component
|
||||||
|
- Place Header above main content area
|
||||||
|
- Ensure flex layout works correctly
|
||||||
|
- Test with both kiosk and remote modes
|
||||||
|
|
||||||
|
2. Verify integration:
|
||||||
|
- Header displays correctly
|
||||||
|
- Sidebar navigation still works
|
||||||
|
- Main content has proper padding
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>grep -n "import.*Header\|<Header" src/app/components/DashboardLayout.tsx 2>/dev/null | head -5</automated>
|
||||||
|
</verify>
|
||||||
|
<done>DashboardLayout integrates new Header component</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
After completing all tasks:
|
||||||
|
1. Run `npm test` — all tests should pass
|
||||||
|
2. Verify components exist: `ls src/app/components/Header.tsx src/app/components/VoltageDisplay.tsx src/app/components/BatteryStatus.tsx`
|
||||||
|
3. Check touch targets: `grep -r "min-h-" src/app/components/*.tsx | grep -E "(Header|Voltage|Battery)"`
|
||||||
|
4. Verify Header integration: `grep "<Header" src/app/components/DashboardLayout.tsx`
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- VoltageDisplay component shows formatted voltage (XX.XV)
|
||||||
|
- BatteryStatus component shows color-coded HIGH/LOW status
|
||||||
|
- Header displays all required info: Logo, Station ID, Version, Time, Date, Solar, Battery, Comm, Login
|
||||||
|
- All touch targets minimum 44px
|
||||||
|
- Components tested and passing
|
||||||
|
- DashboardLayout uses new Header
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/01-foundation-dashboard/01-02-SUMMARY.md`
|
||||||
|
</output>
|
||||||
287
.planning/phases/01-foundation-dashboard/01-03-PLAN.md
Normal file
287
.planning/phases/01-foundation-dashboard/01-03-PLAN.md
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
---
|
||||||
|
phase: 01-foundation-dashboard
|
||||||
|
plan: 03
|
||||||
|
type: execute
|
||||||
|
wave: 2
|
||||||
|
depends_on:
|
||||||
|
- 01-01
|
||||||
|
- 01-02
|
||||||
|
files_modified:
|
||||||
|
- src/app/components/views/RainfallView.tsx
|
||||||
|
- src/app/components/RainfallCard.tsx
|
||||||
|
- src/app/components/StationInfo.tsx
|
||||||
|
- src/app/components/ClockDisplay.tsx
|
||||||
|
- src/app/components/CommStatus.tsx
|
||||||
|
- src/app/components/__tests__/RainfallView.test.tsx
|
||||||
|
- src/app/components/__tests__/RainfallCard.test.tsx
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- DASH-01
|
||||||
|
- DASH-05
|
||||||
|
- DASH-06
|
||||||
|
- DASH-04
|
||||||
|
- UI-01
|
||||||
|
- UI-02
|
||||||
|
- UI-03
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- Dashboard shows 4 rainfall cards (Today/Hourly/MAR/Yearly)
|
||||||
|
- Cards update in real-time from sensor store
|
||||||
|
- Clock displays HH:MM:SS with seconds
|
||||||
|
- Comm status shows ASU/dBm/percentage
|
||||||
|
- Layout adapts to kiosk (1024x600) and remote (Full HD) modes
|
||||||
|
artifacts:
|
||||||
|
- path: src/app/components/views/RainfallView.tsx
|
||||||
|
provides: Main dashboard view
|
||||||
|
exports: RainfallView
|
||||||
|
min_lines: 60
|
||||||
|
- path: src/app/components/RainfallCard.tsx
|
||||||
|
provides: Individual rainfall metric card
|
||||||
|
exports: RainfallCard
|
||||||
|
- path: src/app/components/ClockDisplay.tsx
|
||||||
|
provides: Real-time clock component
|
||||||
|
exports: ClockDisplay
|
||||||
|
key_links:
|
||||||
|
- from: RainfallView
|
||||||
|
to: sensorStore
|
||||||
|
via: useSensorStore hook
|
||||||
|
- from: RainfallCard
|
||||||
|
to: RainfallView
|
||||||
|
via: Props passing rainfall data
|
||||||
|
- from: ClockDisplay
|
||||||
|
to: sensorStore
|
||||||
|
via: Optional sync with store timestamp
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Create the main dashboard view with rainfall cards, clock display, communication status, and dual-mode responsive layout.
|
||||||
|
|
||||||
|
Purpose: This is the primary user interface where operators view rainfall data and station status. Must work on both 7" touchscreen (1024x600) and Full HD remote displays.
|
||||||
|
|
||||||
|
Output: RainfallView as main dashboard, RainfallCard components, ClockDisplay, and CommStatus with responsive layouts.
|
||||||
|
</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-01-SUMMARY.md
|
||||||
|
@.planning/phases/01-foundation-dashboard/01-02-SUMMARY.md
|
||||||
|
@sample_interface/src/app/components/views/RainfallView.tsx
|
||||||
|
|
||||||
|
## Key Implementation Details
|
||||||
|
|
||||||
|
**Rainfall Metrics (4 cards):**
|
||||||
|
- Today: Current day's rainfall in mm
|
||||||
|
- Hourly: Current hour's rainfall in mm
|
||||||
|
- MAR Acc: Monthly accumulation in mm
|
||||||
|
- Yearly Acc: Yearly accumulation in mm
|
||||||
|
|
||||||
|
**Card Design:**
|
||||||
|
- Color-coded: Today (blue), Hourly (cyan), MAR (green), Yearly (purple)
|
||||||
|
- Large value display: XX.X mm
|
||||||
|
- Label below value
|
||||||
|
- Compact for grid layout
|
||||||
|
- Touch-friendly (44px+ tap area)
|
||||||
|
|
||||||
|
**Grid Layout:**
|
||||||
|
- Kiosk (1024x600): 2x2 grid, compact cards
|
||||||
|
- Remote (Full HD): 4x1 or 2x2 with larger cards
|
||||||
|
- Use CSS Grid with responsive breakpoints
|
||||||
|
|
||||||
|
**Clock Display:**
|
||||||
|
- Format: HH:MM:SS (24-hour)
|
||||||
|
- Updates every second
|
||||||
|
- Also shown in Header (this is secondary/alternative)
|
||||||
|
|
||||||
|
**Comm Status:**
|
||||||
|
- ASU: Signal strength (0-31)
|
||||||
|
- dBm: Signal power
|
||||||
|
- Percentage: Derived from ASU
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>task 1: Create RainfallCard component</name>
|
||||||
|
<files>src/app/components/RainfallCard.tsx, src/app/components/__tests__/RainfallCard.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Displays rainfall value in mm (XX.X format)
|
||||||
|
- Shows label (Today/Hourly/MAR Acc/Yearly Acc)
|
||||||
|
- Color-coded border or background
|
||||||
|
- Large readable text
|
||||||
|
- Updates when value changes
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Create src/app/components/RainfallCard.tsx:
|
||||||
|
- Props: label (string), value (number), variant (today|hourly|monthly|yearly)
|
||||||
|
- Format value to one decimal place
|
||||||
|
- Color variants: today=blue, hourly=cyan, monthly=green, yearly=purple
|
||||||
|
- Use shadcn/ui Card component as base
|
||||||
|
- Min-height 100px for touch targets
|
||||||
|
- Value text: text-3xl font-bold
|
||||||
|
- Label text: text-sm text-muted-foreground
|
||||||
|
- Add rain icon from Lucide (CloudRain)
|
||||||
|
|
||||||
|
2. Create tests:
|
||||||
|
- Renders label and formatted value
|
||||||
|
- Applies correct color variant
|
||||||
|
- Updates on value change
|
||||||
|
- Has minimum dimensions
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test -- --run RainfallCard 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>RainfallCard component with 4 color variants created</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>task 2: Create ClockDisplay component</name>
|
||||||
|
<files>src/app/components/ClockDisplay.tsx, src/app/components/__tests__/ClockDisplay.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Displays HH:MM:SS in 24-hour format
|
||||||
|
- Updates every second
|
||||||
|
- Shows date (YYYY-MM-DD) below or beside
|
||||||
|
- Cleans up interval on unmount
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Create src/app/components/ClockDisplay.tsx:
|
||||||
|
- Use useState for current time
|
||||||
|
- Use useEffect with setInterval (1000ms)
|
||||||
|
- Format with date-fns or native Intl
|
||||||
|
- Display: Time large, date smaller
|
||||||
|
- Optional: Sync with sensor store timestamp
|
||||||
|
- Cleanup interval in useEffect return
|
||||||
|
- Touch-friendly container
|
||||||
|
|
||||||
|
2. Create tests:
|
||||||
|
- Renders time in correct format
|
||||||
|
- Renders date in correct format
|
||||||
|
- Cleanup works on unmount (no memory leaks)
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test -- --run ClockDisplay 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>ClockDisplay with real-time updates created</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>task 3: Create CommStatus component</name>
|
||||||
|
<files>src/app/components/CommStatus.tsx, src/app/components/__tests__/CommStatus.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Shows ASU (0-31), dBm, percentage
|
||||||
|
- Visual indicator for signal strength
|
||||||
|
- Updates from sensor store
|
||||||
|
- Compact for header placement
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Create src/app/components/CommStatus.tsx:
|
||||||
|
- Props: asu (number), dBm (number), percentage (number)
|
||||||
|
- Calculate percentage from ASU if not provided: (asu/31)*100
|
||||||
|
- Visual indicator: signal bars or progress
|
||||||
|
- Color: green (good), yellow (fair), red (poor)
|
||||||
|
- Compact display: icon + brief text
|
||||||
|
- Use Lucide Signal icon
|
||||||
|
|
||||||
|
2. Create tests:
|
||||||
|
- Displays all three values
|
||||||
|
- Calculates percentage correctly
|
||||||
|
- Shows appropriate color indicator
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test -- --run CommStatus 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>CommStatus component with signal visualization</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>task 4: Create RainfallView dashboard</name>
|
||||||
|
<files>src/app/components/views/RainfallView.tsx, src/app/components/__tests__/RainfallView.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Displays 4 RainfallCards in grid
|
||||||
|
- Shows optional ClockDisplay
|
||||||
|
- Shows CommStatus
|
||||||
|
- Uses sensor store data
|
||||||
|
- Responsive layout for both modes
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Create/update src/app/components/views/RainfallView.tsx:
|
||||||
|
- Import useSensorStore
|
||||||
|
- Import RainfallCard, ClockDisplay, CommStatus
|
||||||
|
- Get rainfall data from store
|
||||||
|
- Grid layout: 2 cols on mobile/kiosk, 4 cols on desktop/remote
|
||||||
|
- Use CSS Grid: grid-cols-2 lg:grid-cols-4
|
||||||
|
- Gap: gap-4
|
||||||
|
- Padding: p-4
|
||||||
|
- Add section title: "Rainfall Monitor"
|
||||||
|
- Show last update timestamp
|
||||||
|
- Responsive font sizes
|
||||||
|
|
||||||
|
2. Create tests:
|
||||||
|
- Renders all 4 rainfall cards
|
||||||
|
- Passes correct data to each card
|
||||||
|
- Has responsive grid layout
|
||||||
|
- Updates when store changes
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test -- --run RainfallView 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>RainfallView dashboard with responsive grid created</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>task 5: Implement dual-mode responsive layout</name>
|
||||||
|
<files>src/app/components/views/RainfallView.tsx, src/styles/responsive.css</files>
|
||||||
|
<behavior>
|
||||||
|
- Port 8080: Compact layout for 1024x600
|
||||||
|
- Port 9090: Expanded layout for Full HD
|
||||||
|
- Cards resize appropriately
|
||||||
|
- Font sizes scale with mode
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Update RainfallView with mode-aware styling:
|
||||||
|
- Import useDisplayMode from hooks
|
||||||
|
- Apply conditional classes based on mode
|
||||||
|
- Kiosk: More compact padding, smaller gaps
|
||||||
|
- Remote: Larger spacing, bigger fonts
|
||||||
|
|
||||||
|
2. Create src/styles/responsive.css if needed:
|
||||||
|
- Define mode-specific utilities
|
||||||
|
- Or use Tailwind classes inline
|
||||||
|
|
||||||
|
3. Test responsive behavior:
|
||||||
|
- Verify grid adapts to viewport
|
||||||
|
- Cards maintain touch target size
|
||||||
|
- No horizontal scroll at 1024px
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>grep -n "useDisplayMode\|kiosk\|remote" src/app/components/views/RainfallView.tsx 2>/dev/null | head -10</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Dual-mode layout responds to port number</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
After completing all tasks:
|
||||||
|
1. Run `npm test` — all tests should pass
|
||||||
|
2. Verify dashboard exists: `ls src/app/components/views/RainfallView.tsx`
|
||||||
|
3. Check responsive classes: `grep -E "grid-cols-|lg:grid-cols" src/app/components/views/RainfallView.tsx`
|
||||||
|
4. Verify sensor store connection: `grep "useSensorStore" src/app/components/views/RainfallView.tsx`
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- RainfallCard component with 4 color variants exists
|
||||||
|
- RainfallView displays 4 cards in responsive grid
|
||||||
|
- ClockDisplay shows HH:MM:SS updating every second
|
||||||
|
- CommStatus shows ASU/dBm with visual indicator
|
||||||
|
- Layout adapts to kiosk (1024x600) and remote (Full HD) modes
|
||||||
|
- All components tested and passing
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/01-foundation-dashboard/01-03-SUMMARY.md`
|
||||||
|
</output>
|
||||||
357
.planning/phases/01-foundation-dashboard/01-04-PLAN.md
Normal file
357
.planning/phases/01-foundation-dashboard/01-04-PLAN.md
Normal file
@@ -0,0 +1,357 @@
|
|||||||
|
---
|
||||||
|
phase: 01-foundation-dashboard
|
||||||
|
plan: 04
|
||||||
|
type: execute
|
||||||
|
wave: 3
|
||||||
|
depends_on:
|
||||||
|
- 01-01
|
||||||
|
- 01-02
|
||||||
|
- 01-03
|
||||||
|
files_modified:
|
||||||
|
- src/app/components/Sidebar.tsx
|
||||||
|
- src/app/components/LoginIndicator.tsx
|
||||||
|
- src/app/components/Navigation.tsx
|
||||||
|
- src/app/routes.ts
|
||||||
|
- vite.config.ts
|
||||||
|
- .bundlesize.json
|
||||||
|
- src/app/components/__tests__/Sidebar.test.tsx
|
||||||
|
- src/app/components/__tests__/LoginIndicator.test.tsx
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- DASH-07
|
||||||
|
- UI-02
|
||||||
|
- UI-03
|
||||||
|
- UI-04
|
||||||
|
- UI-05
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- Sidebar navigation has Settings, Calibration, Flash Memory links
|
||||||
|
- Login indicator shows logged-in state
|
||||||
|
- Bundle size is under 170KB (target) / 200KB (limit)
|
||||||
|
- All routes defined and working
|
||||||
|
- Touch-friendly navigation (44px+ targets)
|
||||||
|
artifacts:
|
||||||
|
- path: src/app/components/Sidebar.tsx
|
||||||
|
provides: Navigation sidebar
|
||||||
|
exports: Sidebar
|
||||||
|
min_lines: 50
|
||||||
|
- path: src/app/components/LoginIndicator.tsx
|
||||||
|
provides: Login status indicator
|
||||||
|
exports: LoginIndicator
|
||||||
|
- path: src/app/routes.ts
|
||||||
|
provides: Route definitions
|
||||||
|
exports: router
|
||||||
|
- path: .bundlesize.json
|
||||||
|
provides: Bundle size budget config
|
||||||
|
key_links:
|
||||||
|
- from: Sidebar
|
||||||
|
to: routes.ts
|
||||||
|
via: Link components
|
||||||
|
- from: LoginIndicator
|
||||||
|
to: Header
|
||||||
|
via: Header composition
|
||||||
|
- from: routes.ts
|
||||||
|
to: RainfallView
|
||||||
|
via: Route configuration
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Complete the dashboard with navigation, login indicator, route configuration, and verify performance budget (<170KB bundle size).
|
||||||
|
|
||||||
|
Purpose: Navigation provides access to other sections (Settings, Calibration, Flash Memory). Login indicator shows authentication status. Performance verification ensures Pi Zero 2 W compatibility.
|
||||||
|
|
||||||
|
Output: Working navigation, login indicator, routes configured, bundle size verified under budget.
|
||||||
|
</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
|
||||||
|
@sample_interface/src/app/components/Sidebar.tsx
|
||||||
|
@sample_interface/src/app/routes.ts
|
||||||
|
|
||||||
|
## Key Implementation Details
|
||||||
|
|
||||||
|
**Navigation Items:**
|
||||||
|
- Dashboard (home)
|
||||||
|
- Settings (with sub-items)
|
||||||
|
- Calibration
|
||||||
|
- Flash Memory
|
||||||
|
|
||||||
|
**Sidebar Design:**
|
||||||
|
- Fixed width (64px icons-only or 240px expanded)
|
||||||
|
- Touch-friendly items (44px+ height)
|
||||||
|
- Active state highlighting
|
||||||
|
- Collapsible on mobile
|
||||||
|
|
||||||
|
**Login Indicator:**
|
||||||
|
- Green dot/icon when logged in
|
||||||
|
- Gray/red when logged out
|
||||||
|
- Text label: "Logged In" / "Logged Out"
|
||||||
|
- Small and compact for header
|
||||||
|
|
||||||
|
**Routes:**
|
||||||
|
- `/` → Dashboard (RainfallView)
|
||||||
|
- `/utility/*` → Settings (placeholder for Phase 2)
|
||||||
|
- `/calibration` → Calibration (placeholder)
|
||||||
|
- `/flash-memory` → Flash Memory (placeholder)
|
||||||
|
|
||||||
|
**Bundle Size Budget:**
|
||||||
|
- Target: <170KB gzipped initial JS
|
||||||
|
- Limit: <200KB gzipped
|
||||||
|
- Tools: bundlesize, rollup-plugin-analyzer
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>task 1: Create LoginIndicator component</name>
|
||||||
|
<files>src/app/components/LoginIndicator.tsx, src/app/components/__tests__/LoginIndicator.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Shows green status when isLoggedIn=true
|
||||||
|
- Shows gray/red status when isLoggedIn=false
|
||||||
|
- Compact size for header placement
|
||||||
|
- Accessible (aria-label)
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Create src/app/components/LoginIndicator.tsx:
|
||||||
|
- Props: isLoggedIn (boolean), className (optional)
|
||||||
|
- Use shadcn/ui Badge or custom indicator
|
||||||
|
- Logged in: Green dot + "Logged In" text
|
||||||
|
- Logged out: Gray dot + "Login" text
|
||||||
|
- Compact: h-6, text-xs
|
||||||
|
- Use Lucide User icon
|
||||||
|
- Add aria-label for accessibility
|
||||||
|
|
||||||
|
2. Create tests:
|
||||||
|
- Shows correct state for logged in/out
|
||||||
|
- Has accessible label
|
||||||
|
- Accepts custom className
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test -- --run LoginIndicator 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>LoginIndicator with accessible states created</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto" tdd="true">
|
||||||
|
<name>task 2: Update Sidebar navigation</name>
|
||||||
|
<files>src/app/components/Sidebar.tsx, src/app/components/__tests__/Sidebar.test.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Shows Dashboard, Settings, Calibration, Flash Memory links
|
||||||
|
- Touch-friendly items (44px+ height)
|
||||||
|
- Active state for current route
|
||||||
|
- Collapsible sections
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Update src/app/components/Sidebar.tsx:
|
||||||
|
- Import NavLink from react-router
|
||||||
|
- Define navigation items array:
|
||||||
|
* Dashboard (Home icon) → /
|
||||||
|
* Settings (Settings icon) → /utility
|
||||||
|
* Calibration (Gauge icon) → /calibration
|
||||||
|
* Flash Memory (HardDrive icon) → /flash-memory
|
||||||
|
- Map items to NavLink components
|
||||||
|
- Item height: min-h-[44px]
|
||||||
|
- Active state: bg-accent class
|
||||||
|
- Icon + label layout
|
||||||
|
- Collapsible on mobile with hamburger
|
||||||
|
|
||||||
|
2. Create tests:
|
||||||
|
- Renders all navigation items
|
||||||
|
- Items have correct links
|
||||||
|
- Touch target size adequate
|
||||||
|
- Active state applied correctly
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test -- --run Sidebar 2>&1 | grep -E "(PASS|FAIL|✓|✗)"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Sidebar with 4 navigation items created</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>task 3: Configure routes for all views</name>
|
||||||
|
<files>src/app/routes.ts, src/app/components/views/SettingsView.tsx, src/app/components/views/CalibrationView.tsx, src/app/components/views/FlashMemoryView.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- All routes defined in routes.ts
|
||||||
|
- Settings, Calibration, Flash Memory have placeholder views
|
||||||
|
- Routes use lazy loading where appropriate
|
||||||
|
- Navigation between routes works
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Create placeholder views:
|
||||||
|
- src/app/components/views/SettingsView.tsx: "Settings - Phase 2" placeholder
|
||||||
|
- src/app/components/views/CalibrationView.tsx: "Calibration - Phase 3" placeholder
|
||||||
|
- src/app/components/views/FlashMemoryView.tsx: "Flash Memory - Phase 3" placeholder
|
||||||
|
|
||||||
|
2. Update src/app/routes.ts:
|
||||||
|
- Import all views
|
||||||
|
- Define routes array:
|
||||||
|
* path: "/", element: RainfallView
|
||||||
|
* path: "/utility/*", element: SettingsView
|
||||||
|
* path: "/calibration", element: CalibrationView
|
||||||
|
* path: "/flash-memory", element: FlashMemoryView
|
||||||
|
- Use createBrowserRouter
|
||||||
|
- Add error boundary for 404s
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>grep -n "SettingsView\|CalibrationView\|FlashMemoryView" src/app/routes.ts 2>/dev/null | head -10</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Routes configured for all 4 main sections</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>task 4: Integrate LoginIndicator into Header</name>
|
||||||
|
<files>src/app/components/Header.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Header displays LoginIndicator
|
||||||
|
- Gets login state from sensor store
|
||||||
|
- Compact placement in header row
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Update src/app/components/Header.tsx:
|
||||||
|
- Import LoginIndicator
|
||||||
|
- Get isLoggedIn from sensor store (or mock for now)
|
||||||
|
- Place LoginIndicator in header layout
|
||||||
|
- Ensure proper spacing and alignment
|
||||||
|
|
||||||
|
2. Test integration:
|
||||||
|
- Login indicator visible in header
|
||||||
|
- Updates when login state changes
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>grep -n "LoginIndicator" src/app/components/Header.tsx 2>/dev/null | head -5</automated>
|
||||||
|
</verify>
|
||||||
|
<done>LoginIndicator integrated into Header</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>task 5: Configure bundle size checking</name>
|
||||||
|
<files>.bundlesize.json, vite.config.ts, package.json</files>
|
||||||
|
<behavior>
|
||||||
|
- Bundle size limit configured (170KB target, 200KB max)
|
||||||
|
- Build fails if bundle exceeds limit
|
||||||
|
- Size checking integrated in CI
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Create .bundlesize.json:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"files": [
|
||||||
|
{
|
||||||
|
"path": "dist/assets/*.js",
|
||||||
|
"maxSize": "200kb",
|
||||||
|
"compression": "gzip"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Update package.json scripts:
|
||||||
|
- "build:check": "npm run build && bundlesize"
|
||||||
|
- Add bundlesize as devDependency
|
||||||
|
|
||||||
|
3. Update vite.config.ts for optimization:
|
||||||
|
- Manual chunks for vendor code
|
||||||
|
- Drop console in production
|
||||||
|
- Minification settings
|
||||||
|
|
||||||
|
4. Install bundlesize: `pnpm add -D bundlesize`
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>test -f .bundlesize.json && echo "Config exists" || echo "Config missing"</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Bundle size checking configured</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>task 6: Build and verify bundle size</name>
|
||||||
|
<files>dist/</files>
|
||||||
|
<behavior>
|
||||||
|
- Production build succeeds
|
||||||
|
- Bundle size under 200KB limit
|
||||||
|
- Ideally under 170KB target
|
||||||
|
- No build errors or warnings
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Run production build:
|
||||||
|
- `npm run build`
|
||||||
|
- Check for errors
|
||||||
|
|
||||||
|
2. Analyze bundle size:
|
||||||
|
- `npm run build:check` or check dist/ folder
|
||||||
|
- Look at main JS file size
|
||||||
|
- Use rollup-plugin-visualizer if needed
|
||||||
|
|
||||||
|
3. If over budget, investigate:
|
||||||
|
- Run `npx vite-bundle-visualizer`
|
||||||
|
- Identify large dependencies
|
||||||
|
- Consider code splitting
|
||||||
|
|
||||||
|
4. Document actual size in SUMMARY
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>ls -lh dist/assets/*.js 2>/dev/null | awk '{print $5, $9}' | head -5</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Bundle built and size verified (target: <170KB, limit: <200KB)</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>task 7: Final integration test</name>
|
||||||
|
<files>src/app/App.tsx</files>
|
||||||
|
<behavior>
|
||||||
|
- Full app renders without errors
|
||||||
|
- Navigation works between routes
|
||||||
|
- Dashboard shows all data
|
||||||
|
- No console errors
|
||||||
|
</behavior>
|
||||||
|
<action>
|
||||||
|
1. Verify App.tsx integration:
|
||||||
|
- RouterProvider with routes
|
||||||
|
- DashboardLayout with Header and Sidebar
|
||||||
|
- Outlet for route content
|
||||||
|
|
||||||
|
2. Run all tests:
|
||||||
|
- `npm test` — should pass
|
||||||
|
|
||||||
|
3. Verify no TypeScript errors:
|
||||||
|
- `npx tsc --noEmit`
|
||||||
|
|
||||||
|
4. Check for console errors:
|
||||||
|
- Review any warnings in test output
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>npm test 2>&1 | tail -5</automated>
|
||||||
|
</verify>
|
||||||
|
<done>Full integration verified, all tests passing</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
After completing all tasks:
|
||||||
|
1. Run `npm test` — all tests should pass
|
||||||
|
2. Run `npm run build` — should succeed
|
||||||
|
3. Verify bundle size: Check dist/ folder, should be <200KB
|
||||||
|
4. Check routes: `grep -r "path:" src/app/routes.ts | wc -l` should be 4+
|
||||||
|
5. Verify navigation: `grep -r "NavLink\|Link" src/app/components/Sidebar.tsx`
|
||||||
|
6. Check LoginIndicator in Header: `grep "LoginIndicator" src/app/components/Header.tsx`
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- LoginIndicator component shows logged-in state
|
||||||
|
- Sidebar has navigation to Settings, Calibration, Flash Memory
|
||||||
|
- All routes configured and working
|
||||||
|
- Bundle size under 200KB (target <170KB)
|
||||||
|
- No TypeScript errors
|
||||||
|
- All tests passing
|
||||||
|
- Full app integrates without errors
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
After completion, create `.planning/phases/01-foundation-dashboard/01-04-SUMMARY.md`
|
||||||
|
</output>
|
||||||
104
.planning/phases/01-foundation-dashboard/01-CONTEXT.md
Normal file
104
.planning/phases/01-foundation-dashboard/01-CONTEXT.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# Phase 1 Context - Foundation & Dashboard
|
||||||
|
|
||||||
|
**Phase:** 1
|
||||||
|
**Created:** 2026-03-13
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
### Dashboard Layout
|
||||||
|
|
||||||
|
- **Layout**: Keep current 3-panel layout (Sidebar | Header + Content)
|
||||||
|
- **Header content**: All in header - Logo, Time, Date, Station ID, Comm Status, Version, Login Status, Solar Voltage, Battery Voltage
|
||||||
|
- **Logo**: Use TCK logo from `/logo/` directory
|
||||||
|
- **Rainfall cards**: Grid layout with color-coded cards (Today/Hourly/MAR/Yearly)
|
||||||
|
|
||||||
|
### Real-time Data Strategy
|
||||||
|
|
||||||
|
- **Approach**: Real API with mock fallback
|
||||||
|
- **Polling interval**: User adjustable (stored in settings)
|
||||||
|
- **Default polling**: 5 seconds
|
||||||
|
- **Implementation**: Create API client with mock data fallback for demo/offline
|
||||||
|
|
||||||
|
### Dual-Mode Display
|
||||||
|
|
||||||
|
- **Detection**: By port number
|
||||||
|
- Port 8080 → Kiosk mode (1024x600)
|
||||||
|
- Port 9090 → Full HD remote mode
|
||||||
|
- **Implementation**: `window.location.port` detection in app root
|
||||||
|
- **Remote mode**: Expanded dashboard with larger fonts and more information visible
|
||||||
|
|
||||||
|
### State Management
|
||||||
|
|
||||||
|
- **Library**: Zustand (~1.1KB, research-recommended)
|
||||||
|
- **Sensor data**: Global state in Zustand store
|
||||||
|
- **Settings persistence**: localStorage + sync to backend when online
|
||||||
|
- **Architecture**: Offline-first - settings persist locally, sync in background
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Code Context
|
||||||
|
|
||||||
|
### Existing Assets
|
||||||
|
|
||||||
|
**Routes** (`sample_interface/src/app/routes.ts`):
|
||||||
|
- Dashboard at `/` (RainfallView)
|
||||||
|
- Settings under `/utility/*`
|
||||||
|
- Calibration at `/calibration`
|
||||||
|
- Flash Memory at `/flash-memory`
|
||||||
|
|
||||||
|
**Components**:
|
||||||
|
- `DashboardLayout.tsx` - Main layout wrapper
|
||||||
|
- `Header.tsx` - Header component (needs modernization)
|
||||||
|
- `Sidebar.tsx` - Navigation sidebar
|
||||||
|
- UI components in `components/ui/` - shadcn/ui components available
|
||||||
|
|
||||||
|
**Guidelines**: `sample_interface/guidelines/Guidelines.md` - Full UI specification
|
||||||
|
|
||||||
|
### Key Files to Modify
|
||||||
|
|
||||||
|
- `src/app/App.tsx` - Add mode detection (port-based)
|
||||||
|
- `src/app/components/Header.tsx` - Modernize for Phase 1
|
||||||
|
- `src/app/components/views/RainfallView.tsx` - Main dashboard view
|
||||||
|
- New: `src/app/stores/sensorStore.ts` - Zustand store for sensor data
|
||||||
|
- New: `src/app/api/client.ts` - API client with mock fallback
|
||||||
|
|
||||||
|
### Dependencies to Add
|
||||||
|
|
||||||
|
- `zustand` - State management (research recommended)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prior Decisions (from PROJECT.md)
|
||||||
|
|
||||||
|
- React/TypeScript stack
|
||||||
|
- shadcn/ui components
|
||||||
|
- Tailwind CSS
|
||||||
|
- Two-port architecture (8080/9090)
|
||||||
|
- <200KB bundle target for Pi Zero 2 W
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
1. User sees real-time rainfall data (Today, Hourly, MAR Acc, Yearly Acc) updating automatically
|
||||||
|
2. User sees solar and battery voltage with visual status indicators
|
||||||
|
3. User sees station ID, version info, current time/date, and communication status
|
||||||
|
4. User sees login status indicator on dashboard
|
||||||
|
5. Dashboard loads in <2 seconds on Pi Zero 2 W
|
||||||
|
6. All touch targets are minimum 44px (preferably 56px)
|
||||||
|
7. Dashboard displays correctly in both 1024x600 (kiosk) and Full HD (remote) modes
|
||||||
|
8. Navigation menu provides access to Settings, Calibration, and Flash Memory sections
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Out of Scope
|
||||||
|
|
||||||
|
- CSV processing (Phase 3)
|
||||||
|
- Network stack implementation (Phase 3)
|
||||||
|
- Full settings implementation (Phase 2)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Context for Phase 1 planning - decisions that guide research and planning*
|
||||||
176
.planning/phases/01-foundation-dashboard/01-RESEARCH.md
Normal file
176
.planning/phases/01-foundation-dashboard/01-RESEARCH.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# Phase 1: Foundation & Dashboard - Research
|
||||||
|
|
||||||
|
**Phase:** 1
|
||||||
|
**Researched:** 2026-03-13
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Research Question
|
||||||
|
|
||||||
|
What do we need to know to PLAN Phase 1 well? Phase 1 delivers a modern, performant dashboard for rainfall monitoring on Raspberry Pi Zero 2 W hardware.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Findings
|
||||||
|
|
||||||
|
### Dashboard Requirements (from CONTEXT.md + ROADMAP)
|
||||||
|
|
||||||
|
**Must Display:**
|
||||||
|
1. Real-time rainfall data (Today, Hourly, MAR Acc, Yearly Acc) — DASH-01
|
||||||
|
2. Solar voltage reading — DASH-02
|
||||||
|
3. Battery voltage with HIGH/LOW status indicator — DASH-03
|
||||||
|
4. Station ID and version info — DASH-04
|
||||||
|
5. Real-time clock (HH:MM:SS) and date (YYYY-MM-DD) — DASH-05
|
||||||
|
6. Communication status (ASU/dBm/percentage) — DASH-06
|
||||||
|
7. Login status indicator — DASH-07
|
||||||
|
|
||||||
|
**UI Requirements:**
|
||||||
|
- Modern, compact design optimized for 1024x600 — UI-01
|
||||||
|
- Touch-friendly (44px+ touch targets) — UI-02
|
||||||
|
- Responsive layout for Full HD remote access — UI-03
|
||||||
|
- <200KB JS bundle for Pi Zero 2 W — UI-04
|
||||||
|
- Navigation menu to Settings, Calibration, Flash Memory — UI-05
|
||||||
|
|
||||||
|
### Implementation Decisions (Locked)
|
||||||
|
|
||||||
|
**Layout:**
|
||||||
|
- 3-panel layout: Sidebar | Header + Content
|
||||||
|
- Header contains: Logo, Time, Date, Station ID, Comm Status, Version, Login Status, Solar Voltage, Battery Voltage
|
||||||
|
- Rainfall cards in grid with color-coding
|
||||||
|
|
||||||
|
**Dual-Mode Display:**
|
||||||
|
- Port 8080 = Kiosk mode (1024x600)
|
||||||
|
- Port 9090 = Full HD remote mode
|
||||||
|
- Detection via `window.location.port`
|
||||||
|
|
||||||
|
**State Management:**
|
||||||
|
- Zustand (~1.1KB) for sensor data
|
||||||
|
- localStorage + backend sync for settings persistence
|
||||||
|
- Offline-first architecture
|
||||||
|
|
||||||
|
**Real-time Data:**
|
||||||
|
- Polling-based (default 5s, user adjustable)
|
||||||
|
- API client with mock fallback for demo/offline
|
||||||
|
|
||||||
|
### Technology Stack (from existing research)
|
||||||
|
|
||||||
|
**Confirmed Stack:**
|
||||||
|
- React 19.2.4 — 15-20% smaller than v18, required for shadcn/ui
|
||||||
|
- Vite 8.0.0 — Fast HMR, superior tree-shaking
|
||||||
|
- Tailwind CSS 4.2.0 — Zero-runtime CSS
|
||||||
|
- React Router 7.x — Type-safe routing
|
||||||
|
- Zustand 5.0.11 — ~1.1KB state management
|
||||||
|
- shadcn/ui — Tree-shakeable components
|
||||||
|
|
||||||
|
**Bundle Budget:** <170KB initial JS (200KB limit with buffer)
|
||||||
|
|
||||||
|
### Critical Pitfalls to Avoid
|
||||||
|
|
||||||
|
1. **Performance constraints** — Pi Zero 2 W has 1GHz quad-core, 512MB RAM
|
||||||
|
2. **Layout thrashing** — Use React.memo, debounce updates
|
||||||
|
3. **Unbounded data** — Circular buffers for historical data
|
||||||
|
4. **Kiosk anti-patterns** — 44px+ touch targets, no hover-only states
|
||||||
|
5. **Memory leaks** — Proper useEffect cleanup
|
||||||
|
6. **Blocking operations** — Keep CSV/network for Phase 3
|
||||||
|
|
||||||
|
### File Structure (from existing codebase)
|
||||||
|
|
||||||
|
**Routes:**
|
||||||
|
- `/` — Dashboard (RainfallView)
|
||||||
|
- `/utility/*` — Settings
|
||||||
|
- `/calibration` — Calibration
|
||||||
|
- `/flash-memory` — Flash Memory
|
||||||
|
|
||||||
|
**Components to Create/Modify:**
|
||||||
|
- `App.tsx` — Add mode detection
|
||||||
|
- `Header.tsx` — Modernize with all status info
|
||||||
|
- `RainfallView.tsx` — Main dashboard view
|
||||||
|
- `stores/sensorStore.ts` — New Zustand store
|
||||||
|
- `api/client.ts` — API client with mock fallback
|
||||||
|
|
||||||
|
### Dependencies to Add
|
||||||
|
|
||||||
|
- `zustand` — State management
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation Architecture
|
||||||
|
|
||||||
|
### Testable Behaviors
|
||||||
|
|
||||||
|
1. **Dashboard displays all required data fields**
|
||||||
|
- Rainfall metrics (Today, Hourly, MAR, Yearly)
|
||||||
|
- Voltage readings (Solar, Battery with status)
|
||||||
|
- Station info (ID, Version)
|
||||||
|
- Time/Date display
|
||||||
|
- Communication status
|
||||||
|
- Login indicator
|
||||||
|
|
||||||
|
2. **Touch targets meet minimum size**
|
||||||
|
- All interactive elements ≥44px
|
||||||
|
- Preferably 56px for primary actions
|
||||||
|
|
||||||
|
3. **Dual-mode display works**
|
||||||
|
- Port 8080 renders kiosk layout
|
||||||
|
- Port 9090 renders expanded layout
|
||||||
|
|
||||||
|
4. **Performance budget met**
|
||||||
|
- Initial JS <170KB gzipped
|
||||||
|
- LCP <2s on Pi Zero 2 W
|
||||||
|
|
||||||
|
5. **Data updates in real-time**
|
||||||
|
- Polling interval configurable
|
||||||
|
- Updates visible without refresh
|
||||||
|
|
||||||
|
6. **Navigation accessible**
|
||||||
|
- Sidebar shows Settings, Calibration, Flash Memory links
|
||||||
|
- Touch-friendly navigation
|
||||||
|
|
||||||
|
### Observable Outputs
|
||||||
|
|
||||||
|
| Artifact | Location | Verification |
|
||||||
|
|----------|----------|--------------|
|
||||||
|
| Dashboard component | `src/app/components/views/RainfallView.tsx` | Renders all 7 data fields |
|
||||||
|
| Header component | `src/app/components/Header.tsx` | Shows status indicators |
|
||||||
|
| Sensor store | `src/app/stores/sensorStore.ts` | Zustand store with mock data |
|
||||||
|
| API client | `src/app/api/client.ts` | Fetch with fallback |
|
||||||
|
| Mode detection | `src/app/App.tsx` | Port-based routing |
|
||||||
|
| Bundle size | Build output | <170KB initial JS |
|
||||||
|
|
||||||
|
### Key Connections
|
||||||
|
|
||||||
|
- Sensor store → Dashboard view (data flow)
|
||||||
|
- API client → Sensor store (update trigger)
|
||||||
|
- Mode detection → Layout components (responsive behavior)
|
||||||
|
- Header → Sensor store (status indicators)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Standard Stack Recommendations
|
||||||
|
|
||||||
|
### What to Use
|
||||||
|
- React 19 + Vite 8 + Tailwind 4 (established)
|
||||||
|
- Zustand for state (research-validated)
|
||||||
|
- React Router 7 for navigation
|
||||||
|
- shadcn/ui Card, Badge, Separator components
|
||||||
|
- Lucide React for icons
|
||||||
|
|
||||||
|
### What NOT to Use
|
||||||
|
- Redux Toolkit (too heavy)
|
||||||
|
- TanStack Query (overkill for polling)
|
||||||
|
- Material UI (large CSS-in-JS)
|
||||||
|
- Real-time libraries (WebSocket/SSE not needed for 5s polling)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gaps Requiring Decisions
|
||||||
|
|
||||||
|
1. **API endpoint contracts** — Coordinate with Python backend team for exact data format
|
||||||
|
2. **Color scheme for status indicators** — Use existing TCK brand colors?
|
||||||
|
3. **Historical chart implementation** — Recharts is already in dependencies
|
||||||
|
4. **Polling interval bounds** — Min/max acceptable values?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Research synthesis for Phase 1 planning. Based on project CONTEXT.md, ROADMAP.md, and existing codebase research.*
|
||||||
90
.planning/phases/01-foundation-dashboard/01-VALIDATION.md
Normal file
90
.planning/phases/01-foundation-dashboard/01-VALIDATION.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
---
|
||||||
|
phase: 1
|
||||||
|
slug: foundation-dashboard
|
||||||
|
status: draft
|
||||||
|
nyquist_compliant: false
|
||||||
|
wave_0_complete: false
|
||||||
|
created: 2026-03-13
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 1 — Validation Strategy
|
||||||
|
|
||||||
|
> Validation contract for Foundation & Dashboard phase. Focus on component rendering, bundle size, and touch-friendly interactions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Test Infrastructure
|
||||||
|
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| **Framework** | Vitest (bundled with Vite) |
|
||||||
|
| **Config file** | `vitest.config.ts` — Wave 0 creates if missing |
|
||||||
|
| **Quick run command** | `npm test` or `pnpm test` |
|
||||||
|
| **Full suite command** | `npm run test:coverage` |
|
||||||
|
| **Estimated runtime** | ~10 seconds (unit tests only) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sampling Rate
|
||||||
|
|
||||||
|
- **After every task commit:** Run `npm test`
|
||||||
|
- **After every plan wave:** Run `npm run test:coverage`
|
||||||
|
- **Before `/gsd-verify-work`:** Full suite must be green
|
||||||
|
- **Max feedback latency:** 15 seconds
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Per-task Verification Map
|
||||||
|
|
||||||
|
| Task ID | Plan | Wave | Requirement | Test Type | Automated Command | File Exists | Status |
|
||||||
|
|---------|------|------|-------------|-----------|-------------------|-------------|--------|
|
||||||
|
| 1-01-01 | 01 | 1 | UI-01 | component | `npm test -- Header.test.tsx` | ❌ W0 | ⬜ pending |
|
||||||
|
| 1-01-02 | 01 | 1 | DASH-01 | component | `npm test -- RainfallView.test.tsx` | ❌ W0 | ⬜ pending |
|
||||||
|
| 1-02-01 | 02 | 1 | DASH-02 | component | `npm test -- VoltageDisplay.test.tsx` | ❌ W0 | ⬜ pending |
|
||||||
|
| 1-02-02 | 02 | 1 | DASH-03 | component | `npm test -- BatteryStatus.test.tsx` | ❌ W0 | ⬜ pending |
|
||||||
|
| 1-03-01 | 03 | 2 | DASH-04 | component | `npm test -- StationInfo.test.tsx` | ❌ W0 | ⬜ pending |
|
||||||
|
| 1-03-02 | 03 | 2 | DASH-05 | component | `npm test -- ClockDisplay.test.tsx` | ❌ W0 | ⬜ pending |
|
||||||
|
| 1-03-03 | 03 | 2 | DASH-06 | component | `npm test -- CommStatus.test.tsx` | ❌ W0 | ⬜ pending |
|
||||||
|
| 1-04-01 | 04 | 2 | DASH-07 | component | `npm test -- LoginIndicator.test.tsx` | ❌ W0 | ⬜ pending |
|
||||||
|
| 1-04-02 | 04 | 2 | UI-02 | e2e/visual | Manual verification | N/A | ⬜ pending |
|
||||||
|
| 1-04-03 | 04 | 2 | UI-03 | e2e/visual | Manual verification | N/A | ⬜ pending |
|
||||||
|
| 1-04-04 | 04 | 3 | UI-04 | build | `npm run build && bundlesize` | ❌ W0 | ⬜ pending |
|
||||||
|
| 1-04-05 | 04 | 3 | UI-05 | component | `npm test -- Navigation.test.tsx` | ❌ W0 | ⬜ pending |
|
||||||
|
|
||||||
|
*Status: ⬜ pending · ✅ green · ❌ red · ⚠️ flaky*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wave 0 Requirements
|
||||||
|
|
||||||
|
- [ ] `src/app/components/__tests__/Header.test.tsx` — Header component tests
|
||||||
|
- [ ] `src/app/components/__tests__/RainfallView.test.tsx` — Dashboard view tests
|
||||||
|
- [ ] `src/app/stores/__tests__/sensorStore.test.ts` — Zustand store tests
|
||||||
|
- [ ] `vitest.config.ts` — Vitest configuration
|
||||||
|
- [ ] `src/test/setup.ts` — Test utilities and mocks
|
||||||
|
- [ ] `.bundlesize.json` — Bundle size budget configuration
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Manual-Only Verifications
|
||||||
|
|
||||||
|
| Behavior | Requirement | Why Manual | Test Instructions |
|
||||||
|
|----------|-------------|------------|-------------------|
|
||||||
|
| Touch target sizing | UI-02 | Visual measurement | Verify all buttons ≥44px using DevTools inspector |
|
||||||
|
| Kiosk mode rendering | UI-03 | Device-specific | Test on 1024x600 display, verify no horizontal scroll |
|
||||||
|
| Remote mode rendering | UI-03 | Device-specific | Test on Full HD display, verify layout expands |
|
||||||
|
| Pi Zero performance | UI-04 | Hardware-specific | Deploy to Pi, measure LCP with Chrome DevTools |
|
||||||
|
| Real-time data updates | DASH-01 | Time-based | Verify polling updates display every 5 seconds |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Validation Sign-Off
|
||||||
|
|
||||||
|
- [ ] All tasks have `<automated>` verify or Wave 0 dependencies
|
||||||
|
- [ ] Sampling continuity: no 3 consecutive tasks without automated verify
|
||||||
|
- [ ] Wave 0 covers all MISSING references
|
||||||
|
- [ ] No watch-mode flags
|
||||||
|
- [ ] Feedback latency < 15s
|
||||||
|
- [ ] `nyquist_compliant: true` set in frontmatter
|
||||||
|
|
||||||
|
**Approval:** pending 2026-03-13
|
||||||
Reference in New Issue
Block a user