Files
yellowjacket/.planning/phases/13-library-views-phantom-tracks/13-01-PLAN.md
T

12 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
13-library-views-phantom-tracks 01 execute 1
backend/database/sql/queries/audio_files.sql
backend/database/sql/queries/release_groups.sql
backend/database/sql/queries/artists.sql
backend/database/sql/queries/genres.sql
backend/database/search.go
backend/library/query.go
true
VIEW-01
VIEW-02
VIEW-03
VIEW-04
truths artifacts key_links
Backend returns all tracks when no library filter is active (unified view)
Backend returns only tracks from a specific library when library_id is provided
Albums, artists, and genres are filtered to only show entities that have tracks in the selected library
FTS5 search returns results scoped to a specific library when library_id is provided
All existing unfiltered queries continue to work unchanged
path provides contains
backend/database/sql/queries/audio_files.sql GetAllTracksWithFullMetadataByLibrary query WHERE af.library_id
path provides contains
backend/database/sql/queries/release_groups.sql GetAllAlbumsWithDetailsByLibrary, GetAlbumsByArtistByLibrary queries WHERE af.library_id
path provides contains
backend/database/sql/queries/artists.sql GetAlbumArtistsByLibrary query WHERE af.library_id
path provides contains
backend/database/sql/queries/genres.sql GetAllGenresWithCountsByLibrary, GetTracksByGenreByLibrary queries WHERE af.library_id
path provides contains
backend/database/search.go SearchFTSTracksByLibrary method AND tm.library_id
path provides exports
backend/library/query.go GetAllTracksByLibrary, GetAllAlbumsByLibrary, GetAllArtistsByLibrary, GetAllGenresWithCountsByLibrary, GetTracksByGenreByLibrary, GetAlbumsByArtistByLibrary, GetAlbumTracksByLibrary, SearchTracksByLibrary methods
GetAllTracksByLibrary
SearchTracksByLibrary
from to via pattern
backend/library/query.go backend/database/sql/queries/*.sql sqlc-generated Queries methods l.db.Queries.
from to via pattern
backend/library/query.go backend/database/search.go l.db.SearchFTSTracksByLibrary SearchFTSTracksByLibrary
Add library-filtered SQL query variants for all browse views and FTS search so the frontend can request data scoped to a specific library.

Purpose: Phase 13 requires backend filtering for 150K+ track collections (per CONTEXT.md locked decision). Every existing unfiltered query that powers a browse view needs a ByLibrary variant accepting a library_id parameter. Existing unfiltered queries remain unchanged for the "All Libraries" default view.

Output: sqlc-generated query methods + Go wrapper methods on Library struct + filtered FTS search method on DB struct

<execution_context> @/home/caleb/.config/opencode/get-shit-done/workflows/execute-plan.md @/home/caleb/.config/opencode/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/13-library-views-phantom-tracks/13-CONTEXT.md

From backend/library/query.go — existing types to reuse:

type Track struct {
    TrackName, ArtistName, TrackLength, FilePath string
    TrackNumber, DiscNumber int64
    Album string; Genre []string; Year int64
    Composer, FileType string
    SampleRate, BitDepth, Channels, Bitrate, FileSize int64
}

type Artist struct { ID int64; Name string }

type Album struct {
    ID int64; Name, ArtistName, CoverArtPath string
    CoverArtSmall, CoverArtMedium, CoverArtLarge string
    Year int64
}

type GenreWithCount struct { Name string; TrackCount int64 }

// Helper used by GetAllTracks, SearchTracks, GetTracksByGenre:
func mapTrackRow(...) Track

From backend/database/search.go — existing FTS search:

type SearchTrackRow struct {
    FilePath string; LengthMilliseconds int64
    Title, ArtistName string
    TrackNumber, DiscNumber sql.NullInt64
    Album, Genre string; Year int64
    Composer, FileType string
    SampleRate, BitDepth, Channels, Bitrate, FileSize int64
}

func (d *DB) SearchFTSTracks(query string, limit int) ([]SearchTrackRow, error)

From track_metadata VIEW (already includes library_id):

CREATE VIEW IF NOT EXISTS track_metadata AS
SELECT af.id, af.file_path, ... af.library_id
FROM audio_files af
LEFT JOIN recordings r ON ...
LEFT JOIN release_group_recordings rgr ON ...
LEFT JOIN release_groups rg ON ...
LEFT JOIN artist_credits ac ON ...
LEFT JOIN artists a ON ...
LEFT JOIN cover_art ca ON ...
Task 1: Add library-filtered sqlc queries for all browse views backend/database/sql/queries/audio_files.sql backend/database/sql/queries/release_groups.sql backend/database/sql/queries/artists.sql backend/database/sql/queries/genres.sql Add `ByLibrary` variants of each query used by browse views. Each variant is a copy of the existing query with an added `WHERE af.library_id = ?` condition (or equivalent JOIN condition). The `track_metadata` VIEW already includes `af.library_id` as the last column.

audio_files.sql — add these queries:

  1. GetAllTracksWithFullMetadataByLibrary — copy of GetAllTracksWithFullMetadata (line 75) but add WHERE af.library_id = ?1 to the outer query. The existing query JOINs audio_files, so the filter goes on af.library_id. Use sqlc parameter annotation -- :arg library_id.

  2. GetAudioFilesByReleaseGroupByLibrary — copy of GetAudioFilesByReleaseGroup (line 139) but add AND af.library_id = ? alongside the existing WHERE rgr.release_group_id = ?.

release_groups.sql — add these queries:

  1. GetAllAlbumsWithDetailsByLibrary — copy of GetAllAlbumsWithDetails (line 48). This query doesn't directly JOIN audio_files, so add an EXISTS (SELECT 1 FROM audio_files af WHERE af.library_id = ? AND EXISTS (SELECT 1 FROM recordings r JOIN release_group_recordings rgr ON rgr.recording_id = r.id WHERE rgr.release_group_id = rg.id AND r.id = (SELECT recording_id FROM audio_files WHERE id = af.id))) — actually simpler: add WHERE rg.id IN (SELECT DISTINCT rgr2.release_group_id FROM release_group_recordings rgr2 JOIN recordings r2 ON r2.id = rgr2.recording_id JOIN audio_files af2 ON af2.recording_id = r2.id WHERE af2.library_id = ?). Check the existing query structure first and find the simplest approach. Likely: wrap the existing query body and add a subquery filter on rg.id to only include albums that have at least one track in the given library.

  2. GetAlbumsByArtistByLibrary — copy of GetAlbumsByArtist (line 67). Add the same album-in-library subquery filter alongside the existing artist_id filter.

artists.sql — add:

  1. GetAlbumArtistsByLibrary — copy of GetAlbumArtists (line 34). Filter to only artists that have at least one album with at least one track in the given library. Use subquery: WHERE a.id IN (SELECT DISTINCT ac2.artist_id FROM artist_credits ac2 JOIN release_groups rg2 ON rg2.artist_credit_id = ac2.id JOIN release_group_recordings rgr2 ON rgr2.release_group_id = rg2.id JOIN recordings r2 ON r2.id = rgr2.recording_id JOIN audio_files af2 ON af2.recording_id = r2.id WHERE af2.library_id = ?).

genres.sql — add:

  1. GetAllGenresWithCountsByLibrary — copy of GetAllGenresWithCounts (line 66). Filter track counts to only count tracks in the given library. The existing query JOINs through recording_genres → recordings → audio_files, so add AND af.library_id = ? to the existing JOINs.

  2. GetTracksByGenreByLibrary — copy of GetTracksByGenre (line 26). Add AND af.library_id = ? alongside the existing genre name filter.

After adding all queries, run make generate (or sqlc generate from backend/database/) to regenerate Go code. Verify compilation with go build -tags webkit2_41 ./....

Important: Do NOT modify existing queries — only add new ones. The unfiltered variants serve the "All Libraries" default view. cd /mnt/vault/dev/golang/yellowjacket && go generate ./backend/database/... && go build -tags webkit2_41 ./... Seven new sqlc queries exist (ByLibrary variants), sqlc generate succeeds, go build compiles cleanly

Task 2: Add library-filtered Go query methods and FTS search backend/library/query.go backend/database/search.go **backend/library/query.go** — Add `ByLibrary` wrapper methods that mirror each existing method but accept `libraryID int64` and call the `ByLibrary` sqlc query variant. Reuse `mapTrackRow` and cover art URL resolution logic identically.

Add these exported methods to the Library struct:

  1. GetAllTracksByLibrary(libraryID int64) ([]Track, error) — calls l.db.Queries.GetAllTracksWithFullMetadataByLibrary(l.ctx, libraryID), maps via mapTrackRow. Do NOT return errNoTracksInLibrary for empty result — a library with no tracks is valid (not an error). Return empty slice.

  2. GetAllAlbumsByLibrary(libraryID int64) ([]Album, error) — calls GetAllAlbumsWithDetailsByLibrary, maps with cover art URL resolution.

  3. GetAllArtistsByLibrary(libraryID int64) ([]Artist, error) — calls GetAlbumArtistsByLibrary.

  4. GetAlbumsByArtistByLibrary(artistID, libraryID int64) ([]Album, error) — calls GetAlbumsByArtistByLibrary.

  5. GetAllGenresWithCountsByLibrary(libraryID int64) ([]GenreWithCount, error) — calls GetAllGenresWithCountsByLibrary.

  6. GetTracksByGenreByLibrary(genreName string, libraryID int64) ([]Track, error) — calls GetTracksByGenreByLibrary.

  7. GetAlbumTracksByLibrary(albumID, libraryID int64) ([]Track, error) — calls GetAudioFilesByReleaseGroupByLibrary.

  8. SearchTracksByLibrary(query string, libraryID int64) ([]Track, error) — calls l.db.SearchFTSTracksByLibrary(query, searchTrackLimit, libraryID), maps via mapTrackRow.

backend/database/search.go — Add SearchFTSTracksByLibrary:

func (d *DB) SearchFTSTracksByLibrary(
    query string, limit int, libraryID int64,
) ([]SearchTrackRow, error)

Copy from SearchFTSTracks but add AND tm.library_id = ? to the WHERE clause and pass libraryID as the third parameter. The hand-crafted SQL already JOINs track_metadata tm ON tm.id = si.rowid, so the filter is trivial. Add SAFETY comment following project convention.

All methods must follow project conventions:

  • Error wrapping with fmt.Errorf("...: %w", err)
  • slog structured logging with method context
  • godot doc comments ending with period
  • Lines under 100 chars (break as needed)
  • nlreturn blank line after error returns

Verify with go build -tags webkit2_41 ./... and make lint. cd /mnt/vault/dev/golang/yellowjacket && go build -tags webkit2_41 ./... && make lint Eight new Go methods on Library struct + one new SearchFTSTracksByLibrary on DB struct; all compile cleanly, lint passes. Wails binding generation will pick up the new exported methods automatically.

- `go build -tags webkit2_41 ./...` compiles without errors - `make lint` passes (golangci-lint v2 with strict rules) - `sqlc generate` succeeds in backend/database/ - All seven new ByLibrary SQL queries exist in their respective .sql files - All eight new Go methods exist on Library struct - SearchFTSTracksByLibrary exists on DB struct - Existing unfiltered queries and methods are unchanged

<success_criteria>

  • Backend can serve track/album/artist/genre data filtered to a specific library_id
  • Backend can serve FTS5 search results filtered to a specific library_id
  • All new methods are Wails-bindable (exported, on exported struct)
  • No regression in existing unfiltered queries </success_criteria>
After completion, create `.planning/phases/13-library-views-phantom-tracks/13-01-SUMMARY.md`