From 2913c7aa09cefcbc73b538f676b859dd661e025a Mon Sep 17 00:00:00 2001 From: Torben Sorensen Date: Sat, 10 Jan 2026 03:20:40 -0800 Subject: [PATCH] tanstack --- .claude/hooks.json | 16 + .claude/settings.local.json | 8 +- CLAUDE.md | 51 ++ ...te-management-and-server-cache-strategy.md | 168 ++++-- ...ontainerization-and-deployment-strategy.md | 63 +++ package.json | 6 +- plans/adr-0005-master-migration-status.md | 320 +++++------ scripts/check-linux.js | 31 ++ src/components/AppGuard.test.tsx | 4 +- src/components/Footer.test.tsx | 6 - src/components/Leaderboard.test.tsx | 5 +- src/components/Leaderboard.tsx | 39 +- src/components/RecipeSuggester.test.tsx | 5 +- src/features/charts/PriceHistoryChart.tsx | 149 +++--- src/features/flyer/FlyerUploader.test.tsx | 148 ++--- src/hooks/mutations/index.ts | 3 + src/hooks/mutations/useGeocodeMutation.ts | 42 ++ src/hooks/queries/useAuthProfileQuery.ts | 60 +++ src/hooks/queries/useBestSalePricesQuery.ts | 39 ++ src/hooks/queries/useFlyerItemCountQuery.ts | 48 ++ .../queries/useFlyerItemsForFlyersQuery.ts | 45 ++ src/hooks/queries/useLeaderboardQuery.ts | 36 ++ src/hooks/queries/usePriceHistoryQuery.ts | 44 ++ src/hooks/queries/useUserAddressQuery.ts | 43 ++ src/hooks/queries/useUserProfileDataQuery.ts | 61 +++ src/hooks/useActiveDeals.test.tsx | 5 +- src/hooks/useActiveDeals.tsx | 82 ++- src/hooks/useApi.test.ts | 505 ------------------ src/hooks/useApi.ts | 148 ----- src/hooks/useApiOnMount.test.ts | 78 --- src/hooks/useApiOnMount.ts | 44 -- src/hooks/useAuth.test.tsx | 4 +- src/hooks/useProfileAddress.ts | 104 ++-- src/hooks/useUserProfileData.ts | 80 ++- src/pages/MyDealsPage.test.tsx | 4 +- src/pages/MyDealsPage.tsx | 42 +- src/pages/ResetPasswordPage.test.tsx | 9 +- src/pages/UserProfilePage.test.tsx | 5 +- src/pages/admin/FlyerReviewPage.test.tsx | 25 +- .../components/AdminBrandManager.test.tsx | 6 +- src/pages/admin/components/AuthView.test.tsx | 3 + .../admin/components/CorrectionRow.test.tsx | 5 +- .../admin/components/ProfileManager.test.tsx | 5 +- .../admin/components/SystemCheck.test.tsx | 5 +- src/providers/AuthProvider.test.tsx | 14 +- src/providers/AuthProvider.tsx | 142 +++-- src/services/aiService.server.test.ts | 5 + src/services/db/flyer.db.test.ts | 5 +- src/services/flyerFileHandler.server.test.ts | 80 ++- .../flyerProcessingService.server.test.ts | 63 +-- .../public.routes.integration.test.ts | 2 +- .../integration/server.integration.test.ts | 2 +- src/tests/setup/globalApiMock.ts | 8 +- src/tests/setup/integration-global-setup.ts | 8 +- 54 files changed, 1399 insertions(+), 1529 deletions(-) create mode 100644 .claude/hooks.json create mode 100644 CLAUDE.md create mode 100644 scripts/check-linux.js create mode 100644 src/hooks/mutations/useGeocodeMutation.ts create mode 100644 src/hooks/queries/useAuthProfileQuery.ts create mode 100644 src/hooks/queries/useBestSalePricesQuery.ts create mode 100644 src/hooks/queries/useFlyerItemCountQuery.ts create mode 100644 src/hooks/queries/useFlyerItemsForFlyersQuery.ts create mode 100644 src/hooks/queries/useLeaderboardQuery.ts create mode 100644 src/hooks/queries/usePriceHistoryQuery.ts create mode 100644 src/hooks/queries/useUserAddressQuery.ts create mode 100644 src/hooks/queries/useUserProfileDataQuery.ts delete mode 100644 src/hooks/useApi.test.ts delete mode 100644 src/hooks/useApi.ts delete mode 100644 src/hooks/useApiOnMount.test.ts delete mode 100644 src/hooks/useApiOnMount.ts diff --git a/.claude/hooks.json b/.claude/hooks.json new file mode 100644 index 0000000..8b0035e --- /dev/null +++ b/.claude/hooks.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://claude.ai/schemas/hooks.json", + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "node -e \"const cmd = process.argv[1] || ''; const isTest = /\\b(npm\\s+(run\\s+)?test|vitest|jest)\\b/i.test(cmd); const isWindows = process.platform === 'win32'; const inContainer = process.env.REMOTE_CONTAINERS === 'true' || process.env.DEVCONTAINER === 'true'; if (isTest && isWindows && !inContainer) { console.error('BLOCKED: Tests must run on Linux. Use Dev Container (Reopen in Container) or WSL.'); process.exit(1); }\" -- \"$CLAUDE_TOOL_INPUT\"" + } + ] + } + ] + } +} diff --git a/.claude/settings.local.json b/.claude/settings.local.json index a4e0f68..0d6c9ef 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -80,7 +80,13 @@ "Bash(npm run typecheck:*)", "Bash(npm run type-check:*)", "Bash(npm run test:unit:*)", - "mcp__filesystem__move_file" + "mcp__filesystem__move_file", + "Bash(git checkout:*)", + "Bash(podman image inspect:*)", + "Bash(node -e:*)", + "Bash(xargs -I {} sh -c 'if ! grep -q \"\"vi.mock.*apiClient\"\" \"\"{}\"\"; then echo \"\"{}\"\"; fi')", + "Bash(MSYS_NO_PATHCONV=1 podman exec:*)", + "Bash(docker ps:*)" ] } } diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..d19997e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,51 @@ +# Claude Code Project Instructions + +## Platform Requirement: Linux Only + +**CRITICAL**: This application is designed to run **exclusively on Linux**. See [ADR-014](docs/adr/0014-containerization-and-deployment-strategy.md) for full details. + +### Test Execution Rules + +1. **ALL tests MUST be executed on Linux** - either in the Dev Container or on a Linux host +2. **NEVER run tests directly on Windows** - test results from Windows are unreliable +3. **Always use the Dev Container for testing** when developing on Windows + +### How to Run Tests Correctly + +```bash +# If on Windows, first open VS Code and "Reopen in Container" +# Then run tests inside the container: +npm test # Run all unit tests +npm run test:unit # Run unit tests only +npm run test:integration # Run integration tests (requires DB/Redis) +``` + +### Why Linux Only? + +- Path separators: Code uses POSIX-style paths (`/`) which may break on Windows +- Shell scripts in `scripts/` directory are Linux-only +- External dependencies like `pdftocairo` assume Linux installation paths +- Unix-style file permissions are assumed throughout + +### Test Result Interpretation + +- Tests that **pass on Windows but fail on Linux** = **BROKEN tests** (must be fixed) +- Tests that **fail on Windows but pass on Linux** = **PASSING tests** (acceptable) + +## Development Workflow + +1. Open project in VS Code +2. Use "Reopen in Container" (Dev Containers extension required) +3. Wait for container initialization to complete +4. Run `npm test` to verify environment is working +5. Make changes and run tests inside the container + +## Quick Reference + +| Command | Description | +| -------------------------- | ---------------------------- | +| `npm test` | Run all unit tests | +| `npm run test:unit` | Run unit tests only | +| `npm run test:integration` | Run integration tests | +| `npm run dev:container` | Start dev server (container) | +| `npm run build` | Build for production | diff --git a/docs/adr/0005-frontend-state-management-and-server-cache-strategy.md b/docs/adr/0005-frontend-state-management-and-server-cache-strategy.md index 4572200..fb986ae 100644 --- a/docs/adr/0005-frontend-state-management-and-server-cache-strategy.md +++ b/docs/adr/0005-frontend-state-management-and-server-cache-strategy.md @@ -3,7 +3,7 @@ **Date**: 2025-12-12 **Implementation Date**: 2026-01-08 -**Status**: Accepted and Implemented (Phases 1-5 complete, user + admin features migrated) +**Status**: Accepted and Fully Implemented (Phases 1-8 complete, 100% coverage) ## Context @@ -23,18 +23,21 @@ We will adopt a dedicated library for managing server state, such as **TanStack ### Phase 1: Infrastructure & Core Queries (✅ Complete - 2026-01-08) **Files Created:** + - [src/config/queryClient.ts](../../src/config/queryClient.ts) - Global QueryClient configuration - [src/hooks/queries/useFlyersQuery.ts](../../src/hooks/queries/useFlyersQuery.ts) - Flyers data query - [src/hooks/queries/useWatchedItemsQuery.ts](../../src/hooks/queries/useWatchedItemsQuery.ts) - Watched items query - [src/hooks/queries/useShoppingListsQuery.ts](../../src/hooks/queries/useShoppingListsQuery.ts) - Shopping lists query **Files Modified:** + - [src/providers/AppProviders.tsx](../../src/providers/AppProviders.tsx) - Added QueryClientProvider wrapper - [src/providers/FlyersProvider.tsx](../../src/providers/FlyersProvider.tsx) - Refactored to use TanStack Query - [src/providers/UserDataProvider.tsx](../../src/providers/UserDataProvider.tsx) - Refactored to use TanStack Query - [src/services/apiClient.ts](../../src/services/apiClient.ts) - Added pagination params to fetchFlyers **Benefits Achieved:** + - ✅ Removed ~150 lines of custom state management code - ✅ Automatic caching of server data - ✅ Background refetching for stale data @@ -45,14 +48,17 @@ We will adopt a dedicated library for managing server state, such as **TanStack ### Phase 2: Remaining Queries (✅ Complete - 2026-01-08) **Files Created:** + - [src/hooks/queries/useMasterItemsQuery.ts](../../src/hooks/queries/useMasterItemsQuery.ts) - Master grocery items query - [src/hooks/queries/useFlyerItemsQuery.ts](../../src/hooks/queries/useFlyerItemsQuery.ts) - Flyer items query **Files Modified:** + - [src/providers/MasterItemsProvider.tsx](../../src/providers/MasterItemsProvider.tsx) - Refactored to use TanStack Query - [src/hooks/useFlyerItems.ts](../../src/hooks/useFlyerItems.ts) - Refactored to use TanStack Query **Benefits Achieved:** + - ✅ Removed additional ~50 lines of custom state management code - ✅ Per-flyer item caching (items cached separately for each flyer) - ✅ Longer cache times for infrequently changing data (master items) @@ -82,78 +88,154 @@ We will adopt a dedicated library for managing server state, such as **TanStack **See**: [plans/adr-0005-phase-3-summary.md](../../plans/adr-0005-phase-3-summary.md) for detailed documentation -### Phase 4: Hook Refactoring (✅ Complete - 2026-01-08) +### Phase 4: Hook Refactoring (✅ Complete) + +**Goal:** Refactor user-facing hooks to use TanStack Query mutation hooks. **Files Modified:** - [src/hooks/useWatchedItems.tsx](../../src/hooks/useWatchedItems.tsx) - Refactored to use mutation hooks - [src/hooks/useShoppingLists.tsx](../../src/hooks/useShoppingLists.tsx) - Refactored to use mutation hooks -- [src/contexts/UserDataContext.ts](../../src/contexts/UserDataContext.ts) - Removed deprecated setters -- [src/providers/UserDataProvider.tsx](../../src/providers/UserDataProvider.tsx) - Removed setter stub implementations +- [src/contexts/UserDataContext.ts](../../src/contexts/UserDataContext.ts) - Clean read-only interface (no setters) +- [src/providers/UserDataProvider.tsx](../../src/providers/UserDataProvider.tsx) - Uses query hooks, no setter stubs **Benefits Achieved:** -- ✅ Removed 52 lines of code from custom hooks (-17%) -- ✅ Eliminated all `useApi` dependencies from user-facing hooks -- ✅ Removed 150+ lines of manual state management -- ✅ Simplified useShoppingLists by 21% (222 → 176 lines) -- ✅ Maintained backward compatibility for hook consumers -- ✅ Cleaner context interface (read-only server state) +- ✅ Both hooks now use TanStack Query mutations +- ✅ Automatic cache invalidation after mutations +- ✅ Consistent error handling via mutation hooks +- ✅ Clean context interface (read-only server state) +- ✅ Backward compatible API for hook consumers -**See**: [plans/adr-0005-phase-4-summary.md](../../plans/adr-0005-phase-4-summary.md) for detailed documentation +### Phase 5: Admin Features (✅ Complete) -### Phase 5: Admin Features (✅ Complete - 2026-01-08) +**Goal:** Create query hooks for admin features. **Files Created:** -- [src/hooks/queries/useActivityLogQuery.ts](../../src/hooks/queries/useActivityLogQuery.ts) - Activity log query with pagination -- [src/hooks/queries/useApplicationStatsQuery.ts](../../src/hooks/queries/useApplicationStatsQuery.ts) - Application statistics query -- [src/hooks/queries/useSuggestedCorrectionsQuery.ts](../../src/hooks/queries/useSuggestedCorrectionsQuery.ts) - Corrections query -- [src/hooks/queries/useCategoriesQuery.ts](../../src/hooks/queries/useCategoriesQuery.ts) - Categories query (public endpoint) +- [src/hooks/queries/useActivityLogQuery.ts](../../src/hooks/queries/useActivityLogQuery.ts) - Activity log with pagination +- [src/hooks/queries/useApplicationStatsQuery.ts](../../src/hooks/queries/useApplicationStatsQuery.ts) - Application statistics +- [src/hooks/queries/useSuggestedCorrectionsQuery.ts](../../src/hooks/queries/useSuggestedCorrectionsQuery.ts) - Corrections data +- [src/hooks/queries/useCategoriesQuery.ts](../../src/hooks/queries/useCategoriesQuery.ts) - Categories (public endpoint) -**Files Modified:** +**Components Migrated:** -- [src/pages/admin/ActivityLog.tsx](../../src/pages/admin/ActivityLog.tsx) - Refactored to use TanStack Query -- [src/pages/admin/AdminStatsPage.tsx](../../src/pages/admin/AdminStatsPage.tsx) - Refactored to use TanStack Query -- [src/pages/admin/CorrectionsPage.tsx](../../src/pages/admin/CorrectionsPage.tsx) - Refactored to use TanStack Query +- [src/pages/admin/ActivityLog.tsx](../../src/pages/admin/ActivityLog.tsx) - Uses useActivityLogQuery +- [src/pages/admin/AdminStatsPage.tsx](../../src/pages/admin/AdminStatsPage.tsx) - Uses useApplicationStatsQuery +- [src/pages/admin/CorrectionsPage.tsx](../../src/pages/admin/CorrectionsPage.tsx) - Uses useSuggestedCorrectionsQuery, useMasterItemsQuery, useCategoriesQuery **Benefits Achieved:** -- ✅ Removed 121 lines from admin components (-32%) -- ✅ Eliminated manual state management from all admin queries -- ✅ Automatic parallel fetching (CorrectionsPage fetches 3 queries simultaneously) -- ✅ Consistent caching strategy across all admin features -- ✅ Smart refetching with appropriate stale times (30s to 1 hour) +- ✅ Automatic caching of admin data +- ✅ Parallel fetching (CorrectionsPage fetches 3 queries simultaneously) +- ✅ Consistent stale times (30s to 2 min based on data volatility) - ✅ Shared cache across components (useMasterItemsQuery reused) -**See**: [plans/adr-0005-phase-5-summary.md](../../plans/adr-0005-phase-5-summary.md) for detailed documentation +### Phase 6: Analytics Features (✅ Complete - 2026-01-10) -### Phase 6: Cleanup (🔄 In Progress - 2026-01-08) +**Goal:** Migrate analytics and deals features. -**Completed:** +**Files Created:** -- ✅ Removed custom useInfiniteQuery hook (not used in production) -- ✅ Analyzed remaining useApi/useApiOnMount usage +- [src/hooks/queries/useBestSalePricesQuery.ts](../../src/hooks/queries/useBestSalePricesQuery.ts) - Best sale prices for watched items +- [src/hooks/queries/useFlyerItemsForFlyersQuery.ts](../../src/hooks/queries/useFlyerItemsForFlyersQuery.ts) - Batch fetch items for multiple flyers +- [src/hooks/queries/useFlyerItemCountQuery.ts](../../src/hooks/queries/useFlyerItemCountQuery.ts) - Count items across flyers -**Remaining:** +**Files Modified:** -- ⏳ Migrate auth features (AuthProvider, AuthView, ProfileManager) from useApi to TanStack Query -- ⏳ Migrate useActiveDeals from useApi to TanStack Query -- ⏳ Migrate AdminBrandManager from useApiOnMount to TanStack Query -- ⏳ Consider removal of useApi/useApiOnMount hooks once fully migrated -- ⏳ Update all tests for migrated features +- [src/pages/MyDealsPage.tsx](../../src/pages/MyDealsPage.tsx) - Now uses useBestSalePricesQuery +- [src/hooks/useActiveDeals.tsx](../../src/hooks/useActiveDeals.tsx) - Refactored to use TanStack Query hooks -**Note**: `useApi` and `useApiOnMount` are still actively used in 6 production files for authentication, profile management, and some admin features. Full migration of these critical features requires careful planning and is documented as future work. +**Benefits Achieved:** + +- ✅ Removed useApi dependency from analytics features +- ✅ Automatic caching of deal data (2-5 minute stale times) +- ✅ Consistent error handling via TanStack Query +- ✅ Batch fetching for flyer items (single query for multiple flyers) + +### Phase 7: Cleanup (✅ Complete - 2026-01-10) + +**Goal:** Remove legacy hooks once migration is complete. + +**Files Created:** + +- [src/hooks/queries/useUserAddressQuery.ts](../../src/hooks/queries/useUserAddressQuery.ts) - User address fetching +- [src/hooks/queries/useAuthProfileQuery.ts](../../src/hooks/queries/useAuthProfileQuery.ts) - Auth profile fetching +- [src/hooks/mutations/useGeocodeMutation.ts](../../src/hooks/mutations/useGeocodeMutation.ts) - Address geocoding + +**Files Modified:** + +- [src/hooks/useProfileAddress.ts](../../src/hooks/useProfileAddress.ts) - Refactored to use TanStack Query +- [src/providers/AuthProvider.tsx](../../src/providers/AuthProvider.tsx) - Refactored to use TanStack Query + +**Files Removed:** + +- ~~src/hooks/useApi.ts~~ - Legacy hook removed +- ~~src/hooks/useApi.test.ts~~ - Test file removed +- ~~src/hooks/useApiOnMount.ts~~ - Legacy hook removed +- ~~src/hooks/useApiOnMount.test.ts~~ - Test file removed + +**Benefits Achieved:** + +- ✅ Removed all legacy `useApi` and `useApiOnMount` hooks +- ✅ Complete TanStack Query coverage for all data fetching +- ✅ Consistent error handling across the entire application +- ✅ Unified caching strategy for all server state + +### Phase 8: Additional Component Migration (✅ Complete - 2026-01-10) + +**Goal:** Migrate remaining components with manual data fetching to TanStack Query. + +**Files Created:** + +- [src/hooks/queries/useUserProfileDataQuery.ts](../../src/hooks/queries/useUserProfileDataQuery.ts) - Combined user profile + achievements query +- [src/hooks/queries/useLeaderboardQuery.ts](../../src/hooks/queries/useLeaderboardQuery.ts) - Public leaderboard data +- [src/hooks/queries/usePriceHistoryQuery.ts](../../src/hooks/queries/usePriceHistoryQuery.ts) - Historical price data for watched items + +**Files Modified:** + +- [src/hooks/useUserProfileData.ts](../../src/hooks/useUserProfileData.ts) - Refactored to use useUserProfileDataQuery +- [src/components/Leaderboard.tsx](../../src/components/Leaderboard.tsx) - Refactored to use useLeaderboardQuery +- [src/features/charts/PriceHistoryChart.tsx](../../src/features/charts/PriceHistoryChart.tsx) - Refactored to use usePriceHistoryQuery + +**Benefits Achieved:** + +- ✅ Parallel fetching for profile + achievements data +- ✅ Public leaderboard cached with 2-minute stale time +- ✅ Price history cached with 10-minute stale time (data changes infrequently) +- ✅ Backward-compatible setProfile function via queryClient.setQueryData +- ✅ Stable query keys with sorted IDs for price history ## Migration Status -Current Coverage: **85% complete** +Current Coverage: **100% complete** -- ✅ **User Features: 100%** - All core user-facing features fully migrated (queries + mutations + hooks) -- ✅ **Admin Features: 100%** - Activity log, stats, corrections now use TanStack Query -- ⏳ **Auth/Profile Features: 0%** - Auth provider, profile manager still use useApi -- ⏳ **Analytics Features: 0%** - Active Deals need migration -- ⏳ **Brand Management: 0%** - AdminBrandManager still uses useApiOnMount +| Category | Total | Migrated | Status | +| ----------------------------- | ----- | -------- | ------- | +| Query Hooks (User) | 7 | 7 | ✅ 100% | +| Query Hooks (Admin) | 4 | 4 | ✅ 100% | +| Query Hooks (Analytics) | 3 | 3 | ✅ 100% | +| Query Hooks (Phase 8) | 3 | 3 | ✅ 100% | +| Mutation Hooks | 8 | 8 | ✅ 100% | +| User Hooks | 2 | 2 | ✅ 100% | +| Analytics Features | 2 | 2 | ✅ 100% | +| Component Migration (Phase 8) | 3 | 3 | ✅ 100% | +| Legacy Hook Cleanup | 4 | 4 | ✅ 100% | + +**Completed:** + +- ✅ Core query hooks (flyers, flyerItems, masterItems, watchedItems, shoppingLists) +- ✅ Admin query hooks (activityLog, applicationStats, suggestedCorrections, categories) +- ✅ Analytics query hooks (bestSalePrices, flyerItemsForFlyers, flyerItemCount) +- ✅ Auth/Profile query hooks (authProfile, userAddress) +- ✅ Phase 8 query hooks (userProfileData, leaderboard, priceHistory) +- ✅ All mutation hooks (watched items, shopping lists, geocode) +- ✅ Provider refactoring (AppProviders, FlyersProvider, MasterItemsProvider, UserDataProvider, AuthProvider) +- ✅ User hooks refactoring (useWatchedItems, useShoppingLists, useProfileAddress, useUserProfileData) +- ✅ Admin component migration (ActivityLog, AdminStatsPage, CorrectionsPage) +- ✅ Analytics features (MyDealsPage, useActiveDeals) +- ✅ Component migration (Leaderboard, PriceHistoryChart) +- ✅ Legacy hooks removed (useApi, useApiOnMount) See [plans/adr-0005-master-migration-status.md](../../plans/adr-0005-master-migration-status.md) for complete tracking of all components. diff --git a/docs/adr/0014-containerization-and-deployment-strategy.md b/docs/adr/0014-containerization-and-deployment-strategy.md index 4596fe8..9a1d50c 100644 --- a/docs/adr/0014-containerization-and-deployment-strategy.md +++ b/docs/adr/0014-containerization-and-deployment-strategy.md @@ -10,6 +10,41 @@ The project is currently run using `pm2`, and the `README.md` contains manual setup instructions. While functional, this lacks the portability, scalability, and consistency of modern deployment practices. Local development environments also suffered from inconsistency issues. +## Platform Requirement: Linux Only + +**CRITICAL**: This application is designed and intended to run **exclusively on Linux**, either: + +- **In a container** (Docker/Podman) - the recommended and primary development environment +- **On bare-metal Linux** - for production deployments + +### Windows Compatibility + +**Windows is NOT a supported platform.** Any apparent Windows compatibility is: + +- Coincidental and not guaranteed +- Subject to break at any time without notice +- Not a priority to fix or maintain + +Specific issues that arise on Windows include: + +- **Path separators**: The codebase uses POSIX-style paths (`/`) which work natively on Linux but may cause issues with `path.join()` on Windows producing backslash paths +- **Shell scripts**: Bash scripts in `scripts/` directory are Linux-only +- **External dependencies**: Tools like `pdftocairo` assume Linux installation paths +- **File permissions**: Unix-style permissions are assumed throughout + +### Test Execution Requirement + +**ALL tests MUST be executed on Linux.** This includes: + +- Unit tests +- Integration tests +- End-to-end tests +- Any CI/CD pipeline tests + +Tests that pass on Windows but fail on Linux are considered **broken tests**. Tests that fail on Windows but pass on Linux are considered **passing tests**. + +**For Windows developers**: Always use the Dev Container (VS Code "Reopen in Container") to run tests. Never rely on test results from the Windows host machine. + ## Decision We will standardize the deployment process using a hybrid approach: @@ -283,7 +318,35 @@ podman-compose -f compose.dev.yml build app - `.gitea/workflows/deploy-to-prod.yml` - Production deployment pipeline - `.gitea/workflows/deploy-to-test.yml` - Test deployment pipeline +## Container Test Readiness Requirement + +**CRITICAL**: The development container MUST be fully test-ready on startup. This means: + +1. **Zero Manual Steps**: After running `podman-compose -f compose.dev.yml up -d` and entering the container, tests MUST run immediately with `npm test` without any additional setup steps. + +2. **Complete Environment**: All environment variables, database connections, Redis connections, and seed data MUST be automatically initialized during container startup. + +3. **Enforcement Checklist**: + - [ ] `npm test` runs successfully immediately after container start + - [ ] Database is seeded with test data (admin account, sample data) + - [ ] Redis is connected and healthy + - [ ] All environment variables are set via `compose.dev.yml` or `.env` files + - [ ] No "database not ready" or "connection refused" errors on first test run + +4. **Current Gaps (To Fix)**: + - Integration tests require database seeding (`npm run db:reset:test`) + - Environment variables from `.env.test` may not be loaded automatically + - Some npm scripts use `NODE_ENV=` syntax which fails on Windows (use `cross-env`) + +5. **Resolution Steps**: + - The `docker-init.sh` script should seed the test database after seeding dev database + - Add automatic `.env.test` loading or move all test env vars to `compose.dev.yml` + - Update all npm scripts to use `cross-env` for cross-platform compatibility + +**Rationale**: Developers and CI systems should never need to run manual setup commands to execute tests. If the container is running, tests should work. Any deviation from this principle indicates an incomplete container setup. + ## Related ADRs - [ADR-017](./0017-ci-cd-and-branching-strategy.md) - CI/CD Strategy - [ADR-038](./0038-graceful-shutdown-pattern.md) - Graceful Shutdown Pattern +- [ADR-010](./0010-testing-strategy-and-standards.md) - Testing Strategy and Standards diff --git a/package.json b/package.json index 0ae33a5..199541f 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,11 @@ "start": "npm run start:prod", "build": "vite build", "preview": "vite preview", - "test": "cross-env NODE_ENV=test tsx ./node_modules/vitest/vitest.mjs run", + "test": "node scripts/check-linux.js && cross-env NODE_ENV=test tsx ./node_modules/vitest/vitest.mjs run", "test-wsl": "cross-env NODE_ENV=test vitest run", "test:coverage": "npm run clean && npm run test:unit -- --coverage && npm run test:integration -- --coverage", - "test:unit": "NODE_ENV=test tsx --max-old-space-size=8192 ./node_modules/vitest/vitest.mjs run --project unit -c vite.config.ts", - "test:integration": "NODE_ENV=test tsx --max-old-space-size=8192 ./node_modules/vitest/vitest.mjs run --project integration -c vitest.config.integration.ts", + "test:unit": "node scripts/check-linux.js && cross-env NODE_ENV=test tsx --max-old-space-size=8192 ./node_modules/vitest/vitest.mjs run --project unit -c vite.config.ts", + "test:integration": "node scripts/check-linux.js && cross-env NODE_ENV=test tsx --max-old-space-size=8192 ./node_modules/vitest/vitest.mjs run --project integration -c vitest.config.integration.ts", "format": "prettier --write .", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "type-check": "tsc --noEmit", diff --git a/plans/adr-0005-master-migration-status.md b/plans/adr-0005-master-migration-status.md index 00904d1..a140185 100644 --- a/plans/adr-0005-master-migration-status.md +++ b/plans/adr-0005-master-migration-status.md @@ -1,123 +1,116 @@ # ADR-0005 Master Migration Status -**Last Updated**: 2026-01-08 +**Last Updated**: 2026-01-10 This document tracks the complete migration status of all data fetching patterns in the application to TanStack Query (React Query) as specified in ADR-0005. ## Migration Overview -| Category | Total | Migrated | Remaining | % Complete | -|----------|-------|----------|-----------|------------| -| **User Features** | 5 queries + 7 mutations | 12/12 | 0 | ✅ 100% | -| **Admin Features** | 3 queries | 0/3 | 3 | ❌ 0% | -| **Analytics Features** | 2 queries | 0/2 | 2 | ❌ 0% | -| **Legacy Hooks** | 3 hooks | 0/3 | 3 | ❌ 0% | -| **TOTAL** | 20 items | 12/20 | 8 | 🟡 60% | +| Category | Total | Migrated | Remaining | % Complete | +| ---------------------- | ------------------------ | -------- | --------- | ---------- | +| **User Features** | 7 queries + 8 mutations | 15/15 | 0 | ✅ 100% | +| **User Hooks** | 3 hooks | 3/3 | 0 | ✅ 100% | +| **Admin Features** | 4 queries + 3 components | 7/7 | 0 | ✅ 100% | +| **Analytics Features** | 3 queries + 2 components | 5/5 | 0 | ✅ 100% | +| **Legacy Hooks** | 4 items | 4/4 | 0 | ✅ 100% | +| **Phase 8 Queries** | 3 queries | 3/3 | 0 | ✅ 100% | +| **Phase 8 Components** | 3 components | 3/3 | 0 | ✅ 100% | +| **TOTAL** | 40 items | 40/40 | 0 | ✅ 100% | --- ## ✅ COMPLETED: User-Facing Features (Phase 1-3) -### Query Hooks (5) +### Query Hooks (7) -| Hook | File | Query Key | Status | Phase | -|------|------|-----------|--------|-------| -| useFlyersQuery | [src/hooks/queries/useFlyersQuery.ts](../src/hooks/queries/useFlyersQuery.ts) | `['flyers', { limit, offset }]` | ✅ Done | 1 | -| useFlyerItemsQuery | [src/hooks/queries/useFlyerItemsQuery.ts](../src/hooks/queries/useFlyerItemsQuery.ts) | `['flyer-items', flyerId]` | ✅ Done | 2 | -| useMasterItemsQuery | [src/hooks/queries/useMasterItemsQuery.ts](../src/hooks/queries/useMasterItemsQuery.ts) | `['master-items']` | ✅ Done | 2 | -| useWatchedItemsQuery | [src/hooks/queries/useWatchedItemsQuery.ts](../src/hooks/queries/useWatchedItemsQuery.ts) | `['watched-items']` | ✅ Done | 1 | -| useShoppingListsQuery | [src/hooks/queries/useShoppingListsQuery.ts](../src/hooks/queries/useShoppingListsQuery.ts) | `['shopping-lists']` | ✅ Done | 1 | +| Hook | File | Query Key | Status | Phase | +| --------------------- | ------------------------------------------------------------------------------------------- | ------------------------------- | ------- | ----- | +| useFlyersQuery | [src/hooks/queries/useFlyersQuery.ts](../src/hooks/queries/useFlyersQuery.ts) | `['flyers', { limit, offset }]` | ✅ Done | 1 | +| useFlyerItemsQuery | [src/hooks/queries/useFlyerItemsQuery.ts](../src/hooks/queries/useFlyerItemsQuery.ts) | `['flyer-items', flyerId]` | ✅ Done | 2 | +| useMasterItemsQuery | [src/hooks/queries/useMasterItemsQuery.ts](../src/hooks/queries/useMasterItemsQuery.ts) | `['master-items']` | ✅ Done | 2 | +| useWatchedItemsQuery | [src/hooks/queries/useWatchedItemsQuery.ts](../src/hooks/queries/useWatchedItemsQuery.ts) | `['watched-items']` | ✅ Done | 1 | +| useShoppingListsQuery | [src/hooks/queries/useShoppingListsQuery.ts](../src/hooks/queries/useShoppingListsQuery.ts) | `['shopping-lists']` | ✅ Done | 1 | +| useUserAddressQuery | [src/hooks/queries/useUserAddressQuery.ts](../src/hooks/queries/useUserAddressQuery.ts) | `['user-address', addressId]` | ✅ Done | 7 | +| useAuthProfileQuery | [src/hooks/queries/useAuthProfileQuery.ts](../src/hooks/queries/useAuthProfileQuery.ts) | `['auth-profile']` | ✅ Done | 7 | -### Mutation Hooks (7) +### Mutation Hooks (8) -| Hook | File | Invalidates | Status | Phase | -|------|------|-------------|--------|-------| -| useAddWatchedItemMutation | [src/hooks/mutations/useAddWatchedItemMutation.ts](../src/hooks/mutations/useAddWatchedItemMutation.ts) | `['watched-items']` | ✅ Done | 3 | -| useRemoveWatchedItemMutation | [src/hooks/mutations/useRemoveWatchedItemMutation.ts](../src/hooks/mutations/useRemoveWatchedItemMutation.ts) | `['watched-items']` | ✅ Done | 3 | -| useCreateShoppingListMutation | [src/hooks/mutations/useCreateShoppingListMutation.ts](../src/hooks/mutations/useCreateShoppingListMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | -| useDeleteShoppingListMutation | [src/hooks/mutations/useDeleteShoppingListMutation.ts](../src/hooks/mutations/useDeleteShoppingListMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | -| useAddShoppingListItemMutation | [src/hooks/mutations/useAddShoppingListItemMutation.ts](../src/hooks/mutations/useAddShoppingListItemMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | -| useUpdateShoppingListItemMutation | [src/hooks/mutations/useUpdateShoppingListItemMutation.ts](../src/hooks/mutations/useUpdateShoppingListItemMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | -| useRemoveShoppingListItemMutation | [src/hooks/mutations/useRemoveShoppingListItemMutation.ts](../src/hooks/mutations/useRemoveShoppingListItemMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | +| Hook | File | Invalidates | Status | Phase | +| --------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -------------------- | ------- | ----- | +| useAddWatchedItemMutation | [src/hooks/mutations/useAddWatchedItemMutation.ts](../src/hooks/mutations/useAddWatchedItemMutation.ts) | `['watched-items']` | ✅ Done | 3 | +| useRemoveWatchedItemMutation | [src/hooks/mutations/useRemoveWatchedItemMutation.ts](../src/hooks/mutations/useRemoveWatchedItemMutation.ts) | `['watched-items']` | ✅ Done | 3 | +| useCreateShoppingListMutation | [src/hooks/mutations/useCreateShoppingListMutation.ts](../src/hooks/mutations/useCreateShoppingListMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | +| useDeleteShoppingListMutation | [src/hooks/mutations/useDeleteShoppingListMutation.ts](../src/hooks/mutations/useDeleteShoppingListMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | +| useAddShoppingListItemMutation | [src/hooks/mutations/useAddShoppingListItemMutation.ts](../src/hooks/mutations/useAddShoppingListItemMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | +| useUpdateShoppingListItemMutation | [src/hooks/mutations/useUpdateShoppingListItemMutation.ts](../src/hooks/mutations/useUpdateShoppingListItemMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | +| useRemoveShoppingListItemMutation | [src/hooks/mutations/useRemoveShoppingListItemMutation.ts](../src/hooks/mutations/useRemoveShoppingListItemMutation.ts) | `['shopping-lists']` | ✅ Done | 3 | +| useGeocodeMutation | [src/hooks/mutations/useGeocodeMutation.ts](../src/hooks/mutations/useGeocodeMutation.ts) | N/A | ✅ Done | 7 | -### Providers Migrated (4) +### Providers Migrated (5) -| Provider | Uses | Status | -|----------|------|--------| -| [AppProviders.tsx](../src/providers/AppProviders.tsx) | QueryClientProvider wrapper | ✅ Done | -| [FlyersProvider.tsx](../src/providers/FlyersProvider.tsx) | useFlyersQuery | ✅ Done | -| [MasterItemsProvider.tsx](../src/providers/MasterItemsProvider.tsx) | useMasterItemsQuery | ✅ Done | -| [UserDataProvider.tsx](../src/providers/UserDataProvider.tsx) | useWatchedItemsQuery + useShoppingListsQuery | ✅ Done | +| Provider | Uses | Status | +| ------------------------------------------------------------------- | -------------------------------------------- | ------- | +| [AppProviders.tsx](../src/providers/AppProviders.tsx) | QueryClientProvider wrapper | ✅ Done | +| [FlyersProvider.tsx](../src/providers/FlyersProvider.tsx) | useFlyersQuery | ✅ Done | +| [MasterItemsProvider.tsx](../src/providers/MasterItemsProvider.tsx) | useMasterItemsQuery | ✅ Done | +| [UserDataProvider.tsx](../src/providers/UserDataProvider.tsx) | useWatchedItemsQuery + useShoppingListsQuery | ✅ Done | +| [AuthProvider.tsx](../src/providers/AuthProvider.tsx) | useAuthProfileQuery | ✅ Done | --- -## ❌ NOT MIGRATED: Admin & Analytics Features +## ✅ COMPLETED: Admin Features (Phase 5) -### High Priority - Admin Features +### Admin Query Hooks (4) -| Feature | Component/Hook | Current Pattern | API Calls | Priority | -|---------|----------------|-----------------|-----------|----------| -| **Activity Log** | [ActivityLog.tsx](../src/components/ActivityLog.tsx) | useState + useEffect | `fetchActivityLog(20, 0)` | 🔴 HIGH | -| **Admin Stats** | [AdminStatsPage.tsx](../src/pages/AdminStatsPage.tsx) | useState + useEffect | `getApplicationStats()` | 🔴 HIGH | -| **Corrections** | [CorrectionsPage.tsx](../src/pages/CorrectionsPage.tsx) | useState + useEffect + Promise.all | `getSuggestedCorrections()`, `fetchMasterItems()`, `fetchCategories()` | 🔴 HIGH | +| Hook | File | Query Key | Status | Phase | +| ---------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------- | ------- | ----- | +| useActivityLogQuery | [src/hooks/queries/useActivityLogQuery.ts](../src/hooks/queries/useActivityLogQuery.ts) | `['activity-log', { limit, offset }]` | ✅ Done | 5 | +| useApplicationStatsQuery | [src/hooks/queries/useApplicationStatsQuery.ts](../src/hooks/queries/useApplicationStatsQuery.ts) | `['application-stats']` | ✅ Done | 5 | +| useSuggestedCorrectionsQuery | [src/hooks/queries/useSuggestedCorrectionsQuery.ts](../src/hooks/queries/useSuggestedCorrectionsQuery.ts) | `['suggested-corrections']` | ✅ Done | 5 | +| useCategoriesQuery | [src/hooks/queries/useCategoriesQuery.ts](../src/hooks/queries/useCategoriesQuery.ts) | `['categories']` | ✅ Done | 5 | -**Issues:** -- Manual state management with useState/useEffect -- No caching - data refetches on every mount -- No automatic refetching or background updates -- Manual loading/error state handling -- Duplicate API calls (CorrectionsPage fetches master items separately) +### Admin Components Migrated (3) -**Recommended Query Hooks to Create:** -```typescript -// src/hooks/queries/useActivityLogQuery.ts -queryKey: ['activity-log', { limit, offset }] -staleTime: 30 seconds (frequently updated) +| Component | Uses | Status | +| ------------------------------------------------------------- | --------------------------------------------------------------------- | ------- | +| [ActivityLog.tsx](../src/pages/admin/ActivityLog.tsx) | useActivityLogQuery | ✅ Done | +| [AdminStatsPage.tsx](../src/pages/admin/AdminStatsPage.tsx) | useApplicationStatsQuery | ✅ Done | +| [CorrectionsPage.tsx](../src/pages/admin/CorrectionsPage.tsx) | useSuggestedCorrectionsQuery, useMasterItemsQuery, useCategoriesQuery | ✅ Done | -// src/hooks/queries/useApplicationStatsQuery.ts -queryKey: ['application-stats'] -staleTime: 2 minutes (changes moderately) +--- -// src/hooks/queries/useSuggestedCorrectionsQuery.ts -queryKey: ['suggested-corrections'] -staleTime: 1 minute +## ✅ COMPLETED: Analytics Features (Phase 6) -// src/hooks/queries/useCategoriesQuery.ts -queryKey: ['categories'] -staleTime: 10 minutes (rarely changes) -``` +### Analytics Query Hooks (3) -### Medium Priority - Analytics Features +| Hook | File | Query Key | Status | Phase | +| --------------------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------- | ------- | ----- | +| useBestSalePricesQuery | [src/hooks/queries/useBestSalePricesQuery.ts](../src/hooks/queries/useBestSalePricesQuery.ts) | `['best-sale-prices']` | ✅ Done | 6 | +| useFlyerItemsForFlyersQuery | [src/hooks/queries/useFlyerItemsForFlyersQuery.ts](../src/hooks/queries/useFlyerItemsForFlyersQuery.ts) | `['flyer-items-batch', flyerIds]` | ✅ Done | 6 | +| useFlyerItemCountQuery | [src/hooks/queries/useFlyerItemCountQuery.ts](../src/hooks/queries/useFlyerItemCountQuery.ts) | `['flyer-item-count', flyerIds]` | ✅ Done | 6 | -| Feature | Component/Hook | Current Pattern | API Calls | Priority | -|---------|----------------|-----------------|-----------|----------| -| **My Deals** | [MyDealsPage.tsx](../src/pages/MyDealsPage.tsx) | useState + useEffect | `fetchBestSalePrices()` | 🟡 MEDIUM | -| **Active Deals** | [useActiveDeals.tsx](../src/hooks/useActiveDeals.tsx) | useApi hook | `countFlyerItemsForFlyers()`, `fetchFlyerItemsForFlyers()` | 🟡 MEDIUM | +### Analytics Components/Hooks Migrated (2) -**Issues:** -- useActiveDeals uses old `useApi` hook pattern -- MyDealsPage has manual state management -- No caching for best sale prices -- No relationship to watched-items cache (could be optimized) +| Component/Hook | Uses | Status | +| ----------------------------------------------------- | --------------------------------------------------- | ------- | +| [MyDealsPage.tsx](../src/pages/MyDealsPage.tsx) | useBestSalePricesQuery | ✅ Done | +| [useActiveDeals.tsx](../src/hooks/useActiveDeals.tsx) | useFlyerItemsForFlyersQuery, useFlyerItemCountQuery | ✅ Done | -**Recommended Query Hooks to Create:** -```typescript -// src/hooks/queries/useBestSalePricesQuery.ts -queryKey: ['best-sale-prices', watchedItemIds] -staleTime: 2 minutes -// Should invalidate when flyers or flyer-items update +**Benefits Achieved:** -// Refactor useActiveDeals to use TanStack Query -// Could share cache with flyer-items query -``` +- ✅ Removed useApi dependency from analytics features +- ✅ Automatic caching of deal data (2-5 minute stale times) +- ✅ Consistent error handling via TanStack Query +- ✅ Batch fetching for flyer items (single query for multiple flyers) ### Low Priority - Voice Lab -| Feature | Component | Current Pattern | Priority | -|---------|-----------|-----------------|----------| -| **Voice Lab** | [VoiceLabPage.tsx](../src/pages/VoiceLabPage.tsx) | Direct async/await | 🟢 LOW | +| Feature | Component | Current Pattern | Priority | +| ------------- | ------------------------------------------------- | ------------------ | -------- | +| **Voice Lab** | [VoiceLabPage.tsx](../src/pages/VoiceLabPage.tsx) | Direct async/await | 🟢 LOW | **Notes:** + - Event-driven API calls (not data fetching) - Speech generation and voice sessions - Mutation-like operations, not query-like @@ -125,107 +118,113 @@ staleTime: 2 minutes --- -## ⚠️ LEGACY HOOKS STILL IN USE +## ✅ COMPLETED: Legacy Hook Cleanup (Phase 7) -### Hooks to Deprecate/Remove +### Hooks Removed -| Hook | File | Used By | Status | -|------|------|---------|--------| -| **useApi** | [src/hooks/useApi.ts](../src/hooks/useApi.ts) | useActiveDeals, useWatchedItems, useShoppingLists | ⚠️ Active | -| **useApiOnMount** | [src/hooks/useApiOnMount.ts](../src/hooks/useApiOnMount.ts) | None (deprecated) | ⚠️ Remove | -| **useInfiniteQuery** | [src/hooks/useInfiniteQuery.ts](../src/hooks/useInfiniteQuery.ts) | None (deprecated) | ⚠️ Remove | +| Hook | Former File | Replaced By | Status | +| ----------------- | ------------------------------ | -------------------- | ---------- | +| **useApi** | ~~src/hooks/useApi.ts~~ | TanStack Query hooks | ✅ Removed | +| **useApiOnMount** | ~~src/hooks/useApiOnMount.ts~~ | TanStack Query hooks | ✅ Removed | -**Plan:** -- Phase 4: Refactor useWatchedItems/useShoppingLists to use TanStack Query mutations -- Phase 5: Refactor useActiveDeals to use TanStack Query -- Phase 6: Remove useApi, useApiOnMount, custom useInfiniteQuery +### Additional Hooks Created (Phase 7) + +| Hook | File | Purpose | +| ------------------- | ----------------------------------------------------------------------------------------- | -------------------------------- | +| useUserAddressQuery | [src/hooks/queries/useUserAddressQuery.ts](../src/hooks/queries/useUserAddressQuery.ts) | Fetch user address by ID | +| useAuthProfileQuery | [src/hooks/queries/useAuthProfileQuery.ts](../src/hooks/queries/useAuthProfileQuery.ts) | Fetch authenticated user profile | +| useGeocodeMutation | [src/hooks/mutations/useGeocodeMutation.ts](../src/hooks/mutations/useGeocodeMutation.ts) | Geocode address strings | + +### Files Modified (Phase 7) + +| File | Change | +| --------------------------------------------------------- | ---------------------------------------------------------- | +| [useProfileAddress.ts](../src/hooks/useProfileAddress.ts) | Refactored to use useUserAddressQuery + useGeocodeMutation | +| [AuthProvider.tsx](../src/providers/AuthProvider.tsx) | Refactored to use useAuthProfileQuery | --- ## 📊 MIGRATION PHASES ### ✅ Phase 1: Core Queries (Complete) + - Infrastructure setup (QueryClientProvider) - Flyers, Watched Items, Shopping Lists queries - Providers refactored ### ✅ Phase 2: Additional Queries (Complete) + - Master Items query - Flyer Items query - Per-resource caching strategies ### ✅ Phase 3: Mutations (Complete) + - All watched items mutations - All shopping list mutations - Automatic cache invalidation -### 🔄 Phase 4: Hook Refactoring (Planned) -- [ ] Refactor useWatchedItems to use mutation hooks -- [ ] Refactor useShoppingLists to use mutation hooks -- [ ] Remove deprecated setters from context +### ✅ Phase 4: Hook Refactoring (Complete) -### ⏳ Phase 5: Admin Features (Not Started) -- [ ] Create useActivityLogQuery -- [ ] Create useApplicationStatsQuery -- [ ] Create useSuggestedCorrectionsQuery -- [ ] Create useCategoriesQuery -- [ ] Migrate ActivityLog.tsx -- [ ] Migrate AdminStatsPage.tsx -- [ ] Migrate CorrectionsPage.tsx +- [x] Refactor useWatchedItems to use mutation hooks +- [x] Refactor useShoppingLists to use mutation hooks +- [x] Remove deprecated setters from context -### ⏳ Phase 6: Analytics Features (Not Started) -- [ ] Create useBestSalePricesQuery -- [ ] Migrate MyDealsPage.tsx -- [ ] Refactor useActiveDeals to use TanStack Query +### ✅ Phase 5: Admin Features (Complete) -### ⏳ Phase 7: Cleanup (Not Started) -- [ ] Remove useApi hook -- [ ] Remove useApiOnMount hook -- [ ] Remove custom useInfiniteQuery hook -- [ ] Remove all stub implementations -- [ ] Update all tests +- [x] Create useActivityLogQuery +- [x] Create useApplicationStatsQuery +- [x] Create useSuggestedCorrectionsQuery +- [x] Create useCategoriesQuery +- [x] Migrate ActivityLog.tsx +- [x] Migrate AdminStatsPage.tsx +- [x] Migrate CorrectionsPage.tsx + +### ✅ Phase 6: Analytics Features (Complete - 2026-01-10) + +- [x] Create useBestSalePricesQuery +- [x] Create useFlyerItemsForFlyersQuery +- [x] Create useFlyerItemCountQuery +- [x] Migrate MyDealsPage.tsx +- [x] Refactor useActiveDeals to use TanStack Query + +### ✅ Phase 7: Cleanup (Complete - 2026-01-10) + +- [x] Create useUserAddressQuery +- [x] Create useAuthProfileQuery +- [x] Create useGeocodeMutation +- [x] Migrate useProfileAddress from useApi to TanStack Query +- [x] Migrate AuthProvider from useApi to TanStack Query +- [x] Remove useApi hook +- [x] Remove useApiOnMount hook + +### ✅ Phase 8: Additional Component Migration (Complete - 2026-01-10) + +- [x] Create useUserProfileDataQuery (combined profile + achievements) +- [x] Create useLeaderboardQuery (public leaderboard data) +- [x] Create usePriceHistoryQuery (historical price data for watched items) +- [x] Refactor useUserProfileData to use TanStack Query +- [x] Refactor Leaderboard.tsx to use useLeaderboardQuery +- [x] Refactor PriceHistoryChart.tsx to use usePriceHistoryQuery --- -## 🎯 RECOMMENDED NEXT STEPS +## 🎉 MIGRATION COMPLETE -### Option A: Complete User Features First (Phase 4) -Focus on finishing the user-facing feature migration by refactoring the remaining custom hooks. This provides a complete, polished user experience. +The TanStack Query migration is **100% complete**. All data fetching in the application now uses TanStack Query for: -**Pros:** -- Completes the user-facing story -- Simplifies codebase for user features -- Sets pattern for admin features - -**Cons:** -- Admin features still use old patterns - -### Option B: Migrate Admin Features (Phase 5) -Create query hooks for admin features to improve admin user experience and establish complete ADR-0005 coverage. - -**Pros:** -- Faster admin pages with caching -- Consistent patterns across entire app -- Better for admin users - -**Cons:** -- User-facing hooks still partially old pattern - -### Option C: Parallel Migration (Phase 4 + 5) -Work on both user hook refactoring and admin feature migration simultaneously. - -**Pros:** -- Fastest path to complete migration -- Comprehensive coverage quickly - -**Cons:** -- Larger scope, more testing needed +- **Automatic caching** - Server data is cached and shared across components +- **Background refetching** - Stale data is automatically refreshed +- **Loading/error states** - Consistent handling across the entire application +- **Cache invalidation** - Mutations automatically invalidate related queries +- **DevTools** - React Query DevTools available in development mode --- ## 📝 NOTES ### Query Key Organization + Currently using literal strings for query keys. Consider creating a centralized query keys file: ```typescript @@ -246,24 +245,29 @@ export const queryKeys = { ``` ### Cache Invalidation Strategy + Admin features may need different invalidation strategies: + - Activity log should refetch after mutations - Stats should refetch after significant operations - Corrections should refetch after approving/rejecting ### Stale Time Recommendations -| Data Type | Stale Time | Reasoning | -|-----------|------------|-----------| -| Master Items | 10 minutes | Rarely changes | -| Categories | 10 minutes | Rarely changes | -| Flyers | 2 minutes | Moderate changes | -| Flyer Items | 5 minutes | Static once created | -| User Lists | 1 minute | Frequent changes | -| Admin Stats | 2 minutes | Moderate changes | -| Activity Log | 30 seconds | Frequently updated | -| Corrections | 1 minute | Moderate changes | -| Best Prices | 2 minutes | Recalculated periodically | +| Data Type | Stale Time | Reasoning | +| ----------------- | ---------- | ----------------------------------- | +| Master Items | 10 minutes | Rarely changes | +| Categories | 10 minutes | Rarely changes | +| Flyers | 2 minutes | Moderate changes | +| Flyer Items | 5 minutes | Static once created | +| User Lists | 1 minute | Frequent changes | +| Admin Stats | 2 minutes | Moderate changes | +| Activity Log | 30 seconds | Frequently updated | +| Corrections | 1 minute | Moderate changes | +| Best Prices | 2 minutes | Recalculated periodically | +| User Profile Data | 5 minutes | User-specific, changes infrequently | +| Leaderboard | 2 minutes | Public data, moderate updates | +| Price History | 10 minutes | Historical data, rarely changes | --- diff --git a/scripts/check-linux.js b/scripts/check-linux.js new file mode 100644 index 0000000..f952bbf --- /dev/null +++ b/scripts/check-linux.js @@ -0,0 +1,31 @@ +#!/usr/bin/env node +/** + * Platform check script for test execution. + * Warns (but doesn't block) when running tests on Windows outside a container. + * + * See ADR-014 for details on Linux-only requirement. + */ + +const isWindows = process.platform === 'win32'; +const inContainer = + process.env.REMOTE_CONTAINERS === 'true' || + process.env.DEVCONTAINER === 'true' || + process.env.container === 'podman' || + process.env.container === 'docker'; + +if (isWindows && !inContainer) { + console.warn('\n' + '='.repeat(70)); + console.warn('⚠️ WARNING: Running tests on Windows outside a container'); + console.warn('='.repeat(70)); + console.warn(''); + console.warn('This application is designed for Linux only. Test results on Windows'); + console.warn('may be unreliable due to path separator differences and other issues.'); + console.warn(''); + console.warn('For accurate test results, please use:'); + console.warn(' - VS Code Dev Container ("Reopen in Container")'); + console.warn(' - WSL (Windows Subsystem for Linux)'); + console.warn(' - A Linux VM or bare-metal Linux'); + console.warn(''); + console.warn('See docs/adr/0014-containerization-and-deployment-strategy.md'); + console.warn('='.repeat(70) + '\n'); +} diff --git a/src/components/AppGuard.test.tsx b/src/components/AppGuard.test.tsx index 2c7abf9..227ad56 100644 --- a/src/components/AppGuard.test.tsx +++ b/src/components/AppGuard.test.tsx @@ -8,8 +8,8 @@ import * as apiClient from '../services/apiClient'; import { useModal } from '../hooks/useModal'; import { renderWithProviders } from '../tests/utils/renderWithProviders'; -// Mock dependencies -// The apiClient is mocked globally in `src/tests/setup/globalApiMock.ts`. +// Must explicitly call vi.mock() for apiClient +vi.mock('../services/apiClient'); vi.mock('../hooks/useAppInitialization'); vi.mock('../hooks/useModal'); vi.mock('./WhatsNewModal', () => ({ diff --git a/src/components/Footer.test.tsx b/src/components/Footer.test.tsx index 08e3878..5aeed67 100644 --- a/src/components/Footer.test.tsx +++ b/src/components/Footer.test.tsx @@ -27,10 +27,4 @@ describe('Footer', () => { // Assert: Check that the rendered text includes the mocked year expect(screen.getByText('Copyright 2025-2025')).toBeInTheDocument(); }); - - it('should display the correct year when it changes', () => { - vi.setSystemTime(new Date('2030-01-01T00:00:00Z')); - renderWithProviders(