Update deployment health checks. Fix UI bugs.

This commit is contained in:
Eric Gullickson
2025-12-20 10:50:44 -06:00
parent 2bd0981490
commit a17944d79f
8 changed files with 193 additions and 117 deletions

View File

@@ -135,7 +135,7 @@ verify:
- | - |
HEALTH_OK=0 HEALTH_OK=0
for i in 1 2 3 4 5 6; do for i in 1 2 3 4 5 6; do
if docker compose -f $DOCKER_COMPOSE_FILE exec -T mvp-backend curl -sf http://localhost:3001/health > /dev/null 2>&1; then if node -e "require('http').get('http://localhost:3001/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))" > /dev/null 2>&1; then
echo "OK: Backend health check passed" echo "OK: Backend health check passed"
HEALTH_OK=1 HEALTH_OK=1
break break

View File

@@ -10,6 +10,7 @@
"test": "jest", "test": "jest",
"test:watch": "jest --watch", "test:watch": "jest --watch",
"test:feature": "jest --testPathPattern=src/features/${npm_config_feature}", "test:feature": "jest --testPathPattern=src/features/${npm_config_feature}",
"migrate": "npm run migrate:all",
"migrate:all": "ts-node src/_system/migrations/run-all.ts", "migrate:all": "ts-node src/_system/migrations/run-all.ts",
"migrate:feature": "ts-node src/_system/migrations/run-feature.ts", "migrate:feature": "ts-node src/_system/migrations/run-feature.ts",
"schema:generate": "ts-node src/_system/schema/generate.ts", "schema:generate": "ts-node src/_system/schema/generate.ts",

View File

@@ -1,53 +0,0 @@
acura
alfa_romeo
aston_martin
audi
bentley
bmw
buick
cadillac
chevrolet
chrysler
dodge
ferrari
fiat
ford
genesis
gmc
honda
hummer
hyundai
infiniti
isuzu
jaguar
jeep
kia
lamborghini
land_rover
lexus
lincoln
lotus
lucid
maserati
mazda
mclaren
mercury
mini
mitsubishi
nissan
oldsmobile
plymouth
polestar
pontiac
porsche
ram
rivian
rolls_royce
saab
scion
smart
subaru
tesla
toyota
volkswagen
volvo

View File

@@ -20,12 +20,11 @@ Your task is to create a plan that can be dispatched to a seprate set of AI agen
*** ROLE *** *** ROLE ***
You are a senior DevOps SRE in charge of improving the deployment of htis app. You are a senior DevOps SRE in charge of MotoVaultPro. A automotive fleet management application.
*** ACTION *** *** ACTION ***
- You need to add a deployment step where the vehicle-etl process that's currentl configured to be manual, is executed during the gitlab deployment. - There are errors in deployment then also console errors
- Research the directory @data/vehicle-etl/ to understand the process. - It appears when trying to run the npm migrations there is an error.
- The source database will be @data/vehicle-etl/snapshots/vehicle-drop-down.sqlite
- Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository in the context of this change. - Read README.md CLAUDE.md and AI-INDEX.md to understand this code repository in the context of this change.
*** CONTEXT *** *** CONTEXT ***
@@ -33,3 +32,135 @@ You are a senior DevOps SRE in charge of improving the deployment of htis app.
*** CHANGES TO IMPLEMENT *** *** CHANGES TO IMPLEMENT ***
- Plan and recommend the best solution for this change - Plan and recommend the best solution for this change
Here are the relevant logs.
egullickson@mvp-runner-1:~/motovaultpro$ docker compose run --rm mvp-backend npm run migrate
[+] 3/3t 3/31
✔ Network motovaultpro_backend Created 0.1s
✔ Container mvp-postgres Running 0.0s
✔ Container mvp-redis Running 0.0s
Image motovaultpro-mvp-backend Building
[+] Building 196.2s (26/26) FINISHED
=> [internal] load local bake definitions 0.0s
=> => reading from stdin 622B
=> => unpacking to docker.io/library/motovaultpro-mvp-backend:latest 9.5s
=> resolving provenance for metadata file 0.0s
Image motovaultpro-mvp-backend Built
Container motovaultpro-mvp-backend-run-9a43629523b7 Creating
Container motovaultpro-mvp-backend-run-9a43629523b7 Created
npm error Missing script: "migrate"
npm error
npm error To see a list of scripts, run:
npm error npm run
npm error A complete log of this run can be found in: /home/nodejs/.npm/_logs/2025-12-20T16_15_34_540Z-debug-0.log
index-BZX6X6vG.js:21 [Auth Gate] Module loaded, authInitialized: false
index-BZX6X6vG.js:21 [Navigation] State rehydrated successfully
index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object
index-BZX6X6vG.js:21 [TokenInjector] Component loaded
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: true isAuthenticated: false isAuthGateReady: false
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:21 [useIsAuthInitialized] Starting poll for auth init
index-BZX6X6vG.js:27 DataSync: Skipping prefetch - user not authenticated
index-BZX6X6vG.js:27 Window width: 1728 User agent mobile: false Mobile mode: false
index-BZX6X6vG.js:21 [Auth Debug] Mobile: false, Loading: true, Authenticated: false, User: null
index-BZX6X6vG.js:21 [Auth Debug] State check: Object
index-BZX6X6vG.js:21 [DEBUG] setAuthInitialized called with: false (was: false )
index-BZX6X6vG.js:21 [IndexedDB] Loaded 0 items into cache
index-BZX6X6vG.js:21 [IndexedDB] Storage initialized successfully
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #1: initialized=false
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #2: initialized=false
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #3: initialized=false
index-BZX6X6vG.js:21 [Auth0Provider] Redirect callback triggered Object
index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object
index-BZX6X6vG.js:21 [TokenInjector] Component loaded
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: false
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:27 [DEBUG App] Auth gate not ready yet, showing loading state
index-BZX6X6vG.js:21 [Auth Debug] Mobile: false, Loading: false, Authenticated: true, User: present
index-BZX6X6vG.js:21 [Auth Debug] State check: Object
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: false
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:27 [DEBUG App] Auth gate not ready yet, showing loading state
index-BZX6X6vG.js:21 [Auth] IndexedDB storage is ready
index-BZX6X6vG.js:21 [Mobile Auth] Initializing token cache (mobile: false, delay: 100ms)
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #4: initialized=false
index-BZX6X6vG.js:21 [Mobile Auth] Token acquired successfully on attempt 1 Object
index-BZX6X6vG.js:21 [Mobile Auth] Token pre-warming successful
index-BZX6X6vG.js:21 [DEBUG] setAuthInitialized called with: true (was: false )
index-BZX6X6vG.js:21 [DEBUG Auth Gate] Authentication fully initialized
index-BZX6X6vG.js:21 [useIsAuthInitialized] Poll #5: initialized=true
index-BZX6X6vG.js:21 [useIsAuthInitialized] Auth initialized via poll!
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: true
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:21 [Auth0Provider] Component loaded Object
index-BZX6X6vG.js:21 [TokenInjector] Component loaded
index-BZX6X6vG.js:27 [DEBUG App] Render check - isLoading: false isAuthenticated: true isAuthGateReady: true
index-BZX6X6vG.js:27 MotoVaultPro status: Object
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Query function called - fetching vehicles
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:21 [Mobile Auth] Token acquired successfully on attempt 1 Object
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:27 [useVehicles] Hook called - isAuthenticated: true isLoading: false
index-BZX6X6vG.js:21 Uncaught Error: Minified React error #185; visit https://react.dev/errors/185 for the full message or use the non-minified dev environment for full errors and additional helpful warnings.
at yi (index-BZX6X6vG.js:21:32113)
at hi (index-BZX6X6vG.js:21:31638)
at Yc (index-BZX6X6vG.js:21:64112)
at Hc (index-BZX6X6vG.js:21:63724)
at index-BZX6X6vG.js:27:314956
at Zu (index-BZX6X6vG.js:21:97033)
at Ad (index-BZX6X6vG.js:21:113396)
at Fd (index-BZX6X6vG.js:21:113280)
at Ad (index-BZX6X6vG.js:21:113441)
at Fd (index-BZX6X6vG.js:21:113280)

View File

@@ -40,9 +40,11 @@ export function useOptimisticVehicles(vehicles: Vehicle[] = []) {
}; };
// Sync optimistic state with actual vehicles when they change // Sync optimistic state with actual vehicles when they change
// Guard: Skip if both are empty arrays (prevents infinite loop on reference changes)
useEffect(() => { useEffect(() => {
if (vehicles.length === 0 && optimisticVehicles.length === 0) return;
setOptimisticVehicles(vehicles); setOptimisticVehicles(vehicles);
}, [vehicles]); }, [vehicles, optimisticVehicles.length]);
const optimisticCreateVehicle = async (data: CreateVehicleRequest) => { const optimisticCreateVehicle = async (data: CreateVehicleRequest) => {
// Create optimistic vehicle with temporary ID // Create optimistic vehicle with temporary ID

View File

@@ -3,7 +3,7 @@
* @ai-context Non-blocking updates for better UI responsiveness * @ai-context Non-blocking updates for better UI responsiveness
*/ */
import { useTransition, useState, useCallback } from 'react'; import { useTransition, useState, useCallback, useEffect } from 'react';
import { Vehicle } from '../types/vehicles.types'; import { Vehicle } from '../types/vehicles.types';
export function useVehicleSearch(vehicles: Vehicle[]) { export function useVehicleSearch(vehicles: Vehicle[]) {
@@ -42,35 +42,32 @@ export function useVehicleSearch(vehicles: Vehicle[]) {
}); });
}, [vehicles]); }, [vehicles]);
// Update filtered vehicles when vehicles data changes // Auto-sync filtered vehicles when source vehicles change
const updateVehicles = useCallback((newVehicles: Vehicle[]) => { // Note: Direct state update (no startTransition) to avoid deferred render cascades
startTransition(() => { useEffect(() => {
if (!searchQuery.trim()) { if (!searchQuery.trim()) {
setFilteredVehicles(newVehicles); setFilteredVehicles(vehicles);
} else { } else {
// Re-apply search filter to new data const filtered = vehicles.filter(vehicle => {
const filtered = newVehicles.filter(vehicle => { const searchTerm = searchQuery.toLowerCase();
const searchTerm = searchQuery.toLowerCase(); return (
return ( vehicle.nickname?.toLowerCase().includes(searchTerm) ||
vehicle.nickname?.toLowerCase().includes(searchTerm) || vehicle.make?.toLowerCase().includes(searchTerm) ||
vehicle.make?.toLowerCase().includes(searchTerm) || vehicle.model?.toLowerCase().includes(searchTerm) ||
vehicle.model?.toLowerCase().includes(searchTerm) || vehicle.vin.toLowerCase().includes(searchTerm) ||
vehicle.vin.toLowerCase().includes(searchTerm) || vehicle.year?.toString().includes(searchTerm)
vehicle.year?.toString().includes(searchTerm) );
); });
}); setFilteredVehicles(filtered);
setFilteredVehicles(filtered); }
} }, [vehicles, searchQuery]);
});
}, [searchQuery]);
return { return {
searchQuery, searchQuery,
filteredVehicles, filteredVehicles,
isPending, isPending,
handleSearch, handleSearch,
clearSearch, clearSearch
updateVehicles
}; };
} }

View File

@@ -3,7 +3,7 @@
* @ai-context Enhanced with Suspense, optimistic updates, and transitions * @ai-context Enhanced with Suspense, optimistic updates, and transitions
*/ */
import React, { useTransition, useEffect } from 'react'; import React, { useTransition, useMemo } from 'react';
import { Box, Typography, Grid, Button } from '@mui/material'; import { Box, Typography, Grid, Button } from '@mui/material';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import { useVehicles } from '../hooks/useVehicles'; import { useVehicles } from '../hooks/useVehicles';
@@ -40,24 +40,22 @@ export const VehiclesMobileScreen: React.FC<VehiclesMobileScreenProps> = ({
}) => { }) => {
const { data: vehicles, isLoading } = useVehicles(); const { data: vehicles, isLoading } = useVehicles();
const [_isPending, startTransition] = useTransition(); const [_isPending, startTransition] = useTransition();
// Stable reference for empty array (prevents infinite loop when vehicles is undefined)
const safeVehicles = useMemo(
() => (Array.isArray(vehicles) ? vehicles : []),
[vehicles]
);
// React 19 optimistic updates // React 19 optimistic updates
const { const {
optimisticVehicles, optimisticVehicles,
isPending: isOptimisticPending isPending: isOptimisticPending
} = useOptimisticVehicles(Array.isArray(vehicles) ? vehicles : []); } = useOptimisticVehicles(safeVehicles);
// Enhanced search with transitions
const {
filteredVehicles,
updateVehicles
} = useVehicleSearch(optimisticVehicles);
// Update search when optimistic vehicles change
useEffect(() => {
updateVehicles(optimisticVehicles);
}, [optimisticVehicles, updateVehicles]);
// Enhanced search with transitions (auto-syncs when vehicles change)
const { filteredVehicles } = useVehicleSearch(optimisticVehicles);
const handleVehicleSelect = (vehicle: Vehicle) => { const handleVehicleSelect = (vehicle: Vehicle) => {
// Use transition to avoid blocking UI during navigation // Use transition to avoid blocking UI during navigation
startTransition(() => { startTransition(() => {

View File

@@ -3,7 +3,7 @@
* @ai-context Enhanced with Suspense, useOptimistic, and useTransition * @ai-context Enhanced with Suspense, useOptimistic, and useTransition
*/ */
import React, { useState, useEffect, useTransition } from 'react'; import React, { useState, useTransition, useMemo } from 'react';
import { Box, Typography, Grid, Button as MuiButton, TextField, IconButton } from '@mui/material'; import { Box, Typography, Grid, Button as MuiButton, TextField, IconButton } from '@mui/material';
import AddIcon from '@mui/icons-material/Add'; import AddIcon from '@mui/icons-material/Add';
import SearchIcon from '@mui/icons-material/Search'; import SearchIcon from '@mui/icons-material/Search';
@@ -25,33 +25,33 @@ export const VehiclesPage: React.FC = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { data: vehicles, isLoading } = useVehicles(); const { data: vehicles, isLoading } = useVehicles();
const setSelectedVehicle = useAppStore(state => state.setSelectedVehicle); const setSelectedVehicle = useAppStore(state => state.setSelectedVehicle);
// Stable reference for empty array (prevents infinite loop when vehicles is undefined)
const safeVehicles = useMemo(
() => (Array.isArray(vehicles) ? vehicles : []),
[vehicles]
);
// React 19 optimistic updates and transitions // React 19 optimistic updates and transitions
const { const {
optimisticVehicles, optimisticVehicles,
isPending: isOptimisticPending, isPending: isOptimisticPending,
optimisticCreateVehicle, optimisticCreateVehicle,
optimisticDeleteVehicle optimisticDeleteVehicle
} = useOptimisticVehicles(Array.isArray(vehicles) ? vehicles : []); } = useOptimisticVehicles(safeVehicles);
const { const {
searchQuery, searchQuery,
filteredVehicles, filteredVehicles,
isPending: isSearchPending, isPending: isSearchPending,
handleSearch, handleSearch,
clearSearch, clearSearch
updateVehicles
} = useVehicleSearch(optimisticVehicles); } = useVehicleSearch(optimisticVehicles);
const [isPending, startTransition] = useTransition(); const [isPending, startTransition] = useTransition();
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [stagedImageFile, setStagedImageFile] = useState<File | null>(null); const [stagedImageFile, setStagedImageFile] = useState<File | null>(null);
// Update search vehicles when optimistic vehicles change
useEffect(() => {
updateVehicles(optimisticVehicles);
}, [optimisticVehicles, updateVehicles]);
const handleSelectVehicle = (id: string) => { const handleSelectVehicle = (id: string) => {
// Use transition for navigation to avoid blocking UI // Use transition for navigation to avoid blocking UI
startTransition(() => { startTransition(() => {