feat: Redesign dashboard with vehicle-centric layout #196

Closed
opened 2026-02-15 16:25:05 +00:00 by egullickson · 11 comments
Owner

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
## 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
egullickson added the
status
backlog
type
feature
labels 2026-02-15 16:25:14 +00:00
egullickson added
status
in-progress
and removed
status
backlog
labels 2026-02-15 16:26:54 +00:00
egullickson added this to the Sprint 2026-02-02 milestone 2026-02-15 16:31:48 +00:00
Author
Owner

Plan: 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:

  • 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)
  • GlassCard component at shared-minimal/components/mobile/GlassCard.tsx -- click behavior adds hover elevation + cursor pointer
  • 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.

Sub-Issues (milestones map 1:1)

# Sub-Issue Files Depends On
M1 #197 -- Types and vehicle health data layer types/index.ts, hooks/useDashboardData.ts None
M2 #198 -- VehicleRosterCard component components/VehicleRosterCard.tsx (new) M1
M3 #199 -- ActionBar component components/ActionBar.tsx (new) None
M4 #200 -- DashboardScreen composition and cleanup components/DashboardScreen.tsx, index.ts, delete 3 files M1, M2, M3
M5 #201 -- Tests 4 test files (new) M1, M2, M3, M4

Branch: issue-196-redesign-dashboard-vehicle-centric
PR: One PR targeting main, body lists Fixes #196, Fixes #197, Fixes #198, Fixes #199, Fixes #200, Fixes #201


Milestone 1: Types and Vehicle Health Data Layer (#197)

Agent: Feature Agent (frontend data layer)

Files modified:

  • frontend/src/features/dashboard/types/index.ts
  • frontend/src/features/dashboard/hooks/useDashboardData.ts

Type definitions to add:

type VehicleHealth = 'green' | 'yellow' | 'red';

interface AttentionItem {
  label: string;           // e.g., "Oil Change", "Registration"
  urgency: 'overdue' | 'due-soon' | 'upcoming';
  daysUntilDue: number;    // negative = overdue
  source: 'maintenance' | 'document';
}

interface VehicleRosterData {
  vehicle: Vehicle;
  health: VehicleHealth;
  attentionItems: AttentionItem[];  // sorted by urgency, max 3
}

Hook changes:

  1. 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.
  2. Remove fuelLogsApi.getUserFuelLogs() from the fetch (no longer displayed on dashboard).
  3. Group documents by vehicleId using a Map.
  4. Add computeVehicleHealth(schedules: MaintenanceScheduleResponse[], documents: DocumentResponse[]): { health: VehicleHealth; attentionItems: AttentionItem[] } pure function.
  5. Health logic:
    • Collect overdue maintenance items (nextDueDate < now)
    • Collect expired documents (expirationDate < now)
    • Collect due-soon items (within 14 days)
    • Collect upcoming items (within 30 days)
    • Health color: RED if any overdue/expired, YELLOW if any due within 14 days, GREEN otherwise
    • Sort attention items: overdue first (most overdue at top), then due-soon by proximity, then upcoming. Take first 3.
  6. Export useVehicleRoster() derived hook returning VehicleRosterData[].
  7. 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)


Milestone 2: VehicleRosterCard Component (#198)

Agent: Frontend Agent

Files created:

  • frontend/src/features/dashboard/components/VehicleRosterCard.tsx

Component design:

interface VehicleRosterCardProps {
  data: VehicleRosterData;
  onClick: (vehicleId: string) => void;
}

Layout (inside GlassCard):

+-------------------------------------------+
| [VehicleImage]  Year Make Model    [Badge] |
|                                            |
| ! Attention item 1 - OVERDUE              |
| ! Attention item 2 - 18 days              |
| ! Attention item 3 - 25 days              |
|                                            |
| 87,412 mi                                 |
+-------------------------------------------+

Implementation details:

  • Wrap in GlassCard with onClick={() => onClick(data.vehicle.id)}
  • Top row: VehicleImage (height 48, compact), vehicle label via getVehicleLabel(), health badge
  • Health badge: Small colored dot or chip using Tailwind classes
    • GREEN: bg-emerald-500 (light) / bg-emerald-400 (dark)
    • YELLOW: bg-amber-500 / bg-amber-400
    • RED: bg-red-500 / bg-red-400
  • Attention items: Each rendered as a single text line with urgency icon
    • Overdue: text-red-500 with warning icon
    • Due soon: text-amber-600 with clock icon
    • Upcoming: text-gray-500 (secondary text)
    • If no items: "All clear" in green text
  • Odometer: Bottom row, text-sm text-secondary, formatted with toLocaleString()
  • Mobile: Same layout, GlassCard handles responsive padding

Commit: feat: add VehicleRosterCard component (refs #198)


Milestone 3: ActionBar Component (#199)

Agent: Frontend Agent

Files created:

  • frontend/src/features/dashboard/components/ActionBar.tsx

Component design:

interface ActionBarProps {
  onAddVehicle: () => void;
  onLogFuel: () => void;
}

Implementation:

  • Horizontal flex row with gap-2, justify-end
  • Two MUI Buttons:
    • "+ Add Vehicle": variant="contained", size="small", startIcon={<Add />}
    • "Log Fuel": variant="outlined", size="small", startIcon={<LocalGasStation />}
  • Mobile (below sm breakpoint): Buttons remain text+icon but use size="small" for compactness
  • Uses @mui/icons-material icons (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.tsx
  • frontend/src/features/dashboard/index.ts

Files deleted:

  • frontend/src/features/dashboard/components/SummaryCards.tsx
  • frontend/src/features/dashboard/components/QuickActions.tsx
  • frontend/src/features/dashboard/components/RecentActivity.tsx

DashboardScreen new layout:

PendingAssociationBanner (conditional)
+-------------------------------------------+
| Your Fleet              [+ Add Vehicle]    |
|                         [Log Fuel]         |
+-------------------------------------------+
| [VehicleRosterCard]  [VehicleRosterCard]   |
| [VehicleRosterCard]  [VehicleRosterCard]   |
+-------------------------------------------+

Implementation steps:

  1. Replace heading from "Dashboard" to "Your Fleet" (Typography variant="h4")
  2. Remove "Welcome back, {email}" text
  3. Replace SummaryCards + QuickActions + RecentActivity with:
    • ActionBar next to heading (flex row: heading left, action bar right)
    • Vehicle roster grid: div.grid.grid-cols-1.sm:grid-cols-2.gap-4
    • Map useVehicleRoster() data to VehicleRosterCard components
  4. Update loading skeleton: Replace SummaryCardsSkeleton + QuickActionsSkeleton + RecentActivitySkeleton with roster card skeletons (4 placeholder cards using GlassCard with pulsing content)
  5. Keep empty state (0 vehicles) logic unchanged (lines 84-108 in current file)
  6. Keep PendingAssociationBanner and PendingReceipts dialog unchanged
  7. Wire onClick to existing navigation: desktop uses navigate('/garage/vehicles/${id}'), mobile uses onVehicleClick?.(vehicle)
  8. Delete old component files
  9. Update index.ts barrel exports: Remove SummaryCards, QuickActions, RecentActivity and their skeletons. Add VehicleRosterCard, ActionBar.
  10. Verify no other files import the deleted components (check DashboardPage.tsx and App.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.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

Test plan:

  1. useDashboardData.test.ts -- Unit test the pure computeVehicleHealth function:

    • GREEN: no overdue maintenance, no expired/expiring documents
    • YELLOW: maintenance due in 10 days, no overdue
    • YELLOW: registration expiring in 7 days
    • RED: maintenance overdue by 5 days
    • RED: insurance expired 3 days ago
    • RED + YELLOW combo: one overdue, one due soon
    • Attention items sorted correctly (overdue first, then by days)
    • Max 3 items enforced
  2. VehicleRosterCard.test.tsx:

    • Renders vehicle label (year make model)
    • Renders health badge with correct color class
    • Renders attention items text
    • Renders odometer with formatting
    • Calls onClick with vehicle ID when clicked
    • Renders "All clear" when no attention items
  3. ActionBar.test.tsx:

    • Renders both buttons
    • Calls onAddVehicle when Add Vehicle clicked
    • Calls onLogFuel when Log Fuel clicked
  4. DashboardScreen.test.tsx:

    • Renders vehicle roster cards (mock useDashboardData)
    • Renders empty state when 0 vehicles
    • Renders loading skeletons when loading
    • Renders "Your Fleet" heading

Testing approach: Mock useDashboardData / useVehicleRoster hooks. Use @testing-library/react with render, 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 errors
  • npm run type-check -- zero errors
  • npm test -- all pass
  • Manual check: Desktop (2-column grid) and mobile (single column) layouts
  • Verify old components fully deleted, no dead imports

Verdict: AWAITING_REVIEW | Next: QR plan-completeness review

## Plan: 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**: - 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) - `GlassCard` component at `shared-minimal/components/mobile/GlassCard.tsx` -- click behavior adds hover elevation + cursor pointer - 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. | --- ### Sub-Issues (milestones map 1:1) | # | Sub-Issue | Files | Depends On | |---|-----------|-------|------------| | M1 | #197 -- Types and vehicle health data layer | `types/index.ts`, `hooks/useDashboardData.ts` | None | | M2 | #198 -- VehicleRosterCard component | `components/VehicleRosterCard.tsx` (new) | M1 | | M3 | #199 -- ActionBar component | `components/ActionBar.tsx` (new) | None | | M4 | #200 -- DashboardScreen composition and cleanup | `components/DashboardScreen.tsx`, `index.ts`, delete 3 files | M1, M2, M3 | | M5 | #201 -- Tests | 4 test files (new) | M1, M2, M3, M4 | **Branch**: `issue-196-redesign-dashboard-vehicle-centric` **PR**: One PR targeting main, body lists `Fixes #196, Fixes #197, Fixes #198, Fixes #199, Fixes #200, Fixes #201` --- ### Milestone 1: Types and Vehicle Health Data Layer (#197) **Agent**: Feature Agent (frontend data layer) **Files modified**: - `frontend/src/features/dashboard/types/index.ts` - `frontend/src/features/dashboard/hooks/useDashboardData.ts` **Type definitions to add**: ```typescript type VehicleHealth = 'green' | 'yellow' | 'red'; interface AttentionItem { label: string; // e.g., "Oil Change", "Registration" urgency: 'overdue' | 'due-soon' | 'upcoming'; daysUntilDue: number; // negative = overdue source: 'maintenance' | 'document'; } interface VehicleRosterData { vehicle: Vehicle; health: VehicleHealth; attentionItems: AttentionItem[]; // sorted by urgency, max 3 } ``` **Hook changes**: 1. 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`. 2. Remove `fuelLogsApi.getUserFuelLogs()` from the fetch (no longer displayed on dashboard). 3. Group documents by `vehicleId` using a Map. 4. Add `computeVehicleHealth(schedules: MaintenanceScheduleResponse[], documents: DocumentResponse[]): { health: VehicleHealth; attentionItems: AttentionItem[] }` pure function. 5. Health logic: - Collect overdue maintenance items (`nextDueDate < now`) - Collect expired documents (`expirationDate < now`) - Collect due-soon items (within 14 days) - Collect upcoming items (within 30 days) - Health color: RED if any overdue/expired, YELLOW if any due within 14 days, GREEN otherwise - Sort attention items: overdue first (most overdue at top), then due-soon by proximity, then upcoming. Take first 3. 6. Export `useVehicleRoster()` derived hook returning `VehicleRosterData[]`. 7. 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)` --- ### Milestone 2: VehicleRosterCard Component (#198) **Agent**: Frontend Agent **Files created**: - `frontend/src/features/dashboard/components/VehicleRosterCard.tsx` **Component design**: ```typescript interface VehicleRosterCardProps { data: VehicleRosterData; onClick: (vehicleId: string) => void; } ``` **Layout** (inside GlassCard): ``` +-------------------------------------------+ | [VehicleImage] Year Make Model [Badge] | | | | ! Attention item 1 - OVERDUE | | ! Attention item 2 - 18 days | | ! Attention item 3 - 25 days | | | | 87,412 mi | +-------------------------------------------+ ``` **Implementation details**: - Wrap in `GlassCard` with `onClick={() => onClick(data.vehicle.id)}` - Top row: `VehicleImage` (height 48, compact), vehicle label via `getVehicleLabel()`, health badge - Health badge: Small colored dot or chip using Tailwind classes - GREEN: `bg-emerald-500` (light) / `bg-emerald-400` (dark) - YELLOW: `bg-amber-500` / `bg-amber-400` - RED: `bg-red-500` / `bg-red-400` - Attention items: Each rendered as a single text line with urgency icon - Overdue: `text-red-500` with warning icon - Due soon: `text-amber-600` with clock icon - Upcoming: `text-gray-500` (secondary text) - If no items: "All clear" in green text - Odometer: Bottom row, `text-sm text-secondary`, formatted with `toLocaleString()` - Mobile: Same layout, GlassCard handles responsive padding **Commit**: `feat: add VehicleRosterCard component (refs #198)` --- ### Milestone 3: ActionBar Component (#199) **Agent**: Frontend Agent **Files created**: - `frontend/src/features/dashboard/components/ActionBar.tsx` **Component design**: ```typescript interface ActionBarProps { onAddVehicle: () => void; onLogFuel: () => void; } ``` **Implementation**: - Horizontal `flex` row with `gap-2`, `justify-end` - Two MUI Buttons: - "+ Add Vehicle": `variant="contained"`, `size="small"`, `startIcon={<Add />}` - "Log Fuel": `variant="outlined"`, `size="small"`, `startIcon={<LocalGasStation />}` - Mobile (below `sm` breakpoint): Buttons remain text+icon but use `size="small"` for compactness - Uses `@mui/icons-material` icons (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.tsx` - `frontend/src/features/dashboard/index.ts` **Files deleted**: - `frontend/src/features/dashboard/components/SummaryCards.tsx` - `frontend/src/features/dashboard/components/QuickActions.tsx` - `frontend/src/features/dashboard/components/RecentActivity.tsx` **DashboardScreen new layout**: ``` PendingAssociationBanner (conditional) +-------------------------------------------+ | Your Fleet [+ Add Vehicle] | | [Log Fuel] | +-------------------------------------------+ | [VehicleRosterCard] [VehicleRosterCard] | | [VehicleRosterCard] [VehicleRosterCard] | +-------------------------------------------+ ``` **Implementation steps**: 1. Replace heading from "Dashboard" to "Your Fleet" (`Typography variant="h4"`) 2. Remove "Welcome back, {email}" text 3. Replace `SummaryCards` + `QuickActions` + `RecentActivity` with: - `ActionBar` next to heading (flex row: heading left, action bar right) - Vehicle roster grid: `div.grid.grid-cols-1.sm:grid-cols-2.gap-4` - Map `useVehicleRoster()` data to `VehicleRosterCard` components 4. Update loading skeleton: Replace SummaryCardsSkeleton + QuickActionsSkeleton + RecentActivitySkeleton with roster card skeletons (4 placeholder cards using GlassCard with pulsing content) 5. Keep empty state (0 vehicles) logic unchanged (lines 84-108 in current file) 6. Keep `PendingAssociationBanner` and `PendingReceipts` dialog unchanged 7. Wire `onClick` to existing navigation: desktop uses `navigate('/garage/vehicles/${id}')`, mobile uses `onVehicleClick?.(vehicle)` 8. Delete old component files 9. Update `index.ts` barrel exports: Remove `SummaryCards`, `QuickActions`, `RecentActivity` and their skeletons. Add `VehicleRosterCard`, `ActionBar`. 10. Verify no other files import the deleted components (check `DashboardPage.tsx` and `App.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.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` **Test plan**: 1. **`useDashboardData.test.ts`** -- Unit test the pure `computeVehicleHealth` function: - GREEN: no overdue maintenance, no expired/expiring documents - YELLOW: maintenance due in 10 days, no overdue - YELLOW: registration expiring in 7 days - RED: maintenance overdue by 5 days - RED: insurance expired 3 days ago - RED + YELLOW combo: one overdue, one due soon - Attention items sorted correctly (overdue first, then by days) - Max 3 items enforced 2. **`VehicleRosterCard.test.tsx`**: - Renders vehicle label (year make model) - Renders health badge with correct color class - Renders attention items text - Renders odometer with formatting - Calls onClick with vehicle ID when clicked - Renders "All clear" when no attention items 3. **`ActionBar.test.tsx`**: - Renders both buttons - Calls onAddVehicle when Add Vehicle clicked - Calls onLogFuel when Log Fuel clicked 4. **`DashboardScreen.test.tsx`**: - Renders vehicle roster cards (mock useDashboardData) - Renders empty state when 0 vehicles - Renders loading skeletons when loading - Renders "Your Fleet" heading **Testing approach**: Mock `useDashboardData` / `useVehicleRoster` hooks. Use `@testing-library/react` with `render`, `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 errors - `npm run type-check` -- zero errors - `npm test` -- all pass - Manual check: Desktop (2-column grid) and mobile (single column) layouts - Verify old components fully deleted, no dead imports --- *Verdict*: AWAITING_REVIEW | *Next*: QR plan-completeness review
Author
Owner

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
## 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
Author
Owner

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:

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

## 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
Author
Owner

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:

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

## 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
Author
Owner

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

## 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
Author
Owner

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.

## 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.
Author
Owner

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):

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):

import { MaintenanceSchedule } from '../../maintenance/types/maintenance.types';

Confirmed: Type name is MaintenanceSchedule (not MaintenanceScheduleResponse).

GlassCard onClick behavior (GlassCard.tsx:33):

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):

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):

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)
## 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)
Author
Owner

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:

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

## 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
Author
Owner

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:

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

## 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
Author
Owner

Milestone: Execution Complete

Phase: Execution | Agent: Developer | Status: PASS


Milestone Summary

# Sub-Issue Status Commit
M1 #197 -- Types and vehicle health data layer DONE b57b835
M2 #198 -- VehicleRosterCard component DONE 505ab82
M3 #199 -- ActionBar component DONE 767df9e
M4 #200 -- DashboardScreen composition and cleanup DONE 654a7f0
M5 #201 -- Tests DONE f6684e7

Files Changed

New files (7):

  • 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

Modified files (6):

  • frontend/src/features/dashboard/types/index.ts -- new health types
  • frontend/src/features/dashboard/hooks/useDashboardData.ts -- roster data hook
  • frontend/src/features/dashboard/components/DashboardScreen.tsx -- new layout
  • frontend/src/features/dashboard/pages/DashboardPage.tsx -- remove duplicate heading
  • frontend/src/features/dashboard/index.ts -- updated barrel exports
  • frontend/jest.config.ts -- API client mock mapper
  • frontend/setupTests.ts -- TextEncoder polyfill
  • frontend/src/core/api/__mocks__/client.ts -- API client manual mock

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

Validation Results

  • Type-check: PASS (0 errors)
  • Lint: PASS (0 warnings)
  • Tests: 29/29 passing across 4 test suites
  • Old code deleted, no dead imports

Implementation Notes

  • Extracted computeVehicleHealth to utils/computeVehicleHealth.ts for testability (avoids transitive import.meta.env issue in Jest)
  • Added moduleNameMapper for core/api/client in jest config to resolve pre-existing import.meta.env incompatibility with jsdom
  • Document fetching uses two parallel filtered API calls (type=insurance, type=registration) with graceful degradation on failure
  • All deprecated hooks and types from old dashboard removed per project standard

Verdict: PASS | Next: QR post-implementation review

## Milestone: Execution Complete **Phase**: Execution | **Agent**: Developer | **Status**: PASS --- ### Milestone Summary | # | Sub-Issue | Status | Commit | |---|-----------|--------|--------| | M1 | #197 -- Types and vehicle health data layer | DONE | `b57b835` | | M2 | #198 -- VehicleRosterCard component | DONE | `505ab82` | | M3 | #199 -- ActionBar component | DONE | `767df9e` | | M4 | #200 -- DashboardScreen composition and cleanup | DONE | `654a7f0` | | M5 | #201 -- Tests | DONE | `f6684e7` | ### Files Changed **New files (7):** - `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` **Modified files (6):** - `frontend/src/features/dashboard/types/index.ts` -- new health types - `frontend/src/features/dashboard/hooks/useDashboardData.ts` -- roster data hook - `frontend/src/features/dashboard/components/DashboardScreen.tsx` -- new layout - `frontend/src/features/dashboard/pages/DashboardPage.tsx` -- remove duplicate heading - `frontend/src/features/dashboard/index.ts` -- updated barrel exports - `frontend/jest.config.ts` -- API client mock mapper - `frontend/setupTests.ts` -- TextEncoder polyfill - `frontend/src/core/api/__mocks__/client.ts` -- API client manual mock **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` ### Validation Results - Type-check: PASS (0 errors) - Lint: PASS (0 warnings) - Tests: 29/29 passing across 4 test suites - Old code deleted, no dead imports ### Implementation Notes - Extracted `computeVehicleHealth` to `utils/computeVehicleHealth.ts` for testability (avoids transitive `import.meta.env` issue in Jest) - Added `moduleNameMapper` for `core/api/client` in jest config to resolve pre-existing `import.meta.env` incompatibility with jsdom - Document fetching uses two parallel filtered API calls (`type=insurance`, `type=registration`) with graceful degradation on failure - All deprecated hooks and types from old dashboard removed per project standard --- *Verdict*: PASS | *Next*: QR post-implementation review
Author
Owner

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

## 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
egullickson added
status
review
and removed
status
in-progress
labels 2026-02-15 17:07:13 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: egullickson/motovaultpro#196