Skip to content

Albums: bug fixes and improvements - #1453

Merged
rohan-pandeyy merged 9 commits into
AOSSIE-Org:mainfrom
rohan-pandeyy:fix/album-fixes
Aug 3, 2026
Merged

Albums: bug fixes and improvements#1453
rohan-pandeyy merged 9 commits into
AOSSIE-Org:mainfrom
rohan-pandeyy:fix/album-fixes

Conversation

@rohan-pandeyy

@rohan-pandeyy rohan-pandeyy commented Aug 3, 2026

Copy link
Copy Markdown
Member

Fixes most of #1452.

What changed:

  • Deleting an album no longer freezes the app. Two copies of the same UI library were
    installed at once and kept taking focus from each other.
  • A failed delete no longer wipes the album from the list, and the confirmation box no
    longer gets stuck behind the error popup.
  • Albums and album photos load behind placeholder tiles instead of a full window overlay.
    Refresh reports itself on the button instead of blocking the page.
  • The overlay can no longer get stuck if you leave a page while it is still loading.
  • The album cover is now the first photo in the album. The right click "Set as Cover" menu
    and its endpoint are gone.
  • Locked albums no longer show a photo from inside them as their cover.
  • Album created and updated dates were being invented on every read, so they are gone.
  • Album cards are a bit smaller.

Still open on #1452: opening one album still downloads the whole library, removing photos
has no confirmation, and locked albums still show their photo count.

Note: this branch sits on top of #1408, so the diff includes those commits until that one
merges.

Summary by CodeRabbit

  • New Features

    • Album covers now derive automatically from linked images.
    • Locked albums no longer display cover images.
    • Added loading skeletons for albums and album images.
    • Refresh and deletion interactions provide clearer loading and dialog behavior.
    • Album grids and cards use a more compact, responsive layout.
  • Removed

    • Removed manual album cover selection and its API endpoint.
  • Bug Fixes

    • Improved album refresh, deletion, and image-removal state handling.
  • Tests

    • Added coverage for locked albums, loading states, refreshes, deletions, and cover selection behavior.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 537bba99-d076-4cce-9a6d-830a70c10308

📥 Commits

Reviewing files that changed from the base of the PR and between 6b45479 and 382978d.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • backend/app/database/albums.py
  • backend/app/routes/albums.py
  • backend/app/schemas/album.py
  • backend/tests/test_albums.py
  • backend/tests/test_albums_db.py
  • docs/backend/backend_python/openapi.json
  • frontend/package.json
  • frontend/src/api/api-functions/albums.ts
  • frontend/src/api/apiEndpoints.ts
  • frontend/src/components/Albums/AlbumCard.tsx
  • frontend/src/pages/Album/Album.tsx
  • frontend/src/pages/Album/AlbumDetail.tsx
  • frontend/src/pages/__tests__/Album.test.tsx
  • frontend/src/pages/__tests__/AlbumDetail.test.tsx
  • frontend/src/types/Album.ts
💤 Files with no reviewable changes (5)
  • backend/app/schemas/album.py
  • frontend/src/api/apiEndpoints.ts
  • frontend/src/api/api-functions/albums.ts
  • docs/backend/backend_python/openapi.json
  • frontend/src/types/Album.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • backend/tests/test_albums.py
  • backend/tests/test_albums_db.py
  • frontend/package.json
  • frontend/src/components/Albums/AlbumCard.tsx
  • frontend/src/pages/tests/AlbumDetail.test.tsx
  • backend/app/database/albums.py
  • backend/app/routes/albums.py
  • frontend/src/pages/tests/Album.test.tsx
  • frontend/src/pages/Album/Album.tsx
  • frontend/src/pages/Album/AlbumDetail.tsx

Walkthrough

Album covers now derive from the first linked image and remain hidden for locked albums. Cover mutation is removed across the stack. Album pages use local skeletons, non-blocking refresh, cache invalidation, and updated deletion behavior. Radix UI dependencies are updated.

Changes

Album cover and loading behavior

Layer / File(s) Summary
Album cover contract and derivation
backend/app/database/albums.py, backend/app/routes/albums.py, backend/app/schemas/album.py, backend/tests/test_albums*.py, docs/backend/..., frontend/src/api/...
Album responses derive public cover paths from linked images and omit them for locked albums. The cover update route, schema, helpers, and frontend endpoint are removed. Backend tests cover selection and lock behavior.
Album page loading and deletion
frontend/src/pages/Album/Album.tsx, frontend/src/components/Albums/AlbumCard.tsx, frontend/src/types/Album.ts, frontend/src/pages/__tests__/Album.test.tsx
The album page adds skeletons, non-blocking refresh state, query invalidation after deletion, and revised deletion handling. Album cards use denser styling, and timestamp fields are removed from the interface.
Album detail loading and image interaction
frontend/src/pages/Album/AlbumDetail.tsx, frontend/src/pages/__tests__/AlbumDetail.test.tsx
The detail page adds image skeletons, removes cover-selection menus, retains selection indicators, and invalidates album data after image removal. Tests cover loading, unmount cleanup, and menu removal.

Radix UI dependency updates

Layer / File(s) Summary
Radix dependency versions
frontend/package.json
Thirteen Radix UI dependency constraints are updated.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AlbumPage
  participant AlbumQuery
  participant AlbumSkeleton
  participant AlbumGrid
  AlbumPage->>AlbumQuery: request albums
  AlbumQuery-->>AlbumPage: report loading state
  AlbumPage->>AlbumSkeleton: render placeholders
  AlbumQuery-->>AlbumPage: return album data
  AlbumPage->>AlbumGrid: render album cards
Loading

Possibly related issues

Possibly related PRs

Suggested labels: Python, TypeScript/JavaScript, Documentation

Poem

A rabbit sees covers bloom from linked rows,
Locked albums hide what nobody shows.
Skeletons wait while the pages load,
Refresh and deletion follow the code.
Cover menus hop away from view.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title identifies the album scope but uses broad terms that do not describe the primary fixes, cover handling changes, or loading improvements. Use a specific title that summarizes the main album changes, such as cover derivation, loading behavior, and deletion fixes.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

frontend/package.json

Parsing error: Missing semicolon. (2:8)


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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 7

🧹 Nitpick comments (8)
.github/workflows/lint.yml (1)

33-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The exclusion assertions can pass without any formatter installed. The hook rewrites files only when the matching formatter exists, and CI runs the hook test before frontend dependencies are installed. Both sites must change so the test proves exclusion rather than absence.

  • .github/workflows/lint.yml#L33-L35: move the Agent hook tests step below the Install frontend dependencies step so frontend/node_modules/prettier exists when the test runs.
  • scripts/agent-format-hook.test.mjs#L141-L153: add a negative control that formats a non-excluded temporary .json file inside the repository, and skip the exclusion assertion when that control file is unchanged.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/lint.yml around lines 33 - 35, The Agent hook tests step
in .github/workflows/lint.yml#L33-L35 must move below Install frontend
dependencies so the formatter is available. In
scripts/agent-format-hook.test.mjs#L141-L153, add a negative-control temporary
.json file inside the repository, format it, and skip the exclusion assertion
when that control file remains unchanged; update both affected sites
accordingly.
scripts/agent-format-hook.mjs (1)

48-64: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider a few more Ruff invocation forms in the guard.

isRuffFormatCommand anchors on ^ruff\s+format, so these forms pass through unblocked:

  • a path-qualified binary: backend/venv/bin/ruff format . or ./.venv/bin/ruff format
  • npx ruff format and pipenv run ruff format
  • environment assignments after a runner: uv run RUFF_CACHE_DIR=/tmp ruff format ., because ENV_PREFIX is stripped only once before the peel loop

The last case is a one-line fix: move the ENV_PREFIX strip inside the peel loop and add npx/pipenv to RUNNER_PREFIX.

♻️ Proposed widening of the guard
-const RUNNER_PREFIX = /^(?:uv|poetry|pipx|pdm|hatch|rye)\s+run\s+/;
+const RUNNER_PREFIX = /^(?:uv|poetry|pipx|pdm|hatch|rye|pipenv)\s+run\s+|^npx\s+(?:-y\s+)?/;
 const PYTHON_M_PREFIX = /^python[0-9.]*\s+-m\s+/;
 const ENV_PREFIX = /^(?:\w+=\S*\s+)+/;
+// Any leading path segments before the binary name.
+const PATH_PREFIX = /^\S*[\\/]/;
 
 /** True when a single command segment invokes the Ruff formatter. */
 function isRuffFormatCommand(segment) {
-  let s = segment.trim().replace(ENV_PREFIX, '');
+  let s = segment.trim();
 
   // Peel runner wrappers: `uv run ruff format`, `python -m ruff format`.
   for (;;) {
-    const next = s.replace(RUNNER_PREFIX, '').replace(PYTHON_M_PREFIX, '');
+    const next = s
+      .replace(ENV_PREFIX, '')
+      .replace(RUNNER_PREFIX, '')
+      .replace(PYTHON_M_PREFIX, '');
     if (next === s) break;
     s = next;
   }
 
-  return /^ruff\s+format\b/.test(s);
+  return /^ruff\s+format\b/.test(s.replace(PATH_PREFIX, ''));
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/agent-format-hook.mjs` around lines 48 - 64, Update
isRuffFormatCommand and its prefix handling to recognize path-qualified Ruff
binaries, including relative and virtual-environment paths, and add npx and
pipenv to RUNNER_PREFIX. Move ENV_PREFIX stripping into the runner-peeling loop
so assignments after wrappers such as uv run are removed before checking the
command, while preserving detection of Ruff’s format subcommand.
scripts/agent-format-hook.test.mjs (1)

144-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The probe writes into a tracked source tree.

mkdtempSync creates __hook_probe__-* inside frontend/src/components/ui. The finally block removes it on a normal exit. If the process is interrupted, the directory stays behind and pollutes git status. The exclusion assertion only needs isExcluded to see the path prefix, so a fixed non-existent path is enough for the subprocess exit-code check, and the byte-for-byte check can use a temp directory whose relative path still starts with frontend/src/components/ui/. Alternatively, add __hook_probe__-* to .gitignore.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/agent-format-hook.test.mjs` around lines 144 - 157, Move the
exclusion probe setup in the test block around runHook and isExcluded out of the
tracked frontend source tree. Use a fixed or independently created temporary
path that still has the frontend/src/components/ui/ prefix for exclusion
detection, while preserving the unchanged-file assertion and cleanup behavior;
alternatively, ignore the __hook_probe__-* pattern without changing the test’s
assertions.
agent-kit/skills/add-backend-endpoint/SKILL.md (1)

70-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Require a malformed-row test for list endpoints.

The skill mandates row-by-row list construction at Line 58-59, but this checklist only requires success, empty, and one failure path. Add a test with one valid record and one malformed record. backend/tests/test_videos.py already demonstrates this pattern.

As per path instructions, test code must be automated, comprehensive, and cover critical functionality.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent-kit/skills/add-backend-endpoint/SKILL.md` around lines 70 - 74, Update
the tests checklist for backend list endpoints to explicitly require a
malformed-row test containing one valid record and one malformed record, while
retaining the success, empty, and failure-path coverage. Reference the existing
pattern in backend/tests/test_videos.py and ensure the test validates row-by-row
handling.

Source: Path instructions

agent-kit/templates/route.py.md (1)

39-39: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add the required return annotation.

get_all_<resource> is a backend route function without a return type. Add -> GetAll<Resource>Response after adapting the template placeholders.

Proposed fix
-def get_all_<resource>():
+def get_all_<resource>() -> GetAll<Resource>Response:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent-kit/templates/route.py.md` at line 39, Add the required return
annotation to the get_all_<resource> route template, adapting the resource
placeholder so the function returns GetAll<Resource>Response while preserving
the existing route signature and behavior.

Source: Coding guidelines

docs/ai-contributing.md (1)

66-73: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pin or validate third-party skill installations.

npx skills add shadcn/ui does not record the CLI version or the installed skill revision. Different contributors can execute different third-party code and commit different files. Document pinned revisions or require review of generated files before committing them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ai-contributing.md` around lines 66 - 73, Update the third-party skill
installation guidance around npx skills add shadcn/ui to require recording and
pinning the CLI version and installed skill revision, or reviewing the generated
files before committing them. Preserve the instruction to leave the skill in the
installer-managed directory and document the adopted skill and rationale in
third-party-skills.md.
agent-kit/templates/slice.ts.md (1)

61-66: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Type the exported selectors explicitly.

The selectors define an exported TypeScript API boundary, but their return types are inferred. Declare Item[] and Item | null explicitly.

Proposed fix
+import type { Item } from '`@/types/Media`';
 import { RootState } from '`@/app/store`';

-export const selectItems = (state: RootState) => state.feature.items;
+export const selectItems = (state: RootState): Item[] => state.feature.items;

-export const selectCurrentItem = (state: RootState) =>
+export const selectCurrentItem = (state: RootState): Item | null =>
   state.feature.currentViewIndex >= 0
     ? state.feature.items[state.feature.currentViewIndex]
     : null;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent-kit/templates/slice.ts.md` around lines 61 - 66, Update the exported
selectors selectItems and selectCurrentItem with explicit return types of Item[]
and Item | null, respectively, while preserving their existing state access and
conditional behavior.

Source: Coding guidelines

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

36-60: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider batching the per-album cover lookup.

db_get_album_cover_path is called once per unlocked album inside this loop. Each call opens a new SQLite connection. This adds a second per-album query on top of the existing db_get_album_images call, so the number of DB round trips for GET /albums/ scales linearly with the album count.

A single query that groups album_images by album_id and selects the minimum rowid per group can retrieve all covers in one round trip, while the loop still builds the response row by row.

♻️ Proposed batched cover lookup
+def db_get_all_album_cover_paths() -> dict[str, str]:
+    """Cover path per album, in one round trip."""
+    conn = sqlite3.connect(DATABASE_PATH)
+    cursor = conn.cursor()
+    try:
+        cursor.execute(
+            """
+            SELECT ai.album_id, i.path
+            FROM album_images ai
+            JOIN images i ON i.id = ai.image_id
+            WHERE ai.rowid = (
+                SELECT MIN(rowid) FROM album_images WHERE album_id = ai.album_id
+            )
+            """
+        )
+        return dict(cursor.fetchall())
+    finally:
+        conn.close()
 def get_albums():
     """Get all albums. Always returns both locked and unlocked albums."""
     albums = db_get_all_albums()
     album_list = []
+    covers = db_get_all_album_cover_paths()
     for album in albums:
         # Get image count for each album
         image_ids = db_get_album_images(album[0])
         image_count = len(image_ids)
         is_locked = bool(album[3])

         album_list.append(
             Album(
                 album_id=album[0],
                 album_name=album[1],
                 description=album[2] or "",
                 is_locked=is_locked,
                 cover_image_path=(
-                    None if is_locked else db_get_album_cover_path(album[0])
+                    None if is_locked else covers.get(album[0])
                 ),
                 image_count=image_count,
             )
         )
     return GetAlbumsResponse(success=True, albums=album_list)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/routes/albums.py` around lines 36 - 60, Update get_albums to
batch cover retrieval before the response-building loop, using one query that
groups album_images by album_id and selects each group’s minimum rowid, then map
those results by album ID. Replace per-album db_get_album_cover_path calls with
lookups in the batched mapping while preserving None for locked albums.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent-kit/providers/codex.md`:
- Around line 23-25: Update the Codex hook limitation statement in the pre-PR
guidance to say that this repository does not configure Codex hooks, rather than
claiming Codex lacks hook support. Preserve the instruction to run the listed
checks manually, while noting that equivalent hook wiring can be added to
automate them.

In `@agent-kit/README.md`:
- Around line 69-77: Update the third-party skill workflow documentation around
“Adopting a third-party skill” to require a reviewed immutable revision, such as
a commit or tag, for both installation and updates. Instruct users to record the
selected revision alongside the adoption rationale in
references/third-party-skills.md, while preserving the installer-managed skill
locations and update guidance.

In `@agent-kit/references/ci-gates.md`:
- Line 14: Pin every external package used by documented npx commands, using
reviewed exact versions or repository-managed dependencies. Update
agent-kit/README.md lines 72 and 76, agent-kit/providers/codex.md lines 72-72,
agent-kit/references/third-party-skills.md lines 8-10 and 41-43,
docs/ai-contributing.md lines 67 and 71, agent-kit/references/ci-gates.md line
14, and agent-kit/skills/pre-pr-check/SKILL.md line 33; apply the pinning
consistently to markdownlint-cli2, skills update, and the candidate skills add
shadcn/ui commands.

In `@agent-kit/skills/add-backend-endpoint/SKILL.md`:
- Around line 81-85: Update the validation commands in the add-backend-endpoint
playbook to include both backend and sync-microservice PyInstaller build gates,
matching the commands defined in agent-kit/references/ci-gates.md; alternatively
delegate validation to the corrected pre-pr-check skill that already runs them.

In `@agent-kit/skills/pre-pr-check/SKILL.md`:
- Around line 10-12: Update the pre-PR gate instructions in SKILL.md to match
the CI gate set defined in ci-gates.md: always include the agent hook tests, add
both PyInstaller build checks, and remove cargo test. Revise the “same checks as
CI” wording if needed, and preserve CI’s required execution order while
reporting every check without stopping after failures.

In `@backend/AGENTS.md`:
- Around line 69-72: Make the area-specific check commands explicit about their
working directories: in backend/AGENTS.md lines 69-72, use the repository-root
config path or wrap the command with (cd backend && ...); in frontend/AGENTS.md
lines 82-86, wrap npm checks with (cd frontend && ...); and in
frontend/src-tauri/AGENTS.md lines 24-31, wrap Cargo checks with (cd
frontend/src-tauri && ...), preserving the repository-root AGENTS.md command
contract.

In `@scripts/agent-format-hook.test.mjs`:
- Line 5: Update the comment above the test hook to reference
.github/workflows/lint.yml and its Linting job instead of the stale
.github/workflows/pr-check-tests.yml reference.

---

Nitpick comments:
In @.github/workflows/lint.yml:
- Around line 33-35: The Agent hook tests step in
.github/workflows/lint.yml#L33-L35 must move below Install frontend dependencies
so the formatter is available. In scripts/agent-format-hook.test.mjs#L141-L153,
add a negative-control temporary .json file inside the repository, format it,
and skip the exclusion assertion when that control file remains unchanged;
update both affected sites accordingly.

In `@agent-kit/skills/add-backend-endpoint/SKILL.md`:
- Around line 70-74: Update the tests checklist for backend list endpoints to
explicitly require a malformed-row test containing one valid record and one
malformed record, while retaining the success, empty, and failure-path coverage.
Reference the existing pattern in backend/tests/test_videos.py and ensure the
test validates row-by-row handling.

In `@agent-kit/templates/route.py.md`:
- Line 39: Add the required return annotation to the get_all_<resource> route
template, adapting the resource placeholder so the function returns
GetAll<Resource>Response while preserving the existing route signature and
behavior.

In `@agent-kit/templates/slice.ts.md`:
- Around line 61-66: Update the exported selectors selectItems and
selectCurrentItem with explicit return types of Item[] and Item | null,
respectively, while preserving their existing state access and conditional
behavior.

In `@backend/app/routes/albums.py`:
- Around line 36-60: Update get_albums to batch cover retrieval before the
response-building loop, using one query that groups album_images by album_id and
selects each group’s minimum rowid, then map those results by album ID. Replace
per-album db_get_album_cover_path calls with lookups in the batched mapping
while preserving None for locked albums.

In `@docs/ai-contributing.md`:
- Around line 66-73: Update the third-party skill installation guidance around
npx skills add shadcn/ui to require recording and pinning the CLI version and
installed skill revision, or reviewing the generated files before committing
them. Preserve the instruction to leave the skill in the installer-managed
directory and document the adopted skill and rationale in third-party-skills.md.

In `@scripts/agent-format-hook.mjs`:
- Around line 48-64: Update isRuffFormatCommand and its prefix handling to
recognize path-qualified Ruff binaries, including relative and
virtual-environment paths, and add npx and pipenv to RUNNER_PREFIX. Move
ENV_PREFIX stripping into the runner-peeling loop so assignments after wrappers
such as uv run are removed before checking the command, while preserving
detection of Ruff’s format subcommand.

In `@scripts/agent-format-hook.test.mjs`:
- Around line 144-157: Move the exclusion probe setup in the test block around
runHook and isExcluded out of the tracked frontend source tree. Use a fixed or
independently created temporary path that still has the
frontend/src/components/ui/ prefix for exclusion detection, while preserving the
unchanged-file assertion and cleanup behavior; alternatively, ignore the
__hook_probe__-* pattern without changing the test’s assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9a9e02e1-406d-4e67-821e-aec738cd9b21

📥 Commits

Reviewing files that changed from the base of the PR and between 3cd313c and 6b45479.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (61)
  • .agents/skills/add-backend-endpoint/SKILL.md
  • .agents/skills/add-frontend-feature/SKILL.md
  • .agents/skills/onboard/SKILL.md
  • .agents/skills/pre-pr-check/SKILL.md
  • .claude/agents/pictopy-reviewer.md
  • .claude/settings.json
  • .claude/skills/add-backend-endpoint/SKILL.md
  • .claude/skills/add-frontend-feature/SKILL.md
  • .claude/skills/onboard/SKILL.md
  • .claude/skills/pre-pr-check/SKILL.md
  • .cursor/rules/pictopy.mdc
  • .github/copilot-instructions.md
  • .github/workflows/lint.yml
  • .gitignore
  • AGENTS.md
  • CLAUDE.md
  • CONTRIBUTING.md
  • agent-kit/README.md
  • agent-kit/providers/claude-code.md
  • agent-kit/providers/codex.md
  • agent-kit/providers/copilot.md
  • agent-kit/providers/cursor.md
  • agent-kit/references/backend-endpoint-walkthrough.md
  • agent-kit/references/ci-gates.md
  • agent-kit/references/frontend-feature-walkthrough.md
  • agent-kit/references/third-party-skills.md
  • agent-kit/skills/add-backend-endpoint/SKILL.md
  • agent-kit/skills/add-frontend-feature/SKILL.md
  • agent-kit/skills/onboard/SKILL.md
  • agent-kit/skills/pre-pr-check/SKILL.md
  • agent-kit/templates/component.test.tsx.md
  • agent-kit/templates/database.py.md
  • agent-kit/templates/route.py.md
  • agent-kit/templates/slice.ts.md
  • backend/AGENTS.md
  • backend/CLAUDE.md
  • backend/app/database/albums.py
  • backend/app/routes/albums.py
  • backend/app/schemas/album.py
  • backend/tests/test_albums.py
  • backend/tests/test_albums_db.py
  • docs/ai-contributing.md
  • docs/backend/backend_python/openapi.json
  • frontend/AGENTS.md
  • frontend/CLAUDE.md
  • frontend/package.json
  • frontend/src-tauri/AGENTS.md
  • frontend/src-tauri/CLAUDE.md
  • frontend/src/api/api-functions/albums.ts
  • frontend/src/api/apiEndpoints.ts
  • frontend/src/components/Albums/AlbumCard.tsx
  • frontend/src/pages/Album/Album.tsx
  • frontend/src/pages/Album/AlbumDetail.tsx
  • frontend/src/pages/__tests__/Album.test.tsx
  • frontend/src/pages/__tests__/AlbumDetail.test.tsx
  • frontend/src/types/Album.ts
  • mkdocs.yml
  • scripts/agent-format-hook.mjs
  • scripts/agent-format-hook.test.mjs
  • sync-microservice/AGENTS.md
  • sync-microservice/CLAUDE.md
💤 Files with no reviewable changes (5)
  • backend/app/schemas/album.py
  • docs/backend/backend_python/openapi.json
  • frontend/src/api/apiEndpoints.ts
  • frontend/src/api/api-functions/albums.ts
  • frontend/src/types/Album.ts

Comment on lines +23 to +25
- Codex has no equivalent of Claude Code's hooks, so the automatic formatting does not
apply. Run the checks in `agent-kit/skills/pre-pr-check/SKILL.md` manually before
pushing — especially `pre-commit`, since nothing will have reformatted Python for you.

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.

🎯 Functional Correctness | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

OpenAI Codex hooks documentation codex hooks

💡 Result:

In the context of OpenAI Codex, "hooks" refer to an extensibility framework that allows users to inject custom scripts and logic into the agentic loop [1][2]. This system is distinct from general API webhooks [3]. Key features and documentation details for Codex hooks include: Functionality: Hooks enable customization of the agent's behavior at various points in its execution lifecycle, such as session starts, tool usage (pre/post), and permission requests [1][4]. Configuration: Codex discovers hooks through configuration files, specifically hooks.json or inline [hooks] tables within config.toml files [1][5]. Security and Trust: Because hooks allow for script execution, Codex implements a trust model. Non-managed command hooks require the user to review and explicitly trust the definition, which is then recorded against the hook's hash [1][2]. Users can manage these using the /hooks command in the Codex CLI [1]. Management: Enterprise administrators can enforce specific hook configurations using requirements.toml files, which can override or restrict local user, project, or session hook settings [1][6]. Common Hook Events: The framework includes various trigger points, including: - Session lifecycle: SessionStart, SessionEnd [1][2]. - Agent actions: PreToolUse, PostToolUse, SubagentStart, SubagentStop [1][4]. - User interaction: UserPromptSubmit [1][4]. - System tasks: PreCompact, PostCompact, PermissionRequest [1][4]. For further technical details, developers can refer to the official Codex developer documentation at https://developers.openai.com/codex/hooks [1]. Please note that these hooks are specific to the Codex agent environment and should not be confused with standard OpenAI API webhooks used for event notifications [3].

Citations:


🌐 Web query:

OpenAI Codex hooks generally available

💡 Result:

As of August 3, 2026, OpenAI Codex remains a generally available service and is actively supported [1][2]. It is accessible through several platforms, including the ChatGPT desktop application, the web interface, the Codex CLI, and various IDE extensions [1][3]. OpenAI has explicitly confirmed that its standalone Codex application is "here to stay," despite the integration of its capabilities into broader platforms like ChatGPT Work [2]. While the service remains available, OpenAI has recently completed a series of model deprecations [4][5]. On July 23, 2026, OpenAI permanently shut down several legacy Codex-lineage models within the API [4][6][5]. Developers and users who were relying on these older model versions were required to migrate to newer, supported alternatives, such as the GPT-5.6 series, to maintain functionality [7][4][5]. Users can continue to use Codex by signing in with their ChatGPT account (e.g., Plus, Pro, Business, or Enterprise plans) or by utilizing an API key with the updated, supported model versions [3][8].

Citations:


🌐 Web query:

site:developers.openai.com/codex/hooks hooks.json config.toml Codex hooks documentation

💡 Result:

In the Codex ecosystem, hooks allow for the execution of custom logic (such as commands) in response to specific events, such as before or after tool use. Codex discovers these hooks by looking for configuration files located next to active configuration layers [1]. Configuration Methods Codex supports defining hooks using either a standalone hooks.json file or by including an inline [hooks] table directly within a config.toml file [1]. Common file locations include: - ~/.codex/hooks.json or ~/.codex/config.toml (User-level) - /.codex/hooks.json or /.codex/config.toml (Project-level) If a single configuration layer contains both a hooks.json file and inline hooks in config.toml, Codex will merge them and issue a warning at startup; it is recommended to use only one representation per layer [1]. Configuration and Precedence - Feature Toggle: Hooks are enabled by default. To disable them, you can set [features].hooks = false in your config.toml file [1]. - Precedence: Codex processes multiple hook locations. If multiple hooks are found, they are all executed. - Enforcement: Administrators can enforce hook usage via requirements.toml by pinning [features].hooks = true [1]. Example Syntax The following shows an example of a PreToolUse hook defined in TOML: [[hooks.PreToolUse]] matcher = "^Bash$" [[hooks.PreToolUse.hooks]] type = "command" command = '/usr/bin/python3 "$(git rev-parse --show-toplevel)/.codex/hooks/pre_tool_use_policy.py"' timeout = 30 statusMessage = "Checking Bash command" For JSON, the structure mirrors this hierarchy, using "hooks" as the key within a hooks.json file [1].

Citations:


Reword the Codex hook limitation as a repository configuration gap.

Codex supports hooks via hooks.json and inline [hooks] tables in config.toml, so this line should not say Codex has no hook equivalent. Say this repository does not configure Codex hooks, and users must run the listed pre-PR checks manually unless they add equivalent hook wiring.

Proposed documentation change
-Codex has no equivalent of Claude Code's hooks, so the automatic formatting does not
-apply. Run the checks in `agent-kit/skills/pre-pr-check/SKILL.md` manually before
-pushing — especially `pre-commit`, since nothing will have reformatted Python for you.
+This repository does not configure Codex hooks, so automatic formatting does not apply
+here. Run the checks in `agent-kit/skills/pre-pr-check/SKILL.md` manually unless
+equivalent Codex hooks are configured.

[low_effort.and_high_reward]

📝 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
- Codex has no equivalent of Claude Code's hooks, so the automatic formatting does not
apply. Run the checks in `agent-kit/skills/pre-pr-check/SKILL.md` manually before
pushing — especially `pre-commit`, since nothing will have reformatted Python for you.
This repository does not configure Codex hooks, so automatic formatting does not apply
here. Run the checks in `agent-kit/skills/pre-pr-check/SKILL.md` manually unless
equivalent Codex hooks are configured.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent-kit/providers/codex.md` around lines 23 - 25, Update the Codex hook
limitation statement in the pre-PR guidance to say that this repository does not
configure Codex hooks, rather than claiming Codex lacks hook support. Preserve
the instruction to run the listed checks manually, while noting that equivalent
hook wiring can be added to automate them.

Comment thread agent-kit/README.md
Comment on lines +69 to +77
## Adopting a third-party skill

```bash
npx skills add <owner>/<repo>
```

This installs into `.claude/skills/` or `.agents/skills/` and stays updatable with
`npx skills update`. Leave it where the installer put it — moving it here breaks updates —
and record what you adopted and why in `references/third-party-skills.md`.

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Pin third-party skill revisions before installation or update.

The documented npx skills add <owner>/<repo> and npx skills update workflow does not require a reviewed immutable revision. A changed external skill can alter agent instructions and tool requests after repository review. Require a reviewed commit, tag, or equivalent immutable reference. Record that revision in agent-kit/references/third-party-skills.md.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent-kit/README.md` around lines 69 - 77, Update the third-party skill
workflow documentation around “Adopting a third-party skill” to require a
reviewed immutable revision, such as a commit or tag, for both installation and
updates. Instruct users to record the selected revision alongside the adoption
rationale in references/third-party-skills.md, while preserving the
installer-managed skill locations and update guidance.


| Check | Local command |
| --- | --- |
| Markdown | `npx markdownlint-cli2 --config .github/.markdownlint-cli2.jsonc` |

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

if rg -n --glob '*.md' --glob '*.yml' --glob '*.yaml' \
  'npx[[:space:]]+(markdownlint-cli2|skills)([[:space:]]|$)' .
then
  echo "Unpinned npx command found." >&2
  exit 1
fi

Repository: AOSSIE-Org/PictoPy

Length of output: 1172


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf 'Relevant npx occurrences:\n'
rg -n --glob '*.md' --glob '*.yml' --glob '*.yaml' \
  'npx[[:space:]]+([^[:space:]]+)([[:space:]]|$)' .

printf '\nRelevant files:\n'
for f in \
  agent-kit/README.md \
  agent-kit/providers/codex.md \
  agent-kit/references/ci-gates.md \
  agent-kit/references/third-party-skills.md \
  agent-kit/skills/pre-pr-check/SKILL.md \
  docs/ai-contributing.md
do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    sed -n '1,140p' "$f"
  else
    echo "MISSING: $f"
  fi
done

printf '\nDependency/config markers:\n'
rg -n --hidden --glob '!node_modules/**' --glob '!dist/**' --glob '!build/**' \
  'markdownlint-cli2|skills|npx|package-manager|dependencies|devDependencies|toolchain|pyinstaller' \
  .github .vercel .github || true

Repository: AOSSIE-Org/PictoPy

Length of output: 23396


Pin every external package invoked through npx.

These instructions execute unpinned remote packages; a new publication may change install behavior or execute arbitrary code. Use reviewed exact versions or repository-managed dependencies for every documented npx command.

Remaining unpinned occurrences:

  • agent-kit/README.md:72 and same command across agent-kit/providers/codex.md, agent-kit/references/third-party-skills.md
  • agent-kit/README.md:76, agent-kit/references/third-party-skills.md:14, docs/ai-contributing.md:71 for npx skills update
  • agent-kit/references/ci-gates.md:14 and agent-kit/skills/pre-pr-check/SKILL.md:33 for npx markdownlint-cli2
  • Candidate npx skills add shadcn/ui in agent-kit/references/third-party-skills.md:41 and docs/ai-contributing.md:67
🧰 Tools
🪛 LanguageTool

[uncategorized] ~14-~14: The official name of this software platform is spelled with a capital “H”.
Context: ...al command | | --- | --- | | Markdown | npx markdownlint-cli2 --config .github/.markdownlint-cli2.jsonc | | Frontend ...

(GITHUB)

📍 Affects 3 files
  • agent-kit/references/ci-gates.md#L14-L14 (this comment)
  • agent-kit/references/third-party-skills.md#L8-L10
  • agent-kit/references/third-party-skills.md#L41-L43
  • agent-kit/skills/pre-pr-check/SKILL.md#L33-L33
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent-kit/references/ci-gates.md` at line 14, Pin every external package used
by documented npx commands, using reviewed exact versions or repository-managed
dependencies. Update agent-kit/README.md lines 72 and 76,
agent-kit/providers/codex.md lines 72-72,
agent-kit/references/third-party-skills.md lines 8-10 and 41-43,
docs/ai-contributing.md lines 67 and 71, agent-kit/references/ci-gates.md line
14, and agent-kit/skills/pre-pr-check/SKILL.md line 33; apply the pinning
consistently to markdownlint-cli2, skills update, and the candidate skills add
shadcn/ui commands.

Source: Linters/SAST tools

Comment thread agent-kit/skills/add-backend-endpoint/SKILL.md
Comment thread agent-kit/skills/pre-pr-check/SKILL.md
Comment thread backend/AGENTS.md
// Regression tests for the agent format hook.
//
// Run: node scripts/agent-format-hook.test.mjs
// Also runs in CI, in the Linting job of .github/workflows/pr-check-tests.yml.

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the stale workflow reference.

The CI step was added to .github/workflows/lint.yml, in the Linting job. This comment names .github/workflows/pr-check-tests.yml.

📝 Proposed fix
-// Also runs in CI, in the Linting job of .github/workflows/pr-check-tests.yml.
+// Also runs in CI, in the Linting job of .github/workflows/lint.yml.
📝 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
// Also runs in CI, in the Linting job of .github/workflows/pr-check-tests.yml.
// Also runs in CI, in the Linting job of .github/workflows/lint.yml.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/agent-format-hook.test.mjs` at line 5, Update the comment above the
test hook to reference .github/workflows/lint.yml and its Linting job instead of
the stale .github/workflows/pr-check-tests.yml reference.

Deleting an album goes dropdown menu -> confirm dialog, so a Radix menu
and a Radix dialog are mounted at the same moment. Radix keeps its focus
trap in a module-level stack and pauses the previously active scope, but
react-dialog had drifted ahead of every other Radix package and pinned
its own private copies of the shared internals. Two copies meant two
stacks, so neither scope paused the other and they yanked focus back and
forth until the call stack blew, pegging the main thread until reload.

Aligning the Radix packages to one generation collapses all 16 duplicated
internals to a single copy each, including react-dismissable-layer, which
owns the body pointer-events state and could strand the app the same way.
Deleting removed the album from Redux before the request was sent and
never put it back, so a failed delete made the album vanish from the UI
while it still existed on disk — it only reappeared on a manual refresh.
The confirmation dialog also closed only on success, leaving it stacked
under the error dialog when the delete failed.

Drop the optimistic write and let autoInvalidateTags refetch the list,
matching how folder deletion already works, and close the confirmation
when the user confirms rather than when the request resolves.
The album list blocked the whole window behind the global loader while it
fetched, and the grid ran two columns wider than the memories grid at
every breakpoint, so the two pages never lined up.

Swap the loader dispatch for skeleton tiles and adopt the memories grid.
Rendering skeletons first also stops "No Albums Created Yet" flashing
before the first response lands, which the loader overlay used to hide.
Refresh greyed out the whole window behind the global loader for what is
a background refetch. Report the work on the button itself, the way the
memories Regenerate button does, and disable it while a fetch is in
flight so the list stays readable and a second refetch cannot be queued.

The global loader now has no caller left on this page.
The detail page dispatched showLoader while its images loaded and only
hid it again on the success branch. Leaving the page mid-request, or an
all-images fetch that never resolved, left the overlay up with nothing on
the page to clear it — the same stuck-overlay freeze, reached a different
way. It also blocked the window for what the list page now renders as
skeletons.

Drop the loader for skeleton tiles matching ImageCard, and gate the empty
state behind them so it cannot claim the album is empty mid-load.
Removing images and setting a cover both change what the list page shows,
so invalidate ['albums'] rather than leaving it to a remount refetch.
The albums table has no created_at or updated_at column and the Album
response model carries no timestamp fields, so both pages fell through to
new Date().toISOString() on every fetch. Album.created_at was required by
the type but only ever held the moment it was last read, which would have
quietly produced nonsense the first time anything sorted by it.

Nothing read either field, so drop them from the type and both mappers.
Choosing a cover was a right-click menu on every photo in an album, which
is a lot of ceremony for something that can just pick itself. The cover is
now the album's first image, resolved in one join at read time, so a new
album has artwork the moment it has a photo instead of only after someone
goes looking for the menu.

Drops the whole manual path: the PUT /albums/{id}/cover route, its schema,
the db writer, the API wrapper, and the per-image menu whose only item it
was. cover_image_path stays on the response, derived rather than stored.
A locked album listed its cover like any other, so the grid showed a
photo from inside an album whose whole point is that you need a password
to see inside it. The lock badge sat on top of the very content it was
meant to be gating.

Both reads now send no cover path for a locked album, and skip the lookup
rather than fetching a path they will not return. The card already falls
back to the placeholder when the path is absent.
Fit one more column from the sm breakpoint up and tighten the caption
under the cover, so more of the library is visible at a glance without
the tiles feeling cramped. The skeleton follows the same measurements to
keep the grid from resizing as it loads.
@rohan-pandeyy
rohan-pandeyy merged commit 78e7dc3 into AOSSIE-Org:main Aug 3, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant