Commit graph

728 commits

Author SHA1 Message Date
BoulderBadgeDad
fcd768ffee discovery: tune the wave dial — green->blue spectrum, calmer waves, subtler orb, refined header
Feedback pass: red read as "bad", so the spectrum is now green -> blue through cyan (deep/exploratory,
not alarming). The orb glow was way too hot — cut it right down and softened the pulse ring + the colour
aura. Dialed the wiggle back (lower amplitude, fewer cycles, slower) so the adventurous end is lively,
not chaotic. Header is now a small uppercase "ADVENTUROUSNESS" overline above the big colour-shifting
state word, instead of the plain bold title.
2026-06-29 16:54:53 -07:00
BoulderBadgeDad
d9bd5c1d7f discovery: elevate the wave dial — full width + glow, luminous fill, pulsing orb, colour aura 2026-06-29 16:48:34 -07:00
BoulderBadgeDad
c50465984f discovery: replace the slider with a living wave dial (animated)
Scrapped the basic slider for a custom animated control matching the vision: a draggable orb rides an
SVG line that is redrawn every frame (rAF). At the left (value 0) the line is green and waves gently —
"artists you already like", alive but calm; drag the orb right and the colour shifts through the warm
spectrum (green -> yellow -> orange -> red) while the wave grows taller and more erratic (a detuned
second harmonic). The orb glows the current colour and rides the wave; the state word recolours too.
Releasing saves (POST /api/discover/adventurousness) and re-fetches both rec rows. The loop skips work
while the page is hidden. Still one source of truth with the Settings slider.

64 script-integrity tests green.
2026-06-29 16:41:12 -07:00
BoulderBadgeDad
43fbab5704 discovery: rebuild the Adventurousness dial — visible track + compact layout
The first cut had two problems (screenshot): the gradient track was invisible (Chromium does not render
the range inputs own background as the track when appearance:none — must style ::-webkit-slider-runnable-
track / ::-moz-range-track) and the grid layout stretched it full-width into a sparse void.

Now: a contained (max-width 880) single-row flex control — icon | title+state | Safe | gradient track |
Adventurous | live %. The gradient (green->accent->orange) renders on the track pseudo-elements, the
thumb is centred on it, and a % readout anchors the right edge. Mobile wraps the slider to its own row.
2026-06-29 16:33:45 -07:00
BoulderBadgeDad
71a48dd344 discovery: fancy Adventurousness dial on the Discover page (synced with Settings)
A prominent control above the rec rows: gradient slider (Safe green -> accent -> hot orange), glowing
grab-thumb, a compass icon that swaps with the level (lifebuoy/compass/dice/crystal-ball) and a live
state word ("Playing it safe" -> "Deep cuts only"). Dragging updates the label live; releasing saves
and immediately re-fetches both rec rows so the effect is instant.

Shares the config key discover.adventurousness with the Settings -> Discovery slider via a new
GET/POST /api/discover/adventurousness endpoint, so the two controls stay in sync (change one, the
other reflects it on next load) — one source of truth, no divergence.

64 script-integrity tests green; route ruff-clean + compiles.
2026-06-29 16:26:50 -07:00
BoulderBadgeDad
153fad152c discover: generalize the mix modal + convert Time Machine to "Year Mixes" cards
Foundation for folding the remaining playlist sections into the mix-card system:
- openMixModal now takes a custom `actions` list (not just the built-in Download/Sync), a `statusBase`
  so any sections live sync lands in the modal, a `pollerKey` for in-flight re-show, and `fetchTracks`
  for lazy sections that load tracks on open.
- _buildMixCard supports a non-mosaic `coverHtml` + `trackCount` (for art-less cards like decades).
- new _renderMixGrid(container, mixes) renders cards into any sections own grid (not the shared shelf).

Time Machine -> "Year Mixes": a shelf of decade cards (gradient cover + "1980s" label); opening one
lazy-loads /api/discover/decade/{year} into the modal (populating decadeTracksCache so Sync works)
with Download/Sync. Tabs removed.

Fix: _upsertMixCard rendered the WHOLE shared registry into Your Mixes, so decades (added by
_renderMixGrid for the modal lookup) leaked in. Your Mixes now tracks its own _yourMixKeys and renders
only those. 64 script-integrity tests green.
2026-06-29 15:09:26 -07:00
BoulderBadgeDad
880ad74a0a discover: convert the last stragglers (bylt + genre tiles) to .ya-card
Because You Listen To rows now render .ya-card grids (with the Show-all clamp) instead of the old
discover-card carousels. The genre-browser tiles (_renderGenreCard) are converted too for consistency
— that loader is currently dead code (loadGenreBrowser is never called; only the tabbed Browse-by-Genre
modal runs), so this just future-proofs it. With this, no discover-card markup remains in discover.js
— the whole Discover page is one card language (.ya-card + the mix cards), leaving only the Genre
Explorer pills + Artist Map as the intentional splash elements.

64 script-integrity tests green.
2026-06-29 14:39:28 -07:00
BoulderBadgeDad
bf8e8beeb7 discover: convert the two artist sections to the .ya-card grid
Based On Your Listening + Recommended For You now use the same .ya-card as the rest (square cover,
name overlaid, recommendation reason as the subtitle) in wrapping grids with the Show-all clamp,
replacing the bespoke recommended-artist-card layout. The watchlist toggle and live image enrichment
are preserved by keeping their hooks on the new markup — .recommended-artist-card[data-artist-id],
.recommended-card-image, and .recommended-card-watchlist-btn (restyled as a hover-reveal corner pill
that stays lit when watching). _clampGrid wired into both onRendered callbacks.

64 script-integrity tests green.
2026-06-29 14:32:16 -07:00
BoulderBadgeDad
4cab44889c discover: unify album sections onto the .ya-card grid + Show-all clamp
Step 2 of the makeover — the album/release sections now use the same .ya-card as Your Artists
(square cover so art is not cropped, name overlaid, artist beneath) in responsive wrapping grids,
replacing the old spotify-library-card / discover-card / carousels:
- Recent Releases, Your Albums (owned/missing badge kept), Undiscovered Albums, New In Your Genres,
  From Your Labels (one shared _cacheDiscoverCard), Seasonal Albums.
- _insertCacheSection gained a wrapGrid flag; Genre Explorer opts out (wrapGrid=false) so its pill
  layout is not squashed into the card grid (fixes the regression where it broke under the hero).
- new _clampGrid caps a grid at ~12 cards with a Show all (N) / Show less toggle, so a 30-item
  section is not a wall; wired into the cache sections + Recent Releases + Seasonal Albums.

64 script-integrity tests green. Next: the two artist sections (Based On Your Listening / Recommended
For You) need their watchlist + live-enrichment hooks moved before they can switch to .ya-card.
2026-06-29 14:21:52 -07:00
BoulderBadgeDad
9d5e58c4b8 discover: "Your Mixes" shelf — mixes are cards that open to a track list (Spotify-style)
Reworks the Discover page mixes from full-width compact-track tables into a consistent shelf of
playlist cards, the way Spotify Home does it. Each mix is ONE card (a 2x2 mosaic cover from its top
tracks + name/count, hover play button); clicking it opens a modal with the track list + the mix
actions — the track table now lives where a track list belongs (inside the opened mix).

- new "Your Mixes" section + .discover-mix-card / .discover-mixes-grid + .mix-modal styles.
- _buildMixCard / _upsertMixCard / openMixModal + a mix registry; the four personalized track-mix
  loaders (Listening Mix, Popular Picks, Hidden Gems, Discovery Shuffle) collapse their old table
  section into a card via _collapseOldMixSection (which also strips the old section's duplicate
  sync ids so the modal owns the live status).
- modal actions use the standard btn--secondary/btn--primary; Download closes the mix modal first
  so its own modal is interactable; Sync shows live progress IN the modal and survives close/re-open
  (detects an in-flight sync via discoverSyncPollers and re-reveals the status).

64 script-integrity tests green. Next: fold the remaining mixes (Fresh Tape / The Archives /
Seasonal / Daily Mixes) into the same shelf, then unify the album/genre sections onto the card.
2026-06-29 13:40:34 -07:00
nick2000713
bd6db37624 fix(ui): info icons show the button hand, not the text caret (Windows)
The settings info icons are role="button" spans with a text "i" glyph but no
cursor/user-select, so hovering the glyph gave the I-beam text caret on Windows
(Linux happened to resolve a pointer). Add cursor:pointer + user-select:none so
it reads as a button on every platform.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:55:18 +02:00
nick2000713
8d549eb4fa fix(max-performance): hide the same decorative elements reduce-effects hides
Max Performance only neutralized animation/blur/shadow globally but didn't
replicate the reduce-effects-specific display:none rules, so with reduce-
effects OFF the sidebar aura orbs (.sidebar::before/::after) survived as two
hard static circles, the dash-card cursor-glow layers stayed, and nav-button
hover kept the expensive treatment. Depended on whether reduce-effects was on
before enabling Max Performance. Extend those three rule blocks to also match
body.max-performance — flash-free since the body class is server-rendered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:33:03 +02:00
nick2000713
84d6208bc9 perf: add Max Performance mode + stop password-manager autofill storm
Two CPU regressions surfaced in software-rendered / no-GPU containers
(Docker), where transform/opacity and canvas radial-gradient fills
rasterize on the CPU instead of a compositor:

1. Worker-orbs canvas + decorative motion saturate a core and freeze the
   UI. A new opt-in "Max Performance" mode is the nuclear low-power switch:
   body.max-performance CSS kills blur/shadow/filter AND all
   animation/transition (spinners go static), and JS halts every canvas
   loop (orbs, particles, cursor-glow, API sparks) via window._maxPerfActive.
   Reduce Visual Effects is now decoupled from the orbs — they follow their
   own toggle; only Max Performance force-kills them. While Max Performance
   is on, the Orbs/Particles/Reduce-Effects checkboxes lock + grey out, and
   save reads the runtime flags so prefs aren't clobbered.

2. Password managers (Bitwarden et al.) rebuild their autofill overlay on
   every DOM mutation; a captured trace showed Bitwarden using ~6x the CPU
   of the whole app (~400 setupOverlayOnField/sec). suppressPasswordManager-
   Autofill() tags non-credential inputs with data-bwignore / data-1p-ignore
   / data-lpignore / data-form-type=other so the managers skip them; real
   login/PIN fields are left alone.

Wired through: web_server.py (_initial_appearance_context), index.html
(inline flag + body class + checkbox), init.js (applyMaxPerformance +
bootstrap + listener + autofill suppression), settings.js (load/save),
worker-orbs.js / particles.js / api-monitor.js (gates), style.css.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 11:06:13 +02:00
BoulderBadgeDad
3207310448 reduce-effects: kill expensive GPU properties, stop freezing functional motion
"Reduce visual effects" was a sledgehammer: body.reduce-effects * forced
animation:none + transition-duration:0s on every element. That froze every CSS loading
spinner mid-rotation — including the dash-header worker-service spinners (musicbrainz /
spotify / deezer / … .active .<svc>-spinner) — which read as BROKEN rather than "off".
It also killed cheap hover feedback like the Quick Actions buttons.

The actual lag (esp. Firefox, see the #935 sweep) is backdrop-filter / box-shadow /
filter re-rasterizing every frame — NOT the animations themselves. Transform- and
opacity-only motion (the spinners) composites for ~free.

So: keep forcing the expensive properties to none (unchanged — that's the real fix),
but drop the blanket animation/transition kills. !important author declarations outrank
animation + transition declarations in the cascade, so any keyframe/transition that
tries to set blur/shadow/filter is still neutralized even while it runs — the spinner
spins, just without the glow. Net: functional spinners stay alive, Quick Actions hover
(transform + border-colour) returns, box-shadow transitions are no-ops (shadow forced
none), and the GPU-heavy rendering that caused the lag stays gone. The worker-orb CANVAS
is unaffected (JS-gated separately) and stays off under reduce-effects, as intended.

Static guard test pins the contract: the global rule must keep the expensive-property
kills and must NOT reintroduce blanket animation:none / transition:0s.
2026-06-28 16:22:10 -07:00
dev
33fe92a525 Enlarge settings page header 2026-06-29 00:37:01 +02:00
dev
512ec227be Polish security settings controls 2026-06-29 00:33:54 +02:00
dev
8149f35fae Refine settings header layout 2026-06-28 23:57:26 +02:00
dev
1da677ee2d Keep settings logs viewer full width 2026-06-28 23:45:49 +02:00
dev
b43e44219a Merge remote-tracking branch 'upstream/main' into ui/settings-page-cleanup
# Conflicts:
#	webui/static/style.css
2026-06-28 23:29:17 +02:00
dev
0276aa8764 Update settings page overhaul 2026-06-28 22:52:09 +02:00
dev
60dee1b4d8 Align source settings card spacing 2026-06-28 22:47:37 +02:00
dev
e2317de0a4 Remove dead settings CSS 2026-06-28 22:25:28 +02:00
BoulderBadgeDad
6802399805 dashboard/shell: drop frosted-glass blur on Firefox only (#935)
Firefox re-rasterizes blur()/backdrop-filter every composite where Chrome caches it, so the
always-visible shell glass (sidebar header + aura orbs, hero/header buttons) was ~half of
Firefox's idle GPU. gate behind @supports(-moz-appearance:none) so it's Firefox-only: hide the
two blur(28px) sidebar orbs + the dash-card blobs, and drop backdrop-filter on the sidebar
header and hero/header buttons (each keeps its tint, just unfrosted). measured ~20-25% -> ~10-13%
on Firefox, every page (sidebar is always visible). chrome is untouched — the block doesn't
exist there, full frost intact.
2026-06-27 13:36:15 -07:00
BoulderBadgeDad
c6caaaa599 dashboard: hide cursor-glow blobs on Firefox only (#935)
the .dash-card cursor blobs are 16 large blur(48px)/blur(18px) layers. chrome caches them
once; firefox re-rasterizes blur on every composite, so they're a big chunk of idle dashboard
GPU on firefox. they're purely decorative and reduce-effects already hides them. gate behind
@supports(-moz-appearance:none) so it's firefox-only — chrome keeps the full cursor glow,
this block doesn't exist there.
2026-06-27 12:53:10 -07:00
BoulderBadgeDad
9faaf5c50c sidebar orbs: drop scale() from the drift so the blur stops re-rasterizing (#935)
same antipattern as the dash-card blobs: .sidebar::before/::after are blur(28px) and the
orb keyframes animated transform: scale() infinitely → the GPU re-blurred them every frame,
on every page (the sidebar is always visible). keep the translate drift + opacity (both
compositor-only, the blur layer just moves), remove the scale. same look, no per-frame reblur.
2026-06-27 11:34:37 -07:00
BoulderBadgeDad
bce6a91aa2 dashboard: stop the cursor-blob pseudo-elements re-blurring every frame (#935)
each .dash-card renders two accent-blob pseudo-elements — ::before is 1280x1280 blur(48px),
::after 540x540 blur(18px) + mix-blend-mode:screen — and both ran an INFINITE scale-pulse
animation. scaling a blurred element re-rasterizes the blur every frame; with 8 cards × 2
blobs that's 16 huge blurred layers re-blurring at 60fps whether or not the user touches
anything. that's the dashboard's whole-screen repaint / ~36% idle GPU.

remove the infinite pulse (the dashBlob*Pulse animations). the blob still follows the cursor
via --blob-x/y; it just no longer 'breathes' at idle, so when nothing's moving there's nothing
to repaint. trimmed will-change to the props that actually change (left/top).
2026-06-27 11:29:39 -07:00
BoulderBadgeDad
3c33e31985
Merge pull request #928 from nick2000713/fix/post-processing-race-and-followups
Fix import-vs-quarantine race + opt-in rank-based download order + quality-settings UI cleanup
2026-06-25 14:52:53 -07:00
BoulderBadgeDad
7a8b66fd2e Auto-Sync Manager: redesign hourly + weekly boards as horizontal lanes
Replace the side-scrolling column board with vertically-stacked interval lanes (hourly) and day lanes Mon-Sun (weekly). Empty intervals/days collapse to thin dashed strips, busy ones grow; scheduled playlists flow as cards within a lane. Kills the horizontal scroll + the wasted whitespace of the old kanban columns, and the two boards now share one cohesive design.

Polish: accent gradient wash + gradient interval numerals + count badge on filled lanes, drag-over glow/lift, card pop-in animation, hover states. Also preserves the board's scroll position across the full re-render so dropping/removing a playlist no longer snaps it back to the top. Same drag-and-drop handlers + scheduled-card content reused; old column CSS is now unused (harmless).
2026-06-25 13:06:07 -07:00
dev
2668980872 feat(ui): collapsible ⓘ help, rank-based toggle, tidier quality-profile settings
Quality-profile settings UI cleanup:

- Add the "Rank-based download order" toggle (priority mode). It's hidden when
  Best quality is active, since that mode always ranks by quality.
- Plain-language search-strategy options ("fast" / "thorough"); load + save the
  new rank_candidates_by_quality flag.
- Move the long help texts behind a dim ⓘ icon that sits on the (fixed) label
  row and toggles a collapsible body below — the trigger no longer moves on
  open. Applied to: search strategy, rank-based order, off-list fallback,
  AcoustID-verified, and the "How it works" ranked-targets explainer.
  toggleSettingHelp walks to the next .setting-help-body sibling so it works
  regardless of wrapper or an in-between control.
- Fix the "Search strategy" label: zero the flex-row margin so it aligns with
  the ⓘ, and bump it to 12px/brighter so it doesn't read as dim/undersized.
- Remove the duplicate "🎵 Quality Profile" heading inside the tile body.
- Replace the inline "Reset to defaults" link with a proper ↺ button.
- Restore the gap between the "Quality priority" label and the target list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 22:01:09 +02:00
BoulderBadgeDad
ed0a2079cf
Merge pull request #896 from nick2000713/feature/best-quality-search-mode
Global quality system: real-audio verification, best-quality search & quality profiles (please try...not ready to merge)
2026-06-24 20:27:38 -07:00
BoulderBadgeDad
79101e1847 Sync detail: label wing-it rows 'Unmatched', not '→ Wishlist'
A wing-it fallback track shows download_status='wishlist' (the sync stamps that on
every unmatched track) but was never actually added — the sync skips wing_it_* for
the wishlist. Showing '→ Wishlist' implied it was wishlisted. Now those rows read a
muted, non-actionable 'Unmatched' instead. Real wishlisted tracks keep the amber
'→ Wishlist' re-add button.
2026-06-24 18:30:49 -07:00
BoulderBadgeDad
e148f859e7 Sync detail modal: click '→ Wishlist' to re-add a track with the original context
In the dashboard Recent Syncs detail modal, the '→ Wishlist' status on unmatched
tracks is now a button. Clicking it re-adds that exact track to the wishlist with
the SAME context the sync used (source_type='playlist' + the playlist's name/id +
failure_reason), so it's indistinguishable from the original auto-add.

- reconstruct_sync_track_data() (pure, tested): prefers the full cached track from
  tracks_json (by source_track_id, then index) so album art/full data carry over;
  falls back to the track_result fields; refuses non-'wishlist' rows and rows with
  no id (can't re-wishlist a matched/unidentifiable track).
- POST /api/sync/history/<id>/track/<i>/wishlist resolves the entry server-side and
  calls the wishlist service; idempotent (reports added vs already-on-wishlist).
- button shows a busy state then '✓ Re-added' / '✓ On wishlist'.

7 pure tests (full-track preference, id-vs-index match, fallback rebuild, non-
wishlist + out-of-range refusal). JS/PY/ruff clean.
2026-06-24 16:09:55 -07:00
dev
124e8bb21c fix(ui): polish the ranked-target add controls
- dark-style the format <optgroup>/<option> so the dropdown no longer shows
  light "bars" over the dark theme (mirrors the existing
  .library-source-filter-select optgroup treatment)
- replace the fiddly tiny kbps number input with a dropdown of reference
  bitrates (Any / ≥96 / ≥128 / ≥192 / ≥256 / ≥320, default 320) — no typing,
  consistent with the lossless bit-depth/sample-rate selects
- bump control font 11→13px, larger padding + min-height, format select
  min-width so the row is comfortable to use

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:14:12 +02:00
BoulderBadgeDad
bac5da9177 Align playlists: add Plex support + cover art + modal redesign
The align buttons were gated to Navidrome, so Plex users (the actual tester) never
saw them. Plex reorders in place via plexapi moveItem/removeItems — preserves the
playlist's poster/summary/ratingKey (no delete-recreate), same spirit as Navidrome's
overwrite.

- plex_client.reorder_playlist(): moves each desired track into sequence, removes
  any current item not in the ordered list (Mirror drops extras; Keep includes them).
  get_playlist_track_ids() feeds the shared tested plan_align_rewrite.
- /align endpoint dispatches navidrome + plex; reuses the pure planner for both.
- frontend gate opened to navidrome|plex.
- modal redesigned: cover art per row, gradient header, pop/fade animation, hover
  rows, real polish (was a plain numbered list).

plexapi moveItem/removeItems signatures verified against the installed version.
2026-06-24 13:12:09 -07:00
BoulderBadgeDad
8afbfbfeab Align modal: pin footer so the Align buttons aren't clipped on long playlists
The server-order list wasn't flex-shrinking, so a long tracklist pushed the
align footer past the dialog's 80vh cap and overflow:hidden clipped it. Make the
list flex:1/min-height:0 (scrolls) and the footer flex:0 0 auto (always visible).
2026-06-24 13:03:10 -07:00
BoulderBadgeDad
606d1f951d Align playlists: reorder a server playlist to the source order (Navidrome)
Adds the 'Align playlists' action to the out-of-order modal — a dedicated,
order-only write path that does NOT touch the normal sync. Subsonic has no
per-track move, so it overwrites the song list in source order via createPlaylist
+ playlistId (same primitive replace-mode uses; identity/id preserved).

- plan_align_rewrite() (pure, tested): matched server ids in source order; every
  one must already be in the playlist (never injects a track); extras either
  dropped ('Mirror source') or parked at the end ('Keep extras'); returns None on
  stale data so a vanished track can't be written.
- navidrome rewrite_playlist_order() primitive (raw ordered ids).
- /api/server/playlist/<id>/align: validates ids are in the live playlist, then
  rewrites. Navidrome-only for now (Plex/Jellyfin reorder = follow-up).
- modal gets two explained options; missing tracks are NOT added (normal sync's
  job) and that's stated. Metadata-free by design — it only reshuffles existing
  server ids, so there's no sync-parity surface.

Open: confirm createPlaylist+playlistId preserves the playlist comment/image on a
live Navidrome (same risk as replace mode); add a re-apply step if it doesn't.
2026-06-24 12:58:01 -07:00
BoulderBadgeDad
ecd2500c39 Server playlist editor: surface 'accurate but out of order' + read-only server-order view
The editor renders the server column in SOURCE order (reconcile_playlist pairs
each server track to its source row), so a reordered-but-same-membership playlist
read as '5 matched / in sync' when Navidrome's real order actually differed — the
reorder never reaching the server was invisible.

- compute_order_status() (pure, tested): matched tracks' server positions must be
  strictly ascending in source order; uses RELATIVE order so missing/extra tracks
  never false-flag. reconcile entries now carry server_index (additive).
- endpoint returns order_status + server_order (the server's actual sequence).
- editor shows an amber 'out of order' badge on the server column when membership
  matches but sequence differs, opening a read-only modal of the real server order.
  One-way: source order stays the source of truth; no server-side editing.

Tests reproduce the reported 'Real Love Baby moved to #2' case + guard against
false-flagging on missing/extra. The actual 'sync order' WRITE is a separate
follow-up (membership/extra semantics + live identity-preservation test pending).
2026-06-24 12:15:47 -07:00
dev
1f14fb4d5e fix(quarantine): align single rows with groups via fixed-width alt-slot
Move the "▾ N more" toggle out of .verif-actions into a dedicated
.verif-quar-alt-slot div (min-width: 68px) so every row reserves
the same horizontal space — action buttons now stay aligned whether
a row is a single track or the head of a group.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:44:23 +02:00
dev
ef0b68a973 fix(quarantine+quality): persistent group toggle, better alt-row UI, quality upgrade default scope
Quarantine grouping:
- First candidate shown as normal row; others hidden under a "▾ N more"
  button inline in the actions bar — no separate header row
- Group open state tracked in _verifQuarOpenGroups (Set), survives
  periodic re-renders so the list no longer auto-collapses

Quality Upgrade Finder:
- Default scope changed from 'watchlist' to 'all' so it scans the whole
  library when no scope is explicitly configured
- _get_settings rewritten to read the full settings dict at once
  (same pattern as QualityUpgradeScannerJob) to fix silent read failures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:26:41 +02:00
dev
5f5bf4b24e feat(quarantine): consolidate quarantine view into downloads page filter
Removes the duplicate Quarantine tab from the Library History modal and
brings the same-song grouping feature into the ⚠ Unverified/Quarantine
filter on the Downloads page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 15:14:54 +02:00
nick2000713
63374b32f1 Merge remote-tracking branch 'nezreka/dev' into feature/best-quality-search-mode
# Conflicts:
#	core/hifi_client.py
2026-06-23 11:33:50 +02:00
BoulderBadgeDad
5f1ec9ed7e #903: fix mirrored-card layout break from export status div
The live export status was a separate flex child with flex-basis:100%, which became a
greedy item in the card's flex row and squished the info column to min-content (text
wrapping vertically). Inject the status into the card's existing .card-meta line instead
(same approach as the pipeline phase indicator) so it sits inline and leaves the row intact.
Removes the offending div + CSS.
2026-06-22 22:28:07 -07:00
BoulderBadgeDad
c06ef6bb34 #903: mirrored-playlist card export button + live status UI
Phase 6 (UI). Adds an export button to the mirrored-playlist card's hover action row (next
to rename/link/delete). Click -> a small on-brand modal to pick a destination (Sync to
ListenBrainz directly, or Download .jspf). Starts the background export, then polls status
and shows live progress on the card ('Matching 340/1000 · 312 matched' -> 'Synced · 947/1000
matched · view'). Reuses the tested backend job/endpoints; additive (new button + CSS + JS
functions, existing card render untouched apart from the inserted button).
2026-06-22 20:40:03 -07:00
BoulderBadgeDad
df6815c2cc perf(dashboard): remove invisible card blur + redundant shadow layers
Profiling the actually-painted dashboard found two pure-waste GPU costs (no visual
payoff), reclaimable with zero degradation:

- backdrop-filter on cards whose backgrounds are already 90-99% opaque, so the blur
  is invisible: service-card (x3), stat-card-dashboard (x3), activity-feed-container.
  Dropped the filter, nudged opacity to ~0.97 so the unblurred sliver is imperceptible.
- redundant/near-invisible box-shadow layers on the two biggest elements: page-shell
  (near-fullscreen — collapsed two stacked outer shadows to one) and sidebar (dropped
  a duplicate layer + a 0 0 60px accent glow at 6% opacity that's barely visible but a
  costly 60px-blur pass).

Targets the DURING-USE cost, not idle. sidebar-header keeps its blur (genuinely
translucent), and the cursor blob is untouched (that one's a real visual tradeoff).
2026-06-22 17:10:40 -07:00
BoulderBadgeDad
b5b71df3fa perf(dashboard): trim blur radii (orbs 40->28px, header backdrop 28->18px)
GPU fill/blur cost scales with radius. The sidebar aura orbs already fade to
transparent at 70% of their gradient, so dropping their blur 40->28px shrinks the
composited bounding box with no perceptible softness loss. The frosted header's
backdrop-filter is re-blurred whenever the orbs drift behind it; 28->18px cuts
that per-frame work ~a third while keeping the frosted look. Relief is biggest on
weak GPUs (the machines people complain about).
2026-06-22 15:02:19 -07:00
BoulderBadgeDad
9454970a83 perf(dashboard): sidebar header sweep animates transform, not left
The .sidebar-header::after ambient sweep animated `left` (-100% -> 140%) on an
8s infinite loop — forcing a layout recalc every frame it's on screen, on the
sidebar that's present on every page. Convert to transform: translateX() with a
pixel-identical travel path (element is 60% of header width, so translateX(400%)
== the old 240%-of-header sweep) + will-change. Compositor-only now; no per-frame
layout. Zero visual change.
2026-06-22 15:01:49 -07:00
BoulderBadgeDad
89018bb6b3 CSS: .sidebar-header z-index 2 -> 1
Drop the header to the same stacking level as the nav instead of forcing it above.
2026-06-22 12:06:57 -07:00
dev
b761229a00 merge: pull upstream/main (2.7.4) into feature/best-quality-search-mode
- Keep our v3 ranked-targets quality system (filter_and_rank, QualityTarget)
  in soulseek_client.py, settings.js, database presets, and index.html
- Take upstream removal of standalone quality-scanner code:
  QualityScannerDeps + run_quality_scanner moved to repair job
  (core/repair_jobs/quality_upgrade_scanner.py)
- Take upstream AAC-tier addition in database/music_database.py default profile
- Take upstream removal of /api/quality-scanner/* routes from web_server.py
- Remove test_discovery_quality_scanner.py (deleted upstream)
- 47 upstream commits absorbed (2.7.3 + 2.7.4 including re-identify flow,
  dead-folder cleanup, track-number prefix strip, and more)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-19 16:54:51 +02:00
BoulderBadgeDad
1ccc7b5e15 #889: fix re-identify modal header — clip the blurred bg in its own layer
The hero's overflow:hidden was clipping the header content, but removing it let the
blurred background + overlay cover (and steal clicks from) the source tabs below.
Move the decoration into .reid-hero-decor — an absolutely-positioned clip layer
that contains the blur and is pointer-events:none — so the header content (a sibling,
never clipped) shows in full AND the tabs stay clickable.
2026-06-18 16:57:24 -07:00
BoulderBadgeDad
c4c112d17e #889 Phase 5: wire the Re-identify button into the Enhanced library view
Adds a per-track ⇄ action (admin-only, alongside source-info/redownload/delete)
that opens the Re-identify modal seeded with the track's title/artist/album/art.
The loop is now live: click ⇄ → pick a release → file stages + hint writes →
auto-import re-files it under the chosen single/EP/album (and replaces the old
entry on success when 'replace' is ticked).

Double-gated: the button only renders for admins, and /api/reidentify/apply
re-checks is_admin server-side.
2026-06-18 15:39:41 -07:00