docs(01-foundation-dashboard): create phase 1 plans

This commit is contained in:
2026-03-13 06:23:52 +08:00
parent b85d81c2e8
commit feaed41454
7 changed files with 1571 additions and 0 deletions

View 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>

View 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>

View 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>

View 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>

View 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*

View 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.*

View 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