Skip to content

feat: zero-touch library mode with pending review queue#2

Merged
jeffcrouse merged 15 commits intomasterfrom
zero-touch
Mar 19, 2026
Merged

feat: zero-touch library mode with pending review queue#2
jeffcrouse merged 15 commits intomasterfrom
zero-touch

Conversation

@jeffcrouse
Copy link
Member

Summary

  • Zero-touch principle: Familiar never writes to the user's music library. Music mount is read-only in Docker.
  • Remove all library write paths: metadata file writer, organizer execution, import file operations, in-place flac remux, streaming source mutation, destructive dedup execute, and proposed changes file-write scopes. All enrichment is now DB-only.
  • Pending review queue: New tracks enter PENDING_REVIEW status instead of appearing directly in the library. Users review, edit metadata, and approve/skip/replace from a new PendingReviewBrowser UI.
  • PendingReviewBrowser features: grouped by folder, bulk approve/skip, per-track and group metadata editors, queue-analysis toggle, duplicate detection with quality comparison.
  • Bug fixes: Spotify import response serialization (dict vs list), missing pending-review route.
  • New tests: Backend happy-path contract tests (catches Pydantic serialization mismatches), frontend navigation integrity tests (catches sidebar/route/browser registry sync issues).

Test plan

  • CI passes (contract tests, core tests, frontend unit tests, E2E)
  • Navigate to Review in sidebar — PendingReviewBrowser loads
  • Run library sync with new files — tracks appear in review queue, not main library
  • Approve/skip/replace actions work correctly
  • Spotify library import still visible in sidebar
  • Existing library browsing, playback, playlists unaffected

🤖 Generated with Claude Code

jeffcrouse and others added 10 commits March 19, 2026 08:09
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove metadata file writer, organizer execution, import file operations,
in-place flac remux, streaming source mutation, destructive dedup execute,
and proposed changes file-write scopes. All enrichment is now DB-only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New tracks enter PENDING_REVIEW status instead of ACTIVE. Adds duplicate
detection service, pending review API with approve/skip/replace actions,
and ACTIVE-only filters on track listing endpoints.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove import modal, drop zone, and file-write UI controls. Add
PendingReviewBrowser with grouped track review, bulk approve/skip,
queue-analysis toggle, per-track and group metadata editors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PostgreSQL requires ALTER TYPE ADD VALUE to be committed before the new
value can be referenced. Without this, the partial index creation fails
with UnsafeNewEnumValueUsageError.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The DB stores these as JSON arrays but the Pydantic model only accepted
dict, causing a 500 on GET /spotify/import and hiding the sidebar item.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sidebar linked to /library/pending-review but no route existed,
causing a redirect to the artists view via the catch-all route.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: smoke-test GET endpoints with seeded data to catch Pydantic
response model mismatches (e.g. dict vs list). Includes regression test
for the Spotify import serialization bug.

Frontend: verify sidebar items, routes, and browser registrations stay
in sync. Extracts route/sidebar data to routes.ts to enable testing
without importing the React component tree.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Cast scope to text in remove_file_write_scopes migration so it works
  on fresh DBs where the old enum values never existed
- Add one-way comment to bare downgrade pass
- Remove unused TrackStatus imports flagged by ruff
- Fix noqa placement in preview.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7511b152b1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +461 to +465
track.status = TrackStatus.PENDING_REVIEW
# Run duplicate detection for review_info
from app.services.duplicate_detection import detect_duplicate_for_track
track.review_info = await detect_duplicate_for_track(self.db, track)
results["pending_review"] += 1

Choose a reason for hiding this comment

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

P1 Badge Keep new review-queue tracks out of the analysis queue

Marking newly discovered files as PENDING_REVIEW here does not actually prevent analysis from running: backend/app/services/tasks/analysis_queue.py:39-51 still selects every track without a TrackAnalysis row, regardless of status. A normal library sync will therefore analyze unapproved (and even later-skipped) tracks before the user reviews them, which defeats the zero-touch workflow and spends analysis capacity on files that may never be imported.

Useful? React with 👍 / 👎.

Comment on lines +178 to +184
prefix = folder_path.rstrip("/") + "/"
result = await db.execute(
select(Track).where(
Track.status == TrackStatus.PENDING_REVIEW,
Track.file_path.like(prefix + "%"),
# Ensure we only get files directly in this folder (not subfolders)
~Track.file_path.like(prefix + "%/%"),

Choose a reason for hiding this comment

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

P1 Badge Escape folder names before using LIKE in group review actions

folder_path is interpolated directly into two SQL LIKE patterns, so real directory names containing % or _ are treated as wildcards. In folders such as AC_DC or 100% Hits, the group approve/skip/replace endpoints can match tracks from sibling folders that were never selected in the UI and change their status as part of the bulk action.

Useful? React with 👍 / 👎.

Comment on lines +31 to +32
{ path: '/library/proposed-changes', label: 'Changes' },
{ path: '/library/pending-review', label: 'Review' },

Choose a reason for hiding this comment

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

P2 Badge Expose the review queue in mobile navigation too

This adds /library/pending-review to the shared route list, but I checked packages/frontend/src/components/MobileNav/MobileMoreSheet.tsx:28-35 and the mobile library menu still hard-codes the old entries. On the iOS/Capacitor build that means the new review queue has no navigation path, so newly discovered tracks cannot be approved from the mobile UI without manually entering the URL.

Useful? React with 👍 / 👎.

jeffcrouse and others added 5 commits March 19, 2026 08:53
…ation sync

- Stage all ruff auto-fixes (unused imports across 13 files)
- Update scanner tests: new tracks are pending_review, not new
- Fix scanner: mark PENDING_REVIEW tracks as missing when file disappears
- Fix scanner: recover to PENDING_REVIEW or ACTIVE based on review_info
- Fix test_track_by_id_serializes: /tracks/batch is POST, not GET
- Add ix_tracks_pending_review to manual indexes in env.py
- Remove test for deleted /library/imports/recent endpoint
- Add test_contract_happy_path.py to Core test suite ignore list

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…mport

- Fix mypy: type-ignore rowcount on Result, inline save_upload_to_temp
- Fix scanner: set review_info to {} (not None) for non-duplicate pending
  tracks so recovery heuristic works correctly
- Fix scanner test: recovered pending track stays PENDING_REVIEW

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Profile contract lint requires all POST/PATCH endpoints to include
the RequiredProfile dependency.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Filter analysis queue to ACTIVE tracks only, preventing PENDING_REVIEW
  tracks from consuming analysis capacity before approval
- Escape SQL LIKE wildcards in folder paths to prevent matching unrelated
  folders with % or _ characters (e.g. AC_DC, 100% Hits)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dexie !: property declarations don't create runtime properties until the
DB opens. Use db.tables.map(t => t.name) which reflects the declared
schema regardless of open state. Fixes pre-existing CI failure.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jeffcrouse jeffcrouse merged commit 4756247 into master Mar 19, 2026
12 checks passed
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