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 |
|
true |
|
|
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.mdFrom 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 ...
audio_files.sql — add these queries:
-
GetAllTracksWithFullMetadataByLibrary— copy ofGetAllTracksWithFullMetadata(line 75) but addWHERE af.library_id = ?1to the outer query. The existing query JOINs audio_files, so the filter goes onaf.library_id. Use sqlc parameter annotation-- :arg library_id. -
GetAudioFilesByReleaseGroupByLibrary— copy ofGetAudioFilesByReleaseGroup(line 139) but addAND af.library_id = ?alongside the existingWHERE rgr.release_group_id = ?.
release_groups.sql — add these queries:
-
GetAllAlbumsWithDetailsByLibrary— copy ofGetAllAlbumsWithDetails(line 48). This query doesn't directly JOIN audio_files, so add anEXISTS (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: addWHERE 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 onrg.idto only include albums that have at least one track in the given library. -
GetAlbumsByArtistByLibrary— copy ofGetAlbumsByArtist(line 67). Add the same album-in-library subquery filter alongside the existing artist_id filter.
artists.sql — add:
GetAlbumArtistsByLibrary— copy ofGetAlbumArtists(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:
-
GetAllGenresWithCountsByLibrary— copy ofGetAllGenresWithCounts(line 66). Filter track counts to only count tracks in the given library. The existing query JOINs through recording_genres → recordings → audio_files, so addAND af.library_id = ?to the existing JOINs. -
GetTracksByGenreByLibrary— copy ofGetTracksByGenre(line 26). AddAND 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:
-
GetAllTracksByLibrary(libraryID int64) ([]Track, error)— callsl.db.Queries.GetAllTracksWithFullMetadataByLibrary(l.ctx, libraryID), maps viamapTrackRow. Do NOT returnerrNoTracksInLibraryfor empty result — a library with no tracks is valid (not an error). Return empty slice. -
GetAllAlbumsByLibrary(libraryID int64) ([]Album, error)— callsGetAllAlbumsWithDetailsByLibrary, maps with cover art URL resolution. -
GetAllArtistsByLibrary(libraryID int64) ([]Artist, error)— callsGetAlbumArtistsByLibrary. -
GetAlbumsByArtistByLibrary(artistID, libraryID int64) ([]Album, error)— callsGetAlbumsByArtistByLibrary. -
GetAllGenresWithCountsByLibrary(libraryID int64) ([]GenreWithCount, error)— callsGetAllGenresWithCountsByLibrary. -
GetTracksByGenreByLibrary(genreName string, libraryID int64) ([]Track, error)— callsGetTracksByGenreByLibrary. -
GetAlbumTracksByLibrary(albumID, libraryID int64) ([]Track, error)— callsGetAudioFilesByReleaseGroupByLibrary. -
SearchTracksByLibrary(query string, libraryID int64) ([]Track, error)— callsl.db.SearchFTSTracksByLibrary(query, searchTrackLimit, libraryID), maps viamapTrackRow.
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.
<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>