Skip to content

ALBUM Feature Added#851

Open
PrathamMehta101 wants to merge 2 commits intoAOSSIE-Org:mainfrom
PrathamMehta101:ALBUM-FEATURE-ADDED
Open

ALBUM Feature Added#851
PrathamMehta101 wants to merge 2 commits intoAOSSIE-Org:mainfrom
PrathamMehta101:ALBUM-FEATURE-ADDED

Conversation

@PrathamMehta101
Copy link
Copy Markdown

@PrathamMehta101 PrathamMehta101 commented Dec 25, 2025

📸 Album Management Features

🚀 Summary

This PR introduces a comprehensive Album Management System to PictoPy. Users can now organize their photo libraries by creating albums, adding/removing images, and managing album details directly from the UI. This enhances the user experience by providing better organization beyond the chronological timeline.

✨ Key Features

1. Album Creation & Management

  • Create Albums: Easily create new albums from the main Albums page.
  • View All Albums: A dedicated grid view displays all albums with dynamic cover images (using the first image in the album).
  • Rename Albums: Inline editing allows you to rename albums instantly with a single click.
  • Delete Albums: Safely delete albums with a confirmation dialog (images remain in the library).

2. Image Organization

  • Add to Album: Efficiently add images to albums directly from the Home feed using the context menu.
  • Remove from Album: Remove images from an album's detail view without deleting them from the main library.
  • Smart Navigation: Smooth transitions between the main timeline and specific album views.

🛠️ Technical Implementation

Frontend (frontend/)

  • New Components:
    • AlbumImageCard: Specialized card component for album views with "Remove" context actions.
    • AddToAlbumDialog: detailed dialog for selecting albums.
  • State Management: Integrated with TanStack Query for efficient data fetching and caching.
  • UI/UX:
    • Responsive alignment using Tailwind CSS.
    • Iconography from lucide-react (Pencil, Trash, etc.).
    • Frontend-only logic for "Album Covers" to avoid heavy backend queries.

Backend (backend/)

  • API Endpoints:
    • POST /albums/: Create new album.
    • GET /albums/: Retrieve all albums.
    • PUT /albums/{id}: Update album details.
    • DELETE /albums/{id}: Delete album.
    • POST /albums/{id}/images: Add images.
    • DELETE /albums/{id}/images/{img_id}: Remove single image.
  • Database: efficient SQLite queries with foreign key constraints for data integrity.

🧪 Verification

  • Create a new album "Vacation 2024".
  • Add 5 images to "Vacation 2024".
  • Verify the cover image is updated on the main list.
  • Rename to "Summer Trip".
  • Open album and remove 1 photo.
  • Delete the album and verify images persist in the main timeline.

Video Demonstration

https://drive.google.com/file/d/1DiI8t7PxSnzNbAE-QsUYDu2alfJuozjC/view?usp=sharing


Summary by CodeRabbit

  • New Features
    • Album management system: create, view, edit, and delete photo albums
    • Add or remove images from albums
    • Browse and organize images within albums using gallery view
    • Album cover previews and metadata management

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

@github-actions
Copy link
Copy Markdown
Contributor

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 25, 2025

📝 Walkthrough

Walkthrough

This pull request introduces a comprehensive album management system to the frontend, adding API integration, CRUD operations, and UI components, while also adjusting OpenAPI schema definitions for InputType parameter wrapping and removing additionalProperties from ImageInCluster metadata.

Changes

Cohort / File(s) Summary
OpenAPI Schema Updates
docs/backend/backend_python/openapi.json
Wrapped InputType references in query parameter schemas using allOf arrays; added "Input Type" titles; removed additionalProperties flag from ImageInCluster.Metadata field for stricter validation.
Album API Layer
frontend/src/api/api-functions/albums.ts, frontend/src/api/api-functions/index.ts
New TypeScript API wrapper module with CRUD operations: createAlbum, getAlbums, addImagesToAlbum, getAlbumImages, removeImageFromAlbum, deleteAlbum, updateAlbum; defines Album, CreateAlbumPayload, GetAlbumsResponse, GetAlbumImagesResponse, UpdateAlbumRequest interfaces.
Album Endpoints
frontend/src/api/apiEndpoints.ts
Added albumsEndpoints export with parameterized paths for POST/GET /albums/, album-specific operations including image management endpoints.
Album Dialog Component
frontend/src/components/Dialog/AddToAlbumDialog.tsx
New React dialog component that fetches and displays available albums, allowing users to add images to selected albums with loading states and error handling.
Album Image Card Component
frontend/src/components/Media/AlbumImageCard.tsx
New image card component specialized for album gallery display with selection support, favorite toggle, and remove-from-album action in dropdown menu.
Gallery Component Extensions
frontend/src/components/Media/ChronologicalGallery.tsx, frontend/src/components/Media/ImageCard.tsx
Added optional albumId and onRemoveFromAlbum props to support album-specific rendering; ImageCard now includes album action menu with add/remove functionality and AddToAlbumDialog integration.
Album Page Implementation
frontend/src/pages/Album/Album.tsx
New full-featured Album management page with state for album list, selection, image gallery; implements album CRUD operations, image management, inline editing, and chronological gallery view with error handling.
Route Configuration
frontend/src/routes/AppRoutes.tsx
Updated ROUTES.ALBUMS to render Album component replacing ComingSoon placeholder.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement, frontend, backend, UI

Poem

🐰 A warren of albums, so neatly arranged,
With images sorted, their destinies changed,
Add, remove, update—the rabbit's delight,
Gallery scrolls through each cherished sight!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
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.
Title check ❓ Inconclusive The title 'ALBUM Feature Added' is vague and generic, using non-descriptive phrasing that doesn't convey specific details about the album management system being introduced. Consider a more descriptive title such as 'Add album management system with CRUD operations and image organization' to clearly communicate the main feature being added.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ 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.

@PrathamMehta101
Copy link
Copy Markdown
Author

@rahulharpal1603 sir please take a look at this.

@github-actions
Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
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 (7)
frontend/src/components/Dialog/AddToAlbumDialog.tsx (2)

25-29: Consider adding fetchAlbums to the dependency array or wrapping it with useCallback.

The fetchAlbums function is referenced in the useEffect but not included in the dependency array, which may trigger ESLint's exhaustive-deps warning. While it works because fetchAlbums doesn't depend on changing state, wrapping it with useCallback would be cleaner.

🔎 Suggested refactor
+  const fetchAlbums = useCallback(async () => {
+    try {
+      const response = await getAlbums();
+      if (response.success) {
+        setAlbums(response.albums);
+      }
+    } catch (error) {
+      console.error('Failed to fetch albums:', error);
+    }
+  }, []);

   useEffect(() => {
     if (isOpen) {
       fetchAlbums();
     }
-  }, [isOpen]);
+  }, [isOpen, fetchAlbums]);
-
-  const fetchAlbums = async () => {
-    try {
-      const response = await getAlbums();
-      if (response.success) {
-        setAlbums(response.albums);
-      }
-    } catch (error) {
-      console.error('Failed to fetch albums:', error);
-    }
-  };

62-67: Consider adding a loading indicator during initial album fetch.

When the dialog opens, the user may briefly see "No albums found" while albums are being fetched. Consider tracking a separate loading state for the initial fetch to provide better feedback.

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

25-26: The albumId prop appears to be unused beyond conditional checks.

The albumId prop is declared but only used in the condition albumId && onRemoveFromAlbum (line 122). Since onRemoveFromAlbum alone indicates the album context, albumId may be redundant here. Consider removing it if it's not needed, or document its intended purpose if it's for future use.

Also applies to: 35-36

frontend/src/pages/Album/Album.tsx (2)

72-105: Potential N+1 query performance issue when fetching album covers.

This effect fetches getAlbumImages for each album individually to retrieve the cover image. For a large number of albums, this creates many sequential API calls. Consider optimizing by:

  1. Adding a backend endpoint that returns albums with their first image ID/path
  2. Batching the requests
  3. Implementing pagination/lazy loading for covers

The comment on line 84-85 acknowledges this limitation.


145-148: Avoid using any type for error handling.

Using error: any bypasses TypeScript's type safety. Consider using a proper error type or a type guard to safely access error properties.

🔎 Suggested approach
-    } catch (error: any) {
+    } catch (error: unknown) {
       console.error('Failed to create album:', error);
-      alert(error.response?.data?.message || 'Failed to create album');
+      const message = error instanceof Error ? error.message : 'Failed to create album';
+      alert(message);
     } finally {
frontend/src/api/api-functions/albums.ts (2)

23-30: Remove redundant try-catch blocks that only rethrow errors.

All functions use try-catch blocks that simply rethrow the error without any processing, logging, or transformation. This pattern is redundant with async/await—errors naturally propagate up the call stack. Removing these blocks will reduce code complexity and improve readability.

🔎 Example fix for createAlbum (apply pattern to all functions)
 export const createAlbum = async (payload: CreateAlbumPayload) => {
-  try {
-    const response = await apiClient.post(albumsEndpoints.createAlbum, payload);
-    return response.data;
-  } catch (error) {
-    throw error;
-  }
+  const response = await apiClient.post(albumsEndpoints.createAlbum, payload);
+  return response.data;
 };

Also applies to: 32-39, 41-53, 60-75, 77-89, 91-100, 110-123


18-21: Export response interfaces for type safety in consuming code.

The response interfaces GetAlbumsResponse and GetAlbumImagesResponse are not exported, which limits type safety when consuming these API functions. Components may need to type-check the response data, especially when using React Query or similar libraries.

🔎 Proposed fix
-interface GetAlbumsResponse {
+export interface GetAlbumsResponse {
  success: boolean;
  albums: Album[];
}
-interface GetAlbumImagesResponse {
+export interface GetAlbumImagesResponse {
  success: boolean;
  image_ids: string[];
}

Also applies to: 55-58

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 81286fa and c13d078.

📒 Files selected for processing (10)
  • docs/backend/backend_python/openapi.json
  • frontend/src/api/api-functions/albums.ts
  • frontend/src/api/api-functions/index.ts
  • frontend/src/api/apiEndpoints.ts
  • frontend/src/components/Dialog/AddToAlbumDialog.tsx
  • frontend/src/components/Media/AlbumImageCard.tsx
  • frontend/src/components/Media/ChronologicalGallery.tsx
  • frontend/src/components/Media/ImageCard.tsx
  • frontend/src/pages/Album/Album.tsx
  • frontend/src/routes/AppRoutes.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
frontend/src/routes/AppRoutes.tsx (2)
frontend/src/constants/routes.ts (1)
  • ROUTES (1-12)
frontend/src/api/api-functions/albums.ts (1)
  • Album (11-16)
frontend/src/components/Media/ImageCard.tsx (6)
frontend/src/lib/utils.ts (1)
  • cn (5-7)
frontend/src/components/ui/aspect-ratio.tsx (1)
  • AspectRatio (9-9)
frontend/src/components/ui/button.tsx (1)
  • Button (59-59)
frontend/src/components/ui/dropdown-menu.tsx (4)
  • DropdownMenu (240-240)
  • DropdownMenuTrigger (242-242)
  • DropdownMenuContent (243-243)
  • DropdownMenuItem (246-246)
frontend/src/components/Media/ImageTags.tsx (1)
  • ImageTags (11-78)
frontend/src/components/Dialog/AddToAlbumDialog.tsx (1)
  • AddToAlbumDialog (17-93)
🔇 Additional comments (12)
frontend/src/routes/AppRoutes.tsx (1)

12-12: LGTM!

The Album component is correctly imported and wired to the ROUTES.ALBUMS path, following the existing routing pattern.

Also applies to: 24-24

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

6-6: LGTM!

The albums module is correctly re-exported, making it accessible from the aggregated API functions index.

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

1120-1127: LGTM!

The schema restructuring using allOf with an added title is a valid OpenAPI 3.1 pattern, likely auto-generated from FastAPI/Pydantic. This improves documentation generation without changing API behavior.

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

24-26: LGTM!

The optional albumId and onRemoveFromAlbum props are cleanly added to support album-specific rendering behavior.

Also applies to: 35-37


172-193: LGTM!

The conditional rendering logic correctly chooses between AlbumImageCard and ImageCard based on the presence of album context props. Both branches receive the same click handler for consistency.

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

34-43: LGTM!

The album endpoints are correctly defined and align with the OpenAPI specification. The parameterized functions use proper string template literals for dynamic path segments.

frontend/src/components/Media/ImageCard.tsx (2)

90-140: LGTM!

The action cluster with the favourite toggle and dropdown menu is well-implemented. The e.stopPropagation() calls correctly prevent click events from bubbling up to the card's onClick handler.


153-157: LGTM!

The AddToAlbumDialog integration is correctly wired up with the local state management for opening/closing the dialog.

frontend/src/pages/Album/Album.tsx (2)

216-309: Well-structured album detail view.

The detail view implementation with inline editing, chronological gallery integration, and timeline scrollbar is well-organized. The conditional rendering based on albumImages.length provides appropriate feedback for empty albums.


312-374: LGTM!

The album grid view is well-implemented with responsive layout, cover image display with fallback, and proper handling of the empty state.

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

39-43: LGTM - Good defensive coding.

The null check for image?.id before calling toggleFavourite is appropriate and prevents potential runtime errors.

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

60-75: Verify POST method for getAlbumImages.

The getAlbumImages function uses a POST request (Line 65), which is unconventional for a read operation. While this is likely intentional to pass the optional password securely in the request body rather than as a query parameter, please confirm this aligns with the backend API design.

If the backend actually expects a GET request with password as a query parameter or header, update the implementation accordingly.

Comment on lines +4 to +9
interface CreateAlbumPayload {
name: string;
description?: string;
is_hidden?: boolean;
password?: string;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Export CreateAlbumPayload for type safety.

The CreateAlbumPayload interface is not exported, but UpdateAlbumRequest (Line 102) is. For consistency and to enable type-safe usage in consuming components, this interface should also be exported.

🔎 Proposed fix
-interface CreateAlbumPayload {
+export interface CreateAlbumPayload {
  name: string;
  description?: string;
  is_hidden?: boolean;
  password?: string;
}
📝 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
interface CreateAlbumPayload {
name: string;
description?: string;
is_hidden?: boolean;
password?: string;
}
export interface CreateAlbumPayload {
name: string;
description?: string;
is_hidden?: boolean;
password?: string;
}
🤖 Prompt for AI Agents
In frontend/src/api/api-functions/albums.ts around lines 4 to 9, the
CreateAlbumPayload interface is declared but not exported; export it so
consuming modules and components can use the type safely. Modify the declaration
to export the interface (export interface CreateAlbumPayload { ... }) and then
update any local references/imports as needed to use the exported type
consistently with UpdateAlbumRequest.

Comment on lines +48 to +52
className={cn(
'group bg-card cursor-pointer overflow-hidden rounded-lg border transition-all hover:shadow-md',
isSelected ? 'ring-2 ring-[#4088fa]' : '',
className,
)}
Copy link
Copy Markdown
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 Tailwind theme colors instead of hardcoded hex values.

The hardcoded color #4088fa should be replaced with a Tailwind theme color for consistency and maintainability. Hardcoded colors bypass the design system and make theme updates difficult.

🔎 Proposed fix

Replace the hardcoded hex values with Tailwind's primary/blue color scale:

          'group bg-card cursor-pointer overflow-hidden rounded-lg border transition-all hover:shadow-md',
-          isSelected ? 'ring-2 ring-[#4088fa]' : '',
+          isSelected ? 'ring-2 ring-blue-500' : '',
          className,
        )}
          {isSelected && (
-            <div className="absolute top-2 right-2 z-10 rounded-full bg-[#4088fa] p-1">
+            <div className="absolute top-2 right-2 z-10 rounded-full bg-blue-500 p-1">
              <Check className="h-4 w-4 text-white" />
            </div>

Also applies to: 59-63

🤖 Prompt for AI Agents
In frontend/src/components/Media/AlbumImageCard.tsx around lines 48-52 and
59-63, replace the hardcoded hex ring color (#4088fa) with a Tailwind theme
color class (e.g., ring-primary or ring-blue-500 depending on our design tokens)
so the component uses the project theme; update both conditional className
expressions to append the Tailwind ring class instead of the hex string, making
sure to use the correct shade used elsewhere in the codebase (primary or
blue-500) and keep the existing ring width (ring-2).

Comment on lines +66 to +70
<img
src={convertFileSrc(
image.thumbnailPath || image.path || '/placeholder.svg',
)}
alt={'Sample Title'}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix hardcoded alt text for accessibility.

The alt text is hardcoded as 'Sample Title', which violates accessibility standards (WCAG). Screen readers will announce the same generic text for all images, making it impossible for users with visual impairments to distinguish between images.

🔎 Proposed fix
-              alt={'Sample Title'}
+              alt={image.title || image.name || image.path.split('/').pop() || 'Image'}
📝 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
<img
src={convertFileSrc(
image.thumbnailPath || image.path || '/placeholder.svg',
)}
alt={'Sample Title'}
<img
src={convertFileSrc(
image.thumbnailPath || image.path || '/placeholder.svg',
)}
alt={image.title || image.name || image.path.split('/').pop() || 'Image'}
🤖 Prompt for AI Agents
In frontend/src/components/Media/AlbumImageCard.tsx around lines 66 to 70, the
img element uses a hardcoded alt ('Sample Title'); replace this with a
meaningful, accessible alt value derived from the image data (e.g.,
image.altText || image.title || image.filename || a localized fallback) and fall
back to an empty string ("") when the image is purely decorative; ensure you
trim/sanitize the string (avoid undefined) before passing it to alt so screen
readers receive useful or intentionally empty text.

Comment on lines +117 to +123
<DropdownMenuItem
onClick={() => onRemoveFromAlbum(image.id)}
className="text-red-500 focus:bg-red-50 focus:text-red-500"
>
<Trash className="mr-2 h-4 w-4" />
Remove from Album
</DropdownMenuItem>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add null check before calling onRemoveFromAlbum.

The code calls onRemoveFromAlbum(image.id) without verifying that image.id exists, which is inconsistent with the null check at Line 40. If image.id is undefined, this could lead to unexpected behavior in the parent component.

🔎 Proposed fix
                    <DropdownMenuItem
-                      onClick={() => onRemoveFromAlbum(image.id)}
+                      onClick={() => {
+                        if (image?.id) {
+                          onRemoveFromAlbum(image.id);
+                        }
+                      }}
                      className="text-red-500 focus:bg-red-50 focus:text-red-500"
                    >
🤖 Prompt for AI Agents
In frontend/src/components/Media/AlbumImageCard.tsx around lines 117 to 123, the
DropdownMenuItem calls onRemoveFromAlbum(image.id) without ensuring image.id is
defined; add a null/undefined guard so you only call onRemoveFromAlbum when
image.id exists (e.g., wrap the onClick handler to check if image?.id is truthy
before invoking, or disable/hide the menu item when image.id is missing) and
keep the existing styling/roles intact.

Comment on lines +203 to +214
const handleDeleteAlbum = async () => {
if (!selectedAlbum) return;

try {
await deleteAlbum(selectedAlbum.album_id);
setSelectedAlbum(null);
fetchAlbums();
} catch (error) {
console.error('Failed to delete album', error);
alert('Failed to delete album');
}
};
Copy link
Copy Markdown
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 a confirmation dialog before deleting an album.

The handleDeleteAlbum function immediately deletes the album without user confirmation. While album deletion preserves images in the main library, accidentally deleting an album could frustrate users.

🔎 Suggested implementation
 const handleDeleteAlbum = async () => {
   if (!selectedAlbum) return;

+  const confirmed = window.confirm(
+    `Are you sure you want to delete "${selectedAlbum.album_name}"? Images will remain in your library.`
+  );
+  if (!confirmed) return;
+
   try {
     await deleteAlbum(selectedAlbum.album_id);
     setSelectedAlbum(null);
     fetchAlbums();
   } catch (error) {
     console.error('Failed to delete album', error);
     alert('Failed to delete album');
   }
 };
📝 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 handleDeleteAlbum = async () => {
if (!selectedAlbum) return;
try {
await deleteAlbum(selectedAlbum.album_id);
setSelectedAlbum(null);
fetchAlbums();
} catch (error) {
console.error('Failed to delete album', error);
alert('Failed to delete album');
}
};
const handleDeleteAlbum = async () => {
if (!selectedAlbum) return;
const confirmed = window.confirm(
`Are you sure you want to delete "${selectedAlbum.album_name}"? Images will remain in your library.`
);
if (!confirmed) return;
try {
await deleteAlbum(selectedAlbum.album_id);
setSelectedAlbum(null);
fetchAlbums();
} catch (error) {
console.error('Failed to delete album', error);
alert('Failed to delete album');
}
};
🤖 Prompt for AI Agents
In frontend/src/pages/Album/Album.tsx around lines 203 to 214, the deletion
handler immediately deletes the selected album without confirming with the user;
add a confirmation step before calling deleteAlbum. Update handleDeleteAlbum to
first show a confirmation (e.g., window.confirm or trigger the existing modal
component) and only proceed with await deleteAlbum(selectedAlbum.album_id) when
the user explicitly confirms; if the user cancels, return early and do not call
setSelectedAlbum or fetchAlbums. Preserve the existing try/catch so errors are
still logged and alerted.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant