Skip to content

Conversation

@mohi-devhub
Copy link

@mohi-devhub mohi-devhub commented Nov 7, 2025

Implement Infinite Scroll Pagination & Secure Image Queries

📝 Overview

This PR implements an efficient infinite scroll pagination system across the application.
Critically, it also resolves a major SQL injection vulnerability found in the image retrieval queries by refactoring them to use parameterized Common Table Expressions (CTEs).

✨ Key Feature: Infinite Scroll

  • Automatic Loading: : New images are now automatically fetched when the user scrolls to 80% of the page.
  • Frontend Tech: Implemented using TanStack Query v5 (useInfiniteQuery) and the Intersection Observer API for efficient scroll detection.
  • Smart Loader: The loading spinner now displays for a minimum of 200ms to prevent UI flicker on fast network requests.
  • Optimistic Updates: AI tagging operations now use optimistic updates for instant UI feedback.

🔐 Security Improvements (Critical Fix)

  • SQL Injection Eliminated: Patched a critical SQL injection vulnerability by removing raw f-string SQL queries.
  • Secure Queries: All image retrieval logic has been migrated to use parameterized Common Table Expressions (CTEs), which are inherently secure against this vector.
  • Input Sanitization: All user inputs (like limit and offset) are now properly validated and sanitized on the backend.

🔧 Backend & Performance

  • Server-Side Pagination: Implemented an efficient limit/offset pagination system.
  • Optimized Batching: Fetches 50 images per page by default, with a new MAX_PAGE_SIZE of 1000.
  • Centralized Config: Created dedicated configuration files for all pagination constants (e.g., DEFAULT=50, MAX_OFFSET=1M).

🧹 Code Quality & Refactoring

  • Readability: Removed ~260 lines of verbose, redundant comments.
  • Maintainability: Extracted 13 "magic numbers" (hardcoded values) into named constants.
  • Type Safety: The project is fully type-safe with zero TypeScript compilation errors.

🧪 Testing

  • Added a new 371-line comprehensive test suite to cover the new secure, paginated backend logic.
  • Fixed the table creation order in the test setup to respect foreign key constraints.

Fixes #619

Summary by CodeRabbit

  • New Features
    • Infinite scrolling and paginated image loading with page metadata (total, limit, offset); limit/offset query support.
    • Centralized pagination, retry and polling settings shared across frontend/backend.
    • Optimistic folder/tagging updates and improved loader with minimum display time.
    • API response type now supports generic data and pagination fields.
  • Bug Fixes
    • Image metadata consistently returned as structured data.
    • Validation and safer handling for limit/offset and pagination edges.
  • Tests
    • Extensive pagination, filtering, metadata-shape and boundary tests.
  • Documentation
    • API docs updated to describe pagination and new response fields.
  • Chores
    • .gitignore updated to exclude virtual envs and model files.

✏️ Tip: You can customize this high-level summary in your review settings.

- Add server-side pagination to /images endpoint with limit/offset params
- Implement infinite scroll with TanStack Query in Home and AI Tagging pages
- Add pagination configuration constants for consistency
- Improve loader UX with minimum display time
- Implement optimistic updates for AI tagging toggle
- Fix SQL injection vulnerability in image queries using CTE
- Add real-time query refetching for folder operations
- Refactor to eliminate magic numbers and verbose comments

Backend:
- New pagination config with MAX_PAGE_SIZE=1000, DEFAULT=50
- CTE-based queries for security and performance
- Comprehensive test suite for pagination edge cases
- Updated table creation order respecting foreign keys

Frontend:
- Infinite scroll at 80% threshold
- 50 images per page with smooth loading
- Optimistic UI updates for instant feedback
- Centralized retry/stale time configuration
- Clean, maintainable code structure
@github-actions
Copy link
Contributor

github-actions bot commented Nov 7, 2025

⚠️ No issue was linked in the PR description.
Please make sure to link an issue (e.g., 'Fixes #issue_number')

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 7, 2025

Walkthrough

Adds offset/limit pagination end-to-end: backend pagination config and CTE-based DB query returning images + total, route changes with validation and error responses, frontend infinite-scroll integration and pagination constants, tests and OpenAPI updates, plus minor .gitignore and table-init ordering tweaks.

Changes

Cohort / File(s) Summary
Gitignore
/.gitignore
Added ignore entries: \.venv/, *.onnx, backend/models/.
Backend pagination config
backend/app/config/pagination.py
New pagination/retry constants (MAX_PAGE_SIZE, DEFAULT_PAGE_SIZE, MAX_OFFSET_VALUE, MIN_PAGE_SIZE, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_DELAY_MS).
Database layer (images)
backend/app/database/images.py
db_get_all_images now accepts limit/offset, computes total_count, uses a CTE for paginated IDs, aggregates tags/metadata, returns {"images": [...], "total": N}, and improves error logging/handling.
API routes & docs
backend/app/routes/images.py, docs/backend/backend_python/openapi.json
get_all_images accepts validated limit/offset, enforces offset bounds (MAX_OFFSET_VALUE), returns total, limit, offset, adds 400 response for invalid offset; OpenAPI updated.
App/test startup ordering
backend/main.py, backend/tests/conftest.py
Reordered table creation to satisfy foreign-key dependencies; added comments and fail-fast behavior for test setup errors.
Backend tests
backend/tests/test_images.py, backend/tests/test_metadata_fix.py
New/updated tests for pagination behavior, filtering by tagged, deterministic ordering, metadata shape (dict), boundaries, totals, and API response shape.
Frontend pagination config
frontend/src/config/pagination.ts
New exported constants (IMAGES_PER_PAGE, SCROLL_THRESHOLD, MIN_LOADER_DISPLAY_TIME, DEFAULT_RETRY_COUNT, DEFAULT_RETRY_DELAY, TAGGING_STATUS_POLL_INTERVAL, DEFAULT_STALE_TIME).
Frontend API integration
frontend/src/api/api-functions/images.ts
Added FetchAllImagesParams and updated fetchAllImages(params?) to send tagged, limit, and offset as query params; return type adjusted to APIResponse<Image[]>.
Frontend pages (infinite scroll)
frontend/src/pages/Home/Home.tsx, frontend/src/pages/AITagging/AITagging.tsx
Switched to useInfiniteQuery (offset-based), added scroll-threshold triggering, page accumulation, loader/fetch-next handling, and Redux sync of accumulated pages.
Frontend hooks & loader
frontend/src/hooks/useQueryExtension.ts, frontend/src/hooks/useFolderOperations.tsx, frontend/src/components/Loader/GlobalLoader.tsx
Centralized retry/delay defaults, switched invalidate→per-tag refetch, added optimistic updates/rollback and image-cache invalidation, applied tagging poll interval, and enforced minimum loader display time.
Frontend types
frontend/src/types/API.ts
Made APIResponse generic APIResponse<T = any> with data?: T and added optional numeric fields total, limit, and offset.
Onboarding / small frontend fixes
frontend/src/components/OnboardingSteps/*, frontend/src/pages/InitialSteps/InitialSteps.tsx
Guarded completion dispatches with local state flags and fixed effect dependency arrays to avoid repeated dispatches / stale closures.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant FE as Frontend (Home / AITagging)
    participant API as Backend API (/images)
    participant DB as Database

    rect "#e8f0ff"
      note over U,FE: Initial page load (offset=0)
      U->>FE: Visit page
      FE->>API: GET /images?limit=50&offset=0
      API->>DB: db_get_all_images(limit, offset)
      DB->>DB: CTE computes total_count and selects paginated IDs
      DB-->>API: {images:[...], total:N}
      API-->>FE: {success, data:{images,total,limit,offset}}
      FE->>U: Render first page
    end

    rect "#e8ffe8"
      note over U,FE: Infinite scroll -> fetch next page
      U->>FE: Scroll near bottom
      FE->>FE: compute nextOffset = offset + limit
      FE->>API: GET /images?limit=50&offset=50
      API->>DB: db_get_all_images(limit, offset)
      DB-->>API: {images:[...], total:N}
      API-->>FE: Next page data
      FE->>U: Append images
    end

    rect "#fff4dd"
      note over FE: Stop when offset+limit >= total
      FE->>FE: getNextPageParam() returns undefined
      FE->>U: No more fetches
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • backend/app/database/images.py: CTE correctness, total_count semantics, tag aggregation, metadata parsing, and exception handling.
    • backend/app/routes/images.py & OpenAPI: parameter validation, 400 vs 500 responses, response-model alignment.
    • Frontend infinite queries (Home, AITagging): getNextPageParam correctness, scroll listener lifecycle, Redux synchronization.
    • Optimistic update and rollback logic in useFolderOperations.tsx and the refetch strategy changes in useQueryExtension.ts.

Possibly related PRs

Suggested reviewers

  • rahulharpal1603

Poem

🐰 I hopped through rows and counted each frame,

Limits and offsets now play the game.
Pages arrive with a cheerful thump—
Scroll to the bottom, I fetch and I jump.
Gallery grows neat as a carroted lane.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 74.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: implementing infinite scroll pagination for images.
Linked Issues check ✅ Passed All backend and frontend objectives from issue #619 are met: limit/offset query parameters added [images.py, routes/images.py], database pagination logic implemented [database/images.py], API functions updated [api-functions/images.ts], Home page uses infinite scroll [Home.tsx, AITagging.tsx].
Out of Scope Changes check ✅ Passed Changes include scope-appropriate improvements: configuration constants consolidation, optimistic updates with rollback, table creation reordering, and onboarding step fixes—all supporting the pagination feature or addressing dependencies.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/hooks/useFolderOperations.tsx (1)

93-119: Rollback optimistic folder toggles on failure.

onMutate pushes an optimistic setFolders update, but there’s no rollback path. If either mutation fails, the Redux store stays in the optimistic state until some unrelated refetch happens, so the UI shows the wrong tagging status. TanStack recommends returning the previous snapshot from onMutate and restoring it in onError for optimistic updates.(tanstack.com)

Apply a change like:

   const enableAITaggingMutation = usePictoMutation({
     mutationFn: async (folder_id: string) =>
       enableAITagging({ folder_ids: [folder_id] }),
     autoInvalidateTags: ['folders', 'images'],
-    onMutate: async (folder_id: string) => {
-      const updatedFolders = folders.map(f => 
-        f.folder_id === folder_id ? { ...f, AI_Tagging: true } : f
-      );
-      dispatch(setFolders(updatedFolders));
-    },
+    onMutate: async (folder_id: string) => {
+      const previousFolders = folders;
+      const updatedFolders = previousFolders.map((f) =>
+        f.folder_id === folder_id ? { ...f, AI_Tagging: true } : f,
+      );
+      dispatch(setFolders(updatedFolders));
+      return { previousFolders };
+    },
+    onError: (_err, _folderId, context) => {
+      if (context?.previousFolders) {
+        dispatch(setFolders(context.previousFolders));
+      }
+    },
   });

   const disableAITaggingMutation = usePictoMutation({
     mutationFn: async (folder_id: string) =>
       disableAITagging({ folder_ids: [folder_id] }),
     autoInvalidateTags: ['folders', 'images'],
-    onMutate: async (folder_id: string) => {
-      const updatedFolders = folders.map(f => 
-        f.folder_id === folder_id ? { ...f, AI_Tagging: false } : f
-      );
-      dispatch(setFolders(updatedFolders));
-    },
+    onMutate: async (folder_id: string) => {
+      const previousFolders = folders;
+      const updatedFolders = previousFolders.map((f) =>
+        f.folder_id === folder_id ? { ...f, AI_Tagging: false } : f,
+      );
+      dispatch(setFolders(updatedFolders));
+      return { previousFolders };
+    },
+    onError: (_err, _folderId, context) => {
+      if (context?.previousFolders) {
+        dispatch(setFolders(context.previousFolders));
+      }
+    },
   });
backend/app/routes/images.py (1)

88-96: Remove double metadata parsing to avoid 500s.

db_get_all_images now parses the metadata before returning it (see backend/app/database/images.py lines 121-222), so image["metadata"] is already a dict. Running it through image_util_parse_metadata again causes a TypeError: the JSON object must be str, bytes or bytearray, not dict, turning every GET /images into a 500. Please return the value directly and drop the redundant import.

Apply this diff:

-from app.utils.images import image_util_parse_metadata
...
-                metadata=image_util_parse_metadata(image["metadata"]),
+                metadata=image["metadata"],
🧹 Nitpick comments (1)
frontend/src/components/Loader/GlobalLoader.tsx (1)

12-36: Use timeout typing that works in browser builds

NodeJS.Timeout requires Node typings; in a browser-only TypeScript build this namespace is undefined, which can break compilation. Switching to ReturnType<typeof setTimeout> keeps the code portable without changing behavior.

-  const timerRef = useRef<NodeJS.Timeout | null>(null);
+  const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fe2f98d and 767b410.

📒 Files selected for processing (15)
  • .gitignore (1 hunks)
  • backend/app/config/pagination.py (1 hunks)
  • backend/app/database/images.py (1 hunks)
  • backend/app/routes/images.py (3 hunks)
  • backend/main.py (1 hunks)
  • backend/tests/test_images.py (1 hunks)
  • docs/backend/backend_python/openapi.json (4 hunks)
  • frontend/src/api/api-functions/images.ts (1 hunks)
  • frontend/src/components/Loader/GlobalLoader.tsx (1 hunks)
  • frontend/src/config/pagination.ts (1 hunks)
  • frontend/src/hooks/useFolderOperations.tsx (5 hunks)
  • frontend/src/hooks/useQueryExtension.ts (4 hunks)
  • frontend/src/pages/AITagging/AITagging.tsx (2 hunks)
  • frontend/src/pages/Home/Home.tsx (2 hunks)
  • frontend/src/types/API.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (11)
frontend/src/hooks/useQueryExtension.ts (1)
frontend/src/config/pagination.ts (2)
  • DEFAULT_RETRY_COUNT (8-8)
  • DEFAULT_RETRY_DELAY (9-9)
frontend/src/pages/AITagging/AITagging.tsx (6)
frontend/src/components/Media/ChronologicalGallery.tsx (2)
  • MonthMarker (10-14)
  • ChronologicalGallery (25-187)
frontend/src/api/api-functions/images.ts (1)
  • fetchAllImages (11-31)
frontend/src/config/pagination.ts (5)
  • IMAGES_PER_PAGE (5-5)
  • DEFAULT_STALE_TIME (11-11)
  • DEFAULT_RETRY_COUNT (8-8)
  • DEFAULT_RETRY_DELAY (9-9)
  • SCROLL_THRESHOLD (6-6)
frontend/src/types/Media.ts (1)
  • Image (13-22)
frontend/src/features/imageSlice.ts (1)
  • setImages (18-20)
frontend/src/features/loaderSlice.ts (2)
  • showLoader (17-20)
  • hideLoader (21-24)
frontend/src/components/Loader/GlobalLoader.tsx (2)
frontend/src/types/loading.ts (1)
  • GlobalLoaderProps (1-4)
frontend/src/config/pagination.ts (1)
  • MIN_LOADER_DISPLAY_TIME (7-7)
frontend/src/pages/Home/Home.tsx (9)
frontend/src/features/imageSelectors.ts (1)
  • selectImages (5-7)
frontend/src/components/Media/ChronologicalGallery.tsx (2)
  • MonthMarker (10-14)
  • ChronologicalGallery (25-187)
frontend/src/app/store.ts (1)
  • RootState (22-22)
frontend/src/api/api-functions/images.ts (1)
  • fetchAllImages (11-31)
frontend/src/config/pagination.ts (5)
  • IMAGES_PER_PAGE (5-5)
  • DEFAULT_STALE_TIME (11-11)
  • DEFAULT_RETRY_COUNT (8-8)
  • DEFAULT_RETRY_DELAY (9-9)
  • SCROLL_THRESHOLD (6-6)
frontend/src/types/Media.ts (1)
  • Image (13-22)
frontend/src/features/imageSlice.ts (1)
  • setImages (18-20)
frontend/src/features/loaderSlice.ts (2)
  • showLoader (17-20)
  • hideLoader (21-24)
frontend/src/features/infoDialogSlice.ts (1)
  • showInfoDialog (16-30)
backend/tests/test_images.py (4)
backend/app/database/images.py (3)
  • db_create_images_table (52-85)
  • db_bulk_insert_images (88-119)
  • db_insert_image_classes_batch (302-333)
backend/app/database/folders.py (1)
  • db_create_folders_table (15-36)
backend/app/database/yolo_mapping.py (1)
  • db_create_YOLO_classes_table (6-35)
backend/tests/test_folders.py (1)
  • test_db (20-33)
backend/main.py (7)
backend/app/database/yolo_mapping.py (1)
  • db_create_YOLO_classes_table (6-35)
backend/app/database/face_clusters.py (1)
  • db_create_clusters_table (21-39)
backend/app/database/faces.py (1)
  • db_create_faces_table (29-52)
backend/app/database/folders.py (1)
  • db_create_folders_table (15-36)
backend/app/database/albums.py (2)
  • db_create_albums_table (7-26)
  • db_create_album_images_table (29-48)
backend/app/database/images.py (1)
  • db_create_images_table (52-85)
backend/app/database/metadata.py (1)
  • db_create_metadata_table (8-30)
frontend/src/api/api-functions/images.ts (3)
frontend/src/types/API.ts (1)
  • APIResponse (1-11)
frontend/src/api/axiosConfig.ts (1)
  • apiClient (5-12)
frontend/src/api/apiEndpoints.ts (1)
  • imagesEndpoints (1-3)
frontend/src/hooks/useFolderOperations.tsx (2)
frontend/src/config/pagination.ts (2)
  • TAGGING_STATUS_POLL_INTERVAL (10-10)
  • DEFAULT_RETRY_COUNT (8-8)
frontend/src/features/folderSlice.ts (1)
  • setFolders (21-23)
backend/app/database/images.py (1)
backend/app/utils/images.py (1)
  • image_util_parse_metadata (496-513)
backend/app/config/pagination.py (1)
frontend/src/config/pagination.ts (1)
  • DEFAULT_RETRY_COUNT (8-8)
backend/app/routes/images.py (2)
backend/app/schemas/images.py (1)
  • ErrorResponse (59-62)
backend/app/database/images.py (1)
  • db_get_all_images (122-223)
🔇 Additional comments (1)
.gitignore (1)

33-33: Appropriate additions for ML/model file handling and Python env isolation.

The three new entries follow existing .gitignore conventions and best practices:

  • .venv/ complements the existing venv/ entry (line 32), covering both common naming conventions for Python virtual environments.
  • *.onnx prevents large ML model files (in ONNX format, as used for AI tagging) from bloating the repository.
  • backend/models/ centralizes the ignore rule for the model artifacts directory, aligning with the PR's ML feature integration.

Also applies to: 36-37

Fix two critical issues identified during code review:

1. Optimistic Updates Rollback (frontend)
   - Added rollback logic to useFolderOperations mutations
   - enableAITaggingMutation now saves previousFolders and restores on error
   - disableAITaggingMutation now saves previousFolders and restores on error
   - Ensures UI state consistency when mutations fail

2. Remove Double Metadata Parsing (backend)
   - Removed redundant image_util_parse_metadata call in routes/images.py
   - Metadata now parsed only once in db_get_all_images()
   - Eliminates unnecessary overhead and potential errors
   - Metadata correctly returned as dict from database layer

3. Fix Table Creation Order (backend)
   - Reordered table creation to respect foreign key dependencies
   - images table now created before faces, album_images, and metadata
   - Prevents FK constraint violations when PRAGMA foreign_keys = ON
   - Applied fix to main.py and test fixtures (conftest.py, test_images.py)

4. Fix Query Refetch Logic (frontend)
   - Changed autoInvalidateTags to iterate and refetch each tag separately
   - Prevents passing entire array as single composite queryKey
   - Ensures proper cache invalidation for each query

Testing:
- Frontend: TypeScript compiles with 0 errors, all Jest tests pass (6/6)
- Backend: 19/20 pagination tests pass, 2/2 metadata tests pass
- Added test_metadata_fix.py to verify metadata structure
- Fixed test_images.py fixture to properly create test database

Files modified:
- backend/app/routes/images.py (remove double parsing)
- backend/main.py (fix table order)
- backend/tests/conftest.py (fix table order)
- backend/tests/test_images.py (fix test fixtures)
- backend/tests/test_metadata_fix.py (new verification tests)
- frontend/src/hooks/useFolderOperations.tsx (add rollback)
- frontend/src/hooks/useQueryExtension.ts (fix refetch iteration)
@mohi-devhub
Copy link
Author

Fixed issues suggested by CodeRabbit: added rollback for optimistic folder toggles and removed double metadata parsing in /images.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
backend/tests/test_metadata_fix.py (2)

11-31: Consider adding explicit test data setup.

The test only exercises assertions when result["total"] > 0 (line 18). If the database is empty or has been cleared by other tests, the critical assertions on lines 23-30 won't run, and the test passes vacuously without validating metadata structure.

Consider adding explicit test data seeding similar to sample_images_data fixture in backend/tests/test_images.py, or use a dedicated fixture that ensures at least one image exists before running assertions.


33-62: Consider adding explicit test data setup.

Similar to the database test above, this test only exercises assertions when data["total"] > 0 (line 42). The critical metadata structure validations on lines 50-61 won't run if the database is empty.

Consider using a fixture like sample_images_data from backend/tests/test_images.py to guarantee test data exists, ensuring consistent test coverage.

backend/tests/test_images.py (1)

100-107: Consider extracting the metadata JSON into a constant or helper.

The hard-coded metadata JSON string on line 105 is verbose and repeated in test_single_image_dataset (line 375). Extracting this into a module-level constant or helper function would improve maintainability and reduce duplication.

Example:

# At module level
DEFAULT_TEST_METADATA = '{"name": "test.jpg", "date_created": "2024-01-01", "width": 1920, "height": 1080, "file_size": 1024, "item_type": "image", "file_location": "/test"}'

# In fixture
"metadata": DEFAULT_TEST_METADATA,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 767b410 and 6c8f092.

📒 Files selected for processing (7)
  • backend/app/routes/images.py (3 hunks)
  • backend/main.py (1 hunks)
  • backend/tests/conftest.py (1 hunks)
  • backend/tests/test_images.py (1 hunks)
  • backend/tests/test_metadata_fix.py (1 hunks)
  • frontend/src/hooks/useFolderOperations.tsx (5 hunks)
  • frontend/src/hooks/useQueryExtension.ts (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
frontend/src/hooks/useQueryExtension.ts (1)
frontend/src/config/pagination.ts (2)
  • DEFAULT_RETRY_COUNT (8-8)
  • DEFAULT_RETRY_DELAY (9-9)
backend/main.py (7)
backend/app/database/yolo_mapping.py (1)
  • db_create_YOLO_classes_table (6-35)
backend/app/database/face_clusters.py (1)
  • db_create_clusters_table (21-39)
backend/app/database/folders.py (1)
  • db_create_folders_table (15-36)
backend/app/database/albums.py (2)
  • db_create_albums_table (7-26)
  • db_create_album_images_table (29-48)
backend/app/database/images.py (1)
  • db_create_images_table (52-85)
backend/app/database/faces.py (1)
  • db_create_faces_table (29-52)
backend/app/database/metadata.py (1)
  • db_create_metadata_table (8-30)
backend/tests/test_metadata_fix.py (2)
backend/tests/test_images.py (2)
  • app (68-72)
  • client (76-78)
backend/app/database/images.py (1)
  • db_get_all_images (122-223)
frontend/src/hooks/useFolderOperations.tsx (2)
frontend/src/config/pagination.ts (2)
  • TAGGING_STATUS_POLL_INTERVAL (10-10)
  • DEFAULT_RETRY_COUNT (8-8)
frontend/src/features/folderSlice.ts (1)
  • setFolders (21-23)
backend/app/routes/images.py (2)
backend/app/schemas/images.py (1)
  • ErrorResponse (59-62)
backend/app/database/images.py (1)
  • db_get_all_images (122-223)
backend/tests/conftest.py (6)
backend/app/database/face_clusters.py (1)
  • db_create_clusters_table (21-39)
backend/app/database/folders.py (1)
  • db_create_folders_table (15-36)
backend/app/database/albums.py (2)
  • db_create_albums_table (7-26)
  • db_create_album_images_table (29-48)
backend/app/database/images.py (1)
  • db_create_images_table (52-85)
backend/app/database/faces.py (1)
  • db_create_faces_table (29-52)
backend/app/database/metadata.py (1)
  • db_create_metadata_table (8-30)
backend/tests/test_images.py (5)
backend/tests/test_folders.py (2)
  • test_db (20-33)
  • client (76-78)
backend/app/database/images.py (3)
  • db_create_images_table (52-85)
  • db_bulk_insert_images (88-119)
  • db_insert_image_classes_batch (302-333)
backend/app/database/folders.py (1)
  • db_create_folders_table (15-36)
backend/app/database/yolo_mapping.py (1)
  • db_create_YOLO_classes_table (6-35)
backend/tests/test_metadata_fix.py (1)
  • client (65-73)
🔇 Additional comments (26)
frontend/src/hooks/useQueryExtension.ts (4)

18-18: LGTM: Centralized configuration constants.

Importing retry defaults from a dedicated config module improves maintainability and ensures consistent retry behavior across the application.


46-47: LGTM: Configuration-driven retry behavior.

Using the imported constants eliminates magic numbers and ensures consistency with application-wide retry policies.


63-69: LGTM: Per-tag refetch correctly implemented.

The iteration over autoInvalidateTags now properly refetches each query independently, resolving the composite key issue flagged in the previous review. The guard condition ensures safe handling of empty or undefined arrays.


97-98: LGTM: Consistent retry configuration.

Query defaults mirror the mutation retry configuration, ensuring uniform behavior across all Picto operations.

frontend/src/hooks/useFolderOperations.tsx (7)

15-18: LGTM: Centralized polling and retry constants.

Importing configuration constants ensures consistent behavior and simplifies future adjustments to polling intervals and retry logic.


32-33: LGTM: Configuration-driven polling intervals.

Using TAGGING_STATUS_POLL_INTERVAL for both staleTime and refetchInterval ensures consistent polling behavior and makes it easy to adjust polling frequency application-wide.


36-38: LGTM: Appropriate retry and refetch settings for polling query.

Disabling retryOnMount and refetchOnWindowFocus while using the centralized retry count is appropriate for a background polling query, preventing unnecessary network calls while maintaining retry resilience during active polling.


57-62: LGTM: Ensures Redux stays synchronized with query data.

This effect complements the useMutationFeedback callback by ensuring the Redux store reflects query data changes on all refetches, not just the initial load, maintaining consistency after cache invalidations.


92-105: LGTM: Well-implemented optimistic updates with proper rollback.

The optimistic update pattern correctly:

  • Preserves previous state for rollback
  • Immediately reflects UI changes
  • Safely rolls back on error using context
  • Invalidates both 'folders' and 'images' caches since AI tagging affects image metadata

120-133: LGTM: Consistent optimistic update pattern for disable operation.

The disable mutation mirrors the enable pattern with appropriate inverse logic (AI_Tagging: false), maintaining consistency and properly invalidating dependent caches.


148-148: LGTM: Proper cache invalidation for folder deletion.

Adding 'images' to autoInvalidateTags ensures the images cache updates when a folder is deleted, maintaining data consistency without the complexity of optimistic delete (which is appropriate given the destructive nature of the operation).

backend/main.py (1)

47-57: LGTM! Table creation order now respects foreign key dependencies.

The reordering correctly addresses the critical issue flagged in the previous review. The new sequence creates base tables first (YOLO_classes, clusters, folders, albums), followed by images, and finally dependent tables (faces, album_images, metadata) that reference images. This prevents FK constraint initialization failures.

backend/tests/conftest.py (2)

25-31: LGTM! Table creation order now matches the fixed sequence in main.py.

The reordering correctly establishes foreign key dependencies, creating images (line 28) before dependent tables faces, album_images, and metadata. The added comments clearly document the dependency relationships, improving maintainability.


34-35: Good practice: re-raising exceptions halts test execution on setup failure.

The explicit raise ensures that if table creation fails, the test suite stops immediately rather than proceeding with an incomplete database schema, which would cause confusing downstream failures.

backend/tests/test_images.py (6)

16-64: Test isolation setup is comprehensive and follows good practices.

The fixture correctly:

  • Creates an isolated temporary database per test
  • Overrides the global DATABASE_PATH
  • Reloads modules to pick up the new path
  • Creates required tables in dependency order
  • Provides cleanup on teardown

130-145: LGTM! Test validates non-paginated retrieval behavior.

The test correctly verifies that fetching all images without pagination parameters returns all 100 images with limit and offset set to None in the response.


170-181: Excellent pagination consistency check.

This test ensures that paginated results don't overlap across pages by verifying that the sets of image IDs are disjoint. This is a critical property of correct pagination implementation.


278-301: Comprehensive validation error coverage.

The test suite properly validates that invalid pagination parameters (zero, negative, or exceeding maximum values) return appropriate HTTP error codes (422 for validation errors).


302-306: No issues found. The test is correct.

The validation logic correctly returns HTTP 400 when offset (9,999,999) exceeds MAX_OFFSET_VALUE (1,000,000), with limit present. The test expectation aligns with the implementation.


42-45: No issues found. The minimal table creation is sufficient.

Verification confirms that all test methods exclusively use the /images endpoint, which only calls db_get_all_images. This function performs JOINs on only three tables: images, image_classes, and mappings—all of which are created and populated in the test setup. The tables mentioned in your concern (clusters, albums, faces, album_images) are not referenced by any test method or required by the images endpoint functionality. The current table setup is appropriate and complete for the test suite.

backend/app/routes/images.py (6)

5-9: LGTM! Pagination constants properly centralized.

Importing MAX_PAGE_SIZE, MAX_OFFSET_VALUE, and MIN_PAGE_SIZE from the config module promotes maintainability and consistency across the application.


43-45: LGTM! Response model extended with pagination metadata.

Adding total, limit, and offset fields to the response allows clients to implement proper pagination UI (e.g., page numbers, "load more" buttons).


57-64: Query parameter validation is well-structured.

Using FastAPI's Query with ge and le constraints provides automatic validation and generates proper OpenAPI documentation. The constraints (MIN_PAGE_SIZE ≤ limit ≤ MAX_PAGE_SIZE, offset ≥ 0) are appropriate for pagination.


82-84: LGTM! Database call properly uses pagination parameters.

The call to db_get_all_images(tagged=tagged, limit=limit, offset=offset) correctly passes all parameters, and extracting images and total from the result dict aligns with the updated database function signature.


92-92: LGTM! Metadata handling simplified.

Using image["metadata"] directly is correct since db_get_all_images already parses the metadata using image_util_parse_metadata (as seen in backend/app/database/images.py lines 194-195). This eliminates double-parsing that was flagged in the test_metadata_fix.py tests.


108-109: Good practice: preserving HTTP status codes for validation errors.

Re-raising HTTPException before the generic exception handler ensures that 400 Bad Request errors (from offset validation) aren't incorrectly converted to 500 Internal Server Error.

Change offset validation to check offset independently rather than
requiring both limit and offset to be provided.

Before: if limit is not None and offset is not None and offset > MAX_OFFSET_VALUE
After: if offset is not None and offset > MAX_OFFSET_VALUE

This ensures that requests with a large offset but no limit are
correctly rejected, preventing potential performance issues.

Testing:
- Large offset without limit: correctly rejected (400)
- Large offset with limit: correctly rejected (400)
- Valid offset without limit: correctly accepted (200)
- Valid offset with limit: correctly accepted (200)
- All existing tests still pass (19/20)

Fixes potential bypass of offset validation when limit is not specified.
@rahulharpal1603
Copy link
Contributor

@mohi-devhub Please resolve conflicts.

- Integrated favorites feature (isFavourite field) from main
- Preserved infinite scroll pagination (50 images/page)
- Resolved conflicts in images.py: added isFavourite to SELECT query
- Resolved conflicts in Home.tsx: combined useInfiniteQuery with useMutationFeedback
- New features from main: togglefav API, MyFav page, favorites UI
@github-actions github-actions bot added backend enhancement New feature or request frontend labels Nov 15, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/app/routes/images.py (1)

144-151: toggle_favourite is incompatible with new db_get_all_images return type

db_get_all_images() now returns a dict {"images": [...], "total": ...}, but this route still treats it as an iterable of image dicts. As written:

image = next(
    (img for img in db_get_all_images() if img["id"] == image_id), None
)
...
"isFavourite": image.get("isFavourite", False),

will:

  • iterate over dict keys instead of images, so img["id"] will fail, and
  • call .get on None if no image is found, raising an AttributeError.

This is a runtime‑breaking bug whenever the endpoint is hit.

A minimal fix that respects the new API would be:

-        image = next(
-            (img for img in db_get_all_images() if img["id"] == image_id), None
-        )
+        result = db_get_all_images()
+        image = next(
+            (img for img in result.get("images", []) if img["id"] == image_id),
+            None,
+        )
+        if image is None:
+            raise HTTPException(
+                status_code=404,
+                detail="Image not found after toggling favourite",
+            )

Longer term, a dedicated db_get_image_by_id helper would avoid loading all images just to return the updated flag.

🧹 Nitpick comments (4)
frontend/src/pages/Home/Home.tsx (1)

32-59: Well-structured infinite query configuration.

The useInfiniteQuery setup correctly implements offset-based pagination:

  • pageParam drives the offset for each fetch
  • getNextPageParam properly calculates whether more pages exist (nextOffset < total)
  • Query is disabled during search mode to avoid conflicts

Optional refinement for line 56:

The placeholderData: (previousData) => previousData configuration is redundant for infinite queries, as TanStack Query already preserves previous pages during refetches by default. Consider removing it for simplicity:

    initialPageParam: 0,
    enabled: !isSearchActive,
    staleTime: DEFAULT_STALE_TIME,
-   placeholderData: (previousData) => previousData,
    retry: DEFAULT_RETRY_COUNT,
    retryDelay: DEFAULT_RETRY_DELAY,
docs/backend/backend_python/openapi.json (1)

833-915: Pagination query parameters and 400 error contract look consistent

The limit/offset query params and their integer bounds, plus the new 400 response using app__schemas__images__ErrorResponse, align well with the backend route and pagination constraints. If the backend applies a default page size when limit is omitted, consider adding a "default" value in the limit schema to make that explicit in the docs.

backend/app/database/images.py (1)

123-239: db_get_all_images pagination semantics and error handling

The CTE‑based pagination and tag aggregation look solid, and returning {"images": images, "total": total_count} is a good fit for the route.

A couple of points to consider:

  • offset only takes effect when limit is not None and > 0. If callers ever send offset>0 with limit=None, they’ll silently get results from the start. Either:
    • enforce offset being used only together with limit at the route layer, or
    • support offset‑only by emitting a LIMIT -1 OFFSET ? (or similar) when limit is None but offset is provided.
  • On exceptions you log and return {"images": [], "total": 0}. That prevents the API from surfacing genuine DB failures (the route will think “0 images” is a valid result). Consider letting exceptions propagate so the route can return a 500, or at least distinguishing between “no data” and “error” in the return structure.

If you want stricter typing, you could also introduce a small TypedDict for the return value instead of a bare dict.

backend/app/routes/images.py (1)

53-75: Query parameter validation is good; consider explicit default limit semantics

Using MIN_PAGE_SIZE/MAX_PAGE_SIZE and ge=0 for offset gives you strong input validation. However, when limit is None the route passes that through to the DB, which results in an unbounded query and limit=None in the response. If the goal is to always paginate and avoid fetching all images, consider applying a DEFAULT_PAGE_SIZE when limit is omitted and reflecting that in both the response and OpenAPI docs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6a9a058 and cf08514.

📒 Files selected for processing (4)
  • backend/app/database/images.py (2 hunks)
  • backend/app/routes/images.py (3 hunks)
  • docs/backend/backend_python/openapi.json (4 hunks)
  • frontend/src/pages/Home/Home.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
frontend/src/pages/Home/Home.tsx (7)
frontend/src/features/imageSelectors.ts (1)
  • selectImages (5-7)
frontend/src/components/Media/ChronologicalGallery.tsx (2)
  • MonthMarker (10-14)
  • ChronologicalGallery (25-187)
frontend/src/api/api-functions/images.ts (1)
  • fetchAllImages (11-31)
frontend/src/config/pagination.ts (5)
  • IMAGES_PER_PAGE (5-5)
  • DEFAULT_STALE_TIME (11-11)
  • DEFAULT_RETRY_COUNT (8-8)
  • DEFAULT_RETRY_DELAY (9-9)
  • SCROLL_THRESHOLD (6-6)
frontend/src/types/Media.ts (1)
  • Image (13-23)
frontend/src/features/imageSlice.ts (1)
  • setImages (18-20)
frontend/src/hooks/useMutationFeedback.tsx (1)
  • useMutationFeedback (60-135)
backend/app/database/images.py (1)
backend/app/utils/images.py (1)
  • image_util_parse_metadata (496-513)
backend/app/routes/images.py (2)
backend/app/schemas/images.py (1)
  • ErrorResponse (59-62)
backend/app/database/images.py (1)
  • db_get_all_images (123-240)
🔇 Additional comments (12)
frontend/src/pages/Home/Home.tsx (7)

1-21: LGTM! Clean imports for infinite scroll implementation.

The new imports are well-organized and appropriate:

  • React hooks (useCallback, useMemo) for performance optimization
  • TanStack Query's useInfiniteQuery for pagination
  • Centralized pagination configuration constants

61-78: Excellent scroll-based pagination implementation.

The scroll handler correctly:

  • Guards against unnecessary fetches with hasNextPage and !isFetchingNextPage checks
  • Calculates scroll percentage accurately: (scrollTop + clientHeight) / scrollHeight
  • Triggers fetchNextPage() at 80% scroll threshold
  • Properly attaches and cleans up the event listener

85-89: LGTM! Redux synchronization preserves global state.

This effect correctly synchronizes query results to Redux when not in search mode. While this component uses allImages directly for rendering, maintaining Redux state ensures other components can access the image list through Redux selectors.


91-93: LGTM! Clean separation of data sources.

The images memo elegantly switches between:

  • Redux images during face search (preserving search results)
  • Query-fetched images during normal browsing

This maintains compatibility with existing search functionality while enabling infinite scroll for the gallery.


95-103: LGTM! Properly scoped loading feedback.

The mutation feedback correctly shows loading state only during the initial gallery load when not in search mode (isLoading && !isSearchActive). This prevents:

  • Showing loading UI during search (which has its own feedback)
  • Showing loading for subsequent page fetches (handled by the "Loading more images..." indicator on line 127-129)

105-109: LGTM! Dynamic title reflects UI state.

The memoized title correctly displays:

  • Search result count when actively searching with matches
  • Default "Image Gallery" title otherwise

117-134: LGTM! Clean rendering with loading states.

The conditional rendering properly handles:

  • Gallery display with images, title, and timeline integration
  • Inline loading indicator during subsequent page fetches (isFetchingNextPage)
  • Empty state when no images exist

The fragment grouping of the gallery and loading indicator maintains clean JSX structure.

docs/backend/backend_python/openapi.json (1)

1891-1940: Extending GetAllImagesResponse with pagination metadata is backwards‑compatible

Adding total, limit, and offset as optional fields preserves existing clients (success/message/data still required) while exposing enough information for infinite scroll. This shape matches the backend response and frontend expectations.

backend/app/routes/images.py (4)

44-50: Response model pagination fields match backend and docs

Adding total, limit, and offset to GetAllImagesResponse mirrors the DB return shape and OpenAPI schema, and keeping them optional avoids breaking existing consumers.


77-85: Offset upper‑bound validation correctly rejects oversize offsets

The independent offset > MAX_OFFSET_VALUE check ensures large offsets are rejected with a structured 400 ErrorResponse, which closes the earlier gap where offset could bypass validation.


87-112: Integration with db_get_all_images and pagination metadata looks correct

Unpacking result["images"] and result["total"], constructing ImageData (reusing the already‑parsed metadata), and returning total, limit, and offset in the response aligns well with the DB contract and OpenAPI schema. The success message that includes both the page size and total count is also helpful for clients.


114-124: 500‑error mapping is consistent with ErrorResponse model

Re‑raising HTTPException and mapping unexpected exceptions to a 500 with a serialized ErrorResponse keeps error handling predictable and matches the documented 500 schema for this route.

- Convert APIResponse interface to generic type APIResponse<T>
- Add default type parameter (T = any) for backward compatibility
- Type fetchAllImages return as APIResponse<Image[]>
- Remove unsafe type assertion (page?.data as Image[]) in Home.tsx
- TypeScript now correctly infers Image[] type from API response
- Improves type safety and IDE IntelliSense across codebase
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
frontend/src/pages/Home/Home.tsx (1)

79-82: Type safety issue persists: APIResponse.data field remains loosely typed.

This is related to the previous review comment about unsafe type assertion. Line 81 uses page?.data ?? [] which assumes data is an Image[], but the APIResponse interface defines data as { [key: string]: any; } according to past reviews.

The proper fix is to:

  1. Make APIResponse generic (e.g., APIResponse<T>) with properly typed data: T, OR
  2. Create a specific PaginatedResponse<T> interface with data: T[], total: number, offset: number, limit: number

Then update fetchAllImages to return the properly typed response so line 81 can safely use page.data without the nullish coalescing workaround.

🧹 Nitpick comments (2)
frontend/src/pages/Home/Home.tsx (2)

54-54: Reconsider staleTime: 0 for better performance.

Setting staleTime: DEFAULT_STALE_TIME (which is 0) marks data as immediately stale, potentially triggering unnecessary refetches when components remount or the query refocuses.

For image gallery data that doesn't change frequently, consider using a longer staleTime (e.g., 5 minutes: 5 * 60 * 1000) to reduce server load and improve perceived performance.

Apply this diff to use a more appropriate staleTime:

-    staleTime: DEFAULT_STALE_TIME,
+    staleTime: 5 * 60 * 1000, // 5 minutes

Or update DEFAULT_STALE_TIME in frontend/src/config/pagination.ts if this should be a global default.


84-88: Consider optimizing Redux updates during infinite scroll.

The effect dispatches setImages(allImages) every time allImages changes, which happens on every new page load during infinite scroll. This could cause frequent Redux updates and potential performance overhead, especially if multiple components subscribe to the images state.

Consider whether Redux updates are needed on every page load, or only:

  • On initial load
  • When switching from search mode back to normal mode
  • When explicitly refreshing the gallery

If frequent updates are necessary for other components, this is acceptable. Otherwise, consider debouncing or limiting updates.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf08514 and 4340240.

📒 Files selected for processing (3)
  • frontend/src/api/api-functions/images.ts (1 hunks)
  • frontend/src/pages/Home/Home.tsx (2 hunks)
  • frontend/src/types/API.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/api/api-functions/images.ts
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/Home/Home.tsx (7)
frontend/src/features/imageSelectors.ts (1)
  • selectImages (5-7)
frontend/src/components/Media/ChronologicalGallery.tsx (2)
  • MonthMarker (10-14)
  • ChronologicalGallery (25-187)
frontend/src/app/store.ts (1)
  • RootState (22-22)
frontend/src/api/api-functions/images.ts (1)
  • fetchAllImages (12-32)
frontend/src/config/pagination.ts (5)
  • IMAGES_PER_PAGE (5-5)
  • DEFAULT_STALE_TIME (11-11)
  • DEFAULT_RETRY_COUNT (8-8)
  • DEFAULT_RETRY_DELAY (9-9)
  • SCROLL_THRESHOLD (6-6)
frontend/src/features/imageSlice.ts (1)
  • setImages (18-20)
frontend/src/hooks/useMutationFeedback.tsx (1)
  • useMutationFeedback (60-135)
🔇 Additional comments (9)
frontend/src/types/API.ts (2)

1-2: LGTM! Excellent type safety improvement.

Making APIResponse generic with data?: T enables strongly-typed API responses (e.g., APIResponse<Image[]>), providing better IDE support and compile-time safety while maintaining backward compatibility with the any default.


6-8: LGTM! Pagination metadata properly typed.

The optional pagination fields (total, limit, offset) are correctly typed and align with the backend pagination implementation described in the PR objectives.

frontend/src/pages/Home/Home.tsx (7)

1-20: LGTM: Imports are well-organized and appropriate.

The addition of useCallback, useMemo, useInfiniteQuery, and pagination constants properly supports the infinite scroll implementation. Renaming the selector result to reduxImages (line 25) clearly distinguishes Redux state from query-derived images.


60-77: LGTM: Scroll detection logic is well-implemented.

The scroll handler correctly:

  • Guards against missing refs, exhausted pages, and concurrent fetches (line 61)
  • Calculates scroll progress accurately (lines 63-64)
  • Triggers pagination at the configured threshold (line 66)
  • Properly attaches and cleans up the event listener (lines 71-77)

The useCallback dependencies are complete and accurate.


90-92: LGTM: Image selection logic correctly handles search vs. normal mode.

The memoized selector appropriately chooses reduxImages during active search or allImages during normal browsing, with correct dependencies.


94-102: LGTM: Mutation feedback configuration is appropriate.

The loading state logic isPending: isLoading && !isSearchActive (line 95) is correct. Since the query is disabled during search mode (line 53: enabled: !isSearchActive), isLoading should be false anyway, making the !isSearchActive check redundant but harmless as a defensive guard.

The feedback messages appropriately hide success notifications and show relevant error messages.


104-108: LGTM: Title logic appropriately reflects search state.

The memoized title correctly displays search result count when active, or the default gallery title otherwise, with proper dependencies.


117-130: LGTM: Rendering logic properly handles loading states.

The rendering correctly:

  • Displays ChronologicalGallery with all required props including scrollContainerRef for offset calculations
  • Shows a loading indicator only during isFetchingNextPage (lines 125-129)
  • Falls back to EmptyGalleryState when no images are present

The fragment wrapper appropriately contains both the gallery and loading indicator.


44-51: Remove this review comment - pagination metadata is properly typed in APIResponse.

Verification confirms the pagination metadata fields (total, offset, limit) ARE properly typed in the APIResponse interface as optional numbers. The fetchAllImages function returns Promise<APIResponse<Image[]>>, and the getNextPageParam logic correctly accesses these fields with optional chaining, matching the interface definition exactly. No typing issue exists here.

Likely an incorrect or invalid review comment.

@mohi-devhub
Copy link
Author

@rahulharpal1603 Have fixed the issue, please check

CRITICAL BUG FIX - App launch failure on macOS arm64

Root cause: Infinite recursion in onboarding step components causing
the app to freeze/crash on launch when localStorage has saved state.

Issues fixed:
1. AvatarSelectionStep - useEffect missing deps, re-dispatching on every render
2. FolderSetupStep - useEffect missing deps, re-dispatching on every render
3. ThemeSelectionStep - useEffect missing deps, re-dispatching on every render
4. ServerCheck - missing dispatch/stepIndex dependencies
5. InitialSteps - missing navigate dependency

Solution:
- Added hasMarkedCompleted flag to prevent re-dispatching markCompleted
- Added proper useEffect dependencies (dispatch, stepIndex, navigate)
- Prevents infinite loop: useEffect -> dispatch -> state change -> re-render -> useEffect

This fixes issue AOSSIE-Org#645 where the bundled app fails to launch on macOS
Apple Silicon due to infinite recursion in the login/onboarding page.

Tested scenarios:
- Fresh install (no localStorage)
- Returning user (localStorage has avatar/name/theme/folder)
- Step-by-step completion without infinite loops
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
frontend/src/components/OnboardingSteps/ServerCheck.tsx (1)

68-69: LGTM! Dependency array correctly updated.

Adding dispatch and stepIndex to the dependency array correctly addresses potential stale closure issues and aligns with React Hook rules. Since the query success states are stable once true and markCompleted is idempotent, repeated dispatches are not a concern.

Optional consistency note: Other onboarding steps (ThemeSelectionStep, AvatarSelectionStep, FolderSetupStep) use a hasMarkedCompleted guard to prevent duplicate completion dispatches. While not necessary here due to stable query states, adding a similar guard would provide defensive consistency across all onboarding components:

 export const ServerCheck: React.FC<ServerCheckProps> = ({ stepIndex }) => {
   const dispatch = useDispatch();
+  const [hasMarkedCompleted, setHasMarkedCompleted] = React.useState(false);
   // ... queries ...
   useEffect(() => {
     // ... loading and error handling ...
     if (mainBackendSuccess && syncMicroserviceSuccess) {
       dispatch(hideLoader());
-      dispatch(markCompleted(stepIndex));
+      if (!hasMarkedCompleted) {
+        setHasMarkedCompleted(true);
+        dispatch(markCompleted(stepIndex));
+      }
     }
-  }, [/* deps */]);
+  }, [/* deps, hasMarkedCompleted */]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4340240 and 4d7871c.

📒 Files selected for processing (5)
  • frontend/src/components/OnboardingSteps/AvatarSelectionStep.tsx (1 hunks)
  • frontend/src/components/OnboardingSteps/FolderSetupStep.tsx (1 hunks)
  • frontend/src/components/OnboardingSteps/ServerCheck.tsx (1 hunks)
  • frontend/src/components/OnboardingSteps/ThemeSelectionStep.tsx (1 hunks)
  • frontend/src/pages/InitialSteps/InitialSteps.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
frontend/src/components/OnboardingSteps/ThemeSelectionStep.tsx (1)
frontend/src/features/onboardingSlice.ts (1)
  • markCompleted (31-42)
frontend/src/components/OnboardingSteps/AvatarSelectionStep.tsx (1)
frontend/src/features/onboardingSlice.ts (1)
  • markCompleted (31-42)
frontend/src/components/OnboardingSteps/FolderSetupStep.tsx (1)
frontend/src/features/onboardingSlice.ts (1)
  • markCompleted (31-42)
🔇 Additional comments (7)
frontend/src/pages/InitialSteps/InitialSteps.tsx (1)

16-16: LGTM! Effect dependencies correctly updated.

Adding navigate to the dependency array correctly follows React Hook rules and prevents potential stale closure issues.

frontend/src/components/OnboardingSteps/ThemeSelectionStep.tsx (2)

32-32: LGTM! Completion guard pattern correctly implemented.

The hasMarkedCompleted state prevents duplicate markCompleted dispatches when the effect re-runs, which is essential since effects execute even when the component returns null (line 52-53).


35-39: LGTM! Effect logic and dependencies correctly implemented.

The guard condition and dependency array are correct:

  • !hasMarkedCompleted prevents duplicate dispatches across re-renders
  • localStorage.getItem('themeChosen') enables auto-completion when returning to an already-completed step
  • Dependencies (hasMarkedCompleted, dispatch, stepIndex) are complete per React Hook rules
frontend/src/components/OnboardingSteps/AvatarSelectionStep.tsx (2)

35-35: LGTM! Completion guard correctly added.

The hasMarkedCompleted state follows the same pattern as ThemeSelectionStep, preventing duplicate completion dispatches.


38-42: LGTM! Effect implementation matches established pattern.

The guard condition and dependency array correctly implement auto-completion for already-completed steps while preventing duplicate dispatches. Dependencies are complete per React Hook rules.

frontend/src/components/OnboardingSteps/FolderSetupStep.tsx (2)

32-32: LGTM! Completion guard correctly added.

The hasMarkedCompleted state prevents duplicate completion dispatches, maintaining consistency with the other onboarding steps.


35-39: LGTM! Effect correctly implements auto-completion with guard.

The implementation matches the established pattern across onboarding steps:

  • Guard prevents duplicate markCompleted dispatches
  • Enables auto-completion when folderChosen flag exists in localStorage
  • Dependencies (hasMarkedCompleted, dispatch, stepIndex) are complete per React Hook rules

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: Implement Pagination for Image Loading

2 participants