Files
yellowjacket/backend/tracklist/config.go
logan bddfd37a5c
Build & publish Arch package / arch-package (push) Successful in 2m3s
CI / check (push) Canceled after 1m17s
CI / e2e (push) Canceled after 0s
Search index maintenance / maintain-index (push) Canceled after 0s
feat(track-list): show Album by default, and search smart playlists
H-15: the default columns were track, artist and duration, so a library
manager with duplicate detection could not tell its own duplicate
fixtures apart by eye. Album is a default now, in Go and in the
frontend fallback — both, because a fresh install persists the Go list
and the UI renders the TS one until the config arrives.

It does not deliver the finding's stated benefit, and that is worth
recording: the three `Tideline / Aurora Fields / 00:06` rows are
duplicates of the same album, so they read identically with an Album
column too. What tells them apart is the duplicate-detection feature or
a file path column, not this. Album is still the right default for
every other row in the list.

smart-playlist-details joins search-store's scope map. Checked before
adding, as asked: it reads searchCtrl.term in getVisibleTracks and
prints the term in the page, so the header box was disabled and
unlabelled on a view that filters as you type — the fix is a scope
entry, not a disabled state with a reason.
2026-08-12 12:38:36 -04:00

124 lines
3.0 KiB
Go

// Package tracklist manages track-list display configuration.
package tracklist
import (
"errors"
"fmt"
"slices"
)
var (
errUnknownColumnID = errors.New("unknown track-list column ID")
errDuplicateColumn = errors.New("duplicate column ID")
)
// ColumnID identifies a displayable column in the track list.
type ColumnID string
// Valid column identifiers.
const (
ColTrackName ColumnID = "trackName"
ColArtistName ColumnID = "artistName"
ColTrackLength ColumnID = "trackLength"
ColAlbum ColumnID = "album"
ColGenre ColumnID = "genre"
ColYear ColumnID = "year"
ColComposer ColumnID = "composer"
ColTrackNumber ColumnID = "trackNumber"
ColDiscNumber ColumnID = "discNumber"
ColFilePath ColumnID = "filePath"
ColFileType ColumnID = "fileType"
ColSampleRate ColumnID = "sampleRate"
ColBitDepth ColumnID = "bitDepth"
ColChannels ColumnID = "channels"
ColBitrate ColumnID = "bitrate"
ColFileSize ColumnID = "fileSize"
ColPlayCount ColumnID = "playCount"
ColAlbumArt ColumnID = "albumArt"
)
// AllColumnIDs lists every recognised column in default display
// order.
var AllColumnIDs = []ColumnID{
ColAlbumArt,
ColTrackName,
ColArtistName,
ColTrackLength,
ColAlbum,
ColGenre,
ColYear,
ColComposer,
ColTrackNumber,
ColDiscNumber,
ColFilePath,
ColFileType,
ColSampleRate,
ColBitDepth,
ColChannels,
ColBitrate,
ColFileSize,
ColPlayCount,
}
// DefaultColumns is the initial column configuration. Album is in it
// (H-15): without it the three identical `Tideline / Aurora Fields /
// 00:06` rows in this app's own fixture library cannot be told apart,
// in an app that has a duplicate-detection feature. Must match
// DEFAULT_COLUMN_IDS in frontend/src/components/track-list/columns.ts,
// which is what the UI falls back to before the config arrives.
var DefaultColumns = []Column{
{ID: ColTrackName},
{ID: ColArtistName},
{ID: ColAlbum},
{ID: ColTrackLength},
}
// Column represents a visible column in the track list.
type Column struct {
ID ColumnID `json:"id" toml:"ID"`
}
// Config holds track-list display preferences.
type Config struct {
Columns []Column `json:"columns" toml:"Columns"`
}
// ApplyDefaults fills zero-value fields with sensible defaults.
func (c *Config) ApplyDefaults() {
if len(c.Columns) == 0 {
c.Columns = make([]Column, len(DefaultColumns))
copy(c.Columns, DefaultColumns)
}
}
// Validate checks that every column ID is recognised and that
// there are no duplicates.
func (c *Config) Validate() error {
c.ApplyDefaults()
seen := make(map[ColumnID]bool, len(c.Columns))
for _, col := range c.Columns {
if !isValidColumnID(col.ID) {
return fmt.Errorf(
"%w: %q", errUnknownColumnID, col.ID,
)
}
if seen[col.ID] {
return fmt.Errorf(
"%w: %q", errDuplicateColumn, col.ID,
)
}
seen[col.ID] = true
}
return nil
}
// isValidColumnID returns true when id matches a known column.
func isValidColumnID(id ColumnID) bool {
return slices.Contains(AllColumnIDs, id)
}