When a user's subscription is auto-downgraded to free tier (via grace period expiration) and they have more than 2 vehicles, they should be prompted to select which 2 vehicles to keep active on their next login.
The VehicleSelectionDialog exists and works during manual downgrade flows (when user initiates downgrade), but there is no automatic prompt on login after a grace period auto-downgrade.
Expected Behavior
On app load (after authentication), check if:
User is on free tier
User has more than 2 active vehicles
User has not yet made a vehicle selection (no entries in tier_vehicle_selections)
If all conditions are met, show the VehicleSelectionDialog forcing user to select 2 vehicles
Until selection is made, user should not be able to navigate elsewhere in the app
Implementation Notes
Backend:
Endpoint to check if user needs vehicle selection: GET /api/subscriptions/needs-vehicle-selection
Returns { needsSelection: boolean, vehicleCount: number, maxAllowed: number }
Frontend:
Hook in App.tsx or AuthGate to check on authenticated load
Show VehicleSelectionDialog as a modal that cannot be dismissed
On confirm, call existing downgrade API to save selections
Acceptance Criteria
Backend endpoint returns correct needsSelection status
VehicleSelectionDialog shown automatically on login when needed
Dialog cannot be dismissed without making selection
Selected vehicles saved correctly
Mobile + desktop responsive validation
## Summary
When a user's subscription is auto-downgraded to free tier (via grace period expiration) and they have more than 2 vehicles, they should be prompted to select which 2 vehicles to keep active on their next login.
## Related
- Deferred from #58
## Current Behavior
The `VehicleSelectionDialog` exists and works during **manual** downgrade flows (when user initiates downgrade), but there is no automatic prompt on login after a grace period auto-downgrade.
## Expected Behavior
1. On app load (after authentication), check if:
- User is on free tier
- User has more than 2 active vehicles
- User has not yet made a vehicle selection (no entries in `tier_vehicle_selections`)
2. If all conditions are met, show the VehicleSelectionDialog forcing user to select 2 vehicles
3. Until selection is made, user should not be able to navigate elsewhere in the app
## Implementation Notes
**Backend:**
- Endpoint to check if user needs vehicle selection: `GET /api/subscriptions/needs-vehicle-selection`
- Returns `{ needsSelection: boolean, vehicleCount: number, maxAllowed: number }`
**Frontend:**
- Hook in `App.tsx` or AuthGate to check on authenticated load
- Show `VehicleSelectionDialog` as a modal that cannot be dismissed
- On confirm, call existing downgrade API to save selections
## Acceptance Criteria
- [ ] Backend endpoint returns correct `needsSelection` status
- [ ] VehicleSelectionDialog shown automatically on login when needed
- [ ] Dialog cannot be dismissed without making selection
- [ ] Selected vehicles saved correctly
- [ ] Mobile + desktop responsive validation
When a user's subscription is auto-downgraded to free tier via grace period expiration and they have more than 2 vehicles, they need to be prompted to select which vehicles to keep active. This requires:
A new backend endpoint to check if selection is needed
Frontend integration to show a blocking dialog on login
Codebase Analysis Findings
Architecture Assessment:
Backend subscription feature is well-structured with service/repository separation
Existing VehicleSelectionDialog component is reusable with modifications
CallbackPage.tsx follows sequential auth check pattern - ideal integration point
Auth gate in App.tsx provides blocking mechanism after authentication
Update dialog title and message for blocking mode context
Acceptance Criteria:
Dialog cannot be dismissed when blocking=true
Cancel button hidden in blocking mode
Escape key does not close dialog
Appropriate messaging for auto-downgrade scenario
Milestone 4: Frontend - Callback Integration
Agent: Frontend Agent | Estimated Files: 3
Objective: Check vehicle selection requirement after authentication
Implementation:
Option A (Recommended): State-based blocking in App.tsx
After auth gate ready, check useNeedsVehicleSelection
If needsSelection: true, render blocking VehicleSelectionDialog
On confirm, call existing downgrade API with vehicle selections
Invalidate queries and allow app to proceed
// In App.tsx, after isAuthGateReady check
const{data: vehicleSelectionStatus}=useNeedsVehicleSelection();const{data: vehicles}=useVehicles();if(vehicleSelectionStatus?.needsSelection&&vehicles){return(<VehicleSelectionDialogopen={true}blocking={true}onClose={()=>{}}// No-op, dialog is blocking
onConfirm={handleVehicleSelection}vehicles={vehicles}maxSelections={2}targetTier="free"/>);}
Implement handleVehicleSelection:
Call POST /api/subscriptions/downgrade with selected vehicles
Invalidate needs-vehicle-selection query
App re-renders and proceeds normally
Apply same logic to mobile rendering path in App.tsx
After user selected 2 vehicles during downgrade to free tier, all vehicles (including unselected ones) continued to appear in:
Vehicle summary/list screen
Vehicle detail screen (via direct navigation)
Root Cause
GET /api/vehicles endpoint in vehicles.controller.ts:31 called getUserVehicles() which returns ALL vehicles, instead of getUserVehiclesWithTierStatus() which properly checks tier_vehicle_selections table and marks vehicles as active or locked.
getUserVehicles endpoint (list): Now uses getUserVehiclesWithTierStatus() and filters out vehicles with tierStatus='locked', returning only active vehicles.
getVehicle endpoint (detail): Added tier status check that returns 403 TIER_REQUIRED if user tries to access a locked vehicle directly.
Verdict: COMPLETE | Next: Manual testing on mobile and desktop
## Bug Fix: Vehicle Selection Not Hiding Unselected Vehicles
**Phase**: Execution | **Agent**: Developer | **Status**: COMPLETE
### Problem
After user selected 2 vehicles during downgrade to free tier, all vehicles (including unselected ones) continued to appear in:
- Vehicle summary/list screen
- Vehicle detail screen (via direct navigation)
### Root Cause
`GET /api/vehicles` endpoint in `vehicles.controller.ts:31` called `getUserVehicles()` which returns ALL vehicles, instead of `getUserVehiclesWithTierStatus()` which properly checks `tier_vehicle_selections` table and marks vehicles as `active` or `locked`.
### Fix Applied
**File**: `backend/src/features/vehicles/api/vehicles.controller.ts`
1. **`getUserVehicles` endpoint** (list): Now uses `getUserVehiclesWithTierStatus()` and filters out vehicles with `tierStatus='locked'`, returning only active vehicles.
2. **`getVehicle` endpoint** (detail): Added tier status check that returns `403 TIER_REQUIRED` if user tries to access a locked vehicle directly.
### Verification
- Backend lint: PASS (no new errors)
- Backend type-check: PASS
- Repository unit test: PASS
### Commit
`6894848` - fix: filter locked vehicles after tier downgrade selection (refs #60)
*Verdict*: COMPLETE | *Next*: Manual testing on mobile and desktop
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
When a user's subscription is auto-downgraded to free tier (via grace period expiration) and they have more than 2 vehicles, they should be prompted to select which 2 vehicles to keep active on their next login.
Related
Current Behavior
The
VehicleSelectionDialogexists and works during manual downgrade flows (when user initiates downgrade), but there is no automatic prompt on login after a grace period auto-downgrade.Expected Behavior
On app load (after authentication), check if:
tier_vehicle_selections)If all conditions are met, show the VehicleSelectionDialog forcing user to select 2 vehicles
Until selection is made, user should not be able to navigate elsewhere in the app
Implementation Notes
Backend:
GET /api/subscriptions/needs-vehicle-selection{ needsSelection: boolean, vehicleCount: number, maxAllowed: number }Frontend:
App.tsxor AuthGate to check on authenticated loadVehicleSelectionDialogas a modal that cannot be dismissedAcceptance Criteria
needsSelectionstatusPlan: Prompt Vehicle Selection on Login After Auto-Downgrade
Phase: Planning | Agent: Planner | Status: AWAITING_REVIEW
Executive Summary
When a user's subscription is auto-downgraded to free tier via grace period expiration and they have more than 2 vehicles, they need to be prompted to select which vehicles to keep active. This requires:
Codebase Analysis Findings
Architecture Assessment:
VehicleSelectionDialogcomponent is reusable with modificationsCallbackPage.tsxfollows sequential auth check pattern - ideal integration pointApp.tsxprovides blocking mechanism after authenticationKey Files Affected:
subscriptions.service.tssubscriptions.controller.tssubscriptions.routes.tssubscription.api.tsuseSubscription.tsVehicleSelectionDialog.tsxCallbackPage.tsxCallbackMobileScreen.tsxMilestones
Milestone 1: Backend - Check Needs Vehicle Selection
Agent: Feature Agent | Estimated Files: 3
Objective: Create endpoint
GET /api/subscriptions/needs-vehicle-selectionImplementation:
Add
checkNeedsVehicleSelection(userId)method toSubscriptionsService:{ needsSelection: boolean, vehicleCount: number, maxAllowed: number }Add route handler in
SubscriptionsController:Register route in
subscriptions.routes.ts:Acceptance Criteria:
needsSelection: falsefor users who already made selectionsneedsSelection: falsefor pro/enterprise tiersMilestone 2: Frontend - API and Hook
Agent: Frontend Agent | Estimated Files: 2
Objective: Create hook to check if vehicle selection is needed
Implementation:
Add API method in
subscription.api.ts:Add hook in
useSubscription.ts:Acceptance Criteria:
Milestone 3: Frontend - Blocking Vehicle Selection Dialog
Agent: Frontend Agent | Estimated Files: 1-2
Objective: Modify VehicleSelectionDialog to support blocking mode
Implementation:
Add optional
blockingprop toVehicleSelectionDialog:When
blocking={true}:onClose={(_, reason) => reason !== 'backdropClick' && onClose()}disableEscapeKeyDown={blocking}Update dialog title and message for blocking mode context
Acceptance Criteria:
Milestone 4: Frontend - Callback Integration
Agent: Frontend Agent | Estimated Files: 3
Objective: Check vehicle selection requirement after authentication
Implementation:
Option A (Recommended): State-based blocking in App.tsx
useNeedsVehicleSelectionneedsSelection: true, render blocking VehicleSelectionDialogImplement
handleVehicleSelection:POST /api/subscriptions/downgradewith selected vehiclesneeds-vehicle-selectionqueryApply same logic to mobile rendering path in App.tsx
Acceptance Criteria:
Test Plan
Risk Assessment
Dependencies
Out of Scope
Verdict: AWAITING_REVIEW | Next: QR plan-completeness review
QR Review: Plan Completeness
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
Review Criteria
Decision Log Check
blockingpropPolicy Defaults Applied
VERDICT: PASS
Findings
None - plan structure is complete.
Considered But Not Flagged
Verdict: PASS | Next: TW plan-scrub
TW Review: Plan Scrub
Phase: Plan-Review | Agent: Technical Writer | Status: PASS
Documentation Quality Check
Clarity Review
Technical Accuracy
GET /api/subscriptions/needs-vehicle-selectionuseNeedsVehicleSelectiondisableEscapeKeyDown, backdrop handlingImprovements Applied
None required - documentation is production-ready.
VERDICT: PASS
Findings
None - documentation meets quality standards.
Observations
Verdict: PASS | Next: QR plan-code
QR Review: Plan Code
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
RULE 0: Production Reliability
Open Questions Verified:
Q: "What happens if needs-vehicle-selection API fails?"
A: Plan includes error handling with retry/support contact in risk assessment
Q: "What happens if vehicles query is empty?"
A: Plan checks
vehicleSelectionStatus?.needsSelection && vehiclesbefore renderingRULE 1: Project Conformance
subscriptions/featureSubscriptionsRepositorymethodsneedsVehicleSelection,checkNeedsVehicleSelection)RULE 2: Structural Quality
Code Alignment Check
checkNeedsVehicleSelection()getSubscription()useNeedsVehicleSelection()useSubscription()hookVERDICT: PASS
Findings
None - proposed code follows established patterns.
Considered But Not Flagged
&& vehiclescheckVerdict: PASS | Next: QR plan-docs
QR Review: Plan Docs
Phase: Plan-Review | Agent: Quality Reviewer | Status: PASS
Documentation Impact Assessment
Temporal Contamination Check
Comment Quality Standards
Post-Implementation Documentation Tasks
The following documentation updates should be included in milestone completion:
Milestone 1: Update
backend/src/features/subscriptions/README.mdMilestone 4: No external doc updates needed
VERDICT: PASS
Findings
None - documentation requirements are clear.
Documentation Checklist for Implementation
GET /api/subscriptions/needs-vehicle-selectionto subscriptions READMENeedsVehicleSelectionResponsetypeVerdict: PASS | Next: Plan Approved - Ready for Execution
Plan Status: APPROVED
Phase: Planning | Status: APPROVED
Review Cycle Complete
Ready for Execution
Branch:
issue-60-vehicle-selection-promptMilestones:
Estimated Files: 8
Plan approved and ready for execution.
Milestone: Execution Complete
Phase: Execution | Agent: Developer | Status: PASS
Milestones Completed
Implementation Summary
Backend (Milestone 1):
checkNeedsVehicleSelection(userId)method toSubscriptionsServiceGET /api/subscriptions/needs-vehicle-selection{ needsSelection, vehicleCount, maxAllowed }based on tier, vehicle count, and existing selectionsFrontend (Milestones 2-4):
NeedsVehicleSelectionResponsetypeneedsVehicleSelection()API method with proper data extractionuseNeedsVehicleSelection()hook withstaleTime: 0for fresh checksVehicleSelectionDialogwithblockingprop:App.tsxafter auth gate ready:needsVehicleSelectionstatusCommits
feat: add needs-vehicle-selection endpoint (refs #60)feat: add needsVehicleSelection frontend hook (refs #60)feat: add blocking mode to VehicleSelectionDialog (refs #60)feat: integrate vehicle selection dialog on login (refs #60)Quality Checks
npm run type-checkPASSnpm run lintPASS (warnings only, no errors)npm run type-checkPASSnpm run lintPASS (warnings only, no errors)Verdict: PASS | Next: Open PR and Quality Agent review
QR Review: Final Quality Review
Phase: Review | Agent: Quality Agent
Validation Results
Automated Checks
Code Changes Review
Files modified in PR #62:
backend/src/features/subscriptions/api/subscriptions.controller.tsbackend/src/features/subscriptions/api/subscriptions.routes.tsbackend/src/features/subscriptions/domain/subscriptions.service.tsbackend/src/features/subscriptions/domain/subscriptions.types.tsfrontend/src/App.tsxfrontend/src/features/subscription/api/subscription.api.tsfrontend/src/features/subscription/components/VehicleSelectionDialog.tsxfrontend/src/features/subscription/hooks/useSubscription.tsfrontend/src/features/subscription/types/subscription.types.tsRULE 0 (CRITICAL) - Production Reliability
STATUS: PASS
Findings:
Notes:
RULE 1 (HIGH) - Project Standards
STATUS: PASS
Findings:
Naming Conventions
Mobile + Desktop Requirement
Code Organization
Test Coverage
RULE 2 (SHOULD_FIX) - Structural Quality
STATUS: PASS (Minor observations)
Observations:
No God Objects Introduced:
No Code Duplication:
blockingpropNo Dead Code:
Mobile + Desktop Validation
Desktop: Code present in App.tsx routes (lines 1071-1102)
Mobile: Blocking dialog integrated (lines 645-665)
Manual Testing Status: NOT YET COMPLETED (per PR test plan checklist)
RECOMMENDATION: Complete manual mobile/desktop testing before merge.
Acceptance Criteria Review
From issue #60:
needsSelectionstatusFinal Verdict
CONDITIONAL PASS
All automated checks pass. Code quality meets RULE 0/1/2 standards.
Condition: Complete manual mobile + desktop testing (per PR test plan) before merging to ensure blocking dialog UX works correctly on both platforms.
Recommendation: Mark this PR as approved from a code quality perspective, pending manual QA testing.
Reviewed by: Quality Agent
Timestamp: 2026-01-24
Bug Fix: Vehicle Selection Not Hiding Unselected Vehicles
Phase: Execution | Agent: Developer | Status: COMPLETE
Problem
After user selected 2 vehicles during downgrade to free tier, all vehicles (including unselected ones) continued to appear in:
Root Cause
GET /api/vehiclesendpoint invehicles.controller.ts:31calledgetUserVehicles()which returns ALL vehicles, instead ofgetUserVehiclesWithTierStatus()which properly checkstier_vehicle_selectionstable and marks vehicles asactiveorlocked.Fix Applied
File:
backend/src/features/vehicles/api/vehicles.controller.tsgetUserVehiclesendpoint (list): Now usesgetUserVehiclesWithTierStatus()and filters out vehicles withtierStatus='locked', returning only active vehicles.getVehicleendpoint (detail): Added tier status check that returns403 TIER_REQUIREDif user tries to access a locked vehicle directly.Verification
Commit
6894848- fix: filter locked vehicles after tier downgrade selection (refs #60)Verdict: COMPLETE | Next: Manual testing on mobile and desktop