Skip to content

Conversation

@SiddharthJiyani
Copy link
Contributor

@SiddharthJiyani SiddharthJiyani commented Oct 31, 2025

Changes

Backend (Sync Microservice)

  • Added total_images and tagged_images fields to tagging status API response
  • Updated schema, database queries, and routes to include detailed image counts

Frontend

  • Implemented per-folder timestamp tracking in Redux store (folderStatusTimestamps)
  • Timestamp updates only when folder's image counts actually change
  • Added 3-second grace period for folders showing 0 images (prevents premature "empty" message)
  • Three display states based on folder status:
    • Loading: Spinner with "Loading status..." when AI tagging first enabled
    • Empty: Alert icon with "No images found in this folder" when confirmed empty
    • Progress: Progress bar showing percentage

Key Improvements

  • Each folder maintains independent loading state (no cascading effects)
  • Adding/deleting folders doesn't trigger status flickering in other folders
fix_issue_592.mp4

Summary by CodeRabbit

  • New Features
    • Per-folder AI tagging status with real-time loading indicators and per-folder progress display.
    • Visual progress bars, completion checkmarks, and status messages inside each folder card.
    • Folder-level image metrics shown (total images and tagged images) alongside tagging progress.

@github-actions github-actions bot added backend bug Something isn't working frontend labels Oct 31, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 31, 2025

Walkthrough

Adds per-folder timestamp tracking and image-count fields for AI tagging. Backend returns total_images and tagged_images; frontend stores per-folder timestamps, computes per-folder loading state, and renders folder-specific UI (loading, empty, progress, complete).

Changes

Cohort / File(s) Summary
Backend Schema & Routes
sync-microservice/app/schemas/folders.py, sync-microservice/app/routes/folders.py
Adds total_images and tagged_images fields to FolderTaggingInfo schema and includes them in API responses for folder tagging status.
Backend Database Layer
sync-microservice/app/database/folders.py
db_get_tagging_progress now returns total_images and tagged_images alongside tagging percentage; FolderTaggingInfo tuple extended with these fields.
Frontend Types
frontend/src/types/FolderStatus.ts
Extends FolderTaggingInfo interface to include numeric total_images and tagged_images.
Frontend State Management
frontend/src/features/folderSlice.ts
Introduces folderStatusTimestamps: Record<string, number> in FolderState; captures a single timestamp on status updates only when status changed; sets lastUpdatedAt from that timestamp; clears timestamps on status clear.
Frontend UI — Folder Management
frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx
Reworks per-folder rendering to compute status, loading, empty, and completion locally; adds isStatusLoading helper using timestamps; shows Loader, "No images found" Alert, or progress bar and completion check per folder; imports Loader2 and AlertCircle.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant UI as Folder Card UI
    participant Slice as Folder Slice
    participant API as Backend API
    participant Selector as Status Selector

    UI->>API: Toggle AI Tagging / request status
    API-->>Slice: FolderTaggingInfo { folder_id, total_images, tagged_images, tagging_percentage }
    Slice->>Slice: now = Date.now()
    Slice->>Slice: if status changed then folderStatusTimestamps[id] = now
    Slice-->>Selector: updated state + timestamps

    loop Polling / UI refresh
        UI->>Selector: isStatusLoading(folderId, hasAITagging)
        Selector->>Selector: compare folderStatusTimestamps[id] vs now
        alt loading window active
            Selector-->>UI: loading = true
            UI->>UI: show Loader + status text
        else loading window expired
            Selector-->>UI: loading = false
            alt total_images == 0
                UI->>UI: show "No images found in this folder" (Alert)
            else total_images > 0
                UI->>UI: show progress bar + percentage / completion icon
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect timestamp capture and equality checks in frontend/src/features/folderSlice.ts.
  • Verify the backend aggregation and rounding for total_images, tagged_images, and tagging_percentage in sync-microservice/app/database/folders.py.
  • Confirm schema alignment across schemas, routes, and frontend types to avoid mismatches.
  • Review conditional rendering branches in FolderManagementCard.tsx for edge cases (zero images, in-progress, complete).

Possibly related PRs

Suggested labels

UI

Suggested reviewers

  • rahulharpal1603

Poem

🐰 Timestamps hop with every ping,
Counts of images now take wing,
Empty folders speak at last,
Progress hops forward, not stuck in past,
A tiny rabbit cheers this thing! 🥕

Pre-merge checks and finishing touches

✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "fix: display proper status for empty folders in AI tagging" directly and clearly describes the primary change in the changeset. The title is concise, specific, and accurately summarizes the main objective of the PR, which is to distinguish empty folders from folders with 0% progress and display appropriate UI messaging. The changes across frontend and backend all align with this core purpose.
Linked Issues Check ✅ Passed The pull request fully addresses all coding objectives from issue #592. The backend changes [592] add total_images and tagged_images fields across the schema, database queries, and API routes, enabling the API to return distinguishing data. The frontend changes [592] implement per-folder timestamp tracking with a 3-second grace period to detect when folders genuinely contain zero images, and display a clear "No images found in this folder" message with an AlertCircle icon instead of showing a 0% progress bar. The implementation also adds per-folder loading states to prevent flickering across multiple folders, fully satisfying the requirement to detect empty folders and provide meaningful feedback to users.
Out of Scope Changes Check ✅ Passed All code changes in this pull request are directly related to fixing the empty folder status display issue. The backend additions of total_images and tagged_images fields are foundational to the root cause fix outlined in issue #592. The frontend changes, including per-folder timestamp tracking, the 3-second grace period logic, per-folder state management, and the three-state UI rendering (loading, empty, progress), are all necessary technical implementations to distinguish empty folders from 0% progress states. No unrelated or tangential changes have been introduced.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ 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: 0

🧹 Nitpick comments (2)
sync-microservice/app/database/folders.py (1)

115-117: Consider adding validation that tagged_images ≤ total_images.

While the SQL query logic makes it unlikely for tagged_images to exceed total_images, adding an assertion or validation could prevent potential data integrity issues from propagating to the API response.

Example validation:

if tagged_images > total_images:
    logger.warning(
        f"Data integrity issue in folder {folder_id}: "
        f"tagged_images ({tagged_images}) > total_images ({total_images})"
    )
    tagged_images = total_images
sync-microservice/app/schemas/folders.py (1)

10-11: Consider adding cross-field validation for data integrity.

While individual field validation ensures non-negative values, adding a validator to ensure tagged_images <= total_images would improve data integrity guarantees at the API boundary.

Example validator:

from pydantic import field_validator

@field_validator('tagged_images')
@classmethod
def validate_tagged_not_exceeds_total(cls, v, info):
    total = info.data.get('total_images')
    if total is not None and v > total:
        raise ValueError('tagged_images cannot exceed total_images')
    return v
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8980d72 and 8c28636.

📒 Files selected for processing (6)
  • frontend/src/features/folderSlice.ts (2 hunks)
  • frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx (3 hunks)
  • frontend/src/types/FolderStatus.ts (1 hunks)
  • sync-microservice/app/database/folders.py (2 hunks)
  • sync-microservice/app/routes/folders.py (1 hunks)
  • sync-microservice/app/schemas/folders.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx (1)
frontend/src/app/store.ts (1)
  • RootState (22-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: Tauri Build Check (windows-latest)
  • GitHub Check: Tauri Build Check (macos-latest, --target aarch64-apple-darwin)
  • GitHub Check: Tauri Build Check (ubuntu-22.04)
  • GitHub Check: Backend Tests
🔇 Additional comments (10)
frontend/src/types/FolderStatus.ts (1)

4-6: LGTM! Interface correctly extended with image count fields.

The addition of total_images and tagged_images fields aligns with the backend schema changes and enables the frontend to distinguish between empty folders and folders with 0% progress.

sync-microservice/app/database/folders.py (1)

19-20: LGTM! NamedTuple correctly extended with image count fields.

The addition of total_images and tagged_images to the FolderTaggingInfo NamedTuple properly extends the data model to support the new frontend requirements.

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

36-38: LGTM! Route handler correctly propagates new fields.

The API response now includes total_images and tagged_images, enabling the frontend to distinguish between empty folders and folders with active tagging progress.

frontend/src/features/folderSlice.ts (3)

8-8: LGTM! Per-folder timestamp tracking properly initialized.

The addition of folderStatusTimestamps enables independent loading state management for each folder, preventing cascading effects when folders are added or deleted.

Also applies to: 15-15


77-94: LGTM! Timestamp update logic correctly implements change detection.

The implementation captures a single timestamp for the batch and updates per-folder timestamps only when total_images or tagged_images change. This prevents unnecessary timestamp updates while ensuring the UI can detect when folder status has meaningfully changed.

Note: The comparison excludes tagging_percentage since it's derived from the count fields—this is appropriate.


97-101: LGTM! Cleanup correctly clears timestamp state.

The clearTaggingStatus reducer properly resets both the tagging status and timestamps, ensuring a clean state.

frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx (4)

31-33: LGTM! Timestamp selector properly integrated.

The folderStatusTimestamps selector enables per-folder loading state computation in the isStatusLoading helper.


35-51: LGTM! Loading state logic correctly implements 3-second grace period.

The isStatusLoading helper properly handles all states:

  • Returns false when AI tagging is disabled
  • Returns true during initial load (no status or timestamp)
  • Implements a 3-second grace period for empty folders to prevent premature display of "No images found"

Note: The grace period transition from loading to empty relies on subsequent API polls to trigger component re-renders. This is acceptable behavior assuming the tagging status API is polled regularly (e.g., every few seconds). The component will update when taggingStatus changes, even if the folder's timestamp remains unchanged.


61-67: LGTM! Per-folder state variables correctly computed.

The per-folder state computation properly derives loading, hasImages, isEmpty, and isComplete from the tagging status, enabling conditional rendering of appropriate UI elements.


110-146: LGTM! Conditional UI rendering correctly implements three display states.

The implementation properly renders:

  • Loading state: Spinner with "Loading status..." for folders during initial load or grace period
  • Empty state: Alert icon with "No images found in this folder" for confirmed empty folders
  • Progress state: Progress bar with percentage, including green completion indicator and check icon at 100%

The logic correctly handles all edge cases, including folders without status data (shows loading) and completed tagging (shows green checkmark).

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)
frontend/src/features/folderSlice.ts (1)

77-96: Consider cleaning up stale timestamps for removed folders.

The timestamp update logic correctly tracks when each folder's status changes. However, folderStatusTimestamps is not pruned when folders are removed from the payload (e.g., when AI tagging is disabled). This creates a minor memory leak.

Consider adding cleanup logic to remove timestamps for folders no longer in the payload:

 setTaggingStatus(state, action: PayloadAction<FolderTaggingInfo[]>) {
   const map: Record<string, FolderTaggingInfo> = {};
   const now = Date.now();
+  const activeFolderIds = new Set<string>();

   for (const info of action.payload) {
     map[info.folder_id] = info;
+    activeFolderIds.add(info.folder_id);

     const existingStatus = state.taggingStatus[info.folder_id];
     if (
       !existingStatus ||
       existingStatus.total_images !== info.total_images ||
       existingStatus.tagged_images !== info.tagged_images
     ) {
       state.folderStatusTimestamps[info.folder_id] = now;
     }
   }

+  // Clean up timestamps for folders no longer in payload
+  for (const folderId in state.folderStatusTimestamps) {
+    if (!activeFolderIds.has(folderId)) {
+      delete state.folderStatusTimestamps[folderId];
+    }
+  }

   state.taggingStatus = map;
   state.lastUpdatedAt = now;
 },
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c28636 and 94f4a21.

📒 Files selected for processing (2)
  • frontend/src/features/folderSlice.ts (2 hunks)
  • frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx (1)
frontend/src/app/store.ts (1)
  • RootState (22-22)
🔇 Additional comments (5)
frontend/src/features/folderSlice.ts (2)

8-8: LGTM! Per-folder timestamp tracking added.

The folderStatusTimestamps field enables independent loading states for each folder, which aligns with the PR objective to prevent cascading status flickering when folders are added or deleted.


99-103: LGTM! Cleanup logic correctly resets timestamps.

The clearTaggingStatus reducer properly resets both taggingStatus and folderStatusTimestamps together, maintaining consistency.

frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx (3)

2-2: LGTM! New icons imported for enhanced status indicators.

Loader2 and AlertCircle are correctly used for loading and empty folder states, improving the visual feedback for users.


31-33: LGTM! Selector correctly retrieves per-folder timestamps.

The folderStatusTimestamps selector enables the component to access per-folder update times for calculating loading states.


113-149: LGTM! Three-state UI correctly implements PR objectives.

The conditional rendering implements the three distinct states as specified in the PR objectives:

  1. Loading: Spinner with "Loading status..." during initial fetch or 3-second grace period for empty folders
  2. Empty: Alert icon with "No images found in this folder" when folder is confirmed empty
  3. Progress: Progress bar showing tagging percentage, with green styling when complete

This effectively distinguishes between "0 images" and "0% progress," resolving issue #592.

Comment on lines +35 to +51
const isStatusLoading = (folderId: string, folderHasAITagging: boolean) => {
if (!folderHasAITagging) return false;

const status = taggingStatus[folderId];
if (!status) return true;

const timestamp = folderStatusTimestamps[folderId];
if (!timestamp) return true;

const timeSinceUpdate = Date.now() - timestamp;

if (status.total_images === 0 && timeSinceUpdate < 3000) {
return true;
}

return false;
};
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

Handle missing timestamp more gracefully to avoid indefinite loading.

If status exists but timestamp is missing (line 42), the function returns true (loading), which would show a loading spinner indefinitely. This edge case could occur if state is restored from persistence without timestamps or in rare race conditions.

Consider treating a missing timestamp as "skip grace period" rather than "still loading":

 const isStatusLoading = (folderId: string, folderHasAITagging: boolean) => {
   if (!folderHasAITagging) return false;

   const status = taggingStatus[folderId];
   if (!status) return true;

   const timestamp = folderStatusTimestamps[folderId];
-  if (!timestamp) return true;
-
-  const timeSinceUpdate = Date.now() - timestamp;
+  // If no timestamp, treat as "no grace period needed"
+  const timeSinceUpdate = timestamp ? Date.now() - timestamp : Infinity;

   if (status.total_images === 0 && timeSinceUpdate < 3000) {
     return true;
   }

   return false;
 };

This way, if the timestamp is missing, timeSinceUpdate is Infinity, so the 3-second grace period check is bypassed and the actual status (empty or progress) is shown.

📝 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
const isStatusLoading = (folderId: string, folderHasAITagging: boolean) => {
if (!folderHasAITagging) return false;
const status = taggingStatus[folderId];
if (!status) return true;
const timestamp = folderStatusTimestamps[folderId];
if (!timestamp) return true;
const timeSinceUpdate = Date.now() - timestamp;
if (status.total_images === 0 && timeSinceUpdate < 3000) {
return true;
}
return false;
};
const isStatusLoading = (folderId: string, folderHasAITagging: boolean) => {
if (!folderHasAITagging) return false;
const status = taggingStatus[folderId];
if (!status) return true;
const timestamp = folderStatusTimestamps[folderId];
// If no timestamp, treat as "no grace period needed"
const timeSinceUpdate = timestamp ? Date.now() - timestamp : Infinity;
if (status.total_images === 0 && timeSinceUpdate < 3000) {
return true;
}
return false;
};
🤖 Prompt for AI Agents
In frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx around
lines 35 to 51, the function treats a missing timestamp as "still loading" which
can cause an indefinite spinner; change the logic so the 3-second grace period
is only applied when a valid timestamp exists (e.g., if timestamp is missing,
skip the grace check by treating timeSinceUpdate as Infinity or by bypassing the
<3000 check), so status with no timestamp falls back to showing the actual state
instead of staying in loading.

/>
return (
<div
key={index}
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

Use folder.folder_id as key instead of array index.

Using array index as the key prop is a React anti-pattern that can cause rendering issues when folders are reordered, added, or removed. React may incorrectly recycle component instances, leading to stale state or incorrect UI updates.

Apply this diff:

-            key={index}
+            key={folder.folder_id}
📝 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
key={index}
key={folder.folder_id}
🤖 Prompt for AI Agents
In frontend/src/pages/SettingsPage/components/FolderManagementCard.tsx around
line 73, the component uses the array index as the React key which can cause
incorrect re-renders; replace key={index} with key={folder.folder_id} (or
another stable unique id from the folder object) to provide a stable and unique
key for each item, and if folder.folder_id might be undefined add a safe
fallback (e.g., a generated unique id stored on the folder) to ensure keys
remain stable across renders.

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

Labels

backend bug Something isn't working frontend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG: AI Tagging Shows 0% Progress for Empty Folders Instead of "No Images" Message

1 participant