Skip to content

Conversation

@rohan-pandeyy
Copy link
Contributor

@rohan-pandeyy rohan-pandeyy commented Oct 2, 2025

Also, Pictopy now retrieves image metadata! :)

Fixes: #495

This PR is divided into 3 major parts:

  • Image metadata retrieval - fetch and organize image metadata needed for chronological grouping.
  • Chronological gallery view - update the gallery to display images in descending date order (newest first).
  • Timeline scrollbar integration - replace the traditional scrollbar with a custom timeline scrollbar for smoother navigation between months/years.

1. Image metadata retrieval:

Status: Done ✅
image

2. Chronological gallery view

Status: Done ✅
image
image

3. Timeline scrollbar integration

Status: Done ✅

Recording.2025-09-14.195728.mp4

Summary by CodeRabbit

  • New Features

    • Chronological gallery (year/month groups, sticky headers) and interactive vertical timeline scrollbar with markers and drag/hover tooltips.
    • Backend metadata extraction and parsing; structured metadata surfaced to UI and APIs.
    • Utilities for grouping images by year/month and computing markers.
  • Bug Fixes

    • Improved date/location display; clickable map link when coordinates available; metadata parsing fixes.
  • Style

    • Scrollbar-hide and no-select utilities; adjusted page spacing.
  • Documentation

    • OpenAPI schema updated: image metadata is now an object.
  • Tests

    • ResizeObserver mock and test data updated for structured metadata.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 2, 2025

Walkthrough

Centralizes SQLite connections and normalizes image metadata as structured mappings across backend DB, utils, routes, and schemas; frontend adds chronological gallery grouping, a timeline scrollbar with month markers and hooks, UI/CSS tweaks, and a ResizeObserver test polyfill.

Changes

Cohort / File(s) Summary of changes
Backend — images DB & utils
backend/app/database/images.py, backend/app/utils/images.py
Centralized _connect() enabling FK enforcement; added image_util_extract_metadata and image_util_parse_metadata; widened ImageRecord.metadata to `Mapping
Backend — face & cluster flows
backend/app/database/face_clusters.py, backend/app/database/faces.py, backend/app/routes/face_clusters.py, backend/app/schemas/face_clusters.py
Post-fetch metadata normalization via image_util_parse_metadata; route/schema types changed from strDict[str, Any].
Backend — routes & OpenAPI
backend/app/routes/images.py, docs/backend/backend_python/openapi.json
ImageData.metadata changed to structured MetadataModel; routes call image_util_parse_metadata when constructing responses; OpenAPI updated with MetadataModel.
Backend — tests
backend/tests/test_face_clusters.py
Test fixtures updated to use structured dict metadata instead of string-encoded metadata.
Frontend — types
frontend/src/types/Media.ts
Added ImageMetadata interface; Image.metadata changed from stringImageMetadata.
Frontend — date & index utilities
frontend/src/utils/dateUtils.ts
Added groupImagesByYearMonthFromMetadata and createImageIndexMap to group by metadata.date_created and build id→index map.
Frontend — timeline utilities/hooks
frontend/src/utils/timelineUtils.ts
New hooks/utilities: useScroll, useWheel, getMarkerForScrollPosition, clamp, calculateClampedTooltipTop, and TooltipState type.
Frontend — Chronological gallery
frontend/src/components/Media/ChronologicalGallery.tsx
New ChronologicalGallery component: groups images by year/month, sticky headers, computes month offset markers, observes resize/scroll, exposes MonthMarker markers via callback.
Frontend — Timeline scrollbar
frontend/src/components/Timeline/TimelineScrollbar.tsx
New TimelineScrollbar component: vertical track with markers, drag/click/hover interactions, multiple tooltips, scroll sync and measurement logic.
Frontend — pages integration
frontend/src/pages/Home/Home.tsx, frontend/src/pages/AITagging/AITagging.tsx
Replaced flat grid with ChronologicalGallery; added scrollable container ref and monthMarkers state; conditionally render TimelineScrollbar.
Frontend — media info & UI tweaks
frontend/src/components/Media/MediaInfoPanel.tsx, frontend/src/App.css, frontend/src/layout/layout.tsx
MediaInfoPanel reads structured metadata for date/location with fallbacks and map-link handling; added .hide-scrollbar and .no-select CSS utilities; adjusted wrapper spacing (p-4m-4).
Frontend — test setup
frontend/jest.setup.ts
Added minimal global ResizeObserver mock for tests.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Page as Home / AI Tagging
  participant Chrono as ChronologicalGallery
  participant Utils as dateUtils/timelineUtils
  participant Scroll as ScrollContainer
  participant TL as TimelineScrollbar

  User->>Page: Open gallery page
  Page->>Chrono: render(images, scrollContainerRef, onMonthOffsetsChange)
  Chrono->>Utils: group images by year/month
  Chrono->>Scroll: observe headers & measure offsets
  Chrono-->>Page: onMonthOffsetsChange(markers)
  Page->>TL: render(monthMarkers, scrollableRef)
  User->>TL: hover / click / drag
  TL->>Scroll: scrollTo(offset)
  Scroll-->>Chrono: scroll/resize events
  Chrono-->>Page: recompute markers
Loading
sequenceDiagram
  autonumber
  participant Client
  participant Route as images.py (route)
  participant DB as images.py (db)
  participant Util as app.utils.images

  Client->>Route: request insert/update images
  Route->>DB: db_bulk_insert_images(records)
  DB->>Util: image_util_parse_metadata / image_util_extract_metadata
  Util-->>DB: metadata dict / JSON string
  DB->>DB: INSERT ... ON CONFLICT ... (store JSON)
  DB-->>Route: rows with parsed metadata
  Route-->>Client: response with structured metadata
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

backend

Poem

I hop through months with tiny feet,
Markers gleam where timelines meet.
Sticky headers, thumb and drag,
I nudge the scroll and wag my flag.
Metadata hums beneath my beat—🐇

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning The pull request also includes extensive backend and API modifications—such as changes to database schemas and queries for face clusters, updates to routing and OpenAPI schemas for face cluster endpoints, and test adjustments—which are unrelated to the UI-focused scrollbar timeline feature defined in issue #495. Extract the backend schema, face cluster, and related API changes into a separate pull request so that this change remains focused on the frontend timeline scrollbar feature.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title clearly and succinctly highlights the primary enhancement—introducing a custom scrollbar timeline with month‐year markers for the Home and AI Tagging pages—and directly relates to the main user‐facing change in this pull request.
Linked Issues Check ✅ Passed This pull request fully implements all key objectives from issue #495 by providing a custom vertical scrollbar segmented by month and year, a draggable thumb with active month-year tooltips during scroll and drag, hover previews suppressed during thumb interaction, clickable month markers that navigate to the correct gallery section, and dynamic recomputation of markers when the gallery changes.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • 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

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

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/pages/Home/Home.tsx (1)

33-52: Loader never hides when search is toggled mid-fetch.

showLoader fires before isSearchActive flips, but once the guard short-circuits the effect we never dispatch hideLoader, leaving the overlay stuck until search is deactivated. Please move the hide/error handling outside the guard (or otherwise ensure we always clear the loader when the query resolves).

🧹 Nitpick comments (1)
frontend/src/components/Media/MediaInfoPanel.tsx (1)

83-87: Consider extracting specific location fields from metadata.

The current logic handles both string and object metadata types, but using JSON.stringify for object metadata may not provide the best user experience. If ImageMetadata includes specific location fields (e.g., file_location), consider extracting and displaying those directly for better readability.

Example refactor to display file_location if available:

-{typeof currentImage?.metadata === 'string'
-  ? currentImage.metadata
-  : currentImage?.metadata
-    ? JSON.stringify(currentImage.metadata)
-    : 'No location data'}
+{typeof currentImage?.metadata === 'string'
+  ? currentImage.metadata
+  : currentImage?.metadata?.file_location
+    ? currentImage.metadata.file_location
+    : currentImage?.metadata
+      ? JSON.stringify(currentImage.metadata)
+      : 'No location data'}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3363268 and 4ed327b.

📒 Files selected for processing (14)
  • backend/app/database/images.py (12 hunks)
  • backend/app/routes/images.py (2 hunks)
  • docs/backend/backend_python/openapi.json (1 hunks)
  • frontend/jest.setup.ts (1 hunks)
  • frontend/src/App.css (1 hunks)
  • frontend/src/components/Media/ChronologicalGallery.tsx (1 hunks)
  • frontend/src/components/Media/MediaInfoPanel.tsx (1 hunks)
  • frontend/src/components/Timeline/TimelineScrollbar.tsx (1 hunks)
  • frontend/src/layout/layout.tsx (1 hunks)
  • frontend/src/pages/AITagging/AITagging.tsx (3 hunks)
  • frontend/src/pages/Home/Home.tsx (3 hunks)
  • frontend/src/types/Media.ts (1 hunks)
  • frontend/src/utils/dateUtils.ts (2 hunks)
  • frontend/src/utils/timelineUtils.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
frontend/src/pages/AITagging/AITagging.tsx (3)
frontend/src/features/imageSelectors.ts (2)
  • selectIsImageViewOpen (24-27)
  • selectTaggedImages (39-41)
frontend/src/components/Media/ChronologicalGallery.tsx (2)
  • MonthMarker (11-15)
  • ChronologicalGallery (26-190)
frontend/src/components/Timeline/TimelineScrollbar.tsx (1)
  • TimelineScrollbar (39-421)
frontend/src/pages/Home/Home.tsx (5)
frontend/src/components/Media/ChronologicalGallery.tsx (2)
  • MonthMarker (11-15)
  • ChronologicalGallery (26-190)
frontend/src/hooks/useQueryExtension.ts (1)
  • usePictoQuery (80-108)
frontend/src/api/api-functions/images.ts (1)
  • fetchAllImages (5-10)
frontend/src/app/store.ts (1)
  • RootState (22-22)
frontend/src/components/Timeline/TimelineScrollbar.tsx (1)
  • TimelineScrollbar (39-421)
frontend/src/utils/dateUtils.ts (1)
frontend/src/types/Media.ts (1)
  • Image (11-20)
frontend/src/components/Timeline/TimelineScrollbar.tsx (2)
frontend/src/components/Media/ChronologicalGallery.tsx (1)
  • MonthMarker (11-15)
frontend/src/utils/timelineUtils.ts (4)
  • TooltipState (4-8)
  • useScroll (10-32)
  • useWheel (34-53)
  • getMarkerForScrollPosition (55-67)
frontend/src/components/Media/ChronologicalGallery.tsx (4)
frontend/src/types/Media.ts (1)
  • Image (11-20)
frontend/src/features/imageSelectors.ts (1)
  • selectImages (5-7)
frontend/src/utils/dateUtils.ts (2)
  • groupImagesByYearMonthFromMetadata (31-55)
  • createImageIndexMap (58-66)
frontend/src/components/Media/ImageCard.tsx (1)
  • ImageCard (20-111)
frontend/src/utils/timelineUtils.ts (1)
frontend/src/components/Media/ChronologicalGallery.tsx (1)
  • MonthMarker (11-15)
backend/app/database/images.py (1)
frontend/src/types/Media.ts (1)
  • Image (11-20)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Backend Tests
🔇 Additional comments (13)
frontend/src/layout/layout.tsx (1)

12-12: Spacing change is safe – verified that Home and AITagging include their own padding/margin (pr-6, mt-6, mb-8, etc.) and TimelineScrollbar uses absolute positioning without relying on container padding. No further adjustments needed.

frontend/jest.setup.ts (1)

11-18: LGTM! ResizeObserver mock is appropriate for tests.

The minimal mock with empty methods is sufficient for components that instantiate ResizeObserver without requiring actual resize observations during tests. This aligns with the existing polyfill pattern for TextEncoder/TextDecoder.

frontend/src/App.css (2)

200-207: LGTM! Standard cross-browser scrollbar hiding.

The .hide-scrollbar utility correctly implements cross-browser scrollbar hiding using WebKit-specific pseudo-element, IE/Edge -ms-overflow-style, and Firefox scrollbar-width properties.


209-214: LGTM! Standard cross-browser selection disabling.

The .no-select utility correctly implements cross-browser text selection disabling with vendor-prefixed and standard properties.

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

1933-1936: LGTM! Schema aligns with backend metadata type change.

The OpenAPI schema correctly reflects the backend change where ImageData.metadata is now a structured object (Dict[str, Any]) instead of a string. This aligns with the broader PR changes to support structured image metadata.

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

2-2: LGTM! Metadata type change aligns with backend updates.

The type change from str to Dict[str, Any] for ImageData.metadata correctly reflects the backend database layer changes where metadata is now stored and retrieved as structured JSON. This ensures consistent metadata representation across the API layer.

Also applies to: 16-16

frontend/src/types/Media.ts (2)

1-9: LGTM! ImageMetadata interface provides clear metadata structure.

The ImageMetadata interface defines a well-structured representation of image metadata with appropriate field types. The date_created field as string is suitable for ISO date strings used in chronological grouping throughout the application.


17-17: LGTM! Type change enables structured metadata access.

Changing metadata from string to ImageMetadata enables type-safe access to metadata fields throughout the frontend, particularly for the new chronological grouping functionality in ChronologicalGallery and date utilities.

frontend/src/utils/dateUtils.ts (2)

30-55: LGTM! Robust date grouping with proper error handling.

The groupImagesByYearMonthFromMetadata function correctly handles:

  • Missing metadata or date_created fields (early returns)
  • Invalid date strings (NaN check)
  • Nested group initialization
  • Month padding for consistent keys

The logic is sound and appropriate for chronological gallery grouping.


57-66: LGTM! Efficient index lookup with Map.

The createImageIndexMap function provides O(1) lookup performance for mapping image IDs to their indices, which is optimal for the gallery rendering use case where quick index lookups are needed.

frontend/src/pages/AITagging/AITagging.tsx (3)

24-25: LGTM! State additions support timeline scrollbar integration.

The scrollableRef and monthMarkers state are correctly initialized to support the timeline scrollbar feature:

  • scrollableRef provides scroll control for TimelineScrollbar
  • monthMarkers tracks month positions from ChronologicalGallery's onMonthOffsetsChange callback

50-75: LGTM! Layout correctly integrates chronological gallery with timeline scrollbar.

The restructured layout properly implements:

  • Scrollable container with hidden scrollbar (.hide-scrollbar)
  • scrollableRef attachment for scroll control
  • ChronologicalGallery integration with onMonthOffsetsChange callback
  • Retained FaceCollections and MediaView components in the flow

The structure supports the absolutely positioned TimelineScrollbar rendered below.


76-82: LGTM! TimelineScrollbar correctly integrated with conditional rendering.

The TimelineScrollbar integration properly:

  • Conditionally renders only when month markers exist
  • Receives correct props (scrollableRef, monthMarkers)
  • Uses absolute positioning to overlay on the right edge
  • Maintains consistent integration pattern with other pages

@github-actions github-actions bot added UI enhancement New feature or request frontend labels Oct 2, 2025
…cation support

Moved image metadata extraction logic to a utility module and standardized metadata parsing across backend endpoints. Updated backend and frontend types and models to support location, latitude, and longitude in image metadata. Improved frontend display of image location and date.
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 78b31f3 and f30bc22.

📒 Files selected for processing (11)
  • backend/app/database/face_clusters.py (2 hunks)
  • backend/app/database/faces.py (2 hunks)
  • backend/app/database/images.py (12 hunks)
  • backend/app/routes/face_clusters.py (2 hunks)
  • backend/app/routes/images.py (2 hunks)
  • backend/app/schemas/face_clusters.py (2 hunks)
  • backend/app/utils/images.py (3 hunks)
  • docs/backend/backend_python/openapi.json (3 hunks)
  • frontend/src/components/Media/MediaInfoPanel.tsx (2 hunks)
  • frontend/src/pages/Home/Home.tsx (3 hunks)
  • frontend/src/types/Media.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • frontend/src/pages/Home/Home.tsx
  • backend/app/routes/images.py
  • frontend/src/components/Media/MediaInfoPanel.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
backend/app/database/faces.py (1)
backend/app/utils/images.py (1)
  • image_util_parse_metadata (505-522)
backend/app/database/face_clusters.py (1)
backend/app/utils/images.py (1)
  • image_util_parse_metadata (505-522)
backend/app/database/images.py (1)
backend/app/utils/images.py (1)
  • image_util_parse_metadata (505-522)
backend/app/utils/images.py (1)
frontend/src/types/Media.ts (1)
  • Image (12-21)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Backend Tests

Copy link
Contributor

@rahulharpal1603 rahulharpal1603 left a comment

Choose a reason for hiding this comment

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

Hi @rohan-pandeyy

I wanted to merge this today itself, but there are some minor suggestions, please fix those too.

Moved GPS coordinate parsing logic into a dedicated extract_gps_coordinates() helper function for better readability and maintainability. Removed reverse geocoding using geopy as per review feedback to prevent external API overhead. Updated Python and TypeScript metadata models to only include latitude and longitude. Restored original dependencies (httptools, httpx, humanfriendly) and removed geopy from requirements.txt. Updated MediaInfoPanel.tsx to display latitude and longitude directly with Google Maps link integration via Tauri Opener.
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 (3)
frontend/src/components/Media/MediaInfoPanel.tsx (1)

107-119: Consider adding explicit type="button" attribute.

The clickable location button should explicitly specify type="button" to prevent unintended form submission behavior if this component is ever nested within a form context.

             <button
+              type="button"
               onClick={handleLocationClick}
               className="w-full cursor-pointer truncate text-left font-medium text-white hover:underline"
               title={`Lat: ${currentImage.metadata.latitude}, Lon: ${currentImage.metadata.longitude}`}
             >
backend/app/utils/images.py (2)

335-348: Consider extracting nested function for clarity.

The _convert_to_degrees function is nested inside the try block. While functional, extracting it to module level or defining it before the try block would improve readability and testability.

Consider extracting the function:

def _convert_dms_to_degrees(value):
    """Convert GPS DMS (degrees, minutes, seconds) to decimal degrees."""
    if hasattr(value[0], "numerator"):
        d = float(value[0].numerator) / float(value[0].denominator)
    else:
        d = float(value[0])
    if hasattr(value[1], "numerator"):
        m = float(value[1].numerator) / float(value[1].denominator)
    else:
        m = float(value[1])
    if hasattr(value[2], "numerator"):
        s = float(value[2].numerator) / float(value[2].denominator)
    else:
        s = float(value[2])
    return d + (m / 60.0) + (s / 3600.0)

def _extract_gps_coordinates(exif_data: Any) -> Tuple[float | None, float | None]:
    # ... existing code ...
    latitude = _convert_dms_to_degrees(lat_dms)
    # ... etc ...

384-488: LGTM! Robust metadata extraction with good error handling.

The metadata extraction implementation is solid with:

  • Proper fallbacks for missing files
  • Defensive EXIF parsing with multiple exception handlers
  • Consistent return structure
  • Safe date parsing with fallback to mtime

Consider using the logging module instead of print statements for production-ready error reporting.

Example logging setup at module level:

import logging

logger = logging.getLogger(__name__)

Then replace print statements:

-            print(f"[ERROR] Pillow could not open image {image_path}: {e}")
+            logger.error(f"Pillow could not open image {image_path}: {e}")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28025ac and 00f406d.

📒 Files selected for processing (3)
  • backend/app/utils/images.py (3 hunks)
  • frontend/src/components/Media/MediaInfoPanel.tsx (3 hunks)
  • frontend/src/types/Media.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/types/Media.ts
🧰 Additional context used
🧬 Code graph analysis (1)
backend/app/utils/images.py (1)
frontend/src/types/Media.ts (1)
  • Image (13-22)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Tauri Build Check (windows-latest)
🔇 Additional comments (7)
frontend/src/components/Media/MediaInfoPanel.tsx (2)

29-40: LGTM! Clean conditional date rendering.

The date display logic properly handles missing metadata with a user-friendly fallback message, and the formatting is appropriate.


48-58: Verify plugin choice against past review comment.

A past review comment specifically requested using @tauri-apps/plugin-opener to open the Google Maps link, but the implementation uses @tauri-apps/plugin-shell. While plugin-shell's open function may work for URLs, plugin-opener is specifically designed for opening URLs/files with default applications.

Please confirm whether using plugin-shell instead of plugin-opener is intentional. If not, consider updating the import and usage to align with the previous review guidance:

-import { open } from '@tauri-apps/plugin-shell';
+import { open } from '@tauri-apps/plugin-opener';

Based on past review comments.

backend/app/utils/images.py (5)

3-6: LGTM! Imports support new metadata functionality.

The new imports (datetime, json, typing, ExifTags) are appropriate for the metadata extraction features added in this file.


157-165: LGTM! Metadata extraction properly integrated.

The metadata extraction is correctly integrated into the image processing flow, with metadata serialized as JSON for database storage.


328-328: LGTM! GPS tag hardcoded as requested.

The GPS_INFO_TAG is correctly hardcoded to 34853, addressing the previous review comment.

Based on past review comments


490-507: LGTM! Defensive metadata parsing.

The function safely handles various input formats (None, JSON string, dict) with appropriate fallbacks. The read-only Mapping return type is appropriate.


318-318: Verify Python version compatibility for union type hints
The float | None on line 318 requires Python 3.10+. Ensure python_requires=">=3.10" is declared in pyproject.toml/setup.cfg or replace with Optional[float] for older Python versions.

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 (4)
backend/app/utils/images.py (4)

321-335: Consider simplifying the DMS conversion logic.

The current implementation correctly converts GPS coordinates from DMS to decimal degrees. However, the repeated hasattr checks and manual float conversions can be simplified.

Apply this diff to simplify:

-def _convert_to_degrees(value):
-    """Converts a GPS coordinate value from DMS to decimal degrees."""
-    if hasattr(value[0], "numerator"):
-        d = float(value[0].numerator) / float(value[0].denominator)
-    else:
-        d = float(value[0])
-    if hasattr(value[1], "numerator"):
-        m = float(value[1].numerator) / float(value[1].denominator)
-    else:
-        m = float(value[1])
-    if hasattr(value[2], "numerator"):
-        s = float(value[2].numerator) / float(value[2].denominator)
-    else:
-        s = float(value[2])
-    return d + (m / 60.0) + (s / 3600.0)
+def _convert_to_degrees(value):
+    """Converts a GPS coordinate value from DMS to decimal degrees."""
+    def to_float(v):
+        return float(v.numerator) / float(v.denominator) if hasattr(v, "numerator") else float(v)
+    
+    d, m, s = (to_float(v) for v in value[:3])
+    return d + (m / 60.0) + (s / 3600.0)

348-348: Consider moving GPS_INFO_TAG to module level.

Based on past review comments, GPS_INFO_TAG was requested to be hardcoded. Consider defining it as a module-level constant rather than inside the function for better maintainability and reusability.

Add this constant at the top of the file after the imports:

# GPS EXIF tag constant
GPS_INFO_TAG = 34853

Then update the function:

 def _extract_gps_coordinates(exif_data: Any) -> Tuple[float | None, float | None]:
     """
     Extracts GPS coordinates from EXIF data.
     Args:
         exif_data: The EXIF data from an image (PIL.Image.Exif object).
     Returns:
         A tuple containing latitude and longitude, or (None, None) if not found.
     """
     latitude = None
     longitude = None
-    GPS_INFO_TAG = 34853
 
     if exif_data:

381-383: Remove redundant None assignments.

Lines 382-383 set latitude and longitude to None, but they are already initialized to None at lines 346-347. This is redundant.

Apply this diff:

         else:
-            latitude = None
-            longitude = None
+            pass  # gps_data is not a dict, keep None values

159-161: Consider combining thumbnail generation and metadata extraction.

Currently, each image is opened twice: once for thumbnail generation (line 159) and once for metadata extraction (line 160). This could be optimized by extracting metadata during thumbnail generation, reducing I/O operations.

Here's how you could refactor:

  1. Modify image_util_generate_thumbnail to return both success status and metadata:
def image_util_generate_thumbnail(
    image_path: str, thumbnail_path: str, size: Tuple[int, int] = (600, 600)
) -> Tuple[bool, dict]:
    """Generate thumbnail and extract metadata in a single pass."""
    metadata = {}
    try:
        with Image.open(image_path) as img:
            # Extract metadata first while image is open
            width, height = img.size
            mime_type = Image.MIME.get(img.format, "unknown")
            
            # Extract EXIF and GPS data
            # ... (metadata extraction logic) ...
            
            # Then generate thumbnail
            img.thumbnail(size)
            if img.mode in ("RGBA", "P"):
                img = img.convert("RGB")
            img.save(thumbnail_path, "JPEG")
            
        return True, metadata
    except Exception as e:
        logger.error(f"Error processing {image_path}: {e}")
        return False, {}
  1. Update the caller:
-        if image_util_generate_thumbnail(image_path, thumbnail_path):
-            metadata = image_util_extract_metadata(image_path)
+        success, metadata = image_util_generate_thumbnail(image_path, thumbnail_path)
+        if success:
             logger.debug(f"Extracted metadata for {image_path}: {metadata}")

This reduces file I/O and improves performance, especially when processing large image galleries.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00f406d and 3c3df46.

📒 Files selected for processing (2)
  • backend/app/utils/images.py (11 hunks)
  • frontend/src/components/Media/MediaInfoPanel.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/components/Media/MediaInfoPanel.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
backend/app/utils/images.py (2)
frontend/src/types/Media.ts (1)
  • Image (13-22)
backend/app/database/images.py (1)
  • db_delete_images_by_ids (345-377)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Backend Tests
🔇 Additional comments (5)
backend/app/utils/images.py (5)

3-22: LGTM! Proper imports and logger setup.

The new imports support metadata extraction functionality, and the module-level logger setup follows Python best practices.


388-491: Excellent defensive error handling for metadata extraction.

The implementation demonstrates strong defensive programming with multiple fallback layers:

  1. File existence check with minimal metadata fallback
  2. Robust EXIF extraction with support for both getexif() and legacy _getexif()
  3. Safe date parsing with fallback to file modification time
  4. GPS coordinate extraction with graceful degradation
  5. Per-layer exception handling that maintains partial metadata

This approach ensures the image processing pipeline continues even when metadata extraction partially fails, which is appropriate for real-world image processing scenarios.


494-511: LGTM! Safe metadata parsing with proper type handling.

The function correctly handles various input types (None, JSON string, dict) with appropriate fallbacks. Using Mapping[str, Any] as the return type is a good choice to indicate the result should be treated as read-only.


160-161: Metadata extraction and persistence correctly integrated.

The metadata extraction is properly integrated into the image processing flow, with appropriate logging and JSON serialization for database storage.

Also applies to: 168-168


338-338: Verify Python version for union type hints
Union syntax (float | None) requires Python 3.10+. Confirm the project targets Python 3.10+; if not, use Tuple[Optional[float], Optional[float]].

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

🧹 Nitpick comments (1)
backend/app/utils/images.py (1)

339-384: GPS extraction logic is robust.

The function correctly handles GPS coordinate extraction with proper byte decoding, direction references, and comprehensive error handling. The broad exception catching is appropriate given the variability in EXIF data formats.

Optional: Remove unnecessary else block.

The empty else: pass block at line 382 can be removed for cleaner code:

             except (
                 KeyError,
                 IndexError,
                 TypeError,
                 ValueError,
                 AttributeError,
             ):
                 latitude = None
                 longitude = None
-        else:
-            pass
 
     return latitude, longitude
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c3df46 and 58e8a6c.

📒 Files selected for processing (1)
  • backend/app/utils/images.py (11 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/app/utils/images.py (2)
frontend/src/types/Media.ts (1)
  • Image (13-22)
backend/app/database/images.py (1)
  • db_delete_images_by_ids (345-377)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Backend Tests
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Tauri Build Check (windows-latest)
🔇 Additional comments (5)
backend/app/utils/images.py (5)

23-26: LGTM! Constants and logger properly initialized.

The GPS_INFO_TAG is correctly hardcoded as requested in previous reviews, and the logger follows standard Python logging practices.


67-67: LGTM! Proper logging migration.

Replacing print statements with appropriate logger calls (error, info, debug) improves observability and follows best practices.

Also applies to: 80-80, 97-97, 119-119, 209-209, 229-229, 253-253, 255-255, 259-259


164-172: LGTM! Metadata extraction integrated correctly.

The metadata extraction, logging, and JSON serialization for database storage are implemented properly.


387-491: LGTM! Robust metadata extraction with multiple fallback layers.

The function demonstrates excellent defensive programming with:

  • Early return for non-existent files
  • Safe EXIF extraction with fallback to basic file stats
  • Date parsing from EXIF DateTimeOriginal with fallback to file mtime
  • Conditional GPS coordinate inclusion
  • Multiple exception handlers ensuring metadata extraction failures don't halt image processing

The debug log message now correctly references the function name (addressing previous review feedback).


493-510: LGTM! Robust metadata parsing with safe defaults.

The function correctly handles all expected input types (None, JSON string, dict) with appropriate error handling and returns a safe empty dict default.

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 (6)
frontend/src/components/Media/MediaInfoPanel.tsx (2)

49-59: Consider user-facing error feedback.

The implementation correctly addresses the past review comments by using Tauri's opener plugin and opening Google Maps with the suggested URL format. However, the error handling only logs to the console.

Consider showing a user-facing notification when the map fails to open:

       try {
         await open(url);
       } catch (error) {
         console.error('Failed to open map URL:', error);
+        // Display a toast or notification to the user
+        // e.g., toast.error('Failed to open map. Please try again.');
       }

108-121: Add aria-label for improved accessibility.

The location display correctly addresses the past review comments by showing coordinates and making them clickable. The coordinate formatting and visual feedback are well implemented.

Consider adding an aria-label to improve screen reader accessibility:

             <button
               type="button"
               onClick={handleLocationClick}
+              aria-label={`Open location on Google Maps: Latitude ${currentImage.metadata.latitude}, Longitude ${currentImage.metadata.longitude}`}
               className="flex w-full cursor-pointer items-center truncate text-left font-medium text-white hover:underline"
               title={`Lat: ${currentImage.metadata.latitude}, Lon: ${currentImage.metadata.longitude}`}
             >
frontend/src/components/Timeline/TimelineScrollbar.tsx (4)

24-24: Consider using standard abbreviation for September.

"Sept" is less common than "Sep" for September. While not incorrect, using "Sep" would align with more widely recognized conventions.

Apply this diff to use the standard abbreviation:

-  September: 'Sept',
+  September: 'Sep',

244-252: Consider more specific error handling.

The try-catch in the mousemove handler catches all errors generically. While this prevents crashes, it may mask unexpected issues. Consider being more specific about what errors you expect (e.g., DOM reference errors) or add context to the error log.

For example:

 } catch (error) {
-  console.error('Error during drag:', error);
+  console.error('Error during drag (possible DOM reference issue):', error);
   handleMouseUp();
 }

341-359: Consider using a stable key for marker elements.

Using array index as the key can cause issues if markers are reordered or modified. Consider using a combination of month, year, and offset as a unique identifier.

For example:

-{markerPositions.map((marker, index) => {
+{markerPositions.map((marker) => {
   return (
     <div
-      key={index}
+      key={`${marker.year}-${marker.month}-${marker.offset}`}
       className="absolute left-1/2 h-0.5 w-full bg-gray-400 shadow-md"

315-408: Consider adding keyboard navigation and ARIA labels.

The timeline scrollbar currently lacks keyboard accessibility and screen reader support. Consider adding:

  • Arrow key navigation to move between month markers
  • Enter/Space to jump to a marker
  • ARIA labels describing the timeline and current position
  • role="slider" or role="scrollbar" with appropriate ARIA attributes

This would improve usability for keyboard and screen reader users.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 58e8a6c and 9f7ee2c.

📒 Files selected for processing (4)
  • frontend/src/App.css (1 hunks)
  • frontend/src/components/Media/ChronologicalGallery.tsx (1 hunks)
  • frontend/src/components/Media/MediaInfoPanel.tsx (4 hunks)
  • frontend/src/components/Timeline/TimelineScrollbar.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/App.css
  • frontend/src/components/Media/ChronologicalGallery.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/components/Timeline/TimelineScrollbar.tsx (2)
frontend/src/components/Media/ChronologicalGallery.tsx (1)
  • MonthMarker (11-15)
frontend/src/utils/timelineUtils.ts (4)
  • TooltipState (4-8)
  • useScroll (10-32)
  • useWheel (34-53)
  • getMarkerForScrollPosition (55-67)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
🔇 Additional comments (3)
frontend/src/components/Media/MediaInfoPanel.tsx (2)

10-10: LGTM!

The import of SquareArrowOutUpRight is correct and follows the tree-shakable pattern recommended for lucide-react.


29-41: LGTM!

The date handling logic correctly uses metadata.date_created with proper fallback behavior and formatted output. The defensive coding with optional chaining prevents runtime errors.

frontend/src/components/Timeline/TimelineScrollbar.tsx (1)

81-104: LGTM! Robust marker positioning logic.

The memoized calculation correctly handles edge cases:

  • Filters unreachable markers (lines 90-92)
  • Prevents division by zero with safeDenominator (line 94)
  • Clamps positions to valid track bounds (line 101)

Copy link
Contributor

@rahulharpal1603 rahulharpal1603 left a comment

Choose a reason for hiding this comment

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

Thank you so much, @rohan-pandeyy
This issue was open for so long, and you were very patient. I have made some minor changes; other than that, everything looks good.

@rahulharpal1603
Copy link
Contributor

@coderabbitai resolve

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 8, 2025

✅ Actions performed

Comments resolved.

@rahulharpal1603 rahulharpal1603 merged commit 589d71f into AOSSIE-Org:main Oct 8, 2025
9 checks passed
@rohan-pandeyy
Copy link
Contributor Author

Thank you so much, @rohan-pandeyy

This issue was open for so long, and you were very patient. I have made some minor changes; other than that, everything looks good.

@rahulharpal1603 thank you. I genuinely appreciate you taking the time to review it, this issue had quite the journey!!! Really glad to have contributed to such a vast feature.😁

This was referenced Oct 16, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feat: Scrollbar Timeline with {Month-Year} Markers for Home page and AI Tagging page.

2 participants