Replace the current dashboard (summary stat cards + empty activity feed + redundant quick actions) with a vehicle-centric roster layout that shows actionable health data per vehicle. The current dashboard tells users almost nothing -- three numbers (vehicle count, maintenance count, fuel log count), an empty activity section, and navigation buttons that duplicate the sidebar.
Problem Statement
The current dashboard has five UX problems identified during audit:
Low information density -- 60% whitespace, three stat cards showing counts with no context
Non-interactive stat cards -- cursor: auto, no click-through, CTAs only appear when value is 0
Quick Actions duplicate sidebar navigation -- all four actions (Add Vehicle, Log Fuel, Maintenance, My Vehicles) are already in the sidebar and mobile bottom nav
Weak empty states -- single line of gray text with no guided actions
Slim horizontal row below the heading with two buttons:
+ Add Vehicle: Navigates to vehicle creation flow
Log Fuel: Navigates to fuel log entry
Replaces the current Quick Actions section entirely. On mobile, buttons may use icon-only or compact layout.
Heading
Replace generic "Dashboard" with "Your Fleet" (or similar). Remove redundant "Welcome back, email" from header area since email already shows in sidebar/profile.
Empty State (0 vehicles)
Keep existing empty state behavior (already handled in DashboardScreen.tsx:84-108) -- shows welcome message with "Add Your First Vehicle" CTA.
Data Requirements
Current Data (already fetched in useDashboardData)
Vehicles list with all fields
Maintenance schedules per vehicle (with overdue detection)
Fuel logs (no longer needed for dashboard display)
New Data Needed
Documents per vehicle: Query documents table filtered by vehicle_id and documentType IN ('insurance', 'registration') where expirationDate IS NOT NULL, to surface upcoming renewals
Health computation: Aggregate maintenance schedule status + document expiry into per-vehicle health color
API Considerations
The documents API (documentsApi) may need a method to fetch documents by vehicle with expiry filtering, or the dashboard hook can fetch all user documents and filter client-side
Evaluate whether a dedicated backend endpoint is warranted vs. client-side aggregation
frontend/src/features/dashboard/components/ActionBar.tsx -- compact action bar
Modified
frontend/src/features/dashboard/components/DashboardScreen.tsx -- new layout composition
frontend/src/features/dashboard/hooks/useDashboardData.ts -- extend to fetch document expiry data
frontend/src/features/dashboard/types/index.ts (or similar) -- new types for vehicle health
Removed/Deprecated
frontend/src/features/dashboard/components/SummaryCards.tsx -- replaced by vehicle cards
frontend/src/features/dashboard/components/QuickActions.tsx -- replaced by action bar
frontend/src/features/dashboard/components/RecentActivity.tsx -- activity context embedded in vehicle cards
Acceptance Criteria
Dashboard shows a card for each vehicle with year/make/model, health indicator, attention items, and odometer
Health indicator correctly computes green/yellow/red from maintenance schedules AND document expiry dates
Clicking a vehicle card navigates to the vehicle detail page
Cards have proper hover states (elevation, cursor pointer)
Compact action bar with "Add Vehicle" and "Log Fuel" replaces Quick Actions
Old SummaryCards, QuickActions, and RecentActivity components are removed
Works on both desktop (2-column grid) and mobile (single column)
Empty state (0 vehicles) still renders correctly
Loading skeletons updated for new layout
All existing tests updated or replaced
Linting, type-check, and tests pass
Technical Notes
Document expiry data lives in documents table with expirationDate field and documentType enum (insurance, registration)
Maintenance overdue detection already exists in useDashboardData.ts:84-113
Vehicle photos via vehicles.imageUrl / imageStorageBucket
Health computation is client-side (no stored health field on vehicle)
GlassCard component should be reused for card styling consistency
This is a multi-file feature (6+ files) and should be decomposed into sub-issues
## Summary
Replace the current dashboard (summary stat cards + empty activity feed + redundant quick actions) with a **vehicle-centric roster layout** that shows actionable health data per vehicle. The current dashboard tells users almost nothing -- three numbers (vehicle count, maintenance count, fuel log count), an empty activity section, and navigation buttons that duplicate the sidebar.
## Problem Statement
The current dashboard has five UX problems identified during audit:
1. **Low information density** -- 60% whitespace, three stat cards showing counts with no context
2. **Non-interactive stat cards** -- `cursor: auto`, no click-through, CTAs only appear when value is 0
3. **Quick Actions duplicate sidebar navigation** -- all four actions (Add Vehicle, Log Fuel, Maintenance, My Vehicles) are already in the sidebar and mobile bottom nav
4. **Weak empty states** -- single line of gray text with no guided actions
5. **Visual hierarchy issues** -- redundant "Dashboard" heading, duplicate email display, inconsistent heading sizes, monochrome icon colors
Choosing a vehicle-centric layout eliminates problems 1, 2, and 4 entirely, and addresses 3 and 5 as part of the redesign.
## Design Specification
### Layout Structure
```
Desktop (>= 640px):
+----------------------------------------------------------+
| Your Fleet [+ Add Vehicle] [Log Fuel] | <- compact action bar
+----------------------------------------------------------+
| +---------------------------+ +---------------------------+
| | 2019 Ford F-150 [R] | | 2022 Honda Civic [G] |
| | | | |
| | ! Oil Change - OVERDUE | | All maintenance clear |
| | ! Registration - 18 days | | Insurance - 42 days |
| | 87,412 mi | | 34,201 mi |
| +---------------------------+ +---------------------------+
| +---------------------------+ +---------------------------+
| | 2020 Toyota Tacoma [Y] | | 2018 BMW 3 Series [Y] |
| | ... | | ... |
| +---------------------------+ +---------------------------+
Mobile (< 640px):
Single column, compact action bar becomes icon-only or stacks
```
### Vehicle Card Design
Each card displays:
- **Vehicle identity**: Year Make Model (or nickname if set), vehicle photo/placeholder icon
- **Health indicator**: Color-coded badge (green/yellow/red)
- GREEN: No overdue items, nothing due within 14 days
- YELLOW: Items due within 14 days (maintenance or document expiry)
- RED: Overdue maintenance or expired documents
- **Attention items** (sorted by urgency, max 3 shown):
- Overdue maintenance schedules (from `maintenance_schedules.nextDueDate < now`)
- Expiring documents (from `documents.expirationDate` for types `insurance`, `registration`)
- Upcoming maintenance (within 30 days)
- **Odometer**: Current reading from `vehicles.odometerReading`
- **Click behavior**: Entire card is clickable, navigates to `/garage/vehicles/{id}`
- **Hover state**: Elevation increase + subtle border highlight
### Compact Action Bar
Slim horizontal row below the heading with two buttons:
- **+ Add Vehicle**: Navigates to vehicle creation flow
- **Log Fuel**: Navigates to fuel log entry
Replaces the current Quick Actions section entirely. On mobile, buttons may use icon-only or compact layout.
### Heading
Replace generic "Dashboard" with "Your Fleet" (or similar). Remove redundant "Welcome back, email" from header area since email already shows in sidebar/profile.
### Empty State (0 vehicles)
Keep existing empty state behavior (already handled in `DashboardScreen.tsx:84-108`) -- shows welcome message with "Add Your First Vehicle" CTA.
## Data Requirements
### Current Data (already fetched in `useDashboardData`)
- Vehicles list with all fields
- Maintenance schedules per vehicle (with overdue detection)
- Fuel logs (no longer needed for dashboard display)
### New Data Needed
- **Documents per vehicle**: Query `documents` table filtered by `vehicle_id` and `documentType IN ('insurance', 'registration')` where `expirationDate IS NOT NULL`, to surface upcoming renewals
- **Health computation**: Aggregate maintenance schedule status + document expiry into per-vehicle health color
### API Considerations
- The documents API (`documentsApi`) may need a method to fetch documents by vehicle with expiry filtering, or the dashboard hook can fetch all user documents and filter client-side
- Evaluate whether a dedicated backend endpoint is warranted vs. client-side aggregation
## Files Affected
### New Components
- `frontend/src/features/dashboard/components/VehicleRosterCard.tsx` -- per-vehicle card
- `frontend/src/features/dashboard/components/ActionBar.tsx` -- compact action bar
### Modified
- `frontend/src/features/dashboard/components/DashboardScreen.tsx` -- new layout composition
- `frontend/src/features/dashboard/hooks/useDashboardData.ts` -- extend to fetch document expiry data
- `frontend/src/features/dashboard/types/index.ts` (or similar) -- new types for vehicle health
### Removed/Deprecated
- `frontend/src/features/dashboard/components/SummaryCards.tsx` -- replaced by vehicle cards
- `frontend/src/features/dashboard/components/QuickActions.tsx` -- replaced by action bar
- `frontend/src/features/dashboard/components/RecentActivity.tsx` -- activity context embedded in vehicle cards
## Acceptance Criteria
- [ ] Dashboard shows a card for each vehicle with year/make/model, health indicator, attention items, and odometer
- [ ] Health indicator correctly computes green/yellow/red from maintenance schedules AND document expiry dates
- [ ] Clicking a vehicle card navigates to the vehicle detail page
- [ ] Cards have proper hover states (elevation, cursor pointer)
- [ ] Compact action bar with "Add Vehicle" and "Log Fuel" replaces Quick Actions
- [ ] Old SummaryCards, QuickActions, and RecentActivity components are removed
- [ ] Works on both desktop (2-column grid) and mobile (single column)
- [ ] Empty state (0 vehicles) still renders correctly
- [ ] Loading skeletons updated for new layout
- [ ] All existing tests updated or replaced
- [ ] Linting, type-check, and tests pass
## Technical Notes
- Document expiry data lives in `documents` table with `expirationDate` field and `documentType` enum (`insurance`, `registration`)
- Maintenance overdue detection already exists in `useDashboardData.ts:84-113`
- Vehicle photos via `vehicles.imageUrl` / `imageStorageBucket`
- Health computation is client-side (no stored health field on vehicle)
- `GlassCard` component should be reused for card styling consistency
- This is a multi-file feature (6+ files) and should be decomposed into sub-issues
Current state: Dashboard has 8 files (0 tests). Layout is flat: SummaryCards (3 metric counters), VehicleAttention (overdue list), RecentActivity (merged feed), QuickActions (4 nav buttons). DashboardScreen is shared between mobile and desktop via callback props.
Key findings:
Documents API already supports listByVehicle(vehicleId) with useDocumentsByVehicle hook -- no backend work needed
VehicleImage component exists with 3-tier fallback (uploaded photo, make logo, color placeholder)
Maintenance overdue detection already in useDashboardData.ts:84-118 -- fetches schedules per vehicle
Vehicle display utilities exist: getVehicleLabel(), getVehicleSubtitle() in core/utils/vehicleDisplay.ts
Responsive pattern: Tailwind grid-cols-1 sm:grid-cols-2 for 2-column desktop grids
MUI theme: borderRadius: 20 for cards, 44px min touch targets, Ferrari-inspired color palette
No backend changes required. All data sources already exist. This is a frontend-only feature.
Architecture Decisions
Decision
Choice
Rationale
Data fetching
Extend useDashboardData hook
Single query already fetches vehicles + maintenance. Adding document expiry calls keeps one cache entry and one loading state.
Document data source
documentsApi.list() with client-side filtering
Fetching all user documents once (single API call) then grouping by vehicleId is more efficient than N+1 calls to listByVehicle per vehicle. Filter to insurance/registration with non-null expirationDate client-side.
Health computation
Pure function, not a hook
computeVehicleHealth(schedules, documents) as an exportable utility enables unit testing without React Query wrappers.
Card component
GlassCard wrapper with custom content
Reuses existing card pattern (27 files use it), gets click/hover behavior for free.
Grid layout
Tailwind grid-cols-1 sm:grid-cols-2
Matches existing responsive patterns in the codebase (SummaryCards uses same breakpoint).
Fuel logs in dashboard data
Stop fetching
No longer needed for dashboard display. Removes unnecessary API call.
Add documentsApi.list() call to the parallel fetch in useDashboardData (alongside vehicles and fuelLogs). Filter client-side to documentType in ['insurance', 'registration'] with non-null expirationDate.
Remove fuelLogsApi.getUserFuelLogs() from the fetch (no longer displayed on dashboard).
Keep existing useDashboardSummary and useVehiclesNeedingAttention for now (they may still be imported elsewhere). Mark with @deprecated comment if no longer needed after M4.
Commit: feat: add vehicle health types and roster data hook (refs #197)
Testing approach: Mock useDashboardData / useVehicleRoster hooks. Use @testing-library/react with render, screen, fireEvent. Pure functions tested directly without React wrappers.
1. Coverage: Does every acceptance criterion have a corresponding milestone/implementation step?
FAIL - Missing coverage for loading skeletons acceptance criterion. Plan mentions loading skeletons in M4 but doesn't detail the skeleton design for the new vehicle roster layout.
2. File inventory: Are all files to create, modify, and delete explicitly listed?
PASS - All files are accounted for:
New: VehicleRosterCard.tsx, ActionBar.tsx, 4 test files
3. Dependency ordering: Are milestone dependencies correct?
PASS - M1 (data layer) -> M2/M3 (components) -> M4 (composition) -> M5 (tests) is sound. M2 and M3 can proceed in parallel.
4. Type safety: Are proposed type definitions complete?
PASS - AttentionItem and VehicleRosterData types look complete. VehicleHealth union type is appropriate.
5. Data flow: Is the data fetching strategy sound?
FAIL - See RULE 1 findings below. The plan proposes calling documentsApi.list() without filtering, but the API supports filter params that would reduce payload size.
6. Mobile + Desktop: Does the plan address both viewports?
PASS - Explicitly states grid-cols-1 sm:grid-cols-2 for responsive grid. M2 and M3 mention mobile behavior.
7. Existing behavior preservation: Does the plan preserve empty state, PendingAssociationBanner, etc?
PASS - M4 explicitly preserves empty state (lines 84-108), PendingAssociationBanner, and PendingReceipts dialog.
8. Deletion safety: Are files being deleted safe to remove?
PASS - Verified that SummaryCards, QuickActions, and RecentActivity are only imported by DashboardScreen.tsx. The plan correctly updates index.ts barrel exports.
9. Test coverage: Does the test plan cover key behaviors?
PASS - M5 test plan covers pure function (computeVehicleHealth), component behaviors, and DashboardScreen integration. Uses proper testing patterns.
10. RULE 0 (Critical): Any production reliability risks?
PASS - No unhandled errors, security issues, or resource exhaustion risks identified.
11. RULE 1 (High): Does the plan follow project standards?
FAIL - See findings below.
12. RULE 2 (Should-fix): Any structural quality concerns?
PASS - No god objects or duplication. Pure function pattern for health computation is good architectural choice.
Issue: Plan states "Fetching all user documents once (single API call) then grouping by vehicleId is more efficient than N+1 calls to listByVehicle per vehicle." This is incorrect for users with many documents. The plan should leverage the existing documentsApi.list() filter params to request ONLY documentType in ['insurance', 'registration'] with non-null expirationDate.
Failure Mode: Fetching ALL user documents (maintenance manuals, receipts, service records, etc.) when only 2 document types are needed wastes bandwidth and increases payload size, especially for heavy users with hundreds of documents.
Suggested Fix: Change M1 to call documentsApi.list({ type: 'insurance' }) and documentsApi.list({ type: 'registration' }) in parallel, then filter client-side for non-null expirationDate. Alternatively, check if the backend supports multiple types in a single filter (e.g., type=insurance,registration) and use that. The API signature at documents.api.ts:16 shows params?: { vehicleId?: string; type?: string; expiresBefore?: string } - the type param can be used.
Issue: Acceptance criterion "Loading skeletons updated for new layout" has no implementation detail in the plan. What does the roster skeleton look like? How many placeholder cards? What dimensions?
Failure Mode: Developer implementing M4 will have to make design decisions on the fly, leading to inconsistent skeleton design or wasted context on iteration.
Suggested Fix: Add explicit skeleton design to M4: "4 placeholder GlassCard components with pulsing shimmer effect (MUI Skeleton), arranged in grid-cols-1 sm:grid-cols-2 layout. Each skeleton card height matches VehicleRosterCard (~180px). Use existing pattern from other features (e.g., VehicleAttentionSkeleton)."
RULE 2 (SHOULD_FIX): Missing error handling detail for document fetch
Location: M1, useDashboardData hook changes
Issue: Plan doesn't specify what happens if the document API call fails while vehicle/maintenance calls succeed. Current hook returns error state if ANY fetch fails, but adding a third data source increases partial failure complexity.
Failure Mode: Users with document permission issues or API failures would lose the entire dashboard view instead of gracefully degrading to show just vehicles + maintenance.
Suggested Fix: Consider degrading gracefully - if document fetch fails, compute health based only on maintenance schedules (no document expiry warnings). Log the error but don't fail the entire dashboard query. This matches the pattern where recentActivity is allowed to be undefined (line 41 of DashboardScreen.tsx).
Missing Items
Skeleton design specification - Add to M4 as noted above.
Document filtering optimization - Correct the M1 data fetching approach to use type filter param.
Error handling strategy - Clarify partial failure behavior when document API fails but vehicle/maintenance succeed.
VehicleAttention component fate - Plan doesn't mention VehicleAttention.tsx (existing component showing overdue vehicles). Should this be deleted too since vehicle cards now show attention items inline? Or does it serve a different purpose? Check VehicleAttention implementation and decide.
Verdict: FAIL | Next: Planner revision required
Required changes:
Fix document fetching strategy to use type filter param (RULE 1 - blocking)
Optional (RULE 2): Add error handling strategy for partial failures
## QR Review: Plan Completeness
**Phase**: Plan-Review | **Agent**: Quality Reviewer | **Status**: FAIL
---
### Checklist Results
#### 1. Coverage: Does every acceptance criterion have a corresponding milestone/implementation step?
**FAIL** - Missing coverage for loading skeletons acceptance criterion. Plan mentions loading skeletons in M4 but doesn't detail the skeleton design for the new vehicle roster layout.
#### 2. File inventory: Are all files to create, modify, and delete explicitly listed?
**PASS** - All files are accounted for:
- New: VehicleRosterCard.tsx, ActionBar.tsx, 4 test files
- Modified: DashboardScreen.tsx, useDashboardData.ts, types/index.ts, index.ts
- Deleted: SummaryCards.tsx, QuickActions.tsx, RecentActivity.tsx
#### 3. Dependency ordering: Are milestone dependencies correct?
**PASS** - M1 (data layer) -> M2/M3 (components) -> M4 (composition) -> M5 (tests) is sound. M2 and M3 can proceed in parallel.
#### 4. Type safety: Are proposed type definitions complete?
**PASS** - AttentionItem and VehicleRosterData types look complete. VehicleHealth union type is appropriate.
#### 5. Data flow: Is the data fetching strategy sound?
**FAIL** - See RULE 1 findings below. The plan proposes calling `documentsApi.list()` without filtering, but the API supports filter params that would reduce payload size.
#### 6. Mobile + Desktop: Does the plan address both viewports?
**PASS** - Explicitly states `grid-cols-1 sm:grid-cols-2` for responsive grid. M2 and M3 mention mobile behavior.
#### 7. Existing behavior preservation: Does the plan preserve empty state, PendingAssociationBanner, etc?
**PASS** - M4 explicitly preserves empty state (lines 84-108), PendingAssociationBanner, and PendingReceipts dialog.
#### 8. Deletion safety: Are files being deleted safe to remove?
**PASS** - Verified that SummaryCards, QuickActions, and RecentActivity are only imported by DashboardScreen.tsx. The plan correctly updates index.ts barrel exports.
#### 9. Test coverage: Does the test plan cover key behaviors?
**PASS** - M5 test plan covers pure function (computeVehicleHealth), component behaviors, and DashboardScreen integration. Uses proper testing patterns.
#### 10. RULE 0 (Critical): Any production reliability risks?
**PASS** - No unhandled errors, security issues, or resource exhaustion risks identified.
#### 11. RULE 1 (High): Does the plan follow project standards?
**FAIL** - See findings below.
#### 12. RULE 2 (Should-fix): Any structural quality concerns?
**PASS** - No god objects or duplication. Pure function pattern for health computation is good architectural choice.
---
### Findings
#### RULE 1 (HIGH): Inefficient document fetching strategy
- **Location**: M1, `useDashboardData` hook changes
- **Issue**: Plan states "Fetching all user documents once (single API call) then grouping by vehicleId is more efficient than N+1 calls to listByVehicle per vehicle." This is incorrect for users with many documents. The plan should leverage the existing `documentsApi.list()` filter params to request ONLY `documentType in ['insurance', 'registration']` with non-null `expirationDate`.
- **Failure Mode**: Fetching ALL user documents (maintenance manuals, receipts, service records, etc.) when only 2 document types are needed wastes bandwidth and increases payload size, especially for heavy users with hundreds of documents.
- **Suggested Fix**: Change M1 to call `documentsApi.list({ type: 'insurance' })` and `documentsApi.list({ type: 'registration' })` in parallel, then filter client-side for non-null expirationDate. Alternatively, check if the backend supports multiple types in a single filter (e.g., `type=insurance,registration`) and use that. The API signature at `documents.api.ts:16` shows `params?: { vehicleId?: string; type?: string; expiresBefore?: string }` - the `type` param can be used.
#### RULE 1 (HIGH): Missing acceptance criteria coverage - loading skeletons
- **Location**: M4, DashboardScreen changes
- **Issue**: Acceptance criterion "Loading skeletons updated for new layout" has no implementation detail in the plan. What does the roster skeleton look like? How many placeholder cards? What dimensions?
- **Failure Mode**: Developer implementing M4 will have to make design decisions on the fly, leading to inconsistent skeleton design or wasted context on iteration.
- **Suggested Fix**: Add explicit skeleton design to M4: "4 placeholder GlassCard components with pulsing shimmer effect (MUI Skeleton), arranged in grid-cols-1 sm:grid-cols-2 layout. Each skeleton card height matches VehicleRosterCard (~180px). Use existing pattern from other features (e.g., VehicleAttentionSkeleton)."
#### RULE 2 (SHOULD_FIX): Missing error handling detail for document fetch
- **Location**: M1, `useDashboardData` hook changes
- **Issue**: Plan doesn't specify what happens if the document API call fails while vehicle/maintenance calls succeed. Current hook returns error state if ANY fetch fails, but adding a third data source increases partial failure complexity.
- **Failure Mode**: Users with document permission issues or API failures would lose the entire dashboard view instead of gracefully degrading to show just vehicles + maintenance.
- **Suggested Fix**: Consider degrading gracefully - if document fetch fails, compute health based only on maintenance schedules (no document expiry warnings). Log the error but don't fail the entire dashboard query. This matches the pattern where recentActivity is allowed to be undefined (line 41 of DashboardScreen.tsx).
---
### Missing Items
1. **Skeleton design specification** - Add to M4 as noted above.
2. **Document filtering optimization** - Correct the M1 data fetching approach to use `type` filter param.
3. **Error handling strategy** - Clarify partial failure behavior when document API fails but vehicle/maintenance succeed.
4. **VehicleAttention component fate** - Plan doesn't mention VehicleAttention.tsx (existing component showing overdue vehicles). Should this be deleted too since vehicle cards now show attention items inline? Or does it serve a different purpose? Check VehicleAttention implementation and decide.
---
*Verdict*: **FAIL** | *Next*: **Planner revision required**
**Required changes**:
1. Fix document fetching strategy to use `type` filter param (RULE 1 - blocking)
2. Add loading skeleton design spec to M4 (RULE 1 - blocking)
3. Address VehicleAttention component deletion/preservation decision
4. Optional (RULE 2): Add error handling strategy for partial failures
This uses the existing type query parameter on GET /documents to avoid fetching manuals and other irrelevant document types. Two small filtered queries instead of one large unfiltered query.
VehicleAttention.tsxwill be deleted in Milestone 4. The vehicle roster cards embed attention items inline per vehicle, making the separate aggregated attention list redundant.
If the documents API calls fail, the dashboard gracefully degrades:
Vehicle cards still render with maintenance-only health data
Document-related attention items are omitted
Health computation uses only maintenance schedules
No error toast or blocking UI -- the document data is supplementary
Implementation: Wrap the document fetch calls in try/catch within the useDashboardData query function. On failure, set expiryDocs = [] and proceed. This matches the pattern used elsewhere in the app where supplementary data failures don't block primary content.
Verdict: REVISED | Next: QR plan-completeness re-review
## Plan Revision: Addressing QR Plan-Completeness Findings
**Phase**: Planning | **Agent**: Planner | **Status**: REVISED
---
### Revision 1: Document Fetching Strategy (RULE 1 fix)
**Before**: `documentsApi.list()` fetching all documents, filter client-side
**After**: Two parallel calls with server-side type filtering:
```typescript
const [insuranceDocs, registrationDocs] = await Promise.all([
documentsApi.list({ type: 'insurance' }),
documentsApi.list({ type: 'registration' }),
]);
const expiryDocs = [...insuranceDocs, ...registrationDocs]
.filter(d => d.expirationDate != null);
```
This uses the existing `type` query parameter on `GET /documents` to avoid fetching manuals and other irrelevant document types. Two small filtered queries instead of one large unfiltered query.
---
### Revision 2: Loading Skeleton Specification (RULE 1 fix)
Added to **Milestone 4** (DashboardScreen composition):
**Skeleton design**: When `isLoading` is true, render 4 GlassCard skeletons in the same `grid-cols-1 sm:grid-cols-2 gap-4` grid. Each skeleton card contains:
- Top row: `Skeleton variant="circular" width={48} height={48}` (image placeholder) + `Skeleton variant="text" width="60%"` (vehicle name) + `Skeleton variant="circular" width={12} height={12}` (health dot)
- 2x `Skeleton variant="text" width="80%"` (attention item placeholders)
- Bottom: `Skeleton variant="text" width="30%"` (odometer placeholder)
Uses MUI `Skeleton` component for consistency with existing skeleton patterns in the codebase.
---
### Revision 3: VehicleAttention Component Disposition
`VehicleAttention.tsx` **will be deleted** in Milestone 4. The vehicle roster cards embed attention items inline per vehicle, making the separate aggregated attention list redundant.
Updated M4 files deleted list:
- `frontend/src/features/dashboard/components/SummaryCards.tsx`
- `frontend/src/features/dashboard/components/QuickActions.tsx`
- `frontend/src/features/dashboard/components/RecentActivity.tsx`
- `frontend/src/features/dashboard/components/VehicleAttention.tsx` (added)
Barrel exports in `index.ts` will also remove `VehicleAttention` and `VehicleAttentionSkeleton`.
---
### Revision 4: Error Handling Strategy (Graceful Degradation)
Added to **Milestone 1** (data layer):
If the documents API calls fail, the dashboard **gracefully degrades**:
- Vehicle cards still render with maintenance-only health data
- Document-related attention items are omitted
- Health computation uses only maintenance schedules
- No error toast or blocking UI -- the document data is supplementary
Implementation: Wrap the document fetch calls in try/catch within the `useDashboardData` query function. On failure, set `expiryDocs = []` and proceed. This matches the pattern used elsewhere in the app where supplementary data failures don't block primary content.
---
*Verdict*: REVISED | *Next*: QR plan-completeness re-review
This leverages the existing type query parameter on the documents API to avoid fetching irrelevant document types (manuals, receipts, etc.). Server-side filtering reduces payload size and network overhead. The approach is sound.
Revision 2 adds explicit skeleton design specification to M4:
4 GlassCard skeletons in grid-cols-1 sm:grid-cols-2 gap-4 grid
Detailed per-card skeleton structure: circular image placeholder (48x48), text placeholders for vehicle name (60% width), health dot (12x12), 2x attention items (80% width), odometer (30% width)
Uses MUI Skeleton component for consistency
This provides sufficient implementation detail for the developer executing M4. No design decisions left open.
Finding 3: VehicleAttention component fate
STATUS: ADDRESSED
Revision 3 explicitly adds VehicleAttention.tsx to the deletion list in M4 with clear rationale: "vehicle roster cards embed attention items inline per vehicle, making the separate aggregated attention list redundant."
Also updates barrel exports to remove VehicleAttention and VehicleAttentionSkeleton. This completes the file inventory.
Revision 4 adds graceful degradation strategy to M1:
If document API calls fail, set expiryDocs = [] and continue
Vehicle cards render with maintenance-only health data
No blocking error UI (document data is supplementary)
Implementation: try/catch wrapper around document fetch
This is appropriate for supplementary data. Prevents dashboard failure due to documents permission issues or API errors while maintaining core functionality (vehicle + maintenance display).
New Issues
None identified.
The revisions are internally consistent and don't introduce contradictions or new gaps. The sub-issue decomposition, milestone dependencies, type definitions, and test plan remain sound as originally proposed.
Verdict: PASS | Next: TW plan-scrub
## QR Review: Plan Completeness (Re-review)
**Phase**: Plan-Review | **Agent**: Quality Reviewer | **Status**: PASS
---
### Revision Verification
#### Finding 1: Inefficient document fetching strategy
**STATUS**: ADDRESSED
The revision correctly changes from `documentsApi.list()` (unfiltered) to two parallel filtered calls:
```typescript
Promise.all([
documentsApi.list({ type: 'insurance' }),
documentsApi.list({ type: 'registration' }),
])
```
This leverages the existing `type` query parameter on the documents API to avoid fetching irrelevant document types (manuals, receipts, etc.). Server-side filtering reduces payload size and network overhead. The approach is sound.
#### Finding 2: Missing acceptance criteria coverage - loading skeletons
**STATUS**: ADDRESSED
Revision 2 adds explicit skeleton design specification to M4:
- 4 GlassCard skeletons in `grid-cols-1 sm:grid-cols-2 gap-4` grid
- Detailed per-card skeleton structure: circular image placeholder (48x48), text placeholders for vehicle name (60% width), health dot (12x12), 2x attention items (80% width), odometer (30% width)
- Uses MUI `Skeleton` component for consistency
This provides sufficient implementation detail for the developer executing M4. No design decisions left open.
#### Finding 3: VehicleAttention component fate
**STATUS**: ADDRESSED
Revision 3 explicitly adds `VehicleAttention.tsx` to the deletion list in M4 with clear rationale: "vehicle roster cards embed attention items inline per vehicle, making the separate aggregated attention list redundant."
Also updates barrel exports to remove `VehicleAttention` and `VehicleAttentionSkeleton`. This completes the file inventory.
#### Finding 4: Error handling strategy (RULE 2 - graceful degradation)
**STATUS**: ADDRESSED
Revision 4 adds graceful degradation strategy to M1:
- If document API calls fail, set `expiryDocs = []` and continue
- Vehicle cards render with maintenance-only health data
- No blocking error UI (document data is supplementary)
- Implementation: try/catch wrapper around document fetch
This is appropriate for supplementary data. Prevents dashboard failure due to documents permission issues or API errors while maintaining core functionality (vehicle + maintenance display).
---
### New Issues
**None identified.**
The revisions are internally consistent and don't introduce contradictions or new gaps. The sub-issue decomposition, milestone dependencies, type definitions, and test plan remain sound as originally proposed.
---
**Verdict**: PASS | **Next**: TW plan-scrub
PASS -- Each milestone is unambiguous and self-contained. M1 specifies exact type definitions, hook changes with numbered steps, and health logic rules. M2 provides component props interface, layout diagram, and per-element implementation details (colors, icons, text styling). M3 gives complete component spec with MUI component names and variants. M4 lists 10 explicit implementation steps. M5 enumerates test cases per file. A developer can implement from the plan alone without clarifying questions.
2. Terminology Consistency
PASS with one minor note -- The plan uses "health indicator" (issue body), "health badge" (M2 layout), and "health dot" (revision 2 skeleton spec) to refer to the same UI element. However, the type system is consistent: VehicleHealth type, health property on VehicleRosterData, and "Health badge" in M2 implementation details. The "health dot" reference in the skeleton spec (revision 2) is contextually appropriate since the skeleton uses Skeleton variant="circular" width={12} height={12} -- it IS a dot placeholder. The parent issue body uses "health indicator" as a general concept and "color-coded badge" as the design spec. No ambiguity that would cause implementation confusion.
Suggestion (non-blocking): Standardize on "health badge" throughout the plan to match M2's implementation section. The skeleton spec could say "health badge placeholder" instead of "health dot".
3. Code Examples
PASS -- Type definitions (VehicleHealth, AttentionItem, VehicleRosterData) are complete and internally consistent. The VehicleRosterCardProps interface correctly references VehicleRosterData. The ActionBarProps interface is clean. The revised document fetching code snippet (revision 1) is syntactically correct and uses the verified API signature documentsApi.list({ type: 'insurance' }) which matches the actual API at documents.api.ts:16 (params?: { vehicleId?: string; type?: string; expiresBefore?: string }).
One observation: The computeVehicleHealth function signature in M1 step 4 takes (schedules: MaintenanceScheduleResponse[], documents: DocumentResponse[]) -- the actual type in the codebase is MaintenanceSchedule (from maintenance.types.ts), not MaintenanceScheduleResponse. This is a naming discrepancy but the plan calls it a "pure function" with a proposed signature, so the developer will use the correct import. Non-blocking.
4. File Paths
PASS -- All file paths verified against the actual codebase:
core/utils/vehicleDisplay.ts -- EXISTS (contains getVehicleLabel and getVehicleSubtitle)
New files (VehicleRosterCard.tsx, ActionBar.tsx, 4 test files) -- paths consistent with project structure
The plan references "VehicleImage" component -- verified at frontend/src/features/vehicles/components/VehicleImage.tsx. Plan doesn't specify the import path explicitly in M2, but the component name is unambiguous.
5. Formatting
PASS -- The plan uses consistent structure across milestones: Agent assignment, Files section, Type/Interface definitions (code blocks), Implementation details (numbered lists or bullet points), Commit message. Tables are well-formed. Heading levels are consistent (H3 for milestones, H4 for sub-sections within QR review). The revision comment uses clear H3 headings per revision with Before/After pattern.
6. Missing Context
PASS -- All referenced components and utilities are explained or exist in the codebase:
GlassCard -- behavior described (click adds hover elevation + cursor pointer), verified in code
getVehicleLabel() -- referenced with file path, verified
VehicleImage -- described as "3-tier fallback (uploaded photo, make logo, color placeholder)"
PendingAssociationBanner and PendingReceipts -- explicitly noted as preserved
documentsApi.list() -- API signature referenced, verified
Tailwind patterns (grid-cols-1 sm:grid-cols-2) -- explained as matching existing patterns
One minor gap: M4 step 7 mentions "desktop uses navigate('/garage/vehicles/${id}'), mobile uses onVehicleClick?.(vehicle)" but doesn't reference where the navigate import comes from. However, this is standard React Router usage and any React developer would know the import. Non-blocking.
7. Commit Messages
PASS -- All commit messages follow the {type}: {summary} (refs #{index}) convention:
M1: feat: add vehicle health types and roster data hook (refs #197)
M4 + Revision 2: skeleton design spec with 4 GlassCards
Tests updated/replaced
M5: 4 test files with detailed test cases
Linting, type-check, tests pass
Final Validation section
All acceptance criteria are covered. The revision addressed the skeleton gap identified by QR.
Suggestions
(Terminology) Consider standardizing on "health badge" throughout the plan. The skeleton spec (revision 2) uses "health dot" while M2 uses "health badge". Both are understandable, but consistency reduces cognitive load.
(Type name) M1 step 4 references MaintenanceScheduleResponse[] but the existing codebase type is MaintenanceSchedule (imported from maintenance.types.ts in useDashboardData.ts line 12). The implementing developer should use MaintenanceSchedule. Similarly, DocumentResponse should be checked against the actual document type name. Non-blocking since the developer will follow IDE imports.
(Derived hooks deprecation) M1 step 7 says "keep existing useDashboardSummary and useVehiclesNeedingAttention for now" and mark with @deprecated. Since verified that only DashboardScreen.tsx imports these hooks (and DashboardScreen is being rewritten in M4), these hooks will have zero consumers after M4 completes. Consider deleting them in M4 instead of deprecating, to avoid dead code. The useRecentActivity hook is also only used by DashboardScreen. All three derived hooks could be removed in M4 alongside the component cleanup.
(GlassCard hover description) The codebase analysis says "click behavior adds hover elevation + cursor pointer." Verified in GlassCard.tsx:33: the actual behavior is hover:shadow-xl hover:-translate-y-0.5 transition -- this is shadow increase + slight upward translation, not MUI elevation. The plan's description in M2 says "elevation increase + subtle border highlight" which is slightly inaccurate. The actual behavior is shadow increase + vertical translate. Non-blocking since the developer will use GlassCard as-is.
Verdict: PASS | Next: QR plan-code
## TW Review: Plan Scrub
**Phase**: Plan-Review | **Agent**: Technical Writer | **Status**: PASS
---
### Scrub Results
#### 1. Clarity
**PASS** -- Each milestone is unambiguous and self-contained. M1 specifies exact type definitions, hook changes with numbered steps, and health logic rules. M2 provides component props interface, layout diagram, and per-element implementation details (colors, icons, text styling). M3 gives complete component spec with MUI component names and variants. M4 lists 10 explicit implementation steps. M5 enumerates test cases per file. A developer can implement from the plan alone without clarifying questions.
#### 2. Terminology Consistency
**PASS with one minor note** -- The plan uses "health indicator" (issue body), "health badge" (M2 layout), and "health dot" (revision 2 skeleton spec) to refer to the same UI element. However, the type system is consistent: `VehicleHealth` type, `health` property on `VehicleRosterData`, and "Health badge" in M2 implementation details. The "health dot" reference in the skeleton spec (revision 2) is contextually appropriate since the skeleton uses `Skeleton variant="circular" width={12} height={12}` -- it IS a dot placeholder. The parent issue body uses "health indicator" as a general concept and "color-coded badge" as the design spec. No ambiguity that would cause implementation confusion.
**Suggestion (non-blocking)**: Standardize on "health badge" throughout the plan to match M2's implementation section. The skeleton spec could say "health badge placeholder" instead of "health dot".
#### 3. Code Examples
**PASS** -- Type definitions (`VehicleHealth`, `AttentionItem`, `VehicleRosterData`) are complete and internally consistent. The `VehicleRosterCardProps` interface correctly references `VehicleRosterData`. The `ActionBarProps` interface is clean. The revised document fetching code snippet (revision 1) is syntactically correct and uses the verified API signature `documentsApi.list({ type: 'insurance' })` which matches the actual API at `documents.api.ts:16` (`params?: { vehicleId?: string; type?: string; expiresBefore?: string }`).
One observation: The `computeVehicleHealth` function signature in M1 step 4 takes `(schedules: MaintenanceScheduleResponse[], documents: DocumentResponse[])` -- the actual type in the codebase is `MaintenanceSchedule` (from `maintenance.types.ts`), not `MaintenanceScheduleResponse`. This is a naming discrepancy but the plan calls it a "pure function" with a proposed signature, so the developer will use the correct import. Non-blocking.
#### 4. File Paths
**PASS** -- All file paths verified against the actual codebase:
- `frontend/src/features/dashboard/types/index.ts` -- EXISTS
- `frontend/src/features/dashboard/hooks/useDashboardData.ts` -- EXISTS
- `frontend/src/features/dashboard/components/DashboardScreen.tsx` -- EXISTS
- `frontend/src/features/dashboard/components/SummaryCards.tsx` -- EXISTS (to delete)
- `frontend/src/features/dashboard/components/QuickActions.tsx` -- EXISTS (to delete)
- `frontend/src/features/dashboard/components/RecentActivity.tsx` -- EXISTS (to delete)
- `frontend/src/features/dashboard/components/VehicleAttention.tsx` -- EXISTS (to delete, per revision 3)
- `frontend/src/features/dashboard/index.ts` -- EXISTS
- `shared-minimal/components/mobile/GlassCard.tsx` -- EXISTS
- `core/utils/vehicleDisplay.ts` -- EXISTS (contains `getVehicleLabel` and `getVehicleSubtitle`)
- New files (VehicleRosterCard.tsx, ActionBar.tsx, 4 test files) -- paths consistent with project structure
The plan references "VehicleImage" component -- verified at `frontend/src/features/vehicles/components/VehicleImage.tsx`. Plan doesn't specify the import path explicitly in M2, but the component name is unambiguous.
#### 5. Formatting
**PASS** -- The plan uses consistent structure across milestones: Agent assignment, Files section, Type/Interface definitions (code blocks), Implementation details (numbered lists or bullet points), Commit message. Tables are well-formed. Heading levels are consistent (H3 for milestones, H4 for sub-sections within QR review). The revision comment uses clear H3 headings per revision with Before/After pattern.
#### 6. Missing Context
**PASS** -- All referenced components and utilities are explained or exist in the codebase:
- `GlassCard` -- behavior described (click adds hover elevation + cursor pointer), verified in code
- `getVehicleLabel()` -- referenced with file path, verified
- `VehicleImage` -- described as "3-tier fallback (uploaded photo, make logo, color placeholder)"
- `PendingAssociationBanner` and `PendingReceipts` -- explicitly noted as preserved
- `documentsApi.list()` -- API signature referenced, verified
- Tailwind patterns (`grid-cols-1 sm:grid-cols-2`) -- explained as matching existing patterns
One minor gap: M4 step 7 mentions "desktop uses `navigate('/garage/vehicles/${id}')`, mobile uses `onVehicleClick?.(vehicle)`" but doesn't reference where the `navigate` import comes from. However, this is standard React Router usage and any React developer would know the import. Non-blocking.
#### 7. Commit Messages
**PASS** -- All commit messages follow the `{type}: {summary} (refs #{index})` convention:
- M1: `feat: add vehicle health types and roster data hook (refs #197)`
- M2: `feat: add VehicleRosterCard component (refs #198)`
- M3: `feat: add dashboard ActionBar component (refs #199)`
- M4: `feat: rewire DashboardScreen with vehicle roster layout (refs #200)`
- M5: `test: add dashboard redesign tests (refs #201)`
Each references the correct sub-issue index. Types are appropriate (`feat` for features, `test` for tests).
#### 8. Acceptance Criteria Alignment
**PASS** -- Mapping each acceptance criterion to plan coverage:
| Acceptance Criterion | Coverage |
|---------------------|----------|
| Card with year/make/model, health indicator, attention items, odometer | M2: VehicleRosterCard layout spec |
| Health indicator computes green/yellow/red from maintenance + documents | M1: `computeVehicleHealth` with explicit rules |
| Clicking card navigates to vehicle detail | M2: onClick prop, M4 step 7: navigation wiring |
| Hover states (elevation, cursor pointer) | M2: GlassCard provides this, verified in code |
| Compact action bar replaces Quick Actions | M3: ActionBar component, M4: wired into layout |
| Old components removed | M4: SummaryCards, QuickActions, RecentActivity, VehicleAttention deleted |
| Desktop 2-col, mobile 1-col | M2/M4: `grid-cols-1 sm:grid-cols-2` |
| Empty state renders correctly | M4 step 5: preserves lines 84-108 |
| Loading skeletons updated | M4 + Revision 2: skeleton design spec with 4 GlassCards |
| Tests updated/replaced | M5: 4 test files with detailed test cases |
| Linting, type-check, tests pass | Final Validation section |
All acceptance criteria are covered. The revision addressed the skeleton gap identified by QR.
---
### Suggestions
1. **(Terminology)** Consider standardizing on "health badge" throughout the plan. The skeleton spec (revision 2) uses "health dot" while M2 uses "health badge". Both are understandable, but consistency reduces cognitive load.
2. **(Type name)** M1 step 4 references `MaintenanceScheduleResponse[]` but the existing codebase type is `MaintenanceSchedule` (imported from `maintenance.types.ts` in useDashboardData.ts line 12). The implementing developer should use `MaintenanceSchedule`. Similarly, `DocumentResponse` should be checked against the actual document type name. Non-blocking since the developer will follow IDE imports.
3. **(Derived hooks deprecation)** M1 step 7 says "keep existing `useDashboardSummary` and `useVehiclesNeedingAttention` for now" and mark with `@deprecated`. Since verified that only `DashboardScreen.tsx` imports these hooks (and DashboardScreen is being rewritten in M4), these hooks will have zero consumers after M4 completes. Consider deleting them in M4 instead of deprecating, to avoid dead code. The `useRecentActivity` hook is also only used by DashboardScreen. All three derived hooks could be removed in M4 alongside the component cleanup.
4. **(GlassCard hover description)** The codebase analysis says "click behavior adds hover elevation + cursor pointer." Verified in `GlassCard.tsx:33`: the actual behavior is `hover:shadow-xl hover:-translate-y-0.5 transition` -- this is shadow increase + slight upward translation, not MUI elevation. The plan's description in M2 says "elevation increase + subtle border highlight" which is slightly inaccurate. The actual behavior is shadow increase + vertical translate. Non-blocking since the developer will use GlassCard as-is.
---
*Verdict*: **PASS** | *Next*: QR plan-code
frontend/src/features/dashboard/README.md -- Does NOT exist. No dashboard-specific README to update.
frontend/README.md -- Checked. No references to SummaryCards, QuickActions, RecentActivity, or VehicleAttention components. Generic feature structure description only.
docs/README.md -- Checked. No dashboard-specific documentation. Lists backend feature READMEs only (application features).
.ai/context.json -- Checked. No references to dashboard components in feature registry. Frontend work section points to frontend/README.md which is generic.
CLAUDE.md (root) -- Checked. No references to dashboard-specific files.
Findings: No existing documentation references the dashboard components being deleted (SummaryCards, QuickActions, RecentActivity, VehicleAttention). The codebase documentation is at the feature capsule level for backend features, not frontend components.
Missing Documentation Steps
NONE IDENTIFIED.
The plan is a frontend-only redesign with no backend changes. The project's documentation strategy focuses on:
There are no feature-specific frontend component READMEs in the codebase pattern. The dashboard feature does not have a README.md file.
New components are self-documenting:
VehicleRosterCard.tsx -- Component props and layout described in M2
ActionBar.tsx -- Component props described in M3
Type definitions (VehicleHealth, AttentionItem, VehicleRosterData) in M1 serve as inline documentation
Code quality ensures discoverability:
Barrel exports in index.ts (updated in M4) make components discoverable
TypeScript types provide interface contracts
Test files in M5 serve as usage documentation
Review Criteria Results
Does the plan include a documentation update step?
N/A -- No documentation exists for the dashboard feature components. No updates needed.
Will any existing README files become stale after the changes?
NO -- Verified that no README files reference SummaryCards, QuickActions, RecentActivity, or VehicleAttention.
Are the new components/types documented or self-documenting?
YES -- M1 defines types with clear field descriptions. M2 and M3 provide component prop interfaces and layout specs. Test plan in M5 documents behavior.
Does the plan mention running doc-sync after completion?
N/A -- The doc-sync skill is used when documentation (README files) exists and becomes stale. No dashboard README exists, so doc-sync is not applicable.
Are the sub-issue descriptions clear enough to serve as future reference?
YES -- Each milestone provides:
Type definitions with field descriptions (M1)
Component props interfaces (M2, M3)
Implementation steps with specific details (M4)
Test coverage documenting behavior (M5)
Sub-issues created from these milestones will inherit this clarity.
Verdict: PASS | Next: Plan APPROVED
Rationale: The dashboard redesign is a self-contained frontend component change with no existing documentation to update. The plan provides sufficient inline documentation through type definitions, component specs, and test coverage. No README files reference the components being deleted. The project's documentation strategy does not require feature-specific READMEs for frontend components.
## QR Review: Plan Docs
**Phase**: Plan-Review | **Agent**: Quality Reviewer | **Status**: PASS
---
### Documentation Impact
**Files checked**:
- `frontend/src/features/dashboard/README.md` -- Does NOT exist. No dashboard-specific README to update.
- `frontend/README.md` -- Checked. No references to SummaryCards, QuickActions, RecentActivity, or VehicleAttention components. Generic feature structure description only.
- `docs/README.md` -- Checked. No dashboard-specific documentation. Lists backend feature READMEs only (application features).
- `.ai/context.json` -- Checked. No references to dashboard components in feature registry. Frontend work section points to `frontend/README.md` which is generic.
- `CLAUDE.md` (root) -- Checked. No references to dashboard-specific files.
**Findings**: No existing documentation references the dashboard components being deleted (SummaryCards, QuickActions, RecentActivity, VehicleAttention). The codebase documentation is at the feature capsule level for backend features, not frontend components.
---
### Missing Documentation Steps
**NONE IDENTIFIED.**
The plan is a frontend-only redesign with no backend changes. The project's documentation strategy focuses on:
1. Backend feature capsules (`backend/src/features/{feature}/README.md`)
2. Architecture docs (`docs/PLATFORM-SERVICES.md`, `docs/DATABASE-SCHEMA.md`, etc.)
3. General frontend patterns (`frontend/README.md`)
There are no feature-specific frontend component READMEs in the codebase pattern. The dashboard feature does not have a README.md file.
**New components are self-documenting**:
- `VehicleRosterCard.tsx` -- Component props and layout described in M2
- `ActionBar.tsx` -- Component props described in M3
- Type definitions (`VehicleHealth`, `AttentionItem`, `VehicleRosterData`) in M1 serve as inline documentation
**Code quality ensures discoverability**:
- Barrel exports in `index.ts` (updated in M4) make components discoverable
- TypeScript types provide interface contracts
- Test files in M5 serve as usage documentation
---
### Review Criteria Results
1. **Does the plan include a documentation update step?**
- N/A -- No documentation exists for the dashboard feature components. No updates needed.
2. **Will any existing README files become stale after the changes?**
- NO -- Verified that no README files reference SummaryCards, QuickActions, RecentActivity, or VehicleAttention.
3. **Are the new components/types documented or self-documenting?**
- YES -- M1 defines types with clear field descriptions. M2 and M3 provide component prop interfaces and layout specs. Test plan in M5 documents behavior.
4. **Does the plan mention running doc-sync after completion?**
- N/A -- The doc-sync skill is used when documentation (README files) exists and becomes stale. No dashboard README exists, so doc-sync is not applicable.
5. **Are the sub-issue descriptions clear enough to serve as future reference?**
- YES -- Each milestone provides:
- Type definitions with field descriptions (M1)
- Component props interfaces (M2, M3)
- Implementation steps with specific details (M4)
- Test coverage documenting behavior (M5)
- Sub-issues created from these milestones will inherit this clarity.
---
**Verdict**: PASS | **Next**: Plan APPROVED
**Rationale**: The dashboard redesign is a self-contained frontend component change with no existing documentation to update. The plan provides sufficient inline documentation through type definitions, component specs, and test coverage. No README files reference the components being deleted. The project's documentation strategy does not require feature-specific READMEs for frontend components.
RULE 1 (HIGH): Type name mismatch in M1 specification
Location: M1 (Milestone 1: Types and Vehicle Health Data Layer), step 4
Issue: Plan references MaintenanceScheduleResponse[] as the parameter type for computeVehicleHealth(), but the actual codebase type is MaintenanceSchedule (from maintenance.types.ts, imported in useDashboardData.ts:12). The plan also references DocumentResponse[] but needs verification that this matches the actual document type name.
Failure Mode: Developer implementing M1 may use incorrect type imports, causing TypeScript compilation errors or requiring rework during implementation.
Suggested Fix: Change M1 step 4 to use exact type names from the codebase: computeVehicleHealth(schedules: MaintenanceSchedule[], documents: DocumentRecord[]): { health: VehicleHealth; attentionItems: AttentionItem[] }. The document type is DocumentRecord per documents.types.ts:3-25.
RULE 1 (HIGH): Documents API call pattern incompatible with API signature
Location: Revision 1 (Document Fetching Strategy)
Issue: The revised plan proposes documentsApi.list({ type: 'insurance' }) and documentsApi.list({ type: 'registration' }) as two parallel calls. However, the actual API signature at documents.api.ts:16 shows async list(params?: { vehicleId?: string; type?: string; expiresBefore?: string }) - the type parameter is a single string, not an array. The plan assumes each call fetches ONLY that document type, but the API will return all documents of that type for ALL vehicles the user owns (not scoped to the dashboard's vehicle list).
Failure Mode: Two potential issues: (1) If a user has 100+ vehicles and calls this API, it returns documents for all vehicles even if only showing 10 vehicles on dashboard, creating unnecessary data transfer. (2) The API semantics may not match expectation - need to verify backend implementation filters correctly by single type value.
Suggested Fix: Verify the backend GET /documents endpoint correctly handles type=insurance as a single-value filter (not expecting comma-separated values). If correct, the plan is sound but should clarify that the API returns all user documents of that type (not vehicle-scoped). If the API needs vehicle filtering, the plan should use documentsApi.list({ vehicleId }) in a loop or accept the overhead of fetching all user documents and filtering client-side by the dashboard's vehicle list.
RULE 2 Findings
RULE 2 (SHOULD_FIX): Deprecated hooks should be deleted, not marked @deprecated
Issue: Plan says "keep existing useDashboardSummary and useVehiclesNeedingAttention for now" and mark with @deprecated comment. However, verified that only DashboardScreen.tsx imports these hooks (lines 14, 39-41). Since DashboardScreen is being completely rewritten in M4 to use the new useVehicleRoster() hook, these three derived hooks (useDashboardSummary, useVehiclesNeedingAttention, useRecentActivity) will have ZERO consumers after M4 completes.
Failure Mode: Leaving deprecated code creates maintenance burden and confuses future developers. Dead code accumulates tech debt.
Suggested Fix: Delete all three derived hooks (useDashboardSummary, useVehiclesNeedingAttention, useRecentActivity) in M4 alongside the component cleanup. Update barrel exports in index.ts to remove these hook exports. This aligns with the project standard "Delete old code when replacing it" (CLAUDE.md).
TW Suggestion Evaluation
TW Suggestion 1: Standardize on "health badge" terminology
Status: Keep non-blocking Rationale: The terminology variance ("health indicator", "health badge", "health dot") is contextually appropriate. "Health dot" in the skeleton spec accurately describes the Skeleton variant="circular" width={12} height={12} placeholder. No implementation confusion risk.
TW Suggestion 2: Use actual type names (MaintenanceSchedule vs MaintenanceScheduleResponse)
Status: PROMOTE TO BLOCKING (see RULE 1 finding above) Rationale: This is a RULE 1 violation - type names must match the actual codebase to avoid compilation errors. This is not a minor suggestion but a correctness issue.
TW Suggestion 3: Delete deprecated hooks instead of marking @deprecated
Status: PROMOTE TO BLOCKING (see RULE 2 finding above) Rationale: While categorized as RULE 2 (structural quality), this aligns with the explicit project standard "Delete old code when replacing it". Given zero consumers after M4, keeping deprecated code violates project conventions.
Status: Keep non-blocking Rationale: The plan says "elevation increase + subtle border highlight" but actual GlassCard behavior is hover:shadow-xl hover:-translate-y-0.5. The discrepancy is minor since the developer will use GlassCard as-is (not reimplement hover behavior). The component provides the correct hover effect regardless of description accuracy.
Confirmed: type parameter is a single string. The plan's proposed usage documentsApi.list({ type: 'insurance' }) is syntactically correct.
DocumentRecord type (documents.types.ts:3-25):
Confirmed: Type name is DocumentRecord (not DocumentResponse). Field expirationDate?: string | null; exists at line 18.
MaintenanceSchedule type (useDashboardData.ts:12):
Confirmed: App.tsx only imports DashboardScreen from the dashboard feature barrel export. Does not directly import SummaryCards, QuickActions, RecentActivity, or VehicleAttention. Safe to delete.
Barrel exports (index.ts:5-11):
All four components (SummaryCards, VehicleAttention, QuickActions, RecentActivity) and their skeletons are exported, plus the three derived hooks. Plan correctly updates barrel exports in M4.
Verdict: FAIL | Next: Planner revision required
Required changes:
Fix type names in M1: MaintenanceSchedule not MaintenanceScheduleResponse, DocumentRecord not DocumentResponse (RULE 1 - blocking)
Clarify documents API call semantics: does type=insurance return ALL user insurance documents across all vehicles, or only for dashboard vehicles? If all vehicles, is this acceptable overhead? (RULE 1 - blocking clarification needed)
Change M1 step 7 to DELETE deprecated hooks in M4 instead of marking @deprecated (RULE 2 - blocking per project standard)
## QR Review: Plan Code
**Phase**: Plan-Review | **Agent**: Quality Reviewer | **Status**: FAIL
---
### RULE 0 Findings
**None**
### RULE 1 Findings
#### RULE 1 (HIGH): Type name mismatch in M1 specification
- **Location**: M1 (Milestone 1: Types and Vehicle Health Data Layer), step 4
- **Issue**: Plan references `MaintenanceScheduleResponse[]` as the parameter type for `computeVehicleHealth()`, but the actual codebase type is `MaintenanceSchedule` (from `maintenance.types.ts`, imported in `useDashboardData.ts:12`). The plan also references `DocumentResponse[]` but needs verification that this matches the actual document type name.
- **Failure Mode**: Developer implementing M1 may use incorrect type imports, causing TypeScript compilation errors or requiring rework during implementation.
- **Suggested Fix**: Change M1 step 4 to use exact type names from the codebase: `computeVehicleHealth(schedules: MaintenanceSchedule[], documents: DocumentRecord[]): { health: VehicleHealth; attentionItems: AttentionItem[] }`. The document type is `DocumentRecord` per `documents.types.ts:3-25`.
#### RULE 1 (HIGH): Documents API call pattern incompatible with API signature
- **Location**: Revision 1 (Document Fetching Strategy)
- **Issue**: The revised plan proposes `documentsApi.list({ type: 'insurance' })` and `documentsApi.list({ type: 'registration' })` as two parallel calls. However, the actual API signature at `documents.api.ts:16` shows `async list(params?: { vehicleId?: string; type?: string; expiresBefore?: string })` - the `type` parameter is a single string, not an array. The plan assumes each call fetches ONLY that document type, but the API will return all documents of that type for ALL vehicles the user owns (not scoped to the dashboard's vehicle list).
- **Failure Mode**: Two potential issues: (1) If a user has 100+ vehicles and calls this API, it returns documents for all vehicles even if only showing 10 vehicles on dashboard, creating unnecessary data transfer. (2) The API semantics may not match expectation - need to verify backend implementation filters correctly by single type value.
- **Suggested Fix**: Verify the backend `GET /documents` endpoint correctly handles `type=insurance` as a single-value filter (not expecting comma-separated values). If correct, the plan is sound but should clarify that the API returns all user documents of that type (not vehicle-scoped). If the API needs vehicle filtering, the plan should use `documentsApi.list({ vehicleId })` in a loop or accept the overhead of fetching all user documents and filtering client-side by the dashboard's vehicle list.
### RULE 2 Findings
#### RULE 2 (SHOULD_FIX): Deprecated hooks should be deleted, not marked @deprecated
- **Location**: M1 step 7, echoing TW suggestion #3
- **Issue**: Plan says "keep existing `useDashboardSummary` and `useVehiclesNeedingAttention` for now" and mark with `@deprecated` comment. However, verified that only `DashboardScreen.tsx` imports these hooks (lines 14, 39-41). Since DashboardScreen is being completely rewritten in M4 to use the new `useVehicleRoster()` hook, these three derived hooks (`useDashboardSummary`, `useVehiclesNeedingAttention`, `useRecentActivity`) will have ZERO consumers after M4 completes.
- **Failure Mode**: Leaving deprecated code creates maintenance burden and confuses future developers. Dead code accumulates tech debt.
- **Suggested Fix**: Delete all three derived hooks (`useDashboardSummary`, `useVehiclesNeedingAttention`, `useRecentActivity`) in M4 alongside the component cleanup. Update barrel exports in `index.ts` to remove these hook exports. This aligns with the project standard "Delete old code when replacing it" (CLAUDE.md).
### TW Suggestion Evaluation
#### TW Suggestion 1: Standardize on "health badge" terminology
**Status**: Keep non-blocking
**Rationale**: The terminology variance ("health indicator", "health badge", "health dot") is contextually appropriate. "Health dot" in the skeleton spec accurately describes the `Skeleton variant="circular" width={12} height={12}` placeholder. No implementation confusion risk.
#### TW Suggestion 2: Use actual type names (MaintenanceSchedule vs MaintenanceScheduleResponse)
**Status**: PROMOTE TO BLOCKING (see RULE 1 finding above)
**Rationale**: This is a RULE 1 violation - type names must match the actual codebase to avoid compilation errors. This is not a minor suggestion but a correctness issue.
#### TW Suggestion 3: Delete deprecated hooks instead of marking @deprecated
**Status**: PROMOTE TO BLOCKING (see RULE 2 finding above)
**Rationale**: While categorized as RULE 2 (structural quality), this aligns with the explicit project standard "Delete old code when replacing it". Given zero consumers after M4, keeping deprecated code violates project conventions.
#### TW Suggestion 4: GlassCard hover behavior description accuracy
**Status**: Keep non-blocking
**Rationale**: The plan says "elevation increase + subtle border highlight" but actual GlassCard behavior is `hover:shadow-xl hover:-translate-y-0.5`. The discrepancy is minor since the developer will use GlassCard as-is (not reimplement hover behavior). The component provides the correct hover effect regardless of description accuracy.
---
### Verification Against Actual Source Files
**Documents API signature** (`documents.api.ts:16-18`):
```typescript
async list(params?: { vehicleId?: string; type?: string; expiresBefore?: string }) {
const res = await apiClient.get<DocumentRecord[]>('/documents', { params });
return res.data;
}
```
Confirmed: `type` parameter is a single string. The plan's proposed usage `documentsApi.list({ type: 'insurance' })` is syntactically correct.
**DocumentRecord type** (`documents.types.ts:3-25`):
Confirmed: Type name is `DocumentRecord` (not `DocumentResponse`). Field `expirationDate?: string | null;` exists at line 18.
**MaintenanceSchedule type** (`useDashboardData.ts:12`):
```typescript
import { MaintenanceSchedule } from '../../maintenance/types/maintenance.types';
```
Confirmed: Type name is `MaintenanceSchedule` (not `MaintenanceScheduleResponse`).
**GlassCard onClick behavior** (`GlassCard.tsx:33`):
```typescript
onClick && 'cursor-pointer hover:shadow-xl hover:-translate-y-0.5 transition',
```
Confirmed: Hover behavior is shadow + translate (not MUI elevation). Non-blocking since developer uses component as-is.
**DashboardScreen imports** (`DashboardScreen.tsx:10-14`):
```typescript
import { SummaryCards, SummaryCardsSkeleton } from './SummaryCards';
import { VehicleAttention, VehicleAttentionSkeleton } from './VehicleAttention';
import { QuickActions, QuickActionsSkeleton } from './QuickActions';
import { RecentActivity, RecentActivitySkeleton } from './RecentActivity';
import { useDashboardSummary, useVehiclesNeedingAttention, useRecentActivity } from '../hooks/useDashboardData';
```
Confirmed: Only DashboardScreen imports these components and hooks. Safe to delete in M4.
**App.tsx imports** (line 92):
```typescript
import { DashboardScreen as DashboardFeature } from './features/dashboard';
```
Confirmed: App.tsx only imports `DashboardScreen` from the dashboard feature barrel export. Does not directly import SummaryCards, QuickActions, RecentActivity, or VehicleAttention. Safe to delete.
**Barrel exports** (`index.ts:5-11`):
All four components (SummaryCards, VehicleAttention, QuickActions, RecentActivity) and their skeletons are exported, plus the three derived hooks. Plan correctly updates barrel exports in M4.
---
*Verdict*: **FAIL** | *Next*: **Planner revision required**
**Required changes**:
1. Fix type names in M1: `MaintenanceSchedule` not `MaintenanceScheduleResponse`, `DocumentRecord` not `DocumentResponse` (RULE 1 - blocking)
2. Clarify documents API call semantics: does `type=insurance` return ALL user insurance documents across all vehicles, or only for dashboard vehicles? If all vehicles, is this acceptable overhead? (RULE 1 - blocking clarification needed)
3. Change M1 step 7 to DELETE deprecated hooks in M4 instead of marking @deprecated (RULE 2 - blocking per project standard)
The useDashboardData.ts hook already imports MaintenanceSchedule (not MaintenanceScheduleResponse). The DocumentRecord type has fields expirationDate: string | null and documentType: DocumentType.
Revision 6: Documents API Clarification (RULE 1 fix)
documentsApi.list({ type: 'insurance' }) calls GET /documents?type=insurance which is user-scoped (filtered by authenticated user's user_id on the backend). It returns ALL insurance documents across ALL of the user's vehicles, which is the desired behavior -- we need per-user, cross-vehicle document data to compute health per vehicle.
The returned DocumentRecord[] includes vehicleId field, which we group by to map documents to their respective vehicles. This is exactly the same user-scoping pattern as vehiclesApi.getAll() and is correctly efficient (2 API calls for all document types vs N calls per vehicle).
Before: M1 step 7 said "Keep existing derived hooks, mark with @deprecated" After: In M4, when rewiring DashboardScreen, delete these hooks from useDashboardData.ts:
useDashboardSummary() -- no consumers after M4
useVehiclesNeedingAttention() -- replaced by useVehicleRoster()
useRecentActivity() -- no consumers after M4
Also delete the associated types from types/index.ts if they have no consumers:
DashboardSummary -- delete (was only used by useDashboardSummary)
RecentActivityItem -- delete (was only used by useRecentActivity)
VehicleNeedingAttention -- delete (was only used by useVehiclesNeedingAttention)
The existing useDashboardData() hook is retained as the core fetching mechanism, now returning vehicle roster data instead.
Per CLAUDE.md: "Delete old code when replacing it."
TW Non-Blocking Suggestions Accepted
Terminology: Standardized on "health badge" throughout. The skeleton spec "health dot" is renamed to "health badge placeholder" for consistency.
## Plan Revision 2: Addressing QR Plan-Code Findings
**Phase**: Planning | **Agent**: Planner | **Status**: REVISED
---
### Revision 5: Type Name Corrections (RULE 1 fix)
The plan incorrectly referenced types that don't exist. Corrections:
| Plan Said | Actual Type | Location |
|-----------|-------------|----------|
| `MaintenanceScheduleResponse[]` | `MaintenanceSchedule` | `frontend/src/features/maintenance/types/maintenance.types.ts` |
| `DocumentResponse[]` | `DocumentRecord` | `frontend/src/features/documents/types/documents.types.ts` |
Updated `computeVehicleHealth` signature:
```typescript
export function computeVehicleHealth(
schedules: MaintenanceSchedule[],
documents: DocumentRecord[],
): { health: VehicleHealth; attentionItems: AttentionItem[] }
```
The `useDashboardData.ts` hook already imports `MaintenanceSchedule` (not `MaintenanceScheduleResponse`). The `DocumentRecord` type has fields `expirationDate: string | null` and `documentType: DocumentType`.
---
### Revision 6: Documents API Clarification (RULE 1 fix)
`documentsApi.list({ type: 'insurance' })` calls `GET /documents?type=insurance` which is user-scoped (filtered by authenticated user's `user_id` on the backend). It returns ALL insurance documents across ALL of the user's vehicles, which is the desired behavior -- we need per-user, cross-vehicle document data to compute health per vehicle.
The returned `DocumentRecord[]` includes `vehicleId` field, which we group by to map documents to their respective vehicles. This is exactly the same user-scoping pattern as `vehiclesApi.getAll()` and is correctly efficient (2 API calls for all document types vs N calls per vehicle).
---
### Revision 7: Delete Deprecated Hooks (RULE 2 fix, per project standard)
**Before**: M1 step 7 said "Keep existing derived hooks, mark with `@deprecated`"
**After**: In M4, when rewiring DashboardScreen, **delete** these hooks from `useDashboardData.ts`:
- `useDashboardSummary()` -- no consumers after M4
- `useVehiclesNeedingAttention()` -- replaced by `useVehicleRoster()`
- `useRecentActivity()` -- no consumers after M4
Also delete the associated types from `types/index.ts` if they have no consumers:
- `DashboardSummary` -- delete (was only used by `useDashboardSummary`)
- `RecentActivityItem` -- delete (was only used by `useRecentActivity`)
- `VehicleNeedingAttention` -- delete (was only used by `useVehiclesNeedingAttention`)
The existing `useDashboardData()` hook is retained as the core fetching mechanism, now returning vehicle roster data instead.
Per CLAUDE.md: "Delete old code when replacing it."
---
### TW Non-Blocking Suggestions Accepted
1. **Terminology**: Standardized on "health badge" throughout. The skeleton spec "health dot" is renamed to "health badge placeholder" for consistency.
2. **GlassCard hover**: Corrected to "shadow increase + vertical translate" (actual: `hover:shadow-xl hover:-translate-y-0.5`).
---
*Verdict*: REVISED | *Next*: QR plan-code re-review
This eliminates the TypeScript compilation error risk. The revision also provides a verification table showing the corrections and confirms the imports and field names.
Revision 6 clarifies the API semantics: GET /documents?type=insurance is user-scoped (filters by authenticated user's user_id on backend) and returns ALL documents of that type across ALL the user's vehicles. The returned DocumentRecord[] includes a vehicleId field for client-side grouping.
This clarification confirms the approach is correct:
User-scoped queries are efficient (2 API calls total vs N per vehicle)
Cross-vehicle data is exactly what's needed to compute per-vehicle health
Matches the existing pattern used by vehiclesApi.getAll() (user-scoped)
The revision addresses the blocking concern about API semantics and overhead. The approach is sound for single-tenant architecture.
Revision 7 changes the plan from marking hooks @deprecated to deleting them in M4. Specifically:
Hooks to delete (from useDashboardData.ts):
useDashboardSummary()
useVehiclesNeedingAttention()
useRecentActivity()
Types to delete (from types/index.ts):
DashboardSummary
RecentActivityItem
VehicleNeedingAttention
The revision explicitly cites the project standard: "Delete old code when replacing it" (CLAUDE.md). Since these hooks have zero consumers after DashboardScreen is rewritten in M4, deletion is the correct approach.
New Issues
None identified.
The revisions are complete and internally consistent. Additional improvements noted:
TW terminology suggestion accepted: Standardized on "health badge" throughout
TW hover description corrected: Updated GlassCard hover description to match actual implementation
The type corrections, API semantics clarification, and hook deletion strategy fully address all three blocking findings. The plan is now ready for implementation.
Verdict: PASS | Next: Plan APPROVED
## QR Review: Plan Code (Re-review)
**Phase**: Plan-Review | **Agent**: Quality Reviewer | **Status**: PASS
---
### Revision Verification
#### Finding 1: RULE 1 (HIGH) - Type name mismatch in M1 specification
**STATUS**: ADDRESSED
The revision correctly updates the type names:
- Changed `MaintenanceScheduleResponse[]` to `MaintenanceSchedule` (actual type from `maintenance.types.ts`)
- Changed `DocumentResponse[]` to `DocumentRecord` (actual type from `documents.types.ts`)
The corrected function signature now matches the actual codebase types:
```typescript
export function computeVehicleHealth(
schedules: MaintenanceSchedule[],
documents: DocumentRecord[],
): { health: VehicleHealth; attentionItems: AttentionItem[] }
```
This eliminates the TypeScript compilation error risk. The revision also provides a verification table showing the corrections and confirms the imports and field names.
#### Finding 2: RULE 1 (HIGH) - Documents API call pattern semantics clarity
**STATUS**: ADDRESSED
Revision 6 clarifies the API semantics: `GET /documents?type=insurance` is user-scoped (filters by authenticated user's `user_id` on backend) and returns ALL documents of that type across ALL the user's vehicles. The returned `DocumentRecord[]` includes a `vehicleId` field for client-side grouping.
This clarification confirms the approach is correct:
- User-scoped queries are efficient (2 API calls total vs N per vehicle)
- Cross-vehicle data is exactly what's needed to compute per-vehicle health
- Matches the existing pattern used by `vehiclesApi.getAll()` (user-scoped)
The revision addresses the blocking concern about API semantics and overhead. The approach is sound for single-tenant architecture.
#### Finding 3: RULE 2 (SHOULD_FIX) - Delete deprecated hooks instead of marking @deprecated
**STATUS**: ADDRESSED
Revision 7 changes the plan from marking hooks `@deprecated` to **deleting** them in M4. Specifically:
**Hooks to delete** (from `useDashboardData.ts`):
- `useDashboardSummary()`
- `useVehiclesNeedingAttention()`
- `useRecentActivity()`
**Types to delete** (from `types/index.ts`):
- `DashboardSummary`
- `RecentActivityItem`
- `VehicleNeedingAttention`
The revision explicitly cites the project standard: "Delete old code when replacing it" (CLAUDE.md). Since these hooks have zero consumers after DashboardScreen is rewritten in M4, deletion is the correct approach.
---
### New Issues
**None identified.**
The revisions are complete and internally consistent. Additional improvements noted:
1. **TW terminology suggestion accepted**: Standardized on "health badge" throughout
2. **TW hover description corrected**: Updated GlassCard hover description to match actual implementation
The type corrections, API semantics clarification, and hook deletion strategy fully address all three blocking findings. The plan is now ready for implementation.
---
**Verdict**: PASS | **Next**: Plan APPROVED
CONDITIONAL_PASS - Implementation is high quality with RULE 0 compliance and comprehensive test coverage. Code patterns for responsive design are correct.
Before merging: Complete manual verification of mobile (320px, 768px) and desktop (1920px) layouts in browser DevTools. Confirm touch targets, grid behavior, and visual quality at all breakpoints.
Next: Manual responsive testing -> PR approval -> Merge to main
## QR Review: Post-Implementation
**Phase**: Review | **Agent**: Quality Reviewer | **Status**: CONDITIONAL_PASS
---
### Code Quality Assessment
#### RULE 0 (CRITICAL) - Production Reliability
**Status**: PASS
No RULE 0 violations detected:
- Error boundaries implemented in `DashboardScreen.tsx:68-94` with proper error UI
- Loading states handled with skeleton fallbacks
- Graceful degradation for document API failures (`useDashboardData.ts:59-61`)
- No unhandled promise rejections
- Type safety maintained throughout
- No security vulnerabilities introduced
---
#### RULE 1 (HIGH) - Project Conformance
**Status**: CONDITIONAL_PASS (requires manual verification)
**BLOCKING - Mobile+Desktop Manual Testing Required**:
The implementation includes responsive design patterns:
- Grid layout: `grid-cols-1 sm:grid-cols-2` (DashboardScreen.tsx:29, 153)
- MUI breakpoint detection: `useMediaQuery(theme.breakpoints.down('sm'))` (DashboardScreen.tsx:55)
- Responsive dialog sizing: fullScreen on mobile (DashboardScreen.tsx:167)
However, **no manual verification provided** for actual viewport testing:
- [ ] Mobile 320px - verify single column, touch targets >= 44px
- [ ] Mobile 768px - verify 2-column grid behavior
- [ ] Desktop 1920px - verify layout, spacing, no overflow
**ACTION REQUIRED**: Test in browser DevTools at all three breakpoints and confirm layout quality before merging.
**Other RULE 1 Checks**:
- Naming conventions: PASS (camelCase for TS, component names PascalCase)
- TypeScript type-check: PASS (zero errors)
- Linting: PASS with warnings (warnings are pre-existing, not introduced by #196)
- Old code deletion: PASS (SummaryCards, QuickActions, RecentActivity, VehicleAttention removed)
- CI/CD: Not executed (local review only)
---
#### RULE 2 (SHOULD_FIX) - Structural Quality
**Status**: PASS with recommendations
**Recommendations** (non-blocking):
1. **Test coverage gap**: Consider adding visual regression tests or responsive layout tests to verify grid behavior at breakpoints automatically
2. **Linting warnings**: Pre-existing warnings in App.tsx, Auth0Provider.tsx, etc. Consider cleanup in future sprint
**Positive observations**:
- Clean separation of concerns (computeVehicleHealth is a pure function)
- Comprehensive unit test coverage for health computation logic (29 tests passing)
- No god objects or duplicate logic detected
- No dead code or orphaned imports
---
### Acceptance Criteria Verification
| Criterion | Status | Evidence |
|-----------|--------|----------|
| Dashboard shows vehicle cards with year/make/model, health, attention items, odometer | ✅ PASS | VehicleRosterCard.tsx:47-103 |
| Health indicator computes green/yellow/red from maintenance + document expiry | ✅ PASS | computeVehicleHealth.ts:13-71, tests confirm logic |
| Clicking vehicle card navigates to detail page | ✅ PASS | DashboardScreen.tsx:61-66 |
| Cards have hover states | ✅ PASS | GlassCard component provides hover elevation |
| Compact action bar replaces Quick Actions | ✅ PASS | ActionBar.tsx:15-38, DashboardScreen.tsx:149 |
| Old components removed | ✅ PASS | SummaryCards, QuickActions, RecentActivity, VehicleAttention deleted |
| Works on desktop (2-column) and mobile (single column) | ⚠️ CONDITIONAL | Code pattern correct, manual testing required |
| Empty state (0 vehicles) renders | ✅ PASS | DashboardScreen.tsx:111-136, test confirms |
| Loading skeletons updated | ✅ PASS | RosterSkeleton component (DashboardScreen.tsx:28-47) |
| Tests updated/replaced | ✅ PASS | 4 test suites, 29 tests passing |
| Linting, type-check, tests pass | ✅ PASS | Type-check: 0 errors, Tests: 29/29 passing |
---
### Test Results
```
Dashboard Test Suite:
✅ DashboardScreen.test.tsx - 5 tests passing
✅ ActionBar.test.tsx - 3 tests passing
✅ VehicleRosterCard.test.tsx - 7 tests passing
✅ useDashboardData.test.ts - 14 tests passing
Total: 29/29 tests passing
```
**Coverage**: Health computation logic fully tested (green/yellow/red states, sorting, edge cases).
---
### Files Reviewed
**New files (8)**:
- `frontend/src/features/dashboard/components/VehicleRosterCard.tsx`
- `frontend/src/features/dashboard/components/ActionBar.tsx`
- `frontend/src/features/dashboard/utils/computeVehicleHealth.ts`
- `frontend/src/features/dashboard/hooks/__tests__/useDashboardData.test.ts`
- `frontend/src/features/dashboard/components/__tests__/VehicleRosterCard.test.tsx`
- `frontend/src/features/dashboard/components/__tests__/ActionBar.test.tsx`
- `frontend/src/features/dashboard/components/__tests__/DashboardScreen.test.tsx`
- `frontend/src/core/api/__mocks__/client.ts`
**Modified files (5)**:
- `frontend/src/features/dashboard/types/index.ts`
- `frontend/src/features/dashboard/hooks/useDashboardData.ts`
- `frontend/src/features/dashboard/components/DashboardScreen.tsx`
- `frontend/src/features/dashboard/pages/DashboardPage.tsx`
- `frontend/src/features/dashboard/index.ts`
**Deleted files (4)**:
- `frontend/src/features/dashboard/components/SummaryCards.tsx`
- `frontend/src/features/dashboard/components/QuickActions.tsx`
- `frontend/src/features/dashboard/components/RecentActivity.tsx`
- `frontend/src/features/dashboard/components/VehicleAttention.tsx`
---
### Verdict
**CONDITIONAL_PASS** - Implementation is high quality with RULE 0 compliance and comprehensive test coverage. Code patterns for responsive design are correct.
**Before merging**: Complete manual verification of mobile (320px, 768px) and desktop (1920px) layouts in browser DevTools. Confirm touch targets, grid behavior, and visual quality at all breakpoints.
**Next**: Manual responsive testing -> PR approval -> Merge to main
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Replace the current dashboard (summary stat cards + empty activity feed + redundant quick actions) with a vehicle-centric roster layout that shows actionable health data per vehicle. The current dashboard tells users almost nothing -- three numbers (vehicle count, maintenance count, fuel log count), an empty activity section, and navigation buttons that duplicate the sidebar.
Problem Statement
The current dashboard has five UX problems identified during audit:
cursor: auto, no click-through, CTAs only appear when value is 0Choosing a vehicle-centric layout eliminates problems 1, 2, and 4 entirely, and addresses 3 and 5 as part of the redesign.
Design Specification
Layout Structure
Vehicle Card Design
Each card displays:
maintenance_schedules.nextDueDate < now)documents.expirationDatefor typesinsurance,registration)vehicles.odometerReading/garage/vehicles/{id}Compact Action Bar
Slim horizontal row below the heading with two buttons:
Replaces the current Quick Actions section entirely. On mobile, buttons may use icon-only or compact layout.
Heading
Replace generic "Dashboard" with "Your Fleet" (or similar). Remove redundant "Welcome back, email" from header area since email already shows in sidebar/profile.
Empty State (0 vehicles)
Keep existing empty state behavior (already handled in
DashboardScreen.tsx:84-108) -- shows welcome message with "Add Your First Vehicle" CTA.Data Requirements
Current Data (already fetched in
useDashboardData)New Data Needed
documentstable filtered byvehicle_idanddocumentType IN ('insurance', 'registration')whereexpirationDate IS NOT NULL, to surface upcoming renewalsAPI Considerations
documentsApi) may need a method to fetch documents by vehicle with expiry filtering, or the dashboard hook can fetch all user documents and filter client-sideFiles Affected
New Components
frontend/src/features/dashboard/components/VehicleRosterCard.tsx-- per-vehicle cardfrontend/src/features/dashboard/components/ActionBar.tsx-- compact action barModified
frontend/src/features/dashboard/components/DashboardScreen.tsx-- new layout compositionfrontend/src/features/dashboard/hooks/useDashboardData.ts-- extend to fetch document expiry datafrontend/src/features/dashboard/types/index.ts(or similar) -- new types for vehicle healthRemoved/Deprecated
frontend/src/features/dashboard/components/SummaryCards.tsx-- replaced by vehicle cardsfrontend/src/features/dashboard/components/QuickActions.tsx-- replaced by action barfrontend/src/features/dashboard/components/RecentActivity.tsx-- activity context embedded in vehicle cardsAcceptance Criteria
Technical Notes
documentstable withexpirationDatefield anddocumentTypeenum (insurance,registration)useDashboardData.ts:84-113vehicles.imageUrl/imageStorageBucketGlassCardcomponent should be reused for card styling consistencyPlan: Dashboard Redesign with Vehicle-Centric Layout
Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW
Codebase Analysis Summary
Current state: Dashboard has 8 files (0 tests). Layout is flat: SummaryCards (3 metric counters), VehicleAttention (overdue list), RecentActivity (merged feed), QuickActions (4 nav buttons). DashboardScreen is shared between mobile and desktop via callback props.
Key findings:
listByVehicle(vehicleId)withuseDocumentsByVehiclehook -- no backend work neededVehicleImagecomponent exists with 3-tier fallback (uploaded photo, make logo, color placeholder)GlassCardcomponent atshared-minimal/components/mobile/GlassCard.tsx-- click behavior adds hover elevation + cursor pointeruseDashboardData.ts:84-118-- fetches schedules per vehiclegetVehicleLabel(),getVehicleSubtitle()incore/utils/vehicleDisplay.tsgrid-cols-1 sm:grid-cols-2for 2-column desktop gridsborderRadius: 20for cards, 44px min touch targets, Ferrari-inspired color paletteNo backend changes required. All data sources already exist. This is a frontend-only feature.
Architecture Decisions
useDashboardDatahookdocumentsApi.list()with client-side filteringlistByVehicleper vehicle. Filter to insurance/registration with non-null expirationDate client-side.computeVehicleHealth(schedules, documents)as an exportable utility enables unit testing without React Query wrappers.grid-cols-1 sm:grid-cols-2Sub-Issues (milestones map 1:1)
types/index.ts,hooks/useDashboardData.tscomponents/VehicleRosterCard.tsx(new)components/ActionBar.tsx(new)components/DashboardScreen.tsx,index.ts, delete 3 filesBranch:
issue-196-redesign-dashboard-vehicle-centricPR: One PR targeting main, body lists
Fixes #196, Fixes #197, Fixes #198, Fixes #199, Fixes #200, Fixes #201Milestone 1: Types and Vehicle Health Data Layer (#197)
Agent: Feature Agent (frontend data layer)
Files modified:
frontend/src/features/dashboard/types/index.tsfrontend/src/features/dashboard/hooks/useDashboardData.tsType definitions to add:
Hook changes:
documentsApi.list()call to the parallel fetch inuseDashboardData(alongside vehicles and fuelLogs). Filter client-side todocumentType in ['insurance', 'registration']with non-nullexpirationDate.fuelLogsApi.getUserFuelLogs()from the fetch (no longer displayed on dashboard).vehicleIdusing a Map.computeVehicleHealth(schedules: MaintenanceScheduleResponse[], documents: DocumentResponse[]): { health: VehicleHealth; attentionItems: AttentionItem[] }pure function.nextDueDate < now)expirationDate < now)useVehicleRoster()derived hook returningVehicleRosterData[].useDashboardSummaryanduseVehiclesNeedingAttentionfor now (they may still be imported elsewhere). Mark with@deprecatedcomment if no longer needed after M4.Commit:
feat: add vehicle health types and roster data hook (refs #197)Milestone 2: VehicleRosterCard Component (#198)
Agent: Frontend Agent
Files created:
frontend/src/features/dashboard/components/VehicleRosterCard.tsxComponent design:
Layout (inside GlassCard):
Implementation details:
GlassCardwithonClick={() => onClick(data.vehicle.id)}VehicleImage(height 48, compact), vehicle label viagetVehicleLabel(), health badgebg-emerald-500(light) /bg-emerald-400(dark)bg-amber-500/bg-amber-400bg-red-500/bg-red-400text-red-500with warning icontext-amber-600with clock icontext-gray-500(secondary text)text-sm text-secondary, formatted withtoLocaleString()Commit:
feat: add VehicleRosterCard component (refs #198)Milestone 3: ActionBar Component (#199)
Agent: Frontend Agent
Files created:
frontend/src/features/dashboard/components/ActionBar.tsxComponent design:
Implementation:
flexrow withgap-2,justify-endvariant="contained",size="small",startIcon={<Add />}variant="outlined",size="small",startIcon={<LocalGasStation />}smbreakpoint): Buttons remain text+icon but usesize="small"for compactness@mui/icons-materialicons (already in project dependencies)Commit:
feat: add dashboard ActionBar component (refs #199)Milestone 4: DashboardScreen Composition and Cleanup (#200)
Agent: Frontend Agent
Files modified:
frontend/src/features/dashboard/components/DashboardScreen.tsxfrontend/src/features/dashboard/index.tsFiles deleted:
frontend/src/features/dashboard/components/SummaryCards.tsxfrontend/src/features/dashboard/components/QuickActions.tsxfrontend/src/features/dashboard/components/RecentActivity.tsxDashboardScreen new layout:
Implementation steps:
Typography variant="h4")SummaryCards+QuickActions+RecentActivitywith:ActionBarnext to heading (flex row: heading left, action bar right)div.grid.grid-cols-1.sm:grid-cols-2.gap-4useVehicleRoster()data toVehicleRosterCardcomponentsPendingAssociationBannerandPendingReceiptsdialog unchangedonClickto existing navigation: desktop usesnavigate('/garage/vehicles/${id}'), mobile usesonVehicleClick?.(vehicle)index.tsbarrel exports: RemoveSummaryCards,QuickActions,RecentActivityand their skeletons. AddVehicleRosterCard,ActionBar.DashboardPage.tsxandApp.tsx)Commit:
feat: rewire DashboardScreen with vehicle roster layout (refs #200)Milestone 5: Tests (#201)
Agent: Frontend Agent
Files created:
frontend/src/features/dashboard/hooks/__tests__/useDashboardData.test.tsfrontend/src/features/dashboard/components/__tests__/VehicleRosterCard.test.tsxfrontend/src/features/dashboard/components/__tests__/ActionBar.test.tsxfrontend/src/features/dashboard/components/__tests__/DashboardScreen.test.tsxTest plan:
useDashboardData.test.ts-- Unit test the purecomputeVehicleHealthfunction:VehicleRosterCard.test.tsx:ActionBar.test.tsx:DashboardScreen.test.tsx:Testing approach: Mock
useDashboardData/useVehicleRosterhooks. Use@testing-library/reactwithrender,screen,fireEvent. Pure functions tested directly without React wrappers.Commit:
test: add dashboard redesign tests (refs #201)Final Validation
After all milestones complete:
npm run lint-- zero errorsnpm run type-check-- zero errorsnpm test-- all passVerdict: AWAITING_REVIEW | Next: QR plan-completeness review
QR Review: Plan Completeness
Phase: Plan-Review | Agent: Quality Reviewer | Status: FAIL
Checklist Results
1. Coverage: Does every acceptance criterion have a corresponding milestone/implementation step?
FAIL - Missing coverage for loading skeletons acceptance criterion. Plan mentions loading skeletons in M4 but doesn't detail the skeleton design for the new vehicle roster layout.
2. File inventory: Are all files to create, modify, and delete explicitly listed?
PASS - All files are accounted for:
3. Dependency ordering: Are milestone dependencies correct?
PASS - M1 (data layer) -> M2/M3 (components) -> M4 (composition) -> M5 (tests) is sound. M2 and M3 can proceed in parallel.
4. Type safety: Are proposed type definitions complete?
PASS - AttentionItem and VehicleRosterData types look complete. VehicleHealth union type is appropriate.
5. Data flow: Is the data fetching strategy sound?
FAIL - See RULE 1 findings below. The plan proposes calling
documentsApi.list()without filtering, but the API supports filter params that would reduce payload size.6. Mobile + Desktop: Does the plan address both viewports?
PASS - Explicitly states
grid-cols-1 sm:grid-cols-2for responsive grid. M2 and M3 mention mobile behavior.7. Existing behavior preservation: Does the plan preserve empty state, PendingAssociationBanner, etc?
PASS - M4 explicitly preserves empty state (lines 84-108), PendingAssociationBanner, and PendingReceipts dialog.
8. Deletion safety: Are files being deleted safe to remove?
PASS - Verified that SummaryCards, QuickActions, and RecentActivity are only imported by DashboardScreen.tsx. The plan correctly updates index.ts barrel exports.
9. Test coverage: Does the test plan cover key behaviors?
PASS - M5 test plan covers pure function (computeVehicleHealth), component behaviors, and DashboardScreen integration. Uses proper testing patterns.
10. RULE 0 (Critical): Any production reliability risks?
PASS - No unhandled errors, security issues, or resource exhaustion risks identified.
11. RULE 1 (High): Does the plan follow project standards?
FAIL - See findings below.
12. RULE 2 (Should-fix): Any structural quality concerns?
PASS - No god objects or duplication. Pure function pattern for health computation is good architectural choice.
Findings
RULE 1 (HIGH): Inefficient document fetching strategy
useDashboardDatahook changesdocumentsApi.list()filter params to request ONLYdocumentType in ['insurance', 'registration']with non-nullexpirationDate.documentsApi.list({ type: 'insurance' })anddocumentsApi.list({ type: 'registration' })in parallel, then filter client-side for non-null expirationDate. Alternatively, check if the backend supports multiple types in a single filter (e.g.,type=insurance,registration) and use that. The API signature atdocuments.api.ts:16showsparams?: { vehicleId?: string; type?: string; expiresBefore?: string }- thetypeparam can be used.RULE 1 (HIGH): Missing acceptance criteria coverage - loading skeletons
RULE 2 (SHOULD_FIX): Missing error handling detail for document fetch
useDashboardDatahook changesMissing Items
typefilter param.Verdict: FAIL | Next: Planner revision required
Required changes:
typefilter param (RULE 1 - blocking)Plan Revision: Addressing QR Plan-Completeness Findings
Phase: Planning | Agent: Planner | Status: REVISED
Revision 1: Document Fetching Strategy (RULE 1 fix)
Before:
documentsApi.list()fetching all documents, filter client-sideAfter: Two parallel calls with server-side type filtering:
This uses the existing
typequery parameter onGET /documentsto avoid fetching manuals and other irrelevant document types. Two small filtered queries instead of one large unfiltered query.Revision 2: Loading Skeleton Specification (RULE 1 fix)
Added to Milestone 4 (DashboardScreen composition):
Skeleton design: When
isLoadingis true, render 4 GlassCard skeletons in the samegrid-cols-1 sm:grid-cols-2 gap-4grid. Each skeleton card contains:Skeleton variant="circular" width={48} height={48}(image placeholder) +Skeleton variant="text" width="60%"(vehicle name) +Skeleton variant="circular" width={12} height={12}(health dot)Skeleton variant="text" width="80%"(attention item placeholders)Skeleton variant="text" width="30%"(odometer placeholder)Uses MUI
Skeletoncomponent for consistency with existing skeleton patterns in the codebase.Revision 3: VehicleAttention Component Disposition
VehicleAttention.tsxwill be deleted in Milestone 4. The vehicle roster cards embed attention items inline per vehicle, making the separate aggregated attention list redundant.Updated M4 files deleted list:
frontend/src/features/dashboard/components/SummaryCards.tsxfrontend/src/features/dashboard/components/QuickActions.tsxfrontend/src/features/dashboard/components/RecentActivity.tsxfrontend/src/features/dashboard/components/VehicleAttention.tsx(added)Barrel exports in
index.tswill also removeVehicleAttentionandVehicleAttentionSkeleton.Revision 4: Error Handling Strategy (Graceful Degradation)
Added to Milestone 1 (data layer):
If the documents API calls fail, the dashboard gracefully degrades:
Implementation: Wrap the document fetch calls in try/catch within the
useDashboardDataquery function. On failure, setexpiryDocs = []and proceed. This matches the pattern used elsewhere in the app where supplementary data failures don't block primary content.Verdict: REVISED | Next: QR plan-completeness re-review
QR Review: Plan Completeness (Re-review)
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
Revision Verification
Finding 1: Inefficient document fetching strategy
STATUS: ADDRESSED
The revision correctly changes from
documentsApi.list()(unfiltered) to two parallel filtered calls:This leverages the existing
typequery parameter on the documents API to avoid fetching irrelevant document types (manuals, receipts, etc.). Server-side filtering reduces payload size and network overhead. The approach is sound.Finding 2: Missing acceptance criteria coverage - loading skeletons
STATUS: ADDRESSED
Revision 2 adds explicit skeleton design specification to M4:
grid-cols-1 sm:grid-cols-2 gap-4gridSkeletoncomponent for consistencyThis provides sufficient implementation detail for the developer executing M4. No design decisions left open.
Finding 3: VehicleAttention component fate
STATUS: ADDRESSED
Revision 3 explicitly adds
VehicleAttention.tsxto the deletion list in M4 with clear rationale: "vehicle roster cards embed attention items inline per vehicle, making the separate aggregated attention list redundant."Also updates barrel exports to remove
VehicleAttentionandVehicleAttentionSkeleton. This completes the file inventory.Finding 4: Error handling strategy (RULE 2 - graceful degradation)
STATUS: ADDRESSED
Revision 4 adds graceful degradation strategy to M1:
expiryDocs = []and continueThis is appropriate for supplementary data. Prevents dashboard failure due to documents permission issues or API errors while maintaining core functionality (vehicle + maintenance display).
New Issues
None identified.
The revisions are internally consistent and don't introduce contradictions or new gaps. The sub-issue decomposition, milestone dependencies, type definitions, and test plan remain sound as originally proposed.
Verdict: PASS | Next: TW plan-scrub
TW Review: Plan Scrub
Phase: Plan-Review | Agent: Technical Writer | Status: PASS
Scrub Results
1. Clarity
PASS -- Each milestone is unambiguous and self-contained. M1 specifies exact type definitions, hook changes with numbered steps, and health logic rules. M2 provides component props interface, layout diagram, and per-element implementation details (colors, icons, text styling). M3 gives complete component spec with MUI component names and variants. M4 lists 10 explicit implementation steps. M5 enumerates test cases per file. A developer can implement from the plan alone without clarifying questions.
2. Terminology Consistency
PASS with one minor note -- The plan uses "health indicator" (issue body), "health badge" (M2 layout), and "health dot" (revision 2 skeleton spec) to refer to the same UI element. However, the type system is consistent:
VehicleHealthtype,healthproperty onVehicleRosterData, and "Health badge" in M2 implementation details. The "health dot" reference in the skeleton spec (revision 2) is contextually appropriate since the skeleton usesSkeleton variant="circular" width={12} height={12}-- it IS a dot placeholder. The parent issue body uses "health indicator" as a general concept and "color-coded badge" as the design spec. No ambiguity that would cause implementation confusion.Suggestion (non-blocking): Standardize on "health badge" throughout the plan to match M2's implementation section. The skeleton spec could say "health badge placeholder" instead of "health dot".
3. Code Examples
PASS -- Type definitions (
VehicleHealth,AttentionItem,VehicleRosterData) are complete and internally consistent. TheVehicleRosterCardPropsinterface correctly referencesVehicleRosterData. TheActionBarPropsinterface is clean. The revised document fetching code snippet (revision 1) is syntactically correct and uses the verified API signaturedocumentsApi.list({ type: 'insurance' })which matches the actual API atdocuments.api.ts:16(params?: { vehicleId?: string; type?: string; expiresBefore?: string }).One observation: The
computeVehicleHealthfunction signature in M1 step 4 takes(schedules: MaintenanceScheduleResponse[], documents: DocumentResponse[])-- the actual type in the codebase isMaintenanceSchedule(frommaintenance.types.ts), notMaintenanceScheduleResponse. This is a naming discrepancy but the plan calls it a "pure function" with a proposed signature, so the developer will use the correct import. Non-blocking.4. File Paths
PASS -- All file paths verified against the actual codebase:
frontend/src/features/dashboard/types/index.ts-- EXISTSfrontend/src/features/dashboard/hooks/useDashboardData.ts-- EXISTSfrontend/src/features/dashboard/components/DashboardScreen.tsx-- EXISTSfrontend/src/features/dashboard/components/SummaryCards.tsx-- EXISTS (to delete)frontend/src/features/dashboard/components/QuickActions.tsx-- EXISTS (to delete)frontend/src/features/dashboard/components/RecentActivity.tsx-- EXISTS (to delete)frontend/src/features/dashboard/components/VehicleAttention.tsx-- EXISTS (to delete, per revision 3)frontend/src/features/dashboard/index.ts-- EXISTSshared-minimal/components/mobile/GlassCard.tsx-- EXISTScore/utils/vehicleDisplay.ts-- EXISTS (containsgetVehicleLabelandgetVehicleSubtitle)The plan references "VehicleImage" component -- verified at
frontend/src/features/vehicles/components/VehicleImage.tsx. Plan doesn't specify the import path explicitly in M2, but the component name is unambiguous.5. Formatting
PASS -- The plan uses consistent structure across milestones: Agent assignment, Files section, Type/Interface definitions (code blocks), Implementation details (numbered lists or bullet points), Commit message. Tables are well-formed. Heading levels are consistent (H3 for milestones, H4 for sub-sections within QR review). The revision comment uses clear H3 headings per revision with Before/After pattern.
6. Missing Context
PASS -- All referenced components and utilities are explained or exist in the codebase:
GlassCard-- behavior described (click adds hover elevation + cursor pointer), verified in codegetVehicleLabel()-- referenced with file path, verifiedVehicleImage-- described as "3-tier fallback (uploaded photo, make logo, color placeholder)"PendingAssociationBannerandPendingReceipts-- explicitly noted as preserveddocumentsApi.list()-- API signature referenced, verifiedgrid-cols-1 sm:grid-cols-2) -- explained as matching existing patternsOne minor gap: M4 step 7 mentions "desktop uses
navigate('/garage/vehicles/${id}'), mobile usesonVehicleClick?.(vehicle)" but doesn't reference where thenavigateimport comes from. However, this is standard React Router usage and any React developer would know the import. Non-blocking.7. Commit Messages
PASS -- All commit messages follow the
{type}: {summary} (refs #{index})convention:feat: add vehicle health types and roster data hook (refs #197)feat: add VehicleRosterCard component (refs #198)feat: add dashboard ActionBar component (refs #199)feat: rewire DashboardScreen with vehicle roster layout (refs #200)test: add dashboard redesign tests (refs #201)Each references the correct sub-issue index. Types are appropriate (
featfor features,testfor tests).8. Acceptance Criteria Alignment
PASS -- Mapping each acceptance criterion to plan coverage:
computeVehicleHealthwith explicit rulesgrid-cols-1 sm:grid-cols-2All acceptance criteria are covered. The revision addressed the skeleton gap identified by QR.
Suggestions
(Terminology) Consider standardizing on "health badge" throughout the plan. The skeleton spec (revision 2) uses "health dot" while M2 uses "health badge". Both are understandable, but consistency reduces cognitive load.
(Type name) M1 step 4 references
MaintenanceScheduleResponse[]but the existing codebase type isMaintenanceSchedule(imported frommaintenance.types.tsin useDashboardData.ts line 12). The implementing developer should useMaintenanceSchedule. Similarly,DocumentResponseshould be checked against the actual document type name. Non-blocking since the developer will follow IDE imports.(Derived hooks deprecation) M1 step 7 says "keep existing
useDashboardSummaryanduseVehiclesNeedingAttentionfor now" and mark with@deprecated. Since verified that onlyDashboardScreen.tsximports these hooks (and DashboardScreen is being rewritten in M4), these hooks will have zero consumers after M4 completes. Consider deleting them in M4 instead of deprecating, to avoid dead code. TheuseRecentActivityhook is also only used by DashboardScreen. All three derived hooks could be removed in M4 alongside the component cleanup.(GlassCard hover description) The codebase analysis says "click behavior adds hover elevation + cursor pointer." Verified in
GlassCard.tsx:33: the actual behavior ishover:shadow-xl hover:-translate-y-0.5 transition-- this is shadow increase + slight upward translation, not MUI elevation. The plan's description in M2 says "elevation increase + subtle border highlight" which is slightly inaccurate. The actual behavior is shadow increase + vertical translate. Non-blocking since the developer will use GlassCard as-is.Verdict: PASS | Next: QR plan-code
QR Review: Plan Docs
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
Documentation Impact
Files checked:
frontend/src/features/dashboard/README.md-- Does NOT exist. No dashboard-specific README to update.frontend/README.md-- Checked. No references to SummaryCards, QuickActions, RecentActivity, or VehicleAttention components. Generic feature structure description only.docs/README.md-- Checked. No dashboard-specific documentation. Lists backend feature READMEs only (application features)..ai/context.json-- Checked. No references to dashboard components in feature registry. Frontend work section points tofrontend/README.mdwhich is generic.CLAUDE.md(root) -- Checked. No references to dashboard-specific files.Findings: No existing documentation references the dashboard components being deleted (SummaryCards, QuickActions, RecentActivity, VehicleAttention). The codebase documentation is at the feature capsule level for backend features, not frontend components.
Missing Documentation Steps
NONE IDENTIFIED.
The plan is a frontend-only redesign with no backend changes. The project's documentation strategy focuses on:
backend/src/features/{feature}/README.md)docs/PLATFORM-SERVICES.md,docs/DATABASE-SCHEMA.md, etc.)frontend/README.md)There are no feature-specific frontend component READMEs in the codebase pattern. The dashboard feature does not have a README.md file.
New components are self-documenting:
VehicleRosterCard.tsx-- Component props and layout described in M2ActionBar.tsx-- Component props described in M3VehicleHealth,AttentionItem,VehicleRosterData) in M1 serve as inline documentationCode quality ensures discoverability:
index.ts(updated in M4) make components discoverableReview Criteria Results
Does the plan include a documentation update step?
Will any existing README files become stale after the changes?
Are the new components/types documented or self-documenting?
Does the plan mention running doc-sync after completion?
Are the sub-issue descriptions clear enough to serve as future reference?
Verdict: PASS | Next: Plan APPROVED
Rationale: The dashboard redesign is a self-contained frontend component change with no existing documentation to update. The plan provides sufficient inline documentation through type definitions, component specs, and test coverage. No README files reference the components being deleted. The project's documentation strategy does not require feature-specific READMEs for frontend components.
QR Review: Plan Code
Phase: Plan-Review | Agent: Quality Reviewer | Status: FAIL
RULE 0 Findings
None
RULE 1 Findings
RULE 1 (HIGH): Type name mismatch in M1 specification
MaintenanceScheduleResponse[]as the parameter type forcomputeVehicleHealth(), but the actual codebase type isMaintenanceSchedule(frommaintenance.types.ts, imported inuseDashboardData.ts:12). The plan also referencesDocumentResponse[]but needs verification that this matches the actual document type name.computeVehicleHealth(schedules: MaintenanceSchedule[], documents: DocumentRecord[]): { health: VehicleHealth; attentionItems: AttentionItem[] }. The document type isDocumentRecordperdocuments.types.ts:3-25.RULE 1 (HIGH): Documents API call pattern incompatible with API signature
documentsApi.list({ type: 'insurance' })anddocumentsApi.list({ type: 'registration' })as two parallel calls. However, the actual API signature atdocuments.api.ts:16showsasync list(params?: { vehicleId?: string; type?: string; expiresBefore?: string })- thetypeparameter is a single string, not an array. The plan assumes each call fetches ONLY that document type, but the API will return all documents of that type for ALL vehicles the user owns (not scoped to the dashboard's vehicle list).GET /documentsendpoint correctly handlestype=insuranceas a single-value filter (not expecting comma-separated values). If correct, the plan is sound but should clarify that the API returns all user documents of that type (not vehicle-scoped). If the API needs vehicle filtering, the plan should usedocumentsApi.list({ vehicleId })in a loop or accept the overhead of fetching all user documents and filtering client-side by the dashboard's vehicle list.RULE 2 Findings
RULE 2 (SHOULD_FIX): Deprecated hooks should be deleted, not marked @deprecated
useDashboardSummaryanduseVehiclesNeedingAttentionfor now" and mark with@deprecatedcomment. However, verified that onlyDashboardScreen.tsximports these hooks (lines 14, 39-41). Since DashboardScreen is being completely rewritten in M4 to use the newuseVehicleRoster()hook, these three derived hooks (useDashboardSummary,useVehiclesNeedingAttention,useRecentActivity) will have ZERO consumers after M4 completes.useDashboardSummary,useVehiclesNeedingAttention,useRecentActivity) in M4 alongside the component cleanup. Update barrel exports inindex.tsto remove these hook exports. This aligns with the project standard "Delete old code when replacing it" (CLAUDE.md).TW Suggestion Evaluation
TW Suggestion 1: Standardize on "health badge" terminology
Status: Keep non-blocking
Rationale: The terminology variance ("health indicator", "health badge", "health dot") is contextually appropriate. "Health dot" in the skeleton spec accurately describes the
Skeleton variant="circular" width={12} height={12}placeholder. No implementation confusion risk.TW Suggestion 2: Use actual type names (MaintenanceSchedule vs MaintenanceScheduleResponse)
Status: PROMOTE TO BLOCKING (see RULE 1 finding above)
Rationale: This is a RULE 1 violation - type names must match the actual codebase to avoid compilation errors. This is not a minor suggestion but a correctness issue.
TW Suggestion 3: Delete deprecated hooks instead of marking @deprecated
Status: PROMOTE TO BLOCKING (see RULE 2 finding above)
Rationale: While categorized as RULE 2 (structural quality), this aligns with the explicit project standard "Delete old code when replacing it". Given zero consumers after M4, keeping deprecated code violates project conventions.
TW Suggestion 4: GlassCard hover behavior description accuracy
Status: Keep non-blocking
Rationale: The plan says "elevation increase + subtle border highlight" but actual GlassCard behavior is
hover:shadow-xl hover:-translate-y-0.5. The discrepancy is minor since the developer will use GlassCard as-is (not reimplement hover behavior). The component provides the correct hover effect regardless of description accuracy.Verification Against Actual Source Files
Documents API signature (
documents.api.ts:16-18):Confirmed:
typeparameter is a single string. The plan's proposed usagedocumentsApi.list({ type: 'insurance' })is syntactically correct.DocumentRecord type (
documents.types.ts:3-25):Confirmed: Type name is
DocumentRecord(notDocumentResponse). FieldexpirationDate?: string | null;exists at line 18.MaintenanceSchedule type (
useDashboardData.ts:12):Confirmed: Type name is
MaintenanceSchedule(notMaintenanceScheduleResponse).GlassCard onClick behavior (
GlassCard.tsx:33):Confirmed: Hover behavior is shadow + translate (not MUI elevation). Non-blocking since developer uses component as-is.
DashboardScreen imports (
DashboardScreen.tsx:10-14):Confirmed: Only DashboardScreen imports these components and hooks. Safe to delete in M4.
App.tsx imports (line 92):
Confirmed: App.tsx only imports
DashboardScreenfrom the dashboard feature barrel export. Does not directly import SummaryCards, QuickActions, RecentActivity, or VehicleAttention. Safe to delete.Barrel exports (
index.ts:5-11):All four components (SummaryCards, VehicleAttention, QuickActions, RecentActivity) and their skeletons are exported, plus the three derived hooks. Plan correctly updates barrel exports in M4.
Verdict: FAIL | Next: Planner revision required
Required changes:
MaintenanceSchedulenotMaintenanceScheduleResponse,DocumentRecordnotDocumentResponse(RULE 1 - blocking)type=insurancereturn ALL user insurance documents across all vehicles, or only for dashboard vehicles? If all vehicles, is this acceptable overhead? (RULE 1 - blocking clarification needed)Plan Revision 2: Addressing QR Plan-Code Findings
Phase: Planning | Agent: Planner | Status: REVISED
Revision 5: Type Name Corrections (RULE 1 fix)
The plan incorrectly referenced types that don't exist. Corrections:
MaintenanceScheduleResponse[]MaintenanceSchedulefrontend/src/features/maintenance/types/maintenance.types.tsDocumentResponse[]DocumentRecordfrontend/src/features/documents/types/documents.types.tsUpdated
computeVehicleHealthsignature:The
useDashboardData.tshook already importsMaintenanceSchedule(notMaintenanceScheduleResponse). TheDocumentRecordtype has fieldsexpirationDate: string | nullanddocumentType: DocumentType.Revision 6: Documents API Clarification (RULE 1 fix)
documentsApi.list({ type: 'insurance' })callsGET /documents?type=insurancewhich is user-scoped (filtered by authenticated user'suser_idon the backend). It returns ALL insurance documents across ALL of the user's vehicles, which is the desired behavior -- we need per-user, cross-vehicle document data to compute health per vehicle.The returned
DocumentRecord[]includesvehicleIdfield, which we group by to map documents to their respective vehicles. This is exactly the same user-scoping pattern asvehiclesApi.getAll()and is correctly efficient (2 API calls for all document types vs N calls per vehicle).Revision 7: Delete Deprecated Hooks (RULE 2 fix, per project standard)
Before: M1 step 7 said "Keep existing derived hooks, mark with
@deprecated"After: In M4, when rewiring DashboardScreen, delete these hooks from
useDashboardData.ts:useDashboardSummary()-- no consumers after M4useVehiclesNeedingAttention()-- replaced byuseVehicleRoster()useRecentActivity()-- no consumers after M4Also delete the associated types from
types/index.tsif they have no consumers:DashboardSummary-- delete (was only used byuseDashboardSummary)RecentActivityItem-- delete (was only used byuseRecentActivity)VehicleNeedingAttention-- delete (was only used byuseVehiclesNeedingAttention)The existing
useDashboardData()hook is retained as the core fetching mechanism, now returning vehicle roster data instead.Per CLAUDE.md: "Delete old code when replacing it."
TW Non-Blocking Suggestions Accepted
hover:shadow-xl hover:-translate-y-0.5).Verdict: REVISED | Next: QR plan-code re-review
QR Review: Plan Code (Re-review)
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
Revision Verification
Finding 1: RULE 1 (HIGH) - Type name mismatch in M1 specification
STATUS: ADDRESSED
The revision correctly updates the type names:
MaintenanceScheduleResponse[]toMaintenanceSchedule(actual type frommaintenance.types.ts)DocumentResponse[]toDocumentRecord(actual type fromdocuments.types.ts)The corrected function signature now matches the actual codebase types:
This eliminates the TypeScript compilation error risk. The revision also provides a verification table showing the corrections and confirms the imports and field names.
Finding 2: RULE 1 (HIGH) - Documents API call pattern semantics clarity
STATUS: ADDRESSED
Revision 6 clarifies the API semantics:
GET /documents?type=insuranceis user-scoped (filters by authenticated user'suser_idon backend) and returns ALL documents of that type across ALL the user's vehicles. The returnedDocumentRecord[]includes avehicleIdfield for client-side grouping.This clarification confirms the approach is correct:
vehiclesApi.getAll()(user-scoped)The revision addresses the blocking concern about API semantics and overhead. The approach is sound for single-tenant architecture.
Finding 3: RULE 2 (SHOULD_FIX) - Delete deprecated hooks instead of marking @deprecated
STATUS: ADDRESSED
Revision 7 changes the plan from marking hooks
@deprecatedto deleting them in M4. Specifically:Hooks to delete (from
useDashboardData.ts):useDashboardSummary()useVehiclesNeedingAttention()useRecentActivity()Types to delete (from
types/index.ts):DashboardSummaryRecentActivityItemVehicleNeedingAttentionThe revision explicitly cites the project standard: "Delete old code when replacing it" (CLAUDE.md). Since these hooks have zero consumers after DashboardScreen is rewritten in M4, deletion is the correct approach.
New Issues
None identified.
The revisions are complete and internally consistent. Additional improvements noted:
The type corrections, API semantics clarification, and hook deletion strategy fully address all three blocking findings. The plan is now ready for implementation.
Verdict: PASS | Next: Plan APPROVED
Milestone: Execution Complete
Phase: Execution | Agent: Developer | Status: PASS
Milestone Summary
b57b835505ab82767df9e654a7f0f6684e7Files Changed
New files (7):
frontend/src/features/dashboard/components/VehicleRosterCard.tsxfrontend/src/features/dashboard/components/ActionBar.tsxfrontend/src/features/dashboard/utils/computeVehicleHealth.tsfrontend/src/features/dashboard/hooks/__tests__/useDashboardData.test.tsfrontend/src/features/dashboard/components/__tests__/VehicleRosterCard.test.tsxfrontend/src/features/dashboard/components/__tests__/ActionBar.test.tsxfrontend/src/features/dashboard/components/__tests__/DashboardScreen.test.tsxModified files (6):
frontend/src/features/dashboard/types/index.ts-- new health typesfrontend/src/features/dashboard/hooks/useDashboardData.ts-- roster data hookfrontend/src/features/dashboard/components/DashboardScreen.tsx-- new layoutfrontend/src/features/dashboard/pages/DashboardPage.tsx-- remove duplicate headingfrontend/src/features/dashboard/index.ts-- updated barrel exportsfrontend/jest.config.ts-- API client mock mapperfrontend/setupTests.ts-- TextEncoder polyfillfrontend/src/core/api/__mocks__/client.ts-- API client manual mockDeleted files (4):
frontend/src/features/dashboard/components/SummaryCards.tsxfrontend/src/features/dashboard/components/QuickActions.tsxfrontend/src/features/dashboard/components/RecentActivity.tsxfrontend/src/features/dashboard/components/VehicleAttention.tsxValidation Results
Implementation Notes
computeVehicleHealthtoutils/computeVehicleHealth.tsfor testability (avoids transitiveimport.meta.envissue in Jest)moduleNameMapperforcore/api/clientin jest config to resolve pre-existingimport.meta.envincompatibility with jsdomtype=insurance,type=registration) with graceful degradation on failureVerdict: PASS | Next: QR post-implementation review
QR Review: Post-Implementation
Phase: Review | Agent: Quality Reviewer | Status: CONDITIONAL_PASS
Code Quality Assessment
RULE 0 (CRITICAL) - Production Reliability
Status: PASS
No RULE 0 violations detected:
DashboardScreen.tsx:68-94with proper error UIuseDashboardData.ts:59-61)RULE 1 (HIGH) - Project Conformance
Status: CONDITIONAL_PASS (requires manual verification)
BLOCKING - Mobile+Desktop Manual Testing Required:
The implementation includes responsive design patterns:
grid-cols-1 sm:grid-cols-2(DashboardScreen.tsx:29, 153)useMediaQuery(theme.breakpoints.down('sm'))(DashboardScreen.tsx:55)However, no manual verification provided for actual viewport testing:
ACTION REQUIRED: Test in browser DevTools at all three breakpoints and confirm layout quality before merging.
Other RULE 1 Checks:
RULE 2 (SHOULD_FIX) - Structural Quality
Status: PASS with recommendations
Recommendations (non-blocking):
Positive observations:
Acceptance Criteria Verification
Test Results
Coverage: Health computation logic fully tested (green/yellow/red states, sorting, edge cases).
Files Reviewed
New files (8):
frontend/src/features/dashboard/components/VehicleRosterCard.tsxfrontend/src/features/dashboard/components/ActionBar.tsxfrontend/src/features/dashboard/utils/computeVehicleHealth.tsfrontend/src/features/dashboard/hooks/__tests__/useDashboardData.test.tsfrontend/src/features/dashboard/components/__tests__/VehicleRosterCard.test.tsxfrontend/src/features/dashboard/components/__tests__/ActionBar.test.tsxfrontend/src/features/dashboard/components/__tests__/DashboardScreen.test.tsxfrontend/src/core/api/__mocks__/client.tsModified files (5):
frontend/src/features/dashboard/types/index.tsfrontend/src/features/dashboard/hooks/useDashboardData.tsfrontend/src/features/dashboard/components/DashboardScreen.tsxfrontend/src/features/dashboard/pages/DashboardPage.tsxfrontend/src/features/dashboard/index.tsDeleted files (4):
frontend/src/features/dashboard/components/SummaryCards.tsxfrontend/src/features/dashboard/components/QuickActions.tsxfrontend/src/features/dashboard/components/RecentActivity.tsxfrontend/src/features/dashboard/components/VehicleAttention.tsxVerdict
CONDITIONAL_PASS - Implementation is high quality with RULE 0 compliance and comprehensive test coverage. Code patterns for responsive design are correct.
Before merging: Complete manual verification of mobile (320px, 768px) and desktop (1920px) layouts in browser DevTools. Confirm touch targets, grid behavior, and visual quality at all breakpoints.
Next: Manual responsive testing -> PR approval -> Merge to main