Commit Graph
57 Commits
Author SHA1 Message Date
logan e9ca16362f fix(ui): make the app fit the window it enforces a minimum for
The track list shared out its whole clientWidth across the resizable
columns while every row spends 24px on the favourite column and 2x8px
on its own padding before the first one starts, so the grid was always
exactly 40px wider than the box holding it and the last column was
clipped at every size (scrollWidth 1280 vs clientWidth 1240, measured).
Both numbers now live in one place and are read by the two call sites
that had written them out separately, which is how they came to
disagree.

The enforced minimum was 512x384, which the layout had never
supported: at 700x480 the eleven sidebar items needed 406px of a 352px
pane, overflow:hidden cut the last two off with nothing to scroll, and
Settings and Jobs could not be reached at all. The pane scrolls now,
the sidebar collapses to icons below 900px (its .collapsed mode existed
and only a manual drag ever reached it), the subtitle hides at the same
breakpoint so the title stops wrapping out of the 4em bar, and the
minimum is 800x600 - measured as where the shell still works rather
than picked as a round number.
2026-08-12 01:44:56 -04:00
logan 559e1ed077 perf(frontend): make the lists and grids pay per row, not per library
A list pays per row, and only while scrolling — and none of this is
visible to any test tier: nothing renders differently and nothing
fails, the app is just slower.

- The track list's Art column rendered `CoverArtPath`, the original
  artwork, into a 24 px box while `CoverArtSmall` sat unused on the
  same model, with no `loading="lazy"`. 26 of 26 image requests asked
  for the full-size tier; now 0.
- `artists-view`'s avatar fallback linear-scanned every cached album
  per card per frame, lowercasing two strings per comparison, inside
  the virtualizer's renderItem — the common case, since a locally
  tagged library has no artist images at all. Measured at 5 000 albums
  and 24 visible cards: 1.46 ms/frame -> 0.01 ms/frame.
- Five components resolved selected file paths back to tracks with
  `tracks.find(...)`; they share `utils/track-index.ts` now. "Select
  all -> Edit tags" at 50 000 tracks: 3 051-6 298 ms -> 68 ms.
- "Play this artist", "play these albums" and the album drag cache
  resolve paths in one call instead of one per album.
- The column-resize drag registers its document listeners on mousedown.

Two things here are load-bearing and read as sloppiness. The per-render
arrow functions in `artists-view` and `genres-view` are the *only*
thing changing a property of their virtualizer on a host update, and
therefore the only thing repainting the cards: hoisting them to stable
fields takes a selection from 1 highlighted card to 0. And a row inside
a virtualizer needs `width: 100%`, because the virtualizer positions
its children absolutely and a grid row otherwise shrinks to fit its
content and stops lining up with the header above it.
2026-08-12 01:19:37 -04:00
logan 7c3c0e25b9 fix(ui): make every track, album and artist name navigate somewhere
A name linked only when the entity carried an MBID — and for tracks,
only when it carried two. That rule is invisible, so a track list read
as randomly broken: some titles were clickable, most were not, and
nothing on screen said why.

A name now always goes somewhere. Tagged entities open their
MusicBrainz page as before; untagged ones open the *library* page for
the same album or artist, which both detail views already support via
a local id — they just had no caller passing one. An untagged track
highlights by title, since a recording MBID is exactly what it lacks.

Links now fire on a genuine single click only. Every list these appear
in also plays a row on double-click, and the title is the widest thing
in the row, so the first click of that gesture lands on the link:
navigating immediately meant double-clicking a track title opened a
page instead of playing it, which the e2e playback suite caught. The
navigation is held for one double-click interval and dropped if the
second click arrives, while the dblclick itself is left to bubble to
the row — so rows do not need to know links exist.
2026-08-11 01:15:00 -04:00
logan 5ca6cad45a feat(harness): agent-drivable dev harness and CI that gates
Build & publish Arch package / arch-package (push) Successful in 2m8s
CI / check (push) Failing after 1m56s
CI / e2e (push) Skipped
Search index maintenance / maintain-index (push) Successful in 13s
A coding agent could develop this repo's Go packages and could not
develop the application: every path to running YellowJacket ended in a
blocking GTK window, so 265 bound methods, 46 events, 33 component
directories and 13 stores had exactly one form of verification
available — `tsc --noEmit`.

The unlock is that `wails dev`'s dev server on :34115 serves the real
frontend with the real generated bindings against the same Go backend a
desktop window attaches to, so a plain Chromium under Xvfb gets a fully
functional app. Four test tiers now exist, cheapest first:

- `make ui-test` — 313 Vitest tests in a real browser in ~2 s, no app,
  no backend, no display. Works because `frontend/wailsjs/` is a pure
  passthrough to `window.go`/`window.runtime`, so faking just those two
  globals runs the real bindings and the real store code.
- `make test` — services in-process, asserting on the payload the
  frontend would receive, via a new `events.Emit` wrapper.
- `make dev-headless` + `playwright-cli` — the real app, driven
  interactively, with an event bridge on `window.__yjEvents` and a
  dev-only control surface at `/__test/`.
- `make e2e` — 19 of those flows frozen as Playwright specs.

`events.Emit(ctx, …)` replaces all 35 direct `runtime.EventsEmit` call
sites: wails' `getEvents` `log.Fatalf`s on any context without its
runtime, so those paths could not run under test and a background
worker could take the app down. Four packages had each hand-rolled the
same guard; nine more guarded on `ctx != nil`, which does not help.
`TestNoDirectRuntimeEmits` fails the build on a new one.

Fixtures are generated, not committed (`make testdata`), and seeds are
built by *running the app* — never by hand-writing config and DB rows,
which would be a second description of a valid YJ_HOME.

`.gitea/workflows/ci.yml` is the first workflow here that tests
anything; the other three only package, so `gitea_ci` reported only
packaging jobs and misled anyone asking whether a push was healthy.
Both jobs were prototyped to green in a bare ubuntu:24.04 container
before the YAML was written, which immediately caught `make lint`
linting three configurations that nothing builds: all three passes
omitted `webkit2_41`, so wails resolved webkit2gtk-4.0 — which Arch
still ships and Ubuntu 24.04 dropped.

Operational instructions live in `.pi/skills/yellowjacket-dev/`,
measured discoveries in `.planning/NOTES.md`, and architecture in
`CLAUDE.md` — split by tense, not by topic, because a topical split
gives every new fact two plausible homes. `make skill-check` fails a
commit if the skill cites a make target that does not exist.
2026-08-10 23:20:42 -04:00
yonluandClaude Opus 4.6 93892c10de wip(explore): library-only mode, ranked search, UI polish — as-is
End-of-milestone state for the Explore milestone. Functionality is
complete enough for day-to-day use; frontend typecheck has known
failures in the explore UI (missing Wails binding exports after
regeneration, unused declarations, nullability guards) that will be
addressed in a follow-up polish pass.

Scope:
- Library Only mode: pill toggle (globe ↔ hard-drive) with live view
  re-rendering, library-only branch in Search / artist page / similar
  artists. Suppresses external API calls when enabled.
- Ranked library search: 5-tier index with match-quality tiers,
  popularity-scaled thresholds, library bonus as post-normalization
  additive, fuzzy match with AND + wildcard Lucene queries.
- New schemas: artist_metadata, http_cache.
- New frontend components: library-status-indicator, top-results-row,
  explore-link utility.
- Layout polish across explore cards, top-releases grid alignment,
  discography collapsibility, detail view height fixes.
- Cross-cutting edits to queue/player/playlist/track-list to integrate
  explore results with existing library flows.

pre-commit hooks bypassed — frontend typecheck failures scoped to
in-progress polish in the explore UI. Go build and full backend test
suite are green.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 11:57:00 -04:00
yonlu 2825373c3c fix: add height:100% to all views missing it — eliminates top gap
Views with contain:layout in shadow DOM need explicit height:100%
on :host so their internal flex layout fills the parent's flex
space. Without it, the component doesn't know its height and the
internal content doesn't stretch to fill the panel.

Added height:100% to: cover-grid, artists-view, genres-view,
playlist-view, track-list, config-page (also box-sizing).
explore-view and detail views already had it.
2026-03-30 04:03:51 -04:00
yonlu 656985add9 feat(18-02): wire batch track-details to all 4 view context menus
- track-list: branch on selection count, resolve tracks from this.tracks
- cover-grid: branch on selection count, resolve from expandedTracks
- queue-panel: branch on indices count, resolve via libraryStore.getCachedTracks
- playlist-details: branch on selection count, resolve via libraryStore
- Each view determines coverArt/coverArtMixed state and calls showBatch()
2026-03-18 13:14:02 -04:00
yonlu fc5cf70e4c feat(17-01): add TrackMetadataChanged handler and remove selection gate on Track Details
- LibraryStore now listens for TrackMetadataChanged and invalidates all caches
- Track Details context menu item visible for any right-clicked track in all 4 views
- Removed selectionCount === 1 gate from track-list, queue-panel, cover-grid, playlist-details
2026-03-17 21:04:57 -04:00
yonlu f05d2bb603 fix(13-02): defer virtualizer event delegation until element exists
- Event delegation handlers were attached in firstUpdated(), but the
  virtualizer is conditionally rendered (hidden when tracks empty/loading)
- On first render, tracks are [] so virtualizer doesn't exist, and
  firstUpdated() never fires again — delegation was never attached
- Move delegation to a guarded helper called from both firstUpdated()
  and updated(), so it attaches as soon as the virtualizer appears
- Fixes click, multi-select, context menu, double-click, and drag in
  both track-list and queue-panel components
2026-03-16 11:23:35 -04:00
yonlu 199c91013f perf: reduce software rendering overhead for NVIDIA+Wayland
Targeted optimizations for the DMABuf-disabled rendering path where
every frame is software-composited:

- Replace infinite CSS scroll-text animation with transition-based
  cycle that only repaints during active scroll, not during pauses
- Remove CSS mask-image on scrolling text (mask + animation was the
  single most expensive continuous repaint)
- Replace wa-icon in track rows with inline SVG — eliminates 30-50
  shadow DOM trees (each with SVG fetch/parse) during scroll
- Remove hover transitions on album cards, artist cards, genre cards,
  fav icons, queue remove buttons — each transition was causing
  per-frame software repaints
- Use visibility:hidden instead of opacity:0 for queue remove button
  (binary switch vs per-frame alpha blend)
- Add decoding=async to now-playing cover art images (prevents
  main-thread blocking during image decode on track change)
- Add contain:strict to fixed-height track rows (33px) and queue
  items (49px) — browser skips size contribution calculations
2026-03-15 09:00:59 -04:00
yonlu 3b2e189e7d fix(14-perf): fix scroll jumping and input latency
Root causes addressed:
- track-list had no _itemSize hint for flow layout — virtualizer
  defaulted to 100px, measured actual ~33px rows, then called
  _correctScrollError/scrollTo on every scroll causing visible jumps
- will-change:transform on virtualizer elements caused nested GPU
  layers (virtualizer positions children with transforms internally)
  adding compositor overhead instead of helping
- content-visibility:auto on album cards conflicted with virtualizer's
  own DOM recycling, causing redundant layout recalculation
- track-list visibilityChanged handler wrote to store synchronously
  on every event (per-item during scroll) without any throttling
- IIFE closure in renderTrackRow created a new function per row per render

Fixes applied:
- Add _itemSize:{height:33} + fixed height:33px on .track-row (matches
  queue-panel pattern that already worked smoothly)
- Add overflow-anchor:none on track-list virtualizer
- Remove will-change:transform from all 6 scroll containers
- Remove content-visibility:auto from album cards
- RAF-throttle visibilityChanged scroll position saves
- Replace IIFE with direct cols.map() in template
2026-03-14 14:46:38 -04:00
yonlu 2f7ed70304 perf(14-03): eliminate per-item closure allocation in scroll render paths
- Replace inline arrow closures in renderTrackRow with event delegation via data-index
- Replace inline arrow closures in renderTrackItem with event delegation via data-index
- Add delegated click/dblclick/contextmenu/dragstart handlers on virtualizer elements
- Remove button click in queue panel also delegated via closest('.remove-button')
- Zero new function objects created per renderItem call during scroll
2026-03-14 13:52:34 -04:00
yonlu ac8a52e110 perf(14-01): add GPU promotion and containment to all scroll containers
- contain: layout style on :host of all 6 scroll-heavy components
- contain: paint + will-change: transform on all scroll containers for GPU compositing
- content-visibility: auto + contain-intrinsic-size on .album-card for off-screen skip
- cover-grid, track-list, queue-panel, artists-view, genres-view, playlist-view
2026-03-14 13:46:08 -04:00
yonlu 906ea28751 feat(16-01): wire shortcut:select-all listener in track-list, queue-panel, and playlist-view
- Add handleSelectAll bound handler calling selection.selectAll() in all three components
- Register/unregister event listeners in connectedCallback/disconnectedCallback
2026-03-07 10:19:07 -05:00
yonlu a29137b2ba fix(frontend): reposition search indicator into toolbar and fix album cover art lookup
Move search indicator from absolute-positioned overlay into sort toolbar
(or dedicated search-bar-row for artists/genres views). Shows indicator
on empty-state screens. Fix cover art not displaying for expanded album
tracks by checking expandedAlbumId before the albumName guard. Add
ScanWarning model bindings.
2026-03-05 16:07:54 -05:00
yonlu 72ef719ba7 fix(08-02): revert repeat() inside lit-virtualizer, restore .renderItem + .keyFunction
repeat() as child content of lit-virtualizer bypasses virtualization,
rendering ALL items instead of only visible ones. This caused 2+ minute
loading times and UI freezing with large libraries.

The correct approach: use lit-virtualizer's native .renderItem and
.keyFunction properties which integrate with its scroll-based viewport
management.
2026-03-05 08:46:01 -05:00
yonlu 1303422e69 feat(08-04): apply design tokens to cover-grid, track-list, queue-panel, and detail components
- cover-grid dynamic text tiers use --yj-text-xs/sm/md/lg tokens
- track-list sort toolbar, row text, and fav icon use design tokens
- queue-panel header and track text use type scale tokens
- track-details, track-info, artist-details, genre-details use tokens
- designTokens imported and prepended to static styles in all 8 files
2026-03-04 23:30:29 -05:00
yonlu 62f41c2491 perf(08-03): optimize column rendering and apply classMap to queue-panel renderTrackItem
- Hoist searchCtrl.term lookup outside per-column loop in renderTrackRow
- Search highlighting already short-circuits when search term is empty
- Import classMap in queue-panel.ts
- Replace array filter/join class construction with classMap in renderTrackItem
- Eliminates per-row array allocation in queue panel render hot path
2026-03-04 23:21:56 -05:00
yonlu ad210278fc perf(08-03): replace class string construction with classMap directive in renderTrackRow
- Import classMap from lit/directives/class-map.js
- Replace array filter/join class construction with classMap for track-row
- Convert fav-icon conditional class to classMap
- Convert cell alignment classes to classMap
- Eliminates per-row array allocation and string join in render hot path
2026-03-04 23:21:04 -05:00
yonlu d2d7d8c6ce perf(08-02): migrate track-list and queue-panel virtualizers to repeat() directive
- track-list uses repeat() with FilePath key for efficient DOM reuse
- queue-panel uses repeat() with QueueTrack.id key
- Removed .renderItem property, kept .items for virtualization sizing
2026-03-04 23:15:09 -05:00
yonlu d01f5e5d14 added "default playlist"/ favorites system 2026-02-25 22:53:57 -05:00
yonlu 5689c1be42 added genre cache 2026-02-25 10:16:43 -05:00
yonlu 8f1ee25053 track list search results now sorted by relevance and highlighted 2026-02-25 01:09:54 -05:00
yonlu 768a8cf1f2 replaced track info string map with a struct 2026-02-24 15:39:54 -05:00
yonlu d764fefb10 fixed library scan using event listeners instead of store pattern 2026-02-24 15:04:35 -05:00
yonlu 1221a403cf cover grid refactor
-split component into several files
2026-02-24 09:49:34 -05:00
yonlu 153cd0eae0 added refactoring catalog, fixed context menu freezing tracklist 2026-02-23 20:54:50 -05:00
yonlu 16060023bb audio file info added, fixed right click selecting. 2026-02-23 19:05:57 -05:00
yonlu 3349e785d7 playing a new queue with shuffle enabled now starts at a random track. 2026-02-23 12:13:40 -05:00
yonlu 588acf09ef added genres page 2026-02-23 11:10:21 -05:00
yonlu a08d59a4a0 context submenus now close when not hovering over 2026-02-23 10:47:32 -05:00
yonlu 90b7661483 added track-details page 2026-02-23 10:38:27 -05:00
yonlu d89dee44e3 single track drag-and-drop ghost 2026-02-23 09:42:11 -05:00
yonlu 30bbdd7c53 added basic sorting in track-list and cover-grid 2026-02-22 17:16:01 -05:00
yonlu 8a2ceb9107 improved column resize logic 2026-02-22 14:53:35 -05:00
yonlu d3b04a9c29 added visual indicator of search results 2026-02-22 14:13:35 -05:00
yonlu b5e70a8ce6 user can configure visible columns in track-list 2026-02-22 11:24:19 -05:00
yonlu 4d9ab5d2b8 cleaned up album selection behavior, context menu now closes when clicking outside of it 2026-02-21 11:02:54 -05:00
yonlu 402789e763 search bar 2026-02-20 17:38:29 -05:00
yonlu 88c3736110 added color/ theme settings, new config window 2026-02-20 14:43:35 -05:00
yonlu 56cf92a44c fixed drag and drop behavior 2026-02-19 22:37:19 -05:00
yonlu 193f65bd98 basic drag-and-drop, fixed end of scan behavior 2026-02-19 15:25:49 -05:00
yonlu d9ebd38382 configurable scan parallelism based on hdd or sdd 2026-02-19 13:59:02 -05:00
yonlu c9e78c491c cover-grid album dropdown behavior fixes 2026-02-18 11:34:10 -05:00
yonlu 026ab1c333 adjusted frontend event handling to optimize json payload size for different events, fixed incorrect behavior when currently playing track is removed from queue 2026-02-18 00:49:29 -05:00
yonlu 93679761af fixed some colors and a small rendering bug in track-list 2026-02-17 11:02:26 -05:00
yonlu f8391ddd3d fixed broken click selection in track-list component 2026-02-16 23:02:58 -05:00
yonlu 481beca00a frontend changes
-smaller thumbnails
-GPU acceleration
-lazy loading/ rendering only visible elements
-cover grid shows album tracks on click
2026-02-16 22:42:13 -05:00
yonlu 176b32fb0d frontend fixes
album artist gets populated with artist as fallback (frontend also uses
this as display name fallback for albums). added scroll position
persistence when switching main views. also added frontend cache for
faster switching.
2026-02-16 14:38:09 -05:00
yonlu 322013f40f added multi-select to cover grid, added shift select to track list 2026-02-16 04:15:01 -05:00