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()→ modifyMetaslice →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 tovorbiscommentCLI 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.Context — l.ctx is available throughout the scan. The implementation pattern is straightforward:
- Create a cancellable context:
scanCtx, cancelScan := context.WithCancel(l.ctx) - Store
cancelScanso the frontend can trigger it via a Wails binding (e.g.,Library.CancelScan()) - Check
scanCtx.Done()in the filesystem walk loop, the worker pool dispatch, and the DB writer - On cancellation, the
errgroupreturnscontext.Canceled, which is caught and treated as a clean stop - Emit
LibraryScanCancelledevent (distinct fromLibraryScanComplete)
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:
- Frontend key listener: Captures keyboard events at the document level, maps keystrokes to action names using a binding table
- 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):
- When current track is N seconds from ending, pre-load next track's decoder + resampler
- For pure gapless: use
beep.Seq()to chain current and next streamer — but Seq doesn't support the pre-decode timing - For crossfade: use
beep.Mixerto 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
Playermust manage TWO streamer chains simultaneously during crossfade - A
TransitionManageror 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
Queuemust expose a "peek next" capability (already hastracksandcurrentIndex)
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.Mixerduring 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 outdatedgo.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:
- Search artist:
GET /ws/2/artist?query=<name>&fmt=json&limit=25 - Artist discography:
GET /ws/2/release-group?artist=<mbid>&fmt=json&limit=100&inc=artist-credits - Release group releases:
GET /ws/2/release?release-group=<mbid>&fmt=json&inc=media+recordings - 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:
- 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.
- Layout configuration: Stored in TOML config under
[Layout]section. Maps section names to component names. - Layout renderer: A root
<app-layout>component reads config and instantiates the right components in the right sections using dynamic imports. - 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":
-
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
-
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)
- Define hook points as interfaces:
-
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"] } -
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):
- Nothing blocks: Scan cancellation, tag editing, keyboard shortcuts, smart playlists, MusicBrainz browser
- Layout blocks plugins: Plugin UI registration needs the layout component registry
- Gapless is self-contained but is the highest-risk change (audio pipeline)
MVP Recommendation
Build First (low risk, high value, unblocked)
- Scan cancellation — lowest complexity, immediate UX win, architecture already supports it
- Keyboard shortcuts — low complexity, massive usability improvement, no backend changes
- Smart playlists — medium complexity, high value, builds on existing DB infrastructure
Build Second (medium risk, foundational)
- Tag editing — medium complexity, requires new dependencies, needed before MusicBrainz becomes useful
- MusicBrainz browser — medium complexity, first network feature, independent of others
- Layout customization — medium-high complexity, needed before plugins
Build Last (high risk, high complexity)
- Gapless playback + crossfade — highest complexity, fundamental audio pipeline change, can ship independently
- 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
- MusicBrainz API documentation: https://musicbrainz.org/doc/MusicBrainz_API (HIGH confidence — official docs, verified 2026-03-06)
- MusicBrainz rate limiting: https://musicbrainz.org/doc/MusicBrainz_API/Rate_Limiting (HIGH confidence — official docs)
github.com/bogem/id3v2/v2v2.1.4: https://pkg.go.dev/github.com/bogem/id3v2/v2 (HIGH confidence — official pkg.go.dev)github.com/go-flac/go-flacv1.0.0: https://pkg.go.dev/github.com/go-flac/go-flac (HIGH confidence — official pkg.go.dev)github.com/go-flac/flacvorbisv0.2.0: https://pkg.go.dev/github.com/go-flac/flacvorbis (HIGH confidence — official pkg.go.dev)github.com/gopxl/beep/v2v2.1.1: https://pkg.go.dev/github.com/gopxl/beep/v2 (HIGH confidence — official pkg.go.dev, confirms Mixer, Seq, Loop2, effects)- YellowJacket codebase analysis:
.planning/codebase/(HIGH confidence — direct code inspection) - Desktop music player patterns: foobar2000, MusicBee, Strawberry, Deadbeef, Audacious (MEDIUM confidence — training data knowledge of established players)