Skip to content

Conversation

@Isha-upadhyay
Copy link

@Isha-upadhyay Isha-upadhyay commented Dec 14, 2025

Memories Feature – Automatic Time & Location Based Highlights

Fixes #723


Overview

This PR implements the Memories feature for PictoPy, inspired by Google Photos–style memory resurfacing.
Memories are generated automatically from existing gallery images using time and location metadata, without manual album creation and without introducing any new database tables.


What’s Implemented

Time-Based Memory Creation

  • Images are grouped into memories based on date proximity.
  • Photos captured within a short time window (± few days) are clustered together.
  • A memory is created only when at least 2 images exist in the same date range.
  • Example:
    • Multiple photos taken on 18 July 2025 → one memory card.

Location-Aware (Optional, Graceful Fallback)

  • If GPS metadata (latitude & longitude) is available:
    • Images are grouped into Trip memories using spatial proximity.
  • If location data is missing:
    • The system falls back to time-based grouping (month / date window).
    • Memories are still generated even when no location is present.

Important:
Missing location metadata does not block memory creation.


Automatic Memory Types

Each memory is automatically classified as:

  • On This Day – same day/month from a previous year
  • Trip – GPS-based clustering
  • Date Range – time-based memories without location

Dynamic titles are generated automatically, such as:

  • On this day · 2 years ago
  • Trip · July 2025
  • Memories from May 2025

Highlight-Focused UI

  • Each memory card shows:
    • A representative cover image
    • A small set of highlights (not all images)
    • Image count and date range
  • Clicking a memory opens all related images in the media viewer.

Database Changes

  • ❌ No new database tables
  • ❌ No schema migrations
  • ✅ Memories are generated on-the-fly from existing image metadata

This keeps the system lightweight, scalable, and backward-compatible.


🔄 Auto Refresh Behavior

  • Memories are recalculated dynamically on fetch.
  • Newly added images automatically appear in relevant memories.

🧪 Edge Cases Handled

  • Images without date_created → skipped safely
  • Images without GPS → grouped by time
  • Single-image clusters → ignored
  • Mixed datasets (with & without location) → handled gracefully

📸 Screenshots

Screenshot 2025-12-14 173703 Screenshot 2025-12-14 163517 Screenshot 2025-12-14 163553

✅ Checklist

  • Matches issue Implement the Memories page #723 requirements
  • No database changes
  • Works with missing location metadata
  • End-to-end implementation (backend + frontend)
  • Code formatted and type-checked

Summary by CodeRabbit

  • New Features

    • Full Memories experience: responsive Memories page with memory cards (representative image, title, badge, date range, photo count)
    • Memory detail view with formatted date range, optional location, and "View Photos" gallery
    • Browse memories and open galleries from cards
  • Improvements

    • Smarter grouping of photos into memorable sets (trips, on-this-day, ranges)
    • Date range formatting improved for single-day vs. range displays

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 14, 2025

Walkthrough

Adds a new Memories feature: backend FastAPI router that groups images into time/location-based memories and exposes GET /memories/ and GET /memories/{memory_id}/images; frontend clients, types, pages, routes, components, and utilities to fetch and display memories; and OpenAPI updates.

Changes

Cohort / File(s) Summary
Backend: Memories router
backend/app/routes/memories.py
New FastAPI router implementing grouping logic (haversine_distance, time/GPS clustering, decide_memory_type, generate_title, group_images) and endpoints GET /memories/ and GET /{memory_id}/images.
Backend: Router wiring
backend/main.py
Imports and mounts the new memories router at /memories (include_router added in app setup; duplicate include observed).
Frontend: API endpoints & clients
frontend/src/api/apiEndpoints.ts, frontend/src/api/api-functions/memories.ts, frontend/src/api/api-functions/index.ts
New endpoint definitions for memories and helper functions fetchAllMemories and fetchMemoryImages; re-export added in index.ts.
Frontend: Pages & routing
frontend/src/pages/Memories/Memories.tsx, frontend/src/pages/Memories/MemoryDetail.tsx, frontend/src/routes/AppRoutes.tsx
Full Memories page implementation, MemoryDetail component, and route wiring for /memories and /memories/:memoryId.
Frontend: Types
frontend/src/types/memories.ts, frontend/src/types/Media.ts
New MemoriesApiResponse and MemoryImagesApiResponse; Memory/MemoryImage interfaces added; Image.id type changed from string to number.
Frontend: Media components & hooks
frontend/src/components/Media/ChronologicalGallery.tsx, frontend/src/components/Media/MediaThumbnails.tsx, frontend/src/api/api-functions/togglefav.ts, frontend/src/hooks/useToggleFav.ts
Image id typing updated to number across components and hooks; togglefav now accepts number and converts to string in request payload.
Frontend: Utilities
frontend/src/utils/memoryUtils.ts
Added formatDateRange(start, end) for memory date display formatting.
Docs / OpenAPI
docs/backend/backend_python/openapi.json
Added /memories/ and /memories/{memory_id}/images operations and minor schema adjustments (parameter allOf wrapper and tightened ImageInCluster.Metadata).
Manifest
backend/main.py, package.json
App router wiring and package manifest referenced/updated (package.json included in manifest file analyzer).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Browser as Browser / UI
    participant Backend as Backend API
    participant DB as Database

    Note over User,Browser: User opens Memories page
    User->>Browser: Navigate to /memories
    Browser->>Backend: GET /memories/
    Backend->>DB: db_get_all_images()
    DB-->>Backend: images list (id, date, lat, lon, metadata)
    Backend->>Backend: group_images() — cluster by date & proximity
    Backend->>Backend: decide_memory_type(), generate_title()
    Backend-->>Browser: MemoriesApiResponse (memories list)
    Browser->>Browser: Render memory cards

    Note over User,Browser: User opens memory details
    User->>Browser: Click memory card
    Browser->>Backend: GET /memories/{memory_id}/images
    Backend->>Backend: locate memory group, return images
    Backend-->>Browser: MemoryImagesApiResponse (image list)
    Browser->>Browser: Open Gallery with images
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Points to focus on:

  • backend/app/routes/memories.py — grouping algorithm correctness, date parsing, coordinate handling, performance (nested loops), and error cases.
  • backend/main.py — duplicate include_router occurrences and route registration.
  • Cross-cutting change: Image.id type change (string → number) — audit all usages for type mismatches and serialization boundaries.
  • Frontend components that index by id (ChronologicalGallery, MediaThumbnails, MediaView entry points) and API functions/hooks (togglefav, useToggleFav).

Possibly related PRs

Suggested labels

UI

Suggested reviewers

  • rahulharpal1603

Poem

🐇 In burrows of dates and lat-long light,
I nudge old moments into view so bright.
Cards hop forth with a gentle cheer,
Snapshots of journeys now appear.
A rabbit's wink — your memories near!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
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 (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main accomplishment: implementing the memories feature end-to-end for both backend and frontend, which matches the changeset's primary objective.
Linked Issues check ✅ Passed The pull request implements all core requirements from issue #723: automatic memory generation, date/location-based grouping, minimum 2-image threshold, story-style highlights, dynamic titles, and no schema changes.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the memories feature implementation. Type system changes (Image.id string→number) are consistent modifications supporting the feature; no unrelated refactoring detected.
✨ Finishing touches
🧪 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: 8

🧹 Nitpick comments (1)
backend/app/routes/memories.py (1)

340-355: Performance: Re-computing all memories for single memory lookup.

get_memory_images calls db_get_all_images() and group_images() to recompute all memories just to find one by ID. This is inefficient, especially as the image count grows.

Consider caching the grouped memories or restructuring the endpoint to avoid redundant computation. For now, this works but may become a bottleneck.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d07d817 and 22e1b80.

📒 Files selected for processing (12)
  • backend/app/routes/memories.py (1 hunks)
  • backend/main.py (2 hunks)
  • frontend/src/api/api-functions/index.ts (1 hunks)
  • frontend/src/api/api-functions/memories.ts (1 hunks)
  • frontend/src/api/apiEndpoints.ts (1 hunks)
  • frontend/src/components/Media/ChronologicalGallery.tsx (1 hunks)
  • frontend/src/components/Media/MediaThumbnails.tsx (1 hunks)
  • frontend/src/pages/Memories/Memories.tsx (1 hunks)
  • frontend/src/pages/Memories/MemoryDetail.tsx (1 hunks)
  • frontend/src/routes/AppRoutes.tsx (2 hunks)
  • frontend/src/types/Media.ts (2 hunks)
  • frontend/src/types/memories.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
frontend/src/api/api-functions/memories.ts (2)
frontend/src/types/memories.ts (2)
  • MemoriesApiResponse (3-6)
  • MemoryImagesApiResponse (8-11)
frontend/src/api/axiosConfig.ts (1)
  • apiClient (5-12)
frontend/src/types/memories.ts (1)
frontend/src/types/Media.ts (2)
  • Memory (58-67)
  • Image (13-23)
frontend/src/routes/AppRoutes.tsx (1)
frontend/src/constants/routes.ts (1)
  • ROUTES (1-12)
backend/app/routes/memories.py (1)
backend/app/database/images.py (1)
  • db_get_all_images (123-214)
🔇 Additional comments (10)
frontend/src/types/Media.ts (1)

51-67: LGTM! Type definitions align well with backend response.

The MemoryImage and Memory interfaces correctly model the backend response structure. The representative_image being optional properly handles edge cases.

backend/main.py (2)

29-29: LGTM!

The import follows the established pattern used by other route modules.


136-136: LGTM!

Router registration is consistent with other routers in the application.

frontend/src/api/apiEndpoints.ts (1)

34-37: LGTM!

The endpoint definitions follow the established patterns and correctly match the backend route paths.

frontend/src/api/api-functions/index.ts (1)

7-7: LGTM!

The export statement follows the established pattern and correctly exposes the new memories API module.

frontend/src/types/memories.ts (1)

1-11: LGTM!

The API response types follow a consistent pattern with clear structure. The interfaces correctly wrap the domain types (Memory and Image) in success/data envelopes.

frontend/src/components/Media/ChronologicalGallery.tsx (1)

61-67: LGTM!

The type change from Map<string, number> to Map<number, number> correctly aligns with the updated Image.id type. The map key now properly reflects that image IDs are numeric.

frontend/src/components/Media/MediaThumbnails.tsx (1)

4-14: LGTM!

The id type change from string to number correctly aligns with the updated Image interface, maintaining type consistency across the codebase.

frontend/src/api/api-functions/memories.ts (1)

7-17: LGTM!

Both API functions follow the established pattern for API calls in this codebase. The functions are simple, well-typed, and correctly use the shared apiClient instance.

frontend/src/pages/Memories/Memories.tsx (1)

15-167: LGTM with one refactoring note!

The Memories page implementation is well-structured with proper loading states, error handling via useMutationFeedback, and clean UI rendering. The helper functions (getMemoryIcon, getMemoryBadge) are well-organized and the empty state handling is appropriate.

Note: The formatDateRange function (lines 43-63) is duplicated in MemoryDetail.tsx. See the refactoring suggestion in the MemoryDetail.tsx review to extract this to a shared utility.

Comment on lines +30 to +50
const formatDateRange = (start: string, end: string) => {
const s = new Date(start);
const e = new Date(end);

if (s.toDateString() === e.toDateString()) {
return s.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
}

return `${s.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
})} – ${e.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}`;
};
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 | 🟠 Major

Refactor: Extract duplicated date formatting logic.

The formatDateRange function is duplicated between MemoryDetail.tsx and Memories.tsx (lines 43-63). Extract this to a shared utility module to follow DRY principles.

Create a new utility file (e.g., frontend/src/utils/memoryUtils.ts):

export const formatDateRange = (start: string, end: string): string => {
  const s = new Date(start);
  const e = new Date(end);

  if (s.toDateString() === e.toDateString()) {
    return s.toLocaleDateString('en-US', {
      month: 'long',
      day: 'numeric',
      year: 'numeric',
    });
  }

  return `${s.toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
  })}${e.toLocaleDateString('en-US', {
    month: 'short',
    day: 'numeric',
    year: 'numeric',
  })}`;
};

Then import and use it in both components:

+import { formatDateRange } from '@/utils/memoryUtils';
-
-const formatDateRange = (start: string, end: string) => {
-  // ... function body
-};
🤖 Prompt for AI Agents
In frontend/src/pages/Memories/MemoryDetail.tsx around lines 30 to 50, the
formatDateRange function is duplicated across MemoryDetail.tsx and Memories.tsx;
extract it into a shared utility (e.g., frontend/src/utils/memoryUtils.ts) that
exports formatDateRange(start: string, end: string): string, move the existing
logic there, then import and use that exported function in both MemoryDetail.tsx
and Memories.tsx to remove duplication and keep behavior identical.

Comment on lines +74 to +79
{memory.location_name && (
<div className="flex items-center gap-1">
<MapPin className="h-4 w-4" />
{memory.location_name}
</div>
)}
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 | 🔴 Critical

Critical: Accessing undefined Memory property.

The code references memory.location_name (line 77), but this field is not defined in the Memory interface. This will cause a runtime error.

Update the Memory interface to include the optional location_name field (see previous comment for the interface update), or remove this conditional rendering block if location names are not supported.

🤖 Prompt for AI Agents
In frontend/src/pages/Memories/MemoryDetail.tsx around lines 74–79, the
component reads memory.location_name which is not declared on the Memory
interface and can cause a runtime/type error; either add an optional
location_name?: string to the Memory interface where it is defined (e.g.,
frontend/src/types or the file noted in the prior comment) and run typecheck, or
if location names are not supported remove this conditional block from
MemoryDetail.tsx; if you add the field, ensure it is optional (string |
undefined) so the existing conditional rendering remains correct.

Comment on lines +27 to +28
<Route path={ROUTES.MEMORIES} element={<Memories />} />
<Route path={`${ROUTES.MEMORIES}/:memoryId`} element={<MemoryDetail />} />
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 | 🟡 Minor

Minor: Inconsistent indentation on Line 27.

Line 27 is missing leading spaces compared to sibling routes. This is a minor formatting issue.

-      <Route path={ROUTES.MEMORIES} element={<Memories />} />
+        <Route path={ROUTES.MEMORIES} element={<Memories />} />
📝 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
<Route path={ROUTES.MEMORIES} element={<Memories />} />
<Route path={`${ROUTES.MEMORIES}/:memoryId`} element={<MemoryDetail />} />
<Route path={ROUTES.MEMORIES} element={<Memories />} />
<Route path={`${ROUTES.MEMORIES}/:memoryId`} element={<MemoryDetail />} />
🤖 Prompt for AI Agents
In frontend/src/routes/AppRoutes.tsx around lines 27 to 28, the Route on line 27
is indented inconsistently compared to sibling Route entries; fix by aligning
leading spaces to match the surrounding JSX routes (apply the same indentation
level as the following Route line), then run the project's formatter or lint
autofix to ensure consistent styling across the file.


export interface Image {
id: string;
id: number;
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat frontend/src/types/Media.ts

Repository: AOSSIE-Org/PictoPy

Length of output: 1602


🏁 Script executed:

cd frontend/src && git log -n 5 --oneline -- types/Media.ts

Repository: AOSSIE-Org/PictoPy

Length of output: 106


🏁 Script executed:

rg -n '\.id\s*===' frontend/src/ --type=ts --type=tsx -C2

Repository: AOSSIE-Org/PictoPy

Length of output: 89


🏁 Script executed:

rg -n 'Image\.id|image\.id' frontend/src/ --type=ts --type=tsx -C2

Repository: AOSSIE-Org/PictoPy

Length of output: 89


🏁 Script executed:

git diff HEAD~1 frontend/src/types/Media.ts

Repository: AOSSIE-Org/PictoPy

Length of output: 250


🏁 Script executed:

rg -n 'image\.id|Image\.id' frontend/src/ --type=ts -C2

Repository: AOSSIE-Org/PictoPy

Length of output: 1672


🏁 Script executed:

rg -n "localStorage" frontend/src/ --type=ts -C2

Repository: AOSSIE-Org/PictoPy

Length of output: 7020


🏁 Script executed:

rg -n "url.*id|params.*id" frontend/src/ --type=ts -C2

Repository: AOSSIE-Org/PictoPy

Length of output: 458


🏁 Script executed:

git show 22e1b80:frontend/src/types/Media.ts 2>/dev/null | head -20

Repository: AOSSIE-Org/PictoPy

Length of output: 464


🏁 Script executed:

git log --all --oneline -- frontend/src/types/Media.ts | head -5

Repository: AOSSIE-Org/PictoPy

Length of output: 227


🏁 Script executed:

rg -n 'toggleFavourite' frontend/src/ --type=ts -B2 -A5

Repository: AOSSIE-Org/PictoPy

Length of output: 3689


🏁 Script executed:

rg -n 'const toggleFavourite|function toggleFavourite' frontend/src/ --type=ts -A10

Repository: AOSSIE-Org/PictoPy

Length of output: 882


Breaking change: Image.id type changed to number, but the backend API expects a string.

The togglefav mutation function (line 7 in useToggleFav.ts) expects image_id: string, but Image.id is now typed as number. While the wrapper function uses id: any to mask the type error, this will cause a type mismatch at runtime when numeric IDs are sent to the backend endpoint. Update the type to match the backend contract or add explicit type conversion when calling the API.

🤖 Prompt for AI Agents
In frontend/src/types/Media.ts around line 14, Image.id is typed as number but
the backend expects a string; change the id type to string to match the API
contract (and update any code that constructs Image objects to produce string
IDs), OR if IDs can be numeric upstream, leave the type and instead convert to
string at the call site—specifically update useToggleFav.ts to pass image_id:
String(id) (or id.toString()) when invoking the mutation so the HTTP payload
always sends a string.

@Isha-upadhyay Isha-upadhyay mentioned this pull request Dec 14, 2025
6 tasks
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: 5

🧹 Nitpick comments (8)
backend/app/routes/memories.py (3)

15-24: Add type hints to function parameters.

The haversine_distance function lacks type annotations for its parameters, which reduces code clarity and type safety.

-def haversine_distance(lat1, lon1, lat2, lon2) -> float:
+def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:

78-81: Date parsing may fail for non-ISO formats.

The parsing handles ISO format with Z suffix but will fail for other common formats like "YYYY-MM-DD HH:MM:SS" without timezone. Consider adding more robust date parsing or documenting the expected format.

         try:
-            date = datetime.fromisoformat(date_str.replace("Z", "+00:00"))
+            # Handle ISO format with Z suffix and basic ISO format
+            date_str_normalized = date_str.replace("Z", "+00:00")
+            date = datetime.fromisoformat(date_str_normalized)
         except Exception:
+            # Skip images with unparseable dates
             continue

166-175: Consider adding response model and pagination for large galleries.

The endpoint returns all memories without pagination. For galleries with thousands of images, this could result in large responses. Consider adding optional pagination or at minimum a limit parameter for future scalability.

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

51-56: Consider making metadata optional in MemoryImage.

The MemoryImage interface has metadata: ImageMetadata as required, but the Image interface (line 19) has metadata?: ImageMetadata as optional. The backend may return images without metadata, which could cause type errors when accessing representative_image.metadata.

 export interface MemoryImage {
   id: number;
   path: string;
   thumbnailPath: string;
-  metadata: ImageMetadata;
+  metadata?: ImageMetadata;
 }

67-69: Unused fields: latitude, longitude, location_name are not returned by the backend.

The backend group_images function (lines 139-154 in memories.py) does not include latitude, longitude, or location_name in the memory response. These optional fields in the interface are never populated.

Either remove these fields if they're not planned, or update the backend to include them:

 export interface Memory {
   id: string;
   title: string;
   memory_type: 'on_this_day' | 'trip' | 'date_range';
   date_range_start: string;
   date_range_end: string;
   image_count: number;
   representative_image?: MemoryImage;
   images: Image[];
-  latitude?: number;
-  longitude?: number;
-  location_name?: string;
 }
frontend/src/utils/memoryUtils.ts (1)

6-20: Consider extracting locale for future i18n support.

The 'en-US' locale is hardcoded. If internationalization is planned, consider extracting this to a configuration or using the user's browser locale.

+const DEFAULT_LOCALE = 'en-US';
+
 export const formatDateRange = (start: string, end: string): string => {
   // ...
   if (s.toDateString() === e.toDateString()) {
-    return s.toLocaleDateString('en-US', {
+    return s.toLocaleDateString(DEFAULT_LOCALE, {
frontend/src/pages/Memories/Memories.tsx (2)

91-136: Consider adding keyboard accessibility to memory cards.

The memory cards respond to mouse clicks but lack keyboard navigation support, which could limit accessibility for keyboard-only users.

Consider enhancing keyboard accessibility:

 <Card
   key={memory.id}
   className="cursor-pointer transition hover:shadow-lg"
+  tabIndex={0}
+  role="button"
   onClick={() => handleMemoryClick(memory)}
+  onKeyDown={(e) => {
+    if (e.key === 'Enter' || e.key === ' ') {
+      e.preventDefault();
+      handleMemoryClick(memory);
+    }
+  }}
 >

82-89: Optional: Add loading skeleton for better UX.

Currently, the loading state is only communicated via a toast notification from useMutationFeedback. Consider adding a loading skeleton to provide immediate visual feedback in the main content area while memories are being fetched.

Example approach:

if (isLoading) {
  return (
    <div className="flex h-full flex-col px-8 py-6">
      <div className="mb-6">
        <h1 className="text-2xl font-bold">Memories</h1>
        <p className="text-sm text-muted-foreground">
          Relive moments from your past
        </p>
      </div>
      <div className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
        {[1, 2, 3].map((i) => (
          <Card key={i} className="animate-pulse">
            <div className="aspect-video bg-muted" />
            <CardContent className="p-4 space-y-2">
              <div className="h-4 bg-muted rounded w-3/4" />
              <div className="h-3 bg-muted rounded w-1/2" />
            </CardContent>
          </Card>
        ))}
      </div>
    </div>
  );
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 22e1b80 and ae9cea5.

📒 Files selected for processing (6)
  • backend/app/routes/memories.py (1 hunks)
  • frontend/src/api/api-functions/togglefav.ts (1 hunks)
  • frontend/src/hooks/useToggleFav.ts (1 hunks)
  • frontend/src/pages/Memories/Memories.tsx (1 hunks)
  • frontend/src/types/Media.ts (2 hunks)
  • frontend/src/utils/memoryUtils.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
frontend/src/api/api-functions/togglefav.ts (3)
frontend/src/types/API.ts (1)
  • APIResponse (1-8)
frontend/src/api/axiosConfig.ts (1)
  • apiClient (5-12)
frontend/src/api/apiEndpoints.ts (1)
  • imagesEndpoints (1-4)
frontend/src/hooks/useToggleFav.ts (1)
frontend/src/api/api-functions/togglefav.ts (1)
  • togglefav (5-11)
frontend/src/pages/Memories/Memories.tsx (7)
frontend/src/types/Media.ts (2)
  • Image (13-23)
  • Memory (58-70)
frontend/src/features/imageSelectors.ts (1)
  • selectIsImageViewOpen (13-16)
frontend/src/hooks/useQueryExtension.ts (1)
  • usePictoQuery (80-108)
frontend/src/api/api-functions/memories.ts (1)
  • fetchAllMemories (7-10)
frontend/src/features/imageSlice.ts (2)
  • setImages (18-20)
  • setCurrentViewIndex (22-34)
frontend/src/utils/memoryUtils.ts (1)
  • formatDateRange (1-21)
frontend/src/components/Media/MediaView.tsx (1)
  • MediaView (25-224)
backend/app/routes/memories.py (1)
backend/app/database/images.py (1)
  • db_get_all_images (123-214)
🔇 Additional comments (6)
frontend/src/api/api-functions/togglefav.ts (1)

5-11: LGTM!

The type change from string to number aligns with the updated Image.id type, and the toString() conversion maintains backward compatibility with the backend API contract. This properly addresses the previous review comment about type consistency.

frontend/src/hooks/useToggleFav.ts (1)

5-17: LGTM!

The hook's type signatures are properly aligned with the updated togglefav API function. Changing from any to number improves type safety and consistency across the codebase.

frontend/src/pages/Memories/Memories.tsx (4)

1-19: LGTM! Clean imports and state setup.

All necessary dependencies are imported, and the component state is initialized appropriately with local state for selected images and Redux integration for the image viewer.


21-36: LGTM! Data fetching properly integrated.

The query setup with usePictoQuery and feedback handling via useMutationFeedback follows the established patterns in the codebase. Safe data extraction with fallback to empty array prevents runtime errors.


44-66: LGTM! Helper functions are well-structured.

Both getMemoryIcon and getMemoryBadge correctly handle all three memory types (on_this_day, trip, date_range) with appropriate visual distinctions. The implementation is clean and exhaustive.


68-80: LGTM! Clean empty state UI.

The empty state provides clear user guidance with appropriate iconography and messaging, enhancing the user experience when no memories are available.

Comment on lines +27 to +39
def decide_memory_type(mem: Dict[str, Any]) -> str:
now = datetime.now()
date = mem["anchor_date"]
lat = mem["lat"]
lon = mem["lon"]

if date.month == now.month and date.day == now.day and date.year != now.year:
return "on_this_day"

if lat is not None and lon is not None:
return "trip"

return "date_range"
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 | 🟠 Major

Potential timezone mismatch between datetime.now() and parsed dates.

The anchor_date is parsed with timezone info on line 79 (replace("Z", "+00:00")), but datetime.now() returns a naive datetime. Comparing timezone-aware and naive datetimes can raise TypeError or produce incorrect results.

Consider using timezone-aware now:

+from datetime import datetime, timezone
+
 def decide_memory_type(mem: Dict[str, Any]) -> str:
-    now = datetime.now()
+    now = datetime.now(timezone.utc)
     date = mem["anchor_date"]

Or alternatively, strip timezone info from parsed dates if local time comparison is intended.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In backend/app/routes/memories.py around lines 27-39, the function uses
datetime.now() (naive) while mem["anchor_date"] is parsed with timezone info,
which can raise TypeError or give incorrect comparisons; update the code to use
a timezone-aware "now" (e.g., datetime.now(timezone.utc)) and compare against
anchor_date converted to the same timezone
(anchor_date.astimezone(timezone.utc)), or alternatively strip tzinfo from
anchor_date before comparing if local naive comparison is intended; ensure you
perform the month/day/year comparison using consistently timezone-aware or
consistently naive datetimes (or compare .date() after normalizing both to the
same tz).

Comment on lines +42 to +58
def generate_title(mem: Dict[str, Any]) -> str:
mtype = decide_memory_type(mem)
date = mem["anchor_date"]
now = datetime.now()

if mtype == "on_this_day":
years = now.year - date.year
return (
"On this day last year"
if years == 1
else f"On this day · {years} years ago"
)

if mtype == "trip":
return f"Trip · {date.strftime('%B %Y')}"

return f"Memories from {date.strftime('%B %Y')}"
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 | 🟠 Major

Same timezone issue applies here.

The datetime.now() call on line 45 has the same naive vs. aware datetime mismatch issue mentioned above. Apply the same fix to use datetime.now(timezone.utc).

🤖 Prompt for AI Agents
In backend/app/routes/memories.py around lines 42 to 58, the function uses
datetime.now() which produces a naive datetime and can mismatch against
timezone-aware anchor_date; change to use an aware UTC now (e.g.,
datetime.now(timezone.utc)) and ensure you import timezone from datetime; also
if anchor_date may be naive, normalize/convert it to UTC-aware before computing
year differences or formatting so all datetime comparisons are done with the
same tz-awareness.

Comment on lines +178 to +193
@router.get("/{memory_id}/images")
def get_memory_images(memory_id: str):
try:
images = db_get_all_images()
grouped_memories = group_images(images)

for mem in grouped_memories:
if mem["id"] == memory_id:
return {
"success": True,
"data": mem["images"],
}

raise HTTPException(status_code=404, detail="Memory not found")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
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 | 🔴 Critical

Critical: HTTPException is caught by the broad except block, converting 404s to 500s.

The HTTPException raised on line 191 is caught by the except Exception on line 192, which re-raises it as a 500 error with str(e) as the detail. This means clients will never receive the intended 404 status.

 @router.get("/{memory_id}/images")
 def get_memory_images(memory_id: str):
     try:
         images = db_get_all_images()
         grouped_memories = group_images(images)
 
         for mem in grouped_memories:
             if mem["id"] == memory_id:
                 return {
                     "success": True,
                     "data": mem["images"],
                 }
 
         raise HTTPException(status_code=404, detail="Memory not found")
+    except HTTPException:
+        raise
     except Exception as e:
         raise HTTPException(status_code=500, detail=str(e))
🤖 Prompt for AI Agents
In backend/app/routes/memories.py around lines 178-193 the broad except
Exception catches the HTTPException(404) and converts it into a 500; update the
error handling so HTTPException passes through untouched (e.g., in the except
block check if isinstance(e, HTTPException) and re-raise it, otherwise raise a
new HTTPException(status_code=500, detail=str(e))) or replace the broad except
with more specific exception types to avoid swallowing intentional
HTTPExceptions.

Comment on lines +38 to +42
const handleMemoryClick = (memory: Memory) => {
setSelectedMemoryImages(memory.images);
dispatch(setImages(memory.images));
dispatch(setCurrentViewIndex(0));
};
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 | 🟡 Minor

Clear stale state when MediaView closes.

The selectedMemoryImages local state is set when a memory card is clicked but is never cleared when the MediaView modal closes. While this doesn't cause a functional bug (the modal is guarded by isImageViewOpen), it leaves stale image references in memory.

Add an onClose handler to reset the local state:

+  const handleCloseMediaView = () => {
+    setSelectedMemoryImages([]);
+  };
+
   return (
     <div className="flex h-full flex-col px-8 py-6">
       ...
       {isImageViewOpen && (
         <MediaView
           images={selectedMemoryImages}
           type="image"
+          onClose={handleCloseMediaView}
         />
       )}
     </div>
   );

Also applies to: 138-143

🤖 Prompt for AI Agents
In frontend/src/pages/Memories/Memories.tsx around lines 38-42 (and similarly at
138-143), the local state selectedMemoryImages is set when a memory is clicked
but never cleared, leaving stale image references; add an onClose handler to the
MediaView modal that resets selectedMemoryImages to an empty array (and
optionally dispatches a store action to clear images/current index if needed),
and wire that handler into the MediaView component so selectedMemoryImages is
cleared whenever the modal closes.

Comment on lines +1 to +21
export const formatDateRange = (start: string, end: string): string => {
const s = new Date(start);
const e = new Date(end);

if (s.toDateString() === e.toDateString()) {
return s.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
}

return `${s.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
})} – ${e.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}`;
}; No newline at end of file
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 | 🟡 Minor

Add validation for invalid date inputs.

If start or end are malformed strings, new Date() returns an Invalid Date object, and subsequent operations will produce "Invalid Date" strings in the UI.

Consider adding validation:

 export const formatDateRange = (start: string, end: string): string => {
   const s = new Date(start);
   const e = new Date(end);
+
+  if (isNaN(s.getTime()) || isNaN(e.getTime())) {
+    return 'Unknown date';
+  }
 
   if (s.toDateString() === e.toDateString()) {
📝 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
export const formatDateRange = (start: string, end: string): string => {
const s = new Date(start);
const e = new Date(end);
if (s.toDateString() === e.toDateString()) {
return s.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
}
return `${s.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
})} ${e.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}`;
};
export const formatDateRange = (start: string, end: string): string => {
const s = new Date(start);
const e = new Date(end);
if (isNaN(s.getTime()) || isNaN(e.getTime())) {
return 'Unknown date';
}
if (s.toDateString() === e.toDateString()) {
return s.toLocaleDateString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
}
return `${s.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
})} ${e.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})}`;
};
🤖 Prompt for AI Agents
In frontend/src/utils/memoryUtils.ts around lines 1-21, validate the parsed Date
objects before using them: after creating s and e, check if either is invalid
(e.g. isNaN(s.getTime()) || isNaN(e.getTime())), and if so return a safe
fallback (empty string or a localized "Invalid date" placeholder) or throw a
clear error; additionally consider handling the case where start > end by
swapping or returning a single-date string. Ensure no toLocaleDateString calls
happen on invalid Date objects.

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 ae9cea5 and 65b7d8d.

📒 Files selected for processing (1)
  • docs/backend/backend_python/openapi.json (2 hunks)
🔇 Additional comments (1)
docs/backend/backend_python/openapi.json (1)

1120-1127: Approve: input_type parameter schema wrapping looks correct.

The allOf wrapper with explicit title and default metadata improves schema clarity and aligns with OpenAPI best practices.

Comment on lines +1308 to +1326
"/memories/": {
"get": {
"tags": [
"Memories"
],
"summary": "Get Memories",
"operationId": "get_memories_memories__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
},
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 | 🟠 Major

Incomplete API documentation: empty response schema for GET /memories/.

The 200 response schema is empty ("schema": {}), which fails to document the structure of the Memories response. This creates ambiguity for API consumers about what fields, types, and structure to expect.

Replace the empty schema with a proper response schema that documents the memory objects. For example:

         "200": {
           "description": "Successful Response",
           "content": {
             "application/json": {
-              "schema": {}
+              "schema": {
+                "type": "array",
+                "items": {
+                  "$ref": "#/components/schemas/MemoryResponse"
+                }
+              }
             }
           }
         }

Also, add error response documentation (e.g., 500 for server errors) consistent with other endpoints in the spec.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In docs/backend/backend_python/openapi.json around lines 1308 to 1326 the 200
response schema for GET /memories/ is empty; replace the empty "schema": {} with
a concrete response definition that documents an array of memory objects (e.g.,
use an array of a Memory schema or inline object describing fields such as id
(string/integer), user_id (string/integer), content (string),
created_at/updated_at (string, date-time), optional tags (array of strings) and
any other fields your app returns), and add standard error responses (e.g., a
500 response with a reference to a common ErrorResponse schema consistent with
other endpoints or an inline object containing code/message) so clients can
understand both success and failure payloads.

Comment on lines +1327 to 1366
"/memories/{memory_id}/images": {
"get": {
"tags": [
"Memories"
],
"summary": "Get Memory Images",
"operationId": "get_memory_images_memories__memory_id__images_get",
"parameters": [
{
"name": "memory_id",
"in": "path",
"required": true,
"schema": {
"type": "string",
"title": "Memory Id"
}
}
],
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
},
"422": {
"description": "Validation Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
}
}
}
}
}
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 | 🟠 Major

Incomplete API documentation: empty response schema for GET /memories/{memory_id}/images.

The 200 response schema is empty, leaving API consumers without documentation of the image structure. Additionally, error responses beyond 422 Validation Error are not documented; comparable endpoints document 500, 400, or 404 responses.

Replace the empty schema and add comprehensive error handling:

         "200": {
           "description": "Successful Response",
           "content": {
             "application/json": {
-              "schema": {}
+              "schema": {
+                "type": "array",
+                "items": {
+                  "$ref": "#/components/schemas/ImageData"
+                }
+              }
             }
           }
         },
+        "404": {
+          "description": "Memory not found",
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/HTTPValidationError"
+              }
+            }
+          }
+        },
+        "500": {
+          "description": "Internal Server Error",
+          "content": {
+            "application/json": {
+              "schema": {
+                "$ref": "#/components/schemas/HTTPValidationError"
+              }
+            }
+          }
+        }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In docs/backend/backend_python/openapi.json around lines 1327 to 1366, the 200
response for GET /memories/{memory_id}/images uses an empty schema and lacks
other common error responses; update the 200 response to reference or inline the
correct image array schema (e.g., an array of Image objects with id, url,
caption, width, height, created_at) and add standard error responses mirrored
from comparable endpoints (400 Bad Request, 404 Not Found, 500 Internal Server
Error) with appropriate $ref schemas (e.g., HTTPError or ErrorModel) or inline
error object schemas so API consumers have a complete contract.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement the Memories page

2 participants