Files
yellowjacket/.planning/research/FEATURES.md
T

34 KiB

Feature Landscape: v1.1 Features & Extensibility

Domain: Desktop music player — new capabilities milestone Researched: 2026-03-06 Confidence: HIGH (grounded in codebase analysis, official documentation, established desktop music player patterns)


Overview

This research covers 8 feature areas for YellowJacket v1.1: tag editing, scan cancellation, smart playlists, customizable keyboard shortcuts, gapless playback + crossfade, MusicBrainz browser, layout customization, and plugin system. Each is categorized as table stakes, differentiator, or anti-feature relative to the desktop music player domain.


1. Tag Editing

Table Stakes

Feature Why Expected Complexity Dependencies
Edit title, artist, album, genre, year, track number Every music manager (MusicBee, foobar2000, Clementine, Strawberry) supports this. Users expect to correct metadata without leaving the app. MEDIUM Existing metadata extraction pipeline, new tag writing libraries
Edit single track Right-click → edit properties is the universal pattern LOW Tag writing backend
Batch edit multiple tracks Select multiple → edit shared fields (e.g., set all to same album). This is the primary workflow for fixing album imports. MEDIUM Single-track editing must work first
Write changes to actual audio files Tags must persist to the file on disk, not just the DB. Users expect changes to survive re-imports and transfers to other players. MEDIUM Tag writing libraries (format-specific)
Update DB after tag write After writing tags to file, the DB must reflect the new metadata without requiring a full rescan. LOW Existing DB update queries
Cover art assignment Set/replace embedded cover art from an image file MEDIUM Image handling + tag writing

Differentiators

Feature Value Proposition Complexity Dependencies
Undo/redo for tag edits Safety net — rare in music players, very valued when present HIGH Requires edit history tracking
Auto-capitalize/clean tag values Consistent library appearance with minimal effort LOW String utilities
Filename-to-tag inference Parse "Artist - Title.mp3" patterns to pre-fill fields MEDIUM Regex/pattern engine
Tag-to-filename rename Rename files based on tag template (e.g., "%artist% - %title%.%ext%") HIGH File system operations, template engine

Anti-Features

Anti-Feature Why Avoid What to Do Instead
Auto-tag from online DB in tag editor Conflates two features — tag editing and metadata lookup. MusicBrainz browser is the separate feature for this. Keep tag editing purely manual; MusicBrainz browser is the lookup tool
Destructive batch operations without confirmation Mass edits can corrupt a library. Always show preview/confirmation dialog for batch edits
Writing tags during playback of that file File locking conflicts on Windows; potential corruption on any OS Queue the write for after playback stops, or copy-on-write

Implementation Notes

Tag writing requires format-specific libraries (the existing dhowden/tag is read-only):

  • MP3 (ID3v2): github.com/bogem/id3v2/v2 — mature, pure Go, supports ID3v2.3/2.4 read+write, handles text frames, pictures, comments. Confirmed: tag.Open()tag.SetArtist()tag.Save() pattern. v2.1.4 is current.
  • FLAC (Vorbis Comments): github.com/go-flac/go-flac + github.com/go-flac/flacvorbis — parse FLAC file, modify vorbis comment metadata blocks, save back. Confirmed: flac.ParseFile() → modify Meta slice → f.Save(). v1.0.0/v0.2.0 current (v2 exists).
  • OGG Vorbis: No mature pure-Go write library exists. Options: (a) skip OGG tag writing initially, (b) use go-flac/flacvorbis-style approach with raw vorbis comment manipulation if a library surfaces, or (c) shell out to vorbiscomment CLI tool.
  • WAV: WAV metadata (INFO chunks, ID3 headers) is rarely edited. Skip for v1.1.

Critical constraint: The existing dhowden/tag library is read-only. Tag writing is a completely separate code path requiring new dependencies. Tag reading continues through dhowden/tag; writing uses format-specific libraries.

DB sync pattern: After writing tags to file, update the specific DB rows rather than triggering a full rescan. Extract the new metadata from the written file (or trust the values just written), update the recordings, artists, release_groups, and audio_files tables, then emit a TrackMetadataChanged event to sync the frontend.


2. Scan Cancellation

Table Stakes

Feature Why Expected Complexity Dependencies
Cancel button during scan Large libraries take minutes to scan. Users expect to be able to stop a scan in progress. Every file manager and media player with scanning provides this. LOW Existing scan pipeline with context.Context
Graceful stop (don't corrupt DB) Cancellation must not leave the DB in an inconsistent state. Complete in-flight transactions, skip remaining files. LOW Existing transaction batching
Scan progress reporting Users need to see what's happening — "Processing 340/2000 files" — to decide whether to wait or cancel. LOW Existing ScanProgress event (already partially implemented)

Differentiators

Feature Value Proposition Complexity Dependencies
Pause and resume scan Stop temporarily, resume later without re-scanning already-processed files HIGH Would need scan state persistence
Background scan with low priority Scan without impacting playback or UI responsiveness LOW Already partially handled by worker pool concurrency tuning

Anti-Features

Anti-Feature Why Avoid What to Do Instead
Immediate hard kill (kill goroutines) Data corruption risk — partial writes, broken entity caches Use context cancellation for cooperative shutdown
Auto-cancel on any error Users want the scan to continue past individual file failures Continue scanning, accumulate warnings (already the pattern)

Implementation Notes

The existing scan pipeline already uses context.Contextl.ctx is available throughout the scan. The implementation pattern is straightforward:

  1. Create a cancellable context: scanCtx, cancelScan := context.WithCancel(l.ctx)
  2. Store cancelScan so the frontend can trigger it via a Wails binding (e.g., Library.CancelScan())
  3. Check scanCtx.Done() in the filesystem walk loop, the worker pool dispatch, and the DB writer
  4. On cancellation, the errgroup returns context.Canceled, which is caught and treated as a clean stop
  5. Emit LibraryScanCancelled event (distinct from LibraryScanComplete)

Key insight: The existing scan already uses errgroup which respects context cancellation. The DB writer goroutine processes whatever is in its batch channel, so in-flight batches complete cleanly. The only new code needed is: (a) storing/exposing the cancel function, (b) checking context in the walk loop, (c) a new event for cancellation.

Complexity is LOW because the architecture already supports this pattern. The scan pipeline's multi-phase design means cancellation at any phase is naturally bounded.


3. Smart Playlists

Table Stakes

Feature Why Expected Complexity Dependencies
Filter by genre "All Jazz tracks" — the most basic smart playlist rule LOW Existing genre data in DB
Filter by year/year range "Tracks from 1990-1999" LOW Existing year field in DB
Filter by artist "All tracks by Artist X" LOW Existing artist data
Combine multiple rules (AND) "Jazz tracks from the 1990s" — users expect to stack filters MEDIUM Rule evaluation engine
Auto-update when library changes Smart playlists should refresh when tracks are added/removed. This is the defining feature vs. manual playlists. MEDIUM Event subscription to library changes
Name and save smart playlists Persist rule definitions, show in sidebar alongside regular playlists LOW New DB table for rule definitions

Differentiators

Feature Value Proposition Complexity Dependencies
Filter by play count "Most played" / "Never played" — requires play count tracking (not currently implemented) MEDIUM New play_count column or table
Filter by date added "Recently added" — very popular smart playlist LOW Existing file modification time or new added_at column
Filter by rating Requires rating system (not currently implemented) MEDIUM New rating feature
OR logic and nested groups "(Genre=Jazz OR Genre=Blues) AND Year>1980" — powerful but complex UI HIGH Recursive rule evaluation, complex UI builder
Random/limit results "Random 50 Jazz tracks" — playlist-as-radio LOW SQL ORDER BY RANDOM() LIMIT N
Sort order in rules "Newest first" / "Alphabetical by artist" LOW SQL ORDER BY clause

Anti-Features

Anti-Feature Why Avoid What to Do Instead
Full SQL WHERE clause as input Exposes DB internals, injection risk, terrible UX Structured rule builder with defined fields and operators
Complex nested boolean logic in v1 Overwhelms users, complex UI, rarely used Start with flat AND rules; add OR/nesting later if demanded
Real-time updating during playback Unnecessary overhead — smart playlists don't need sub-second freshness Refresh on library scan completion and on explicit refresh

Implementation Notes

Rule model — keep it simple for v1:

SmartPlaylistRule {
  Field:    "genre" | "year" | "artist" | "album" | "title" | "date_added"
  Operator: "equals" | "not_equals" | "contains" | "greater_than" | "less_than" | "between"
  Value:    string (or string pair for "between")
}

SmartPlaylist {
  ID:        int64
  Name:      string
  Rules:     []SmartPlaylistRule  // all ANDed together
  SortField: string (optional)
  SortOrder: "asc" | "desc"
  Limit:     int (0 = unlimited)
}

Storage: New smart_playlists table (id, name, rules_json, sort_field, sort_order, limit_count) with rules stored as JSON in a TEXT column. This avoids a complex relational schema for rules and is trivially extensible.

Query generation: Each rule maps to a SQL WHERE clause fragment. Rules are joined with AND. The existing track_metadata VIEW provides all the needed columns for filtering. Generated SQL uses parameterized queries (NOT string concatenation) to prevent injection.

Refresh strategy: Smart playlists evaluate lazily — results are computed on access and cached. Cache is invalidated on LibraryScanComplete events. This avoids expensive re-evaluation on every library change.

Depends on: Existing track_metadata VIEW, playlist sidebar UI, event system.


4. Customizable Keyboard Shortcuts

Table Stakes

Feature Why Expected Complexity Dependencies
Play/pause hotkey Space bar is universal; must work LOW Existing player controls
Next/previous track Arrow keys or media key equivalents LOW Existing queue navigation
Volume up/down Standard audio app functionality LOW Existing volume control
Mute toggle Expected in any audio application LOW Existing mute functionality
Search focus Ctrl+F or / to focus search — standard in any list-heavy app LOW Existing search bar
Default keybindings that work out of box Users shouldn't have to configure anything to get basic shortcuts LOW Hardcoded defaults with override capability

Differentiators

Feature Value Proposition Complexity Dependencies
Full customization UI Visual keybinding editor with conflict detection MEDIUM Settings page extension
Import/export keybindings Share/backup custom configs LOW TOML serialization (already used for config)
Scoped shortcuts (global vs. component-specific) Different bindings when focus is in search vs. track list MEDIUM Focus tracking
"When focused" context awareness Arrows navigate track list when it's focused, but control volume when player is focused MEDIUM Component focus management

Anti-Features

Anti-Feature Why Avoid What to Do Instead
Global OS-level hotkeys (outside app window) Platform-specific, conflicts with OS shortcuts, security concerns on Wayland App-scoped shortcuts only; MPRIS2 handles media keys
Vim-mode or complex modal keybindings Niche appeal, confusing for 99% of users Simple single/modifier key combos (Ctrl+X, Shift+X)
Shortcut for every possible action Overwhelming configuration UI Cover the 10-15 most common actions; rest accessible via menus

Implementation Notes

Architecture — event-driven, backend-aware:

The shortcut system has two layers:

  1. Frontend key listener: Captures keyboard events at the document level, maps keystrokes to action names using a binding table
  2. Action dispatch: Frontend calls the appropriate Wails binding or emits a frontend event for UI-only actions

Binding table structure:

interface KeyBinding {
  action: string;        // "play_pause", "next_track", "volume_up", etc.
  key: string;           // "Space", "ArrowRight", etc. (KeyboardEvent.key)
  modifiers: string[];   // ["ctrl"], ["shift"], ["ctrl", "shift"], []
  scope?: string;        // "global" | "tracklist" | "queue" (optional, default "global")
}

Storage: Add [Shortcuts] section to TOML config. Default bindings are hardcoded; user overrides merge on top. Config change emits ShortcutConfigChanged event.

Conflict detection: When user changes a binding, check for conflicts within the same scope. Show warning if two actions share the same keystroke.

Default bindings (the 12 essentials):

Action Default Key Scope
Play/Pause Space global
Stop . (period) global
Next Track Ctrl+Right global
Previous Track Ctrl+Left global
Volume Up Ctrl+Up global
Volume Down Ctrl+Down global
Mute M global
Search Focus Ctrl+F global
Toggle Queue Q global
Toggle Shuffle S global
Toggle Repeat R global
Select All (track list) Ctrl+A tracklist

Key insight: Keyboard shortcuts must NOT interfere with text input. When a text input or textarea has focus, the shortcut system must be disabled (except for Escape to blur). This is the #1 pitfall in keyboard shortcut implementations.


5. Gapless Playback + Crossfade

Table Stakes

Feature Why Expected Complexity Dependencies
Gapless playback (no silence between tracks) Expected by any serious music listener. Albums are meant to flow. Strawberry, foobar2000, Deadbeef, Audacious all support this. HIGH Fundamental change to audio pipeline
Crossfade setting (on/off, duration) Standard feature in every modern music player. Even basic mobile players have this. MEDIUM Gapless infrastructure + mixer
Crossfade duration control Users expect 1-10 second configurable fade LOW UI slider + config storage
Gapless without crossfade (default) Pure gapless (no overlap) should be the default. Crossfade is opt-in. HIGH Pre-decode/buffer next track

Differentiators

Feature Value Proposition Complexity Dependencies
Per-album gapless (auto-detect live albums) Disable crossfade within albums, enable between albums MEDIUM Album boundary detection in queue
ReplayGain normalization Consistent volume across tracks from different sources HIGH ReplayGain tag parsing + volume adjustment
Fade-in on play, fade-out on pause Smoother start/stop experience LOW Volume envelope on play/pause

Anti-Features

Anti-Feature Why Avoid What to Do Instead
DSP effects chain (equalizer, reverb, etc.) Scope explosion — not part of gapless/crossfade Defer to plugin system if ever needed
Crossfade for all transitions (including manual skip) Crossfade on skip feels sluggish Only crossfade on auto-advance; manual skip is instant
Pre-loading entire tracks into memory Memory explosion with FLAC files (50-100MB per track) Buffer only the crossfade overlap region (last/first N seconds)

Implementation Notes

This is the highest-complexity feature in v1.1. The current audio pipeline plays one track at a time with a single streamer chain. Gapless playback requires pre-decoding the next track and seamlessly transitioning.

Current pipeline: file → decode → resample → BufferedStreamer → Ctrl → Volume → Speaker

Gapless pipeline (conceptual):

  1. When current track is N seconds from ending, pre-load next track's decoder + resampler
  2. For pure gapless: use beep.Seq() to chain current and next streamer — but Seq doesn't support the pre-decode timing
  3. For crossfade: use beep.Mixer to overlap the fade-out of current with fade-in of next

beep library support:

  • beep.Mixer — adds/mixes multiple streamers. This is the foundation for crossfade.
  • beep.Seq() — sequences streamers end-to-end. Foundation for gapless without crossfade.
  • effects.Volume — volume control already used; can create fade curves by adjusting volume over time.
  • beep.Take() — extract N samples from a streamer. Useful for defining crossfade regions.

Architecture change required:

  • The Player must manage TWO streamer chains simultaneously during crossfade
  • A TransitionManager or equivalent coordinates pre-loading the next track
  • The playbackFinishedHandler (callback from beep when track ends) must trigger next-track pre-loading rather than waiting for the callback
  • The Queue must expose a "peek next" capability (already has tracks and currentIndex)

Crossfade implementation sketch:

[Track A ~~~~~~~~ fade-out]
                [fade-in ~~~~~~~~ Track B]
                |-- overlap (N seconds) --|
  • Track A's volume ramps from 1.0 → 0.0 over N seconds
  • Track B's volume ramps from 0.0 → 1.0 over N seconds
  • Both feed into a beep.Mixer during the overlap period
  • After overlap, Track A is closed, Track B continues alone

Config addition: [Playback] section with GaplessEnabled (bool, default true), CrossfadeEnabled (bool, default false), CrossfadeDurationMs (int, default 3000, range 500-10000).

Critical constraint: The beep speaker.Play() can only be called once; the speaker's mixer is the root. All track management must happen within the streamer chain that the speaker is already playing. This means using a persistent beep.Mixer as the root streamer, adding/removing track streamers from it.


6. MusicBrainz Browser

Table Stakes

Feature Why Expected Complexity Dependencies
Search artists by name The entry point — user types artist name, gets results MEDIUM MusicBrainz API integration, HTTP client
View artist discography (release groups) Browse albums/EPs/singles by an artist MEDIUM API browse: release-groups by artist
View album track listing See what tracks are on a release MEDIUM API lookup: release with recordings
View album editions (releases within a release group) Different pressings, reissues, deluxe editions MEDIUM API browse: releases by release-group
Rate limiting compliance MusicBrainz requires max 1 request/second with meaningful User-Agent LOW HTTP rate limiter, User-Agent header
Offline-safe (read-only, no writes) Read-only browsing — no MusicBrainz account needed LOW No authentication required for reads

Differentiators

Feature Value Proposition Complexity Dependencies
Link local tracks to MusicBrainz recordings Associate library tracks with MBIDs for definitive identity HIGH Matching algorithm, DB schema changes
Show cover art from Cover Art Archive Display album art from MusicBrainz's linked image archive MEDIUM coverartarchive.org API
Cache API responses locally Avoid re-fetching on every browse session MEDIUM SQLite cache table with TTL
Search recordings Find specific songs across all releases LOW MusicBrainz recording search API

Anti-Features

Anti-Feature Why Avoid What to Do Instead
Auto-tag from MusicBrainz This is Picard's domain — extremely complex matching logic Read-only browsing only. Users can manually apply info from browse to tag editor.
Write data to MusicBrainz Requires OAuth, community guidelines compliance, edit approval Strictly read-only
Download/stream from MusicBrainz MusicBrainz is a metadata database, not a music source Display metadata only
Background MusicBrainz scanning of entire library Rate limiting makes this impractical (1 req/sec = 3600 tracks/hour max) On-demand browsing only

Implementation Notes

MusicBrainz API: REST API at https://musicbrainz.org/ws/2/. JSON format via fmt=json parameter. No API key required, but must set meaningful User-Agent header: YellowJacket/<version> (contact-url-or-email).

Rate limiting: Strict 1 request/second. Implement with a time.Ticker-based rate limiter in the Go backend. All API calls go through a single rate-limited HTTP client.

Go libraries available:

  • github.com/michiwend/gomusicbrainz — Go client, but may be outdated
  • go.uploadedlobster.com/musicbrainzws2 — another Go client on SourceHut
  • Recommended: Build a thin HTTP client — the API is simple REST/JSON. A custom client with rate limiting, User-Agent, and JSON parsing is ~200 lines and avoids third-party dependency risk.

API patterns needed for read-only browsing:

  1. Search artist: GET /ws/2/artist?query=<name>&fmt=json&limit=25
  2. Artist discography: GET /ws/2/release-group?artist=<mbid>&fmt=json&limit=100&inc=artist-credits
  3. Release group releases: GET /ws/2/release?release-group=<mbid>&fmt=json&inc=media+recordings
  4. Release track listing: GET /ws/2/release/<mbid>?fmt=json&inc=recordings+media+artist-credits

Frontend architecture: New view (musicbrainz-browser component) accessible from sidebar. Search bar, results list, detail panels for artist/album/release. Navigation is drill-down: search → artist → release group → release → tracks.

Caching strategy: Cache API responses in SQLite (mb_cache table: url, response_json, fetched_at). TTL of 24 hours for search results, 7 days for entity lookups (MusicBrainz data changes infrequently). Cache reduces API calls and improves responsiveness.

This is YellowJacket's first network feature — the app is currently fully offline. Need to handle: network errors gracefully, timeout configuration, offline mode (show cached data), user notification of network status.


7. Layout Customization System

Table Stakes

Feature Why Expected Complexity Dependencies
Resizable panels (sidebar, queue, main) Basic expectation in any multi-panel desktop app. Users want wider sidebar or hidden queue. MEDIUM CSS grid/flexbox with drag handles
Show/hide queue panel Already partially implemented (queue toggle button exists) LOW Existing queue panel toggle
Show/hide sidebar sections Collapse navigation sections user doesn't need LOW Sidebar configuration
Persist layout across restarts Layout changes must survive app restart LOW TOML config section

Differentiators

Feature Value Proposition Complexity Dependencies
Section-based component placement (MusicBee-style) Users choose what goes where — put album art in sidebar, now-playing at top, etc. This is MusicBee's signature feature. HIGH Component registry, layout engine, size constraints
Component size constraints Components declare min/max sizes; layout engine respects constraints MEDIUM Component metadata system
Layout presets "Compact", "Full", "Mini player" — quick switch between configurations MEDIUM Preset definitions + switch mechanism
Detachable panels Pop out queue or now-playing to separate window HIGH Wails multi-window support (limited in v2)

Anti-Features

Anti-Feature Why Avoid What to Do Instead
Free-form drag-and-drop layout Overwhelming complexity, hard to make look good Section-based: defined slots with selectable components
CSS theme editor Users don't want to write CSS Extend existing theme system (accent color, background shade)
Mobile-responsive layout This is a desktop app with fixed minimum size Optimize for 1024x768 minimum

Implementation Notes

MusicBee-style layout means section-based composition:

The UI is divided into named sections (slots):

  • header (top bar)
  • sidebar (left panel)
  • main (center content area)
  • footer (bottom bar — now playing + player controls)
  • right-panel (queue panel or other content)

Each section has a list of components it can host. Components declare their size constraints (min width/height). Users configure which component goes in which section via a settings UI.

Implementation approach:

  1. Component registry: Each component registers itself with metadata (name, description, supported sections, min/max size). This is a TypeScript Map, not a plugin system yet.
  2. Layout configuration: Stored in TOML config under [Layout] section. Maps section names to component names.
  3. Layout renderer: A root <app-layout> component reads config and instantiates the right components in the right sections using dynamic imports.
  4. Resize handles: CSS resize or custom drag handles on section boundaries. Store widths/heights as percentages in config.

Start simple for v1.1:

  • Phase 1: Resizable panels (sidebar width, queue width) with drag handles + persistence
  • Phase 2: Show/hide sections + layout presets
  • Phase 3: Component-in-section customization (the full MusicBee-style system)

The full section-based system is the v1.1 "foundation" — functional but not complete.

Depends on: Config system (TOML), existing component architecture, CSS grid layout.


8. Plugin System

Table Stakes

Feature Why Expected Complexity Dependencies
Defined plugin API (what plugins can do) Without clear API boundaries, plugins break on every update HIGH API design + stability commitment
Plugin loading/unloading Install/remove plugins without rebuilding the app HIGH Dynamic loading mechanism
Plugin configuration Plugins need their own settings that persist MEDIUM Extend config system
Plugin isolation (one plugin crash doesn't kill app) Critical for stability HIGH Error boundaries, sandboxing

Differentiators

Feature Value Proposition Complexity Dependencies
UI component plugins (custom panels, visualizations) Plugins can add new views to the layout system HIGH Layout customization system + component registry
Backend hook plugins (custom metadata sources, scrobblers) Plugins can intercept/extend backend operations HIGH Hook system in Go backend
Plugin marketplace/registry Discover and install plugins HIGH External infrastructure
TypeScript/JavaScript plugin runtime Lowest barrier to entry for plugin authors MEDIUM Webview already runs JS

Anti-Features

Anti-Feature Why Avoid What to Do Instead
Go plugin system (plugin package) Linux-only, version-fragile, build-tag sensitive, widely considered broken Use process-based or embedded scripting approach
Full filesystem access for plugins Security nightmare Sandboxed API with explicit permissions
Plugin binary distribution Build reproducibility, platform issues Source-based distribution (TypeScript/JS bundles)
Network access for plugins without user consent Privacy concern Require explicit network permission declaration

Implementation Notes

Plugin systems in Go desktop apps are notoriously difficult. The plugin package is Linux-only and requires exact build-tag matching. Wails v2 doesn't have a plugin framework.

Recommended approach for v1.1 "foundation":

  1. Frontend-first plugins (TypeScript):

    • Plugins are JS/TS bundles loaded dynamically into the webview
    • They register with the component registry (layout system) to add UI
    • They access backend data through the existing Wails binding layer
    • Isolation via Shadow DOM for UI, try/catch for errors
  2. Backend hooks (Go):

    • Define hook points as interfaces: OnTrackPlay, OnLibraryScan, OnMetadataChange, etc.
    • Internal Go "plugins" implement these interfaces
    • For v1.1, hooks are compile-time (not dynamic) — the plugin system defines the API, but plugins are compiled in
    • Dynamic loading deferred to future (hashicorp/go-plugin RPC, or WASM)
  3. Plugin manifest:

    {
      "name": "my-plugin",
      "version": "1.0.0",
      "description": "Does a thing",
      "entry": "index.js",
      "hooks": ["onTrackPlay", "onLibraryScan"],
      "ui": [{"component": "my-panel", "sections": ["sidebar", "right-panel"]}],
      "permissions": ["network"]
    }
    
  4. Plugin directory: ~/.config/yellowjacket/plugins/<name>/ containing manifest + JS bundle

v1.1 scope should be the API definition and loading mechanism — not a full marketplace. "Working foundation" means: plugins can be loaded, they can register UI components, they can subscribe to backend events. The API surface is deliberately small and stable.

Depends on: Layout customization system (for UI plugins), event system (for hook subscriptions), config system (for plugin settings).


Feature Dependencies

Scan Cancellation ──── (standalone, no dependencies)
     │
Tag Editing ────────── (standalone, needs new libraries)
     │
Smart Playlists ────── depends on: existing DB/track_metadata VIEW
     │
Keyboard Shortcuts ─── (standalone, frontend-primary)
     │
Gapless + Crossfade ── depends on: audio pipeline refactor
     │
MusicBrainz Browser ── depends on: HTTP client (new), network handling (new)
     │
Layout Customization ── depends on: component registry (new)
     │
Plugin System ──────── depends on: Layout Customization, Event system, Config system

Dependency ordering (what blocks what):

  1. Nothing blocks: Scan cancellation, tag editing, keyboard shortcuts, smart playlists, MusicBrainz browser
  2. Layout blocks plugins: Plugin UI registration needs the layout component registry
  3. Gapless is self-contained but is the highest-risk change (audio pipeline)

MVP Recommendation

Build First (low risk, high value, unblocked)

  1. Scan cancellation — lowest complexity, immediate UX win, architecture already supports it
  2. Keyboard shortcuts — low complexity, massive usability improvement, no backend changes
  3. Smart playlists — medium complexity, high value, builds on existing DB infrastructure

Build Second (medium risk, foundational)

  1. Tag editing — medium complexity, requires new dependencies, needed before MusicBrainz becomes useful
  2. MusicBrainz browser — medium complexity, first network feature, independent of others
  3. Layout customization — medium-high complexity, needed before plugins

Build Last (high risk, high complexity)

  1. Gapless playback + crossfade — highest complexity, fundamental audio pipeline change, can ship independently
  2. Plugin system — highest complexity, depends on layout system, explicitly a "foundation" for v1.1

Defer (explicitly)

  • Tag-to-filename rename
  • Undo/redo for tag edits
  • Play count tracking (needed for some smart playlist rules)
  • Rating system
  • Plugin marketplace
  • Dynamic Go plugin loading
  • Detachable panels (Wails v2 limitation)

Sources