Skip to content

Add comments throughout the codebase #355#500

Closed
GovindBuildsAI wants to merge 4 commits intoAOSSIE-Org:mainfrom
GovindBuildsAI:FIx-issue---Add-comments-throughout-the-codebase-#355
Closed

Add comments throughout the codebase #355#500
GovindBuildsAI wants to merge 4 commits intoAOSSIE-Org:mainfrom
GovindBuildsAI:FIx-issue---Add-comments-throughout-the-codebase-#355

Conversation

@GovindBuildsAI
Copy link

@GovindBuildsAI GovindBuildsAI commented Sep 5, 2025

added comments throughout the codebase to make it more understandable

Summary by CodeRabbit

  • New Features

    • Integrated face detection with face embeddings and automatic persistence.
    • Global loader and information dialog now appear based on app state.
    • OpenAPI spec is generated at startup for easier API exploration.
    • Model selection for detection respects user preferences.
  • Improvements

    • More reliable startup/shutdown with structured logging.
    • Automatic cache refresh on startup and data refresh.
    • Images API now includes tagging status and tags in responses.
    • Health checks report combined app, database, and watcher status.
  • Documentation

    • Expanded inline docs and type exports across backend and frontend.

Ashutoshx7 and others added 4 commits September 1, 2025 12:22
* chore: remove unused files from web-ui

* file move from web-ui to landing-page and also add the framer package which was use but not in the package.json

* remove unused frontend files
added comments throughout the codebase to make it more understandable
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 5, 2025

Walkthrough

Introduces typed image models and revised DB queries, a new FaceDetector integrating YOLO and FaceNet with DB persistence, FaceNet/YOLO utility modules, FastAPI lifespan with OpenAPI generation and logging, explicit cache invalidation in app entry points, refined microservice startup/health logic, and frontend wiring for global loader/info dialog. Mostly adds documentation/comments elsewhere.

Changes

Cohort / File(s) Summary of Changes
App startup and cache control
app.py
Added module docstring; on startup invalidate caches; refresh entry invalidates specific caches (albums:get_all_albums, folder_structure:get_folder_structure).
Backend lifespan, OpenAPI, logging, routers
backend/main.py
Added async lifespan context manager; startup creates tables, generates OpenAPI JSON, starts microservice; shutdown closes executors; initialized structured logging; registered additional routers; health endpoint docstring.
Database image models and queries
backend/app/database/images.py
Added typed ImageRecord and ImageClassPair; expanded schema docs and constraints; rewrote bulk insert to dict-based named params; enhanced get_all_images to aggregate tags and include isTagged; refined untagged image selection to AI-enabled folders.
Face detection pipeline
backend/app/models/FaceDetector.py
Implemented YOLO-based face detection and FaceNet embeddings; preprocess, detect, embed; persist embeddings with bbox/confidence; close releases resources; added docs and initialization.
Model path settings and comments
backend/app/config/settings.py
Added inline comments on model choices and paths; no functional value changes.
YOLO utilities
backend/app/utils/YOLO.py
Added docstrings, stable visualization colors, annotated class names, detailed docs for NMS/IoU; enhanced model path selection via metadata-driven preferences with defaults.
FaceNet utilities
backend/app/utils/FaceNet.py
New helpers: image preprocessing, embedding normalization, cosine similarity, model path accessor.
Images API response modeling
backend/app/routes/images.py
Introduced Pydantic models (ImageData, GetAllImagesResponse); endpoint returns typed response using db_get_all_images; added docs and import cleanup.
Frontend root wiring
frontend/src/App.tsx
Connected GlobalLoader and InfoDialog to Redux state via useSelector; added header comments; minor import duplication.
Frontend store types
frontend/src/app/store.ts
Exported RootState and AppDispatch types; added extensive docs; no runtime changes.
Frontend routes docs
frontend/src/routes/AppRoutes.tsx
Documentation and comments added; routing unchanged.
Sync microservice lifecycle
sync-microservice/app/core/lifespan.py
Tracked watcher_started; validated DB on startup; conditional watcher shutdown; improved lifecycle logging and handling.
Sync microservice health
sync-microservice/app/routes/health.py
Health check now combines DB and watcher status into overall status; structured response fields computed explicitly; added docs.
Sync microservice main docs
sync-microservice/main.py
Added module docs and sectioned comments; route registration clarified; functionality unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant FastAPI as FastAPI App
  participant Lifespan as Lifespan Ctx
  participant DB as Database
  participant Logger as Logger
  participant MS as Sync Microservice
  participant FS as Filesystem (docs path)

  User->>FastAPI: Start server
  activate FastAPI
  FastAPI->>Lifespan: enter()
  activate Lifespan
  Lifespan->>Logger: init app logger
  Lifespan->>DB: create tables (folders, images, yolo_mappings, clusters, faces, albums, album_images, metadata)
  Lifespan->>FastAPI: generate_openapi_json()
  FastAPI->>FS: write docs/backend/backend_python/openapi.json
  Lifespan->>MS: start microservice utility
  Lifespan-->>FastAPI: yield (app ready)
  deactivate Lifespan

  Note over User,FastAPI: App serves requests...

  User-->>FastAPI: Shutdown signal
  FastAPI->>Lifespan: exit()
  activate Lifespan
  Lifespan->>FastAPI: shutdown ProcessPoolExecutor
  Lifespan-->>FastAPI: complete shutdown
  deactivate Lifespan
  deactivate FastAPI
Loading
sequenceDiagram
  autonumber
  participant Caller as Detector Client
  participant FaceDetector as FaceDetector.detect_faces
  participant YOLO as YOLO Detector
  participant CV as Image Loader
  participant FaceNet as FaceNet Model
  participant DB as db_insert_face_embeddings_by_image_id

  Caller->>FaceDetector: detect_faces(image_id, image_path)
  FaceDetector->>CV: load image
  alt Load fails
    FaceDetector-->>Caller: None
  else Loaded
    FaceDetector->>YOLO: infer(image)
    YOLO-->>FaceDetector: boxes, scores, class_ids
    loop each detection (conf>=threshold)
      FaceDetector->>CV: crop + pad face ROI
      FaceDetector->>FaceNet: preprocess ROI
      FaceDetector->>FaceNet: embed(ROI)
      FaceNet-->>FaceDetector: embedding
    end
    alt any embeddings
      FaceDetector->>DB: insert embeddings + bbox + confidence
      DB-->>FaceDetector: ack
    end
    FaceDetector-->>Caller: {ids, processed_faces, num_faces}
  end
Loading
sequenceDiagram
  autonumber
  actor Client
  participant Health as /health (Sync MS)
  participant DB as db_check_database_connection
  participant Watcher as watcher_util_is_watcher_running

  Client->>Health: GET /api/v1/health
  Health->>DB: check connection
  DB-->>Health: True/False
  Health->>Watcher: is running?
  Watcher-->>Health: True/False
  Health-->>Client: {status: healthy|unhealthy, database, watcher}
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~70 minutes

Possibly related PRs

  • Gsoc'25 Frontend Revamp #489 — Touches the same image/face pipeline: DB images retrieval, images route response, and FaceDetector persistence of confidences/bboxes.

Poem

A whisk of code, a hare-brained hop,
Lifespans bloom and watchers pop.
Faces found, embeddings stowed,
OpenAPI maps the road.
Loaders glow, dialogs sing—
Cache dust bunnies take to wing.
Hippity ship—it’s shipping spring! 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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

Caution

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

⚠️ Outside diff range comments (5)
backend/app/database/images.py (2)

233-236: Don’t swallow DB errors in db_get_all_images().

Returning [] on exception hides real failures and makes the API return 200 with empty data.

Apply:

-    except Exception as e:
-        print(f"Error getting all images: {e}")
-        return []
+    except Exception as e:
+        print(f"Error getting all images: {e}")
+        raise

272-283: db_get_untagged_images() returns ImageRecord but omits isTagged.

Add isTagged to match the declared TypedDict.

Apply:

         for image_id, path, folder_id, thumbnail_path, metadata in results:
             untagged_images.append(
                 {
                     "id": image_id,
                     "path": path,
                     "folder_id": folder_id,
                     "thumbnailPath": thumbnail_path,
                     "metadata": metadata,
+                    "isTagged": False,
                 }
             )
backend/app/routes/images.py (1)

46-52: folder_id should be int to match DB and schema.

Database returns INTEGER; current model defines str.

Apply:

-    folder_id: str
+    folder_id: int
backend/app/utils/YOLO.py (2)

241-249: Off-by-one excludes the last COCO class and lacks bounds checks.

class_id >= len(class_names) - 1 incorrectly treats index 79 as unknown; negative ids also not handled.

-        if score < confidence_threshold or class_id >= len(class_names) - 1:
-            color = colors[-1]
-            label = "unknown"
+        if score < confidence_threshold or class_id < 0 or class_id >= len(class_names):
+            color = (0, 0, 0)  # fallback color
+            label = "unknown"
         else:
             color = colors[class_id]
             label = class_names[class_id]

300-309: Potential IndexError on out-of-range class_id in mask drawing.

Add a safe fallback to avoid crashes when class_id is invalid.

-    for box, class_id in zip(boxes, classes):
-        color = colors[class_id]
+    for box, class_id in zip(boxes, classes):
+        if 0 <= int(class_id) < len(colors):
+            color = colors[int(class_id)]
+        else:
+            color = (0, 0, 0)
🧹 Nitpick comments (25)
app.py (2)

27-30: Startup invalidate_cache() is likely a no-op with current in-memory cache

Given utils.cache uses a process-local dict, a cold start begins empty; this call is harmless but redundant. If you prefer to keep it for clarity/future persistence, tweak the comment to avoid implying stale state exists at boot.

Apply this small wording tweak:

-    # Clear any stale cache data on startup to ensure fresh data
-    # This prevents issues with outdated cached information
+    # Defensive: ensure empty cache on process start.
+    # Current cache is in-memory and already empty on cold start; kept for clarity/future persistence.
     invalidate_cache()

46-50: Extract cache keys into constants and add logging

  • Replace string literals with named constants to prevent typos:
    - invalidate_cache("albums:get_all_albums")
    + invalidate_cache(ALBUMS_ALL_KEY)- invalidate_cache("folder_structure:get_folder_structure")
    + invalidate_cache(FOLDER_STRUCTURE_KEY)
  • Define at top of app.py after imports:
    ALBUMS_ALL_KEY = "albums:get_all_albums"
    FOLDER_STRUCTURE_KEY = "folder_structure:get_folder_structure"
    import logging
    logger = logging.getLogger(__name__)
  • Add observability for each invalidation:
    logger.info("Cache invalidated: %s", ALBUMS_ALL_KEY)
    logger.info("Cache invalidated: %s", FOLDER_STRUCTURE_KEY)
  • If invalidate_cache may run alongside readers, ensure cache invalidations are thread-safe (e.g., add locking or use atomic swaps).
frontend/src/routes/AppRoutes.tsx (2)

24-24: Prefer react-router-dom for web imports.

Use the DOM bundle consistently across the app.

-import { Routes, Route } from 'react-router';
+import { Routes, Route, Navigate } from 'react-router-dom';

46-73: Add a catch-all route for unknown paths.

Prevents blank screens or confusing states.

       <Route element={<Layout />}>
@@
         {/* Person-based image organization using face clustering */}
         <Route path={ROUTES.PERSON} element={<PersonImages />} />
+        {/* Fallback: redirect unknown routes to home */}
+        <Route path="*" element={<Navigate to={ROUTES.HOME} replace />} />
       </Route>
frontend/src/app/store.ts (1)

40-60: Add typed Redux hooks to remove inline RootState annotations.

Simplifies usage and avoids repetitive types.

Create a new file (outside this diff) at frontend/src/app/hooks.ts:

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

Then replace useSelector((state: RootState) => ...) with useAppSelector(...) where used.

frontend/src/App.tsx (1)

56-65: Optional: Place overlays inside BrowserRouter.

If InfoDialog/Loader ever navigate or use Links, nesting them under BrowserRouter avoids context issues.

sync-microservice/app/core/lifespan.py (4)

32-35: Avoid module-level globals; keep lifecycle state on app.state.

Using a module global can bite under reloads, tests, or multiple app instances. Store the flag on app.state instead.

Apply this diff within the shown ranges:

-# Global variable to track file system watcher status
-# Used to ensure proper cleanup during shutdown
-watcher_started = False
+# (moved to app.state to avoid module-level globals)
-    global watcher_started
+    if not hasattr(app.state, "watcher_started"):
+        app.state.watcher_started = False
-        watcher_started = watcher_util_start_folder_watcher()
+        app.state.watcher_started = watcher_util_start_folder_watcher()
-        if watcher_started:
+        if getattr(app.state, "watcher_started", False):
             watcher_util_stop_folder_watcher()

Also applies to: 64-66, 83-84, 105-107


41-43: Add return type for async lifespan.

Helps tooling and readability.

-from contextlib import asynccontextmanager
+from contextlib import asynccontextmanager
+from typing import AsyncIterator
@@
-async def lifespan(app: FastAPI):
+async def lifespan(app: FastAPI) -> AsyncIterator[None]:

70-81: Use structured logging instead of print.

Switching to logging integrates with Uvicorn/Infra logs and supports levels/formatting.

Minimal changes:

-        print("Starting PictoPy Sync Microservice...")
+        logger.info("Starting PictoPy Sync Microservice...")
@@
-            print("Failed to connect to PictoPy database")
-            print(
-                "Make sure the main PictoPy backend is set up and the database exists"
-            )
+            logger.error("Failed to connect to PictoPy database")
+            logger.info("Ensure the main PictoPy backend is set up and the database exists")
@@
-        print("Database connection successful")
+        logger.info("Database connection successful")
@@
-        print("Sync microservice is ready!")
+        logger.info("Sync microservice is ready!")
@@
-        print("\nReceived keyboard interrupt (Ctrl+C)")
-        print("Initiating graceful shutdown...")
+        logger.warning("Received keyboard interrupt (Ctrl+C). Initiating graceful shutdown...")
@@
-        print(f"Unexpected error during startup: {e}")
+        logger.exception("Unexpected error during startup: %s", e)
@@
-        print("Shutting down sync microservice...")
+        logger.info("Shutting down sync microservice...")
@@
-            print("Folder watcher stopped")
+            logger.info("Folder watcher stopped")
@@
-        print("Shutdown complete")
+        logger.info("Shutdown complete")

Outside the ranges above, add once at top:

import logging
logger = logging.getLogger(__name__)

Also applies to: 90-110


72-80: Startup hard-fails if DB is down — consider readiness vs. liveness.

Aborting startup is fine if this service must not run without DB. If you want the health endpoint to be reachable even when dependencies are down, start the app and report “unhealthy” instead, letting orchestration use readiness to gate traffic.

Is the intended behavior to refuse to start without a DB, or should we expose /health as unhealthy until dependencies recover?

sync-microservice/main.py (1)

56-60: Don’t override Uvicorn’s default logging config with None.

Passing log_config=None disables the default structured logs. Prefer omitting it so you inherit Uvicorn’s defaults.

-    config = Config(app=app, host="0.0.0.0", port=8001, log_config=None)
+    config = Config(app=app, host="0.0.0.0", port=8001)

Optional simplification:

-if __name__ == "__main__":
-    # Configure uvicorn server for direct execution
-    config = Config(app=app, host="0.0.0.0", port=8001, log_config=None)
-    server = Server(config)
-    
-    # Start the sync microservice
-    server.run()
+if __name__ == "__main__":
+    import uvicorn
+    uvicorn.run(app, host="0.0.0.0", port=8001)
sync-microservice/app/routes/health.py (3)

27-41: Document response codes and intent (liveness vs. readiness).

Consider clarifying whether /health is liveness (always 200) or readiness (503 when unhealthy).


42-50: Offload the synchronous DB check to a threadpool.

Avoid blocking the event loop by running the sqlite check in a threadpool.

-from app.database.folders import db_check_database_connection
+from app.database.folders import db_check_database_connection
+from starlette.concurrency import run_in_threadpool
@@
-    db_status = db_check_database_connection()
+    db_status = await run_in_threadpool(db_check_database_connection)

51-55: Optionally set HTTP 503 when unhealthy (readiness).

If this endpoint is used by load balancers for readiness, set 503 when dependencies are down.

-from fastapi import APIRouter
+from fastapi import APIRouter, Response, status
@@
-@router.get("/health", response_model=HealthCheckResponse)
-async def health_check():
+@router.get("/health", response_model=HealthCheckResponse)
+async def health_check(response: Response):
@@
-    return HealthCheckResponse(
+    if overall_status != "healthy":
+        response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
+    return HealthCheckResponse(
         status=overall_status,
         database="connected" if db_status else "disconnected",
         watcher="running" if watcher_status else "stopped",
     )

If you prefer liveness semantics, keep 200 but expose a separate /ready endpoint that returns 503 when not ready.

backend/main.py (2)

27-27: Remove duplicate import of CustomizeLogger.

The module imports CustomizeLogger twice.

Apply this diff:

- from app.custom_logging import CustomizeLogger

Also applies to: 36-36


41-54: Gate side effects at startup (OpenAPI write, sync service) via env flags.

Avoid doing heavy I/O or spawning processes unconditionally on app start.

Apply this diff:

-    generate_openapi_json()
+    if os.getenv("GENERATE_OPENAPI_JSON", "true").lower() == "true":
+        generate_openapi_json()
@@
-    microservice_util_start_sync_service()
+    if os.getenv("START_SYNC_SERVICE", "true").lower() == "true":
+        if not microservice_util_start_sync_service():
+            app.logger.warning("Sync microservice failed to start")
+    else:
+        app.logger.info("START_SYNC_SERVICE disabled; skipping sync microservice startup")
backend/app/database/images.py (3)

265-267: Use 1/0 instead of TRUE/FALSE for SQLite booleans.

More portable across SQLite builds.

Apply:

-            WHERE f.AI_Tagging = TRUE
-            AND i.isTagged = FALSE
+            WHERE f.AI_Tagging = 1
+            AND i.isTagged = 0

74-100: Consider enabling foreign keys per-connection (PRAGMA).

SQLite requires PRAGMA foreign_keys=ON per connection; otherwise CASCADE won’t apply.

Optional: execute cursor.execute("PRAGMA foreign_keys = ON") after connect() in writers.


132-147: Replace prints with structured logging.

Use the project logger for consistency and observability.

If permissible, add:

from loguru import logger

Then replace print(...) with logger.error/info as appropriate.

Also applies to: 233-235, 347-350, 385-387, 419-421

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

117-126: 500 error body doesn’t match documented model.

Raising HTTPException nests the model under "detail", diverging from responses={500: {"model": ErrorResponse}}.

Apply:

-        raise HTTPException(
-            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
-            detail=ErrorResponse(
-                success=False,
-                error="Internal server error",
-                message=f"Unable to retrieve images: {str(e)}",
-            ).model_dump(),
-        )
+        return JSONResponse(
+            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
+            content=ErrorResponse(
+                success=False,
+                error="Internal server error",
+                message=f"Unable to retrieve images: {str(e)}",
+            ).model_dump(),
+        )

Add import (outside this hunk):

from fastapi.responses import JSONResponse
backend/app/config/settings.py (1)

41-49: Prefer Path objects and env-driven config for portability.

Using pathlib.Path improves path joins and OS portability; consider moving these to a typed Settings (e.g., Pydantic) with env overrides.

backend/app/utils/YOLO.py (2)

120-124: Use integer colors compatible with OpenCV.

Current colors are float; cv2 expects int tuples. Generate uint8 once for consistency.

-rng = np.random.default_rng(3)  # Fixed seed for reproducible colors
-colors = rng.uniform(0, 255, size=(len(class_names), 3))
+rng = np.random.default_rng(3)  # Fixed seed for reproducible colors
+colors = rng.integers(0, 256, size=(len(class_names), 3), dtype=np.uint8)

331-334: Normalize and validate user preference for model size.

Lowercasing helps avoid silent fallbacks due to casing mismatches.

-        user_prefs = metadata["user_preferences"]
-        model_size = user_prefs.get("YOLO_model_size", "small")
+        user_prefs = metadata["user_preferences"]
+        model_size = str(user_prefs.get("YOLO_model_size", "small")).lower()
backend/app/models/FaceDetector.py (2)

86-90: Replace prints with structured logging.

Use the project’s logger to control verbosity and avoid stdout noise.


127-132: Return JSON-friendly types for ids.

Stringifying a numpy array is awkward for consumers. Return a list of ints.

-        return {
-            "ids": f"{class_ids}",
+        return {
+            "ids": class_ids.tolist() if hasattr(class_ids, "tolist") else list(class_ids),
             "processed_faces": processed_faces,
             "num_faces": len(embeddings),
         }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e985b25 and a0a0a19.

📒 Files selected for processing (14)
  • app.py (1 hunks)
  • backend/app/config/settings.py (1 hunks)
  • backend/app/database/images.py (7 hunks)
  • backend/app/models/FaceDetector.py (3 hunks)
  • backend/app/routes/images.py (3 hunks)
  • backend/app/utils/FaceNet.py (1 hunks)
  • backend/app/utils/YOLO.py (2 hunks)
  • backend/main.py (7 hunks)
  • frontend/src/App.tsx (1 hunks)
  • frontend/src/app/store.ts (1 hunks)
  • frontend/src/routes/AppRoutes.tsx (1 hunks)
  • sync-microservice/app/core/lifespan.py (2 hunks)
  • sync-microservice/app/routes/health.py (1 hunks)
  • sync-microservice/main.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (9)
frontend/src/routes/AppRoutes.tsx (2)
frontend/src/pages/InitialSteps/InitialSteps.tsx (1)
  • InitialSteps (7-21)
frontend/src/constants/routes.ts (1)
  • ROUTES (1-11)
app.py (1)
utils/cache.py (1)
  • invalidate_cache (44-55)
sync-microservice/main.py (2)
backend/main.py (1)
  • lifespan (40-60)
sync-microservice/app/core/lifespan.py (1)
  • lifespan (42-109)
sync-microservice/app/routes/health.py (3)
sync-microservice/app/database/folders.py (1)
  • db_check_database_connection (44-68)
sync-microservice/app/utils/watcher.py (1)
  • watcher_util_is_watcher_running (211-213)
sync-microservice/app/schemas/health.py (1)
  • HealthCheckResponse (5-10)
sync-microservice/app/core/lifespan.py (3)
sync-microservice/app/database/folders.py (1)
  • db_check_database_connection (44-68)
sync-microservice/app/utils/watcher.py (2)
  • watcher_util_start_folder_watcher (216-271)
  • watcher_util_stop_folder_watcher (274-302)
backend/main.py (1)
  • lifespan (40-60)
backend/app/models/FaceDetector.py (6)
backend/app/models/YOLO.py (2)
  • YOLO (14-103)
  • close (31-33)
backend/app/utils/YOLO.py (1)
  • YOLO_util_get_model_path (312-353)
backend/app/models/FaceNet.py (3)
  • FaceNet (8-25)
  • get_embedding (16-21)
  • close (23-25)
backend/app/utils/FaceNet.py (2)
  • FaceNet_util_get_model_path (110-117)
  • FaceNet_util_preprocess_image (30-63)
backend/app/database/faces.py (1)
  • db_insert_face_embeddings_by_image_id (90-134)
backend/app/models/ObjectClassifier.py (1)
  • close (24-30)
frontend/src/App.tsx (4)
frontend/src/app/store.ts (1)
  • RootState (73-73)
frontend/src/contexts/ThemeContext.tsx (1)
  • ThemeProvider (23-64)
frontend/src/routes/AppRoutes.tsx (1)
  • AppRoutes (44-75)
frontend/src/components/Loader/GlobalLoader.tsx (1)
  • GlobalLoader (5-19)
backend/app/routes/images.py (2)
backend/app/database/images.py (1)
  • db_get_all_images (151-237)
backend/app/schemas/images.py (1)
  • ErrorResponse (47-50)
backend/main.py (2)
backend/app/utils/microservice.py (1)
  • microservice_util_start_sync_service (13-43)
backend/app/custom_logging.py (2)
  • CustomizeLogger (37-79)
  • make_logger (39-51)
🪛 Biome (2.1.2)
frontend/src/App.tsx

[error] 32-32: Shouldn't redeclare 'useSelector'. Consider to delete it or rename it.

'useSelector' is defined here:

(lint/suspicious/noRedeclare)

🔇 Additional comments (18)
app.py (3)

1-12: Clear, helpful module docstring — LGTM

Accurately describes entry points and cache semantics.


19-24: Function docstring reads well — LGTM

Concise and aligned with behavior.


35-42: Refresh docstring — LGTM

Good guidance on when/why selective invalidation happens.

frontend/src/routes/AppRoutes.tsx (1)

1-20: Docs-only change LGTM.

Helpful, accurate module and component JSDoc without altering behavior.

Also applies to: 37-43

frontend/src/app/store.ts (1)

73-81: Type exports LGTM.

RootState and AppDispatch improve selector/dispatch typing across the app.

sync-microservice/app/core/lifespan.py (2)

1-13: Nice, clear module docs.

The responsibilities and phases are well documented and easy to follow.


83-86: Watcher start failures: confirm desired readiness semantics.

watcher_util_start_folder_watcher() can return False for “no folders” or “already running.” The app still announces readiness. That’s OK if “no folders” is acceptable; otherwise treat it as “unready.”

Do we want to fail startup when no folders are configured, or is it acceptable to run with watcher_status="stopped"?

sync-microservice/main.py (4)

1-19: Good high-level app docs.


33-39: Lifespan wiring looks correct.


45-49: Router registration is clear and versioned.


45-49: All routers correctly define APIRouter Verified that each module in sync-microservice/app/routes exposes router = APIRouter(...) as expected.

sync-microservice/app/routes/health.py (1)

1-13: Helpful endpoint/module docs.

backend/main.py (3)

132-137: LGTM: helpful health endpoint docstring.


39-61: Scope drift vs PR title.

This adds lifecycle management, OpenAPI emission, process pool, and microservice startup—beyond “add comments”. Please confirm scope or update PR description.


77-79: Remove logging config verification comment

app/logging_config.json is present and includes the required logger fields (path, filename, level, rotation, retention, format).

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

93-115: LGTM: clear mapping to strongly-typed response model.


18-20: Pydantic v2 confirmed—model_dump usage is valid.

Requirements pin Pydantic to 2.x (sync-microservice: 2.11.7; backend: 2.7.1), and there are no remaining .dict() calls.

backend/app/utils/FaceNet.py (1)

30-63: Preprocessing doc and steps look good.

Comment on lines +21 to +25
# YOLO models specifically trained for face detection
# Optimized for detecting human faces in images
SMALL_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Small_Face.onnx" # Fast face detection
NANO_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Nano_Face.onnx" # Fastest face detection
MEDIUM_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Medium_Face.onnx" # Most accurate face detection
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Duplicate face-model constants — remove the second block.

You re-declare SMALL/NANO/MEDIUM_FACE_DETECTION_MODEL above, then again at Lines 27–31. The latter silently overrides the commented ones and invites drift.

Apply this diff to drop the duplicate block:

-# Face Detection Models:
-SMALL_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Small_Face.onnx"
-NANO_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Nano_Face.onnx"
-MEDIUM_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Medium_Face.onnx"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# YOLO models specifically trained for face detection
# Optimized for detecting human faces in images
SMALL_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Small_Face.onnx" # Fast face detection
NANO_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Nano_Face.onnx" # Fastest face detection
MEDIUM_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Medium_Face.onnx" # Most accurate face detection
# YOLO models specifically trained for face detection
# Optimized for detecting human faces in images
SMALL_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Small_Face.onnx" # Fast face detection
NANO_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Nano_Face.onnx" # Fastest face detection
MEDIUM_FACE_DETECTION_MODEL = f"{MODEL_EXPORTS_PATH}/YOLOv11_Medium_Face.onnx" # Most accurate face detection
🤖 Prompt for AI Agents
In backend/app/config/settings.py around lines 21–31 there are duplicate
declarations of SMALL_FACE_DETECTION_MODEL, NANO_FACE_DETECTION_MODEL, and
MEDIUM_FACE_DETECTION_MODEL: remove the second block (the re-declaration at
approximately lines 27–31) so the constants are defined only once (keep the
first, commented/annotated block at lines 21–25), ensuring no silent overrides
or drift.

Comment on lines +33 to +36
ImageId = str # Unique identifier for images
ImagePath = str # File system path to image
FolderId = str # Unique identifier for folders
ClassId = int # Object detection class identifier
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Type mismatch: folder_id should be int (matches DB schema).

You declare FolderId as str, but the schema uses INTEGER and code treats it as int elsewhere. Align types.

Apply:

-ImageId = str      # Unique identifier for images
-ImagePath = str    # File system path to image
-FolderId = str     # Unique identifier for folders
-ClassId = int      # Object detection class identifier
+ImageId = str      # Unique identifier for images
+ImagePath = str    # File system path to image
+FolderId = int     # Unique identifier for folders (SQLite INTEGER FK)
+ClassId = int      # Object detection class identifier
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ImageId = str # Unique identifier for images
ImagePath = str # File system path to image
FolderId = str # Unique identifier for folders
ClassId = int # Object detection class identifier
ImageId = str # Unique identifier for images
ImagePath = str # File system path to image
FolderId = int # Unique identifier for folders (SQLite INTEGER FK)
ClassId = int # Object detection class identifier
🤖 Prompt for AI Agents
In backend/app/database/images.py around lines 33 to 36, the FolderId type alias
is declared as str but the DB schema and other code expect an int; update the
alias to FolderId = int and adjust any local uses or annotations in this file to
reflect int (and run type-checks/quick search to fix any mismatched annotations
that assumed str).

Comment on lines 58 to +79
def detect_faces(self, image_id: int, image_path: str):
"""
Detect faces in an image and generate face embeddings.

This method performs the complete face detection and recognition pipeline:
1. Loads the image using OpenCV
2. Uses YOLO to detect face bounding boxes
3. Extracts face regions with padding
4. Generates face embeddings using FaceNet
5. Stores face data in the database

Args:
image_id (int): Unique identifier for the image
image_path (str): Path to the image file

Returns:
dict: Dictionary containing detection results with:
- ids: Class IDs from YOLO detection
- processed_faces: Preprocessed face images
- num_faces: Number of faces detected
None: If image loading fails
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

image_id type mismatch with DB layer.

DB expects an ImageId (uuid string), but signature/docstring use int. Align types to prevent subtle bugs and schema drift.

Apply this diff to update the API contract:

-    def detect_faces(self, image_id: int, image_path: str):
+    def detect_faces(self, image_id: str, image_path: str):
@@
-            image_id (int): Unique identifier for the image
+            image_id (str): Unique identifier for the image (uuid)

Run to confirm usages:


🏁 Script executed:

#!/bin/bash
# Check DB API and call sites for image_id typing/usage
rg -nP 'db_insert_face_embeddings_by_image_id\s*\(' -C2
rg -nP '\bImageId\b|\bimage_id:\s*(int|str)' -C2

Length of output: 5265


Refactor detect_faces signature to use string ImageId
Change image_id type from int to str in backend/app/models/FaceDetector.py detect_faces (line 58) and update its docstring to match the ImageId alias:

-    def detect_faces(self, image_id: int, image_path: str):
+    def detect_faces(self, image_id: str, image_path: str):
@@
-            image_id (int): Unique identifier for the image
+            image_id (str): Unique identifier for the image (uuid)
🤖 Prompt for AI Agents
In backend/app/models/FaceDetector.py around lines 58-79, change the
detect_faces signature from image_id: int to image_id: str (or to the ImageId
alias if your codebase defines one) and update the docstring to mention ImageId
and that it is a string; update the type hint in the signature, replace any
references in the docstring that say int with ImageId/str, ensure you import or
reference the ImageId alias if used, and scan/update local usage and external
callers to accept/convert string IDs accordingly so types remain consistent.

Comment on lines +101 to 116
# Create bounding box dictionary in JSON format for database storage
bbox = {"x": x1, "y": y1, "width": x2 - x1, "height": y2 - y1}
bboxes.append(bbox)
confidences.append(float(score))

# Extract face region with padding for better recognition
padding = 20
face_img = img[
max(0, y1 - padding) : min(img.shape[0], y2 + padding),
max(0, x1 - padding) : min(img.shape[1], x2 + padding),
]

# Preprocess face image for FaceNet
processed_face = FaceNet_util_preprocess_image(face_img)
processed_faces.append(processed_face)

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add guard for empty/invalid crops before preprocessing.

Zero-size crops cause cv2.resize to throw.

                 face_img = img[
                     max(0, y1 - padding) : min(img.shape[0], y2 + padding),
                     max(0, x1 - padding) : min(img.shape[1], x2 + padding),
                 ]
-                
+                # Skip invalid crops
+                if face_img.size == 0 or face_img.shape[0] == 0 or face_img.shape[1] == 0:
+                    continue
                 
                 # Preprocess face image for FaceNet
                 processed_face = FaceNet_util_preprocess_image(face_img)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Create bounding box dictionary in JSON format for database storage
bbox = {"x": x1, "y": y1, "width": x2 - x1, "height": y2 - y1}
bboxes.append(bbox)
confidences.append(float(score))
# Extract face region with padding for better recognition
padding = 20
face_img = img[
max(0, y1 - padding) : min(img.shape[0], y2 + padding),
max(0, x1 - padding) : min(img.shape[1], x2 + padding),
]
# Preprocess face image for FaceNet
processed_face = FaceNet_util_preprocess_image(face_img)
processed_faces.append(processed_face)
# Create bounding box dictionary in JSON format for database storage
bbox = {"x": x1, "y": y1, "width": x2 - x1, "height": y2 - y1}
bboxes.append(bbox)
confidences.append(float(score))
# Extract face region with padding for better recognition
padding = 20
face_img = img[
max(0, y1 - padding) : min(img.shape[0], y2 + padding),
max(0, x1 - padding) : min(img.shape[1], x2 + padding),
]
# Skip invalid crops
if face_img.size == 0 or face_img.shape[0] == 0 or face_img.shape[1] == 0:
continue
# Preprocess face image for FaceNet
processed_face = FaceNet_util_preprocess_image(face_img)
processed_faces.append(processed_face)
🤖 Prompt for AI Agents
In backend/app/models/FaceDetector.py around lines 101 to 116, the code extracts
a padded face crop then immediately preprocesses it, but zero-size or invalid
crops will cause cv2.resize (inside FaceNet_util_preprocess_image) to throw; add
a guard after extracting face_img to check its height and width are > 0 (e.g.,
face_img.size or face_img.shape) and skip this detection if not valid (append no
processed face / continue), optionally log a debug/warning with bbox info; only
call FaceNet_util_preprocess_image and append to processed_faces when the crop
is non-empty and valid.

Comment on lines 70 to 84
def FaceNet_util_normalize_embedding(embedding):
"""
Normalize a face embedding to unit length.

This function normalizes the face embedding vector to have unit length,
which is essential for accurate cosine similarity computation.

Args:
embedding: Face embedding vector

Returns:
Normalized embedding vector with unit length
"""
return embedding / np.linalg.norm(embedding)

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid division by zero during embedding normalization.

Zero vectors can occur; add epsilon.

-def FaceNet_util_normalize_embedding(embedding):
+def FaceNet_util_normalize_embedding(embedding):
@@
-    return embedding / np.linalg.norm(embedding)
+    norm = np.linalg.norm(embedding)
+    if norm == 0:
+        return embedding
+    return embedding / norm
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def FaceNet_util_normalize_embedding(embedding):
"""
Normalize a face embedding to unit length.
This function normalizes the face embedding vector to have unit length,
which is essential for accurate cosine similarity computation.
Args:
embedding: Face embedding vector
Returns:
Normalized embedding vector with unit length
"""
return embedding / np.linalg.norm(embedding)
def FaceNet_util_normalize_embedding(embedding):
"""
Normalize a face embedding to unit length.
This function normalizes the face embedding vector to have unit length,
which is essential for accurate cosine similarity computation.
Args:
embedding: Face embedding vector
Returns:
Normalized embedding vector with unit length
"""
norm = np.linalg.norm(embedding)
if norm == 0:
return embedding
return embedding / norm
🤖 Prompt for AI Agents
In backend/app/utils/FaceNet.py around lines 70 to 84, the normalization divides
by np.linalg.norm(embedding) which can be zero for zero vectors; change to
compute the norm, replace it with a small epsilon when it is zero (e.g. norm =
np.linalg.norm(embedding); norm = max(norm, 1e-10) or use np.maximum for arrays)
and then return embedding / norm so division-by-zero is avoided.

Comment on lines +212 to 215
# Compute IoU (handle division by zero)
iou = intersection_area / union_area

return iou
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard IoU division by zero.

Degenerate boxes can make union_area zero, yielding inf/NaN and breaking NMS.

-    iou = intersection_area / union_area
+    iou = np.divide(
+        intersection_area,
+        union_area,
+        out=np.zeros_like(union_area, dtype=np.float32),
+        where=union_area > 0,
+    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Compute IoU (handle division by zero)
iou = intersection_area / union_area
return iou
# Compute IoU (handle division by zero)
iou = np.divide(
intersection_area,
union_area,
out=np.zeros_like(union_area, dtype=np.float32),
where=union_area > 0,
)
return iou
🤖 Prompt for AI Agents
In backend/app/utils/YOLO.py around lines 212 to 215, the IoU computation
divides by union_area which can be zero for degenerate boxes; change the
calculation to guard against division by zero by checking if union_area is zero
(or below a small epsilon) and return 0.0 in that case, otherwise compute
intersection_area / union_area; ensure you use a small epsilon (e.g., 1e-6) or
an explicit conditional to avoid inf/NaN and keep the return type as a float.

Comment on lines 121 to 126
CORSMiddleware,
allow_origins=["*"], # Allows all origins
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
allow_methods=["*"], # Allow all HTTP methods
allow_headers=["*"], # Allow all headers
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

CORS: credentials with wildcard origins is insecure.

Browsers reject this per spec; also unsafe. Either disable credentials with "*" or specify explicit origins.

Quick safe fix:

-    allow_origins=["*"],  # Allows all origins
-    allow_credentials=True,
+    allow_origins=["*"],  # Allows all origins
+    allow_credentials=False,  # credentials not allowed with wildcard origins

Optionally, read allowed origins from env and keep credentials enabled.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CORSMiddleware,
allow_origins=["*"], # Allows all origins
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
allow_methods=["*"], # Allow all HTTP methods
allow_headers=["*"], # Allow all headers
)
CORSMiddleware,
allow_origins=["*"], # Allows all origins
allow_credentials=False, # credentials not allowed with wildcard origins
allow_methods=["*"], # Allow all HTTP methods
allow_headers=["*"], # Allow all headers
)
🤖 Prompt for AI Agents
In backend/main.py around lines 121-126 the CORS config uses allow_origins=["*"]
while allow_credentials=True which is invalid/insecure; fix by either (A) set
allow_credentials=False when using "*" OR (B) replace the wildcard with an
explicit list of allowed origins (e.g., read a comma-separated ALLOWED_ORIGINS
from env, split into a list) and keep allow_credentials=True; implement the
chosen option and ensure the middleware uses that boolean/list accordingly.

Comment on lines 23 to +24
import { BrowserRouter } from 'react-router';
import { useSelector } from 'react-redux';
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Quick verification script: ensure no other wrong BrowserRouter imports.

Run to find mis-imports across the repo.

Also applies to: 32-32


🏁 Script executed:

#!/bin/bash
# Find BrowserRouter imported from 'react-router' (should be none)
rg -nP "import\\s*\\{[^}]*BrowserRouter[^}]*\\}\\s*from\\s*['\"]react-router['\"]" -C2

# (Optional) Normalize Routes/Route to react-router-dom for consistency
rg -nP "import\\s*\\{[^}]*\\b(Routes|Route)\\b[^}]*\\}\\s*from\\s*['\"]react-router['\"]" -C2

Length of output: 1237


Correct import targets from react-router to react-router-dom
Update all instances of:

  • import { BrowserRouter } from 'react-router'; in frontend/src/App.tsx (line 23) and frontend/src/pages/__tests__/allPages.test.tsx (line 10)
  • import { Routes, Route } from 'react-router'; in frontend/src/routes/AppRoutes.tsx (line 24)
    to import from 'react-router-dom'.
🤖 Prompt for AI Agents
In frontend/src/App.tsx around lines 23-24, the BrowserRouter import is
incorrectly sourced from 'react-router'; change it to import BrowserRouter from
'react-router-dom'. Also update the same incorrect BrowserRouter import in
frontend/src/pages/__tests__/allPages.test.tsx (line ~10) and change the Routes
and Route import in frontend/src/routes/AppRoutes.tsx (around line 24) to import
{ Routes, Route } from 'react-router-dom'. Ensure the named imports match their
exported names from 'react-router-dom' and run tests/build to verify no
remaining import errors.


// React imports
import React from 'react';
import { BrowserRouter } from 'react-router';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix BrowserRouter import source.

BrowserRouter is exported from react-router-dom, not react-router.

-import { BrowserRouter } from 'react-router';
+import { BrowserRouter } from 'react-router-dom';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { BrowserRouter } from 'react-router';
// frontend/src/App.tsx
import { BrowserRouter } from 'react-router-dom';
🤖 Prompt for AI Agents
In frontend/src/App.tsx around line 23, the code imports BrowserRouter from
'react-router' but BrowserRouter is exported from 'react-router-dom'; change the
import source to 'react-router-dom' (i.e., replace the current import statement
to import BrowserRouter from 'react-router-dom') so the component resolves
correctly at runtime.

Comment on lines +24 to +25
import { useSelector } from 'react-redux';

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove duplicate useSelector import.

Redefinition causes a compile-time error (noRedeclare).

 import { useSelector } from 'react-redux';
@@
-import { useSelector } from 'react-redux';

Also applies to: 32-32

🤖 Prompt for AI Agents
In frontend/src/App.tsx around lines 24-25 (and also line 32), there's a
duplicate import of useSelector from 'react-redux' causing a noRedeclare compile
error; remove the redundant import so useSelector is imported only once (keep
the correctly placed import and delete the other duplicate line) and ensure any
references to useSelector still resolve to the remaining import.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants