Commit Graph

13 Commits

Author SHA1 Message Date
Psychotoxical 6d7464fddf refactor(waveform): co-locate seekbar feature into features/waveform 2026-06-29 22:30:53 +02:00
Psychotoxical e1ff4385d6 feat(settings): add "Square Corners" appearance toggle (#1215)
* feat(settings): add "Square Corners" appearance toggle

A Display toggle in Settings → Appearance → Visual Options that overrides
the active theme to render cards and cover art with square (non-rounded)
corners, for a sharper, boxy look.

Covered surfaces: grid cards (album/playlist/artist/song/because) and
their covers, detail-header heros (album/playlist/tracks), the Now
Playing / Radio view, the fullscreen player, the cover lightbox, the
queue panel cover, and the mini player.

Persisted in themeStore and driven by an `html[data-square-corners]`
attribute set in App.tsx — applied in both the main and mini-player
webviews (the mini player rehydrates the theme store on the shared
`storage` event). Strings added for all 12 locales.

* docs(changelog): add Square Corners to 1.50.0 CHANGELOG and What's New
2026-06-29 06:48:26 +02:00
Psychotoxical d70060923b refactor(settings): interface scale as a segmented picker (#1197)
Replace the Interface Scale zoom slider and its tick row with the shared
SettingsSegmented control — one button per preset (80–150%), matching the
Lyrics, Queue and Track-transitions sections. Drops the slider/tick
alignment issue entirely. Legacy off-preset values snap to the nearest
preset so one button is always active. Removes the now-dead slider CSS.
2026-06-27 02:11:09 +02:00
Psychotoxical d49424e95b feat(settings): add a Compact buttons appearance toggle (#1189)
* feat(ui): prototype compact hero and toolbar buttons (Large/Small appearance setting)

* feat(settings): rename action-button size toggle to "Compact buttons"

Promote the hero-button prototype to a real, app-wide setting.

- rename heroButtonSize → buttonSize, data-hero-buttons → data-button-size,
  hero-action-bar/hero-btn-label → compact-action-bar/compact-btn-label
- relabel "Hero buttons" → "Compact buttons" and broaden the description
  (action + toolbar buttons across detail pages and browse views) in all 11 locales
- move the feature CSS out of cover-lightbox.css into its own compact-buttons.css
- add tests: themeStore buttonSize toggle, SelectionToggleButton

* docs(changelog): compact buttons appearance toggle (#1189)

* docs(credits): compact buttons appearance toggle (#1189)
2026-06-25 14:49:09 +02:00
Psychotoxical b6ac879b14 refactor(settings): SettingsSubCard primitives across all of Settings (#1182)
* feat(settings): add SettingsSubCard primitives for sub-section controls

Reusable wrappers (SettingsSubCard / SettingsField / SettingsRow /
SettingsValue / SettingsCallout) that encapsulate the settings-norm-* sub-card
styles. Per-mode and detail controls inside a settings section should use these
instead of hand-rolling the box with inline padding or one-off classes — the
lack of a shared primitive is what let recent additions drift off the pattern.

* refactor(settings): move the Audio tab onto the SettingsSubCard primitives

Normalization, Track transitions and Hi-Res now build their sub-cards from the
shared primitives instead of raw settings-norm-* markup. Track Previews, which
rendered its sub-options bare with inline styles and dividers, now uses the same
sub-card so the whole Audio tab is consistent. Behaviour and styling unchanged;
Track Previews' start/duration become labelled slider rows like Normalization.

* refactor(settings): move the remaining tabs onto SettingsSubCard

Apply the shared sub-card primitives across the rest of Settings so every tab
matches the Audio-tab reference look:

- Appearance: grid columns, window buttons, UI scale, font, seekbar
- Library: random-mix blacklist, rating-filter detail
- Input: in-app + global keybind lists
- Integrations: Discord templates, external-artwork BYOK
- System: language, Linux Wayland text render, clock format, logging
- Storage: media dir, hot-cache detail, downloads folder
- Backup: mode picker + per-mode export/import

Detail/per-mode controls now sit in the bordered sub-card; toggle groups stay on
SettingsToggle. Behaviour unchanged.

* refactor(settings): box the data-heavy and shared sections in SettingsSubCard

Bring the previously-skipped 'borderline' sections onto the sub-card too:

- Playback speed: wrap the settings-mode controls in a sub-card; the compact
  player-popup path is left untouched (shares the component via the compact flag)
- Theme scheduler: mode picker + theme/time selects move into the sub-card
- Cover-cache + analytics strategy: the per-server tables sit in a sub-card

Behaviour unchanged; the table layouts and the playback-rate-* internals stay as
they are, only the surrounding sub-card is added for visual consistency.
2026-06-24 23:13:10 +02:00
Psychotoxical 7c724a642f chore(eslint): add ESLint toolchain and clean src to strict 0/0 (#1165)
* chore(eslint): add eslint toolchain and configs

* fix(eslint): resolve gradual-config errors (rules-of-hooks, no-empty, prefer-const, …)

* chore(eslint): clear unused vars in config, contexts, app and test

* chore(eslint): clear unused vars in api and music-network

* chore(eslint): clear unused vars in utils

* chore(eslint): clear unused vars in store

* chore(eslint): clear unused vars in cover and hooks

* chore(eslint): clear unused vars in components

* chore(eslint): clear unused vars in pages

* chore(eslint): remove explicit any in src

* chore(eslint): align react-hooks exhaustive-deps

* chore(eslint): zero gradual config on src

* chore(eslint): strict hook rules in store and utils

* chore(eslint): strict hook rules in hooks and cover

* chore(eslint): strict hook rules in components

* chore(eslint): strict hook rules in pages and contexts

* chore(eslint): document scripts ignore in eslint config

* chore(eslint): add lint script and zero strict findings

* chore(eslint): address review round 1 (gradual 0/0, per-site disable reasons)

* chore(eslint): tighten two set-state disable comments (review round 2 LOW)

* chore(nix): sync npmDepsHash with package-lock.json

* docs(changelog): add Under the Hood entry for ESLint setup (PR #1165)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-06-24 01:33:34 +02:00
Psychotoxical d50c9c444d refactor(settings): reusable SettingsGroup/SettingsToggle + boxed sections across tabs (#1126)
* refactor(settings): extract reusable SettingsGroup component

Pull the boxed sub-section pattern (bordered panel + accent uppercase
header) introduced for the Audio tab into a reusable <SettingsGroup
title desc> component, and migrate NormalizationBlock and
PlaybackBehaviorBlock onto it. No visual change.

* feat(settings): box the System behavior section into groups

Split the System -> App Behavior card (which bundled tray toggles, Linux
rendering tweaks and the clock format) into titled SettingsGroup panels
(Tray / Linux rendering / Clock) for a clearer, consistent boxed look.
Adds the group titles across all 9 locales.

* feat(settings): box Appearance visual options; optional SettingsGroup title

Split the Appearance -> Visual options card into a Display group and a
Window group (Linux custom titlebar controls). SettingsGroup now allows an
optional/omitted title for plain boxed panels. Group titles added across
all 9 locales.

* feat(settings): box Discord cover source and templates

Group the Discord cover-source toggles and the activity templates into
separate SettingsGroup panels (templates reuse their existing title/desc),
dropping the manual indent and inline header. No new strings.

* feat(settings): box the Music Network section

Give the scrobble-master toggle, the enrichment-primary picker and the
provider list each a boxed SettingsGroup with an accent header, and switch
the add-a-service provider rows to the boxed panel style for consistent
contrast with the rest of settings.

* feat(settings): titled boxes for Integrations sections + cover source label

Wrap the Discord enable toggle, the Discord cover-source picker (now with a
"Cover art source" title and explainer), Bandsintown and Show-in-Now-Playing
in titled SettingsGroup panels so each single-item section gets the same
accent-headed boxed look. New cover-source strings across all 9 locales.

* feat(settings): box Lyrics tab sections

Wrap the lyrics-sources customizer and the sidebar-style picker in titled
SettingsGroup panels (reusing the existing section titles).

* refactor(settings): consistent content inset in SettingsGroup

Add a body wrapper with a small left inset so every boxed section indents its
controls uniformly (title stays flush at the box edge). Defined once in the
component instead of per section.

* feat(settings): box Storage tab sections incl. cover art cache

Wrap media directory, cover art cache strategy, next-track buffering and
downloads in titled SettingsGroup panels (reusing existing section titles);
align the cover-cache table's first column flush with the new content inset.

* feat(settings): box Library tab, relocate Lucky Mix, add SettingsToggle

- Add a reusable SettingsToggle component for the repeated label/desc/switch row.
- Box the Random Mix blacklist (grouping built-in keywords with the custom
  filter), Ratings and the Analytics strategy section.
- Move the "Show Lucky Mix in menu" toggle out of the blacklist and up next to
  split-mix navigation and now-playing-at-top in the sidebar customizer.

* feat(settings): box remaining Appearance sections

Wrap library grid, UI scale, font and seekbar style in titled SettingsGroup
panels (reusing the existing section titles).

* feat(settings): box System language, logging and backup sections

Wrap language and logging in titled SettingsGroup panels, and rework the
backup section to drop its redundant inline header (the subsection already
provides it) and box its content. About / Contributors / Licenses stay as
free display content.

* feat(settings): box remaining Audio sections

Wrap Hi-Res, equalizer, playback speed, audio output device and track
previews in titled SettingsGroup panels (reusing the existing section titles).

* feat(settings): box Input tab keybinding sections

Wrap the in-app and global shortcut lists in titled SettingsGroup panels.

* feat(settings): box the Personalisation tab

Convert every customizer (sidebar, home, artist, queue toolbar, playlist,
player bar) to plain inner containers and wrap each in a titled SettingsGroup,
so the boxed look is consistent without nested cards. Box the queue display
mode picker too.

* feat(settings): box the Themes sections above the store

Add a boxed option to the flat Themes sections and apply it to Your Themes,
Auto-Switch Theme and Import (the theme store stays unboxed). Drop the inner
settings-card from InstalledThemes, ThemeImportSection and the scheduler so
the SettingsGroup is the only frame.

* fix(settings): give the Themes boxes a card carrier and accent header

The flat Themes sections had no settings-card behind them, so the bg-app
SettingsGroup had no contrast against the page. Box each above-store section
as settings-card > titled SettingsGroup (accent header) like the rest.

* fix(settings): clean up the server library sync status line

Collapse the offline status to a single icon + phrase ("Server offline —
sync deferred") instead of a redundant "Deferred" badge alongside it, and
drop the now-unused string across all 9 locales.

* fix(queue): keep toolbar toggle buttons coloured active while hovered

The generic .queue-round-btn:hover rule outweighed .active in specificity, so
an active button kept the hover colour until the pointer left — the toggle
only looked on after moving the mouse away. Add an explicit active-hover rule
(mirroring the mini player) so active wins immediately on click.

* fix(settings): fix Lyrics triple title, drop dead Discord cover keys

- Remove LyricsSourcesCustomizer's own section header (the subsection already
  titles it) and make its inner cards plain; the Lyrics boxes are now title-less.
- Drop the dead discordCoverSource/discordCoverSourceDesc i18n keys (superseded
  by discordCoverTitle/Desc) and un-jam the libraryIndexServer status line in
  all 9 locales.

* refactor(settings): drop redundant group titles and double frames

Single-group sections duplicated the subsection title in the group header — make
those groups title-less so the subsection header is the only title. A card whose
sole child is one group now collapses via CSS so single-group sections render a
single frame (matching the card-less Lyrics layout) instead of a card-in-group
double border. Multi-group cards keep their frame as the grouping container.

* fix(settings): keep the flat Themes cards framed when collapsing single-group cards

The single-group card collapse also flattened the Themes section cards, which —
not being inside an accordion — are themselves the contrast surface. Exclude
.themes-section descendants from the collapse.

* refactor(settings): use SettingsToggle for Appearance and System toggle rows

Replace the hand-rolled toggle-row markup in the Appearance visual options and
System tray/Linux/changelog rows with the shared SettingsToggle component, and
extend it with a searchText prop and ReactNode descriptions for the remaining
call sites.

* refactor(settings): roll SettingsToggle out to Integrations, Audio and Storage

Replace hand-rolled toggle rows in the Discord/Bandsintown/Now-Playing,
Hi-Res, hot cache and playback behaviour/rate sections with SettingsToggle.
Make its label optional (desc-only rows whose title is the group header) and
add searchText/id pass-throughs.

* refactor(settings): use SettingsToggle for the remaining standard toggle rows

Track previews master, sidebar lyrics style, YouLyPlus/static-only, queue
display mode and the sidebar split-nav/now-playing/lucky-mix rows now use the
shared component. Genuinely custom rows (drag lists, the skip-star threshold,
the AudioMuse row with its inline link, the fine-step advanced badge) stay
hand-rolled.

* fix(settings): drop duplicate titles on Hi-Res/Bandsintown/Now-Playing, un-jam locale line

These single-toggle sections kept a group title identical to their subsection
header; once the single-group card collapses the title rendered twice stacked.
Make them title-less (the subsection header is the label). Also split the
discordCoverTitle locale entry onto its own line in all 9 locales.

* feat(settings): restore section icons in boxed Themes headers

SettingsGroup gained an optional accent icon slot rendered before the
title; ThemesTab forwards the Palette/Clock/Upload icons the boxed
sections lost when they moved off the bespoke <h2> header.

* style(settings): re-indent SettingsGroup children to nesting level

Pure whitespace: children wrapped in SettingsGroup were left at their
pre-wrap indent. No content change (git diff -w is empty).

* fix(settings): clarify Native Hi-Res Playback description

The old copy led with the disabled-state behaviour ("forces 44.1 kHz"),
which read as if the toggle itself locked output to 44.1 kHz. Describe
what enabling actually does: play each track at its native sample rate
(reconfiguring the output device to match) instead of resampling to
44.1 kHz. Updated across all 9 locales.

* docs: changelog + what's-new for the settings layout refactor (#1126)
2026-06-18 22:24:34 +02:00
Psychotoxical 028eb65f7d feat(titlebar): selectable window button styles + minimize toggle (#1083)
* feat(titlebar): selectable window button styles + minimize toggle

Custom title bar (Linux) gains a window-button style picker, mirroring
the seekbar style picker pattern. Six form-named styles: dots, dotsGlyph,
flat, pill, outline, glyph. All buttons now carry minimize/maximize/close
glyphs for clear, colour-blind-friendly iconography; dots reveal glyphs on
hover, dotsGlyph always shows them.

- New authStore state windowButtonStyle (default dots) + showMinimizeButton,
  with rehydrate validation falling back to dots on unknown values.
- WindowButtonPreview reuses the real .titlebar-btn classes for WYSIWYG tiles.
- Picker + minimize toggle render under the Custom title bar setting, gated
  on the toggle being on.
- Monochrome styles use --text-primary glyphs and stronger borders for
  contrast on dark themes.
- Dev-build grey marker scoped to the real title bar so previews show true
  colours.
- i18n keys in all 9 locales; setter and rehydrate tests.

* docs(changelog): window button styles (#1083)
2026-06-13 23:52:56 +02:00
Psychotoxical f9df918c72 feat(themes): community Theme Store + semantic-token refactor (#1009)
* feat(themes): add semantic tokens for the theme-store contract (B0 P1)

Additive: define --highlight, --accent-2, --bg-deep, --bg-elevated and
--text-on-accent on the :root base as --ctp-* mappings. They resolve
per-theme automatically and nothing consumes them yet (zero behaviour
change) — groundwork for replacing direct --ctp-* use in components.

* refactor(themes): components consume semantic tokens, not --ctp-* (B0 P2)

Replace every direct --ctp-* reference in component/layout/track CSS and
TSX inline styles with the readable semantic token (--bg-app, --accent,
--highlight, --text-on-accent, …). --ctp-* now survives only as the
Catppuccin palette layer the base maps from, and as the deliberate
categorical rainbow in Composers/Genres/artistsHelpers (left untouched).

This is the readable contract surface for the community theme store.
Divergences (theme set a semantic var != its --ctp- source) are
corrections — the element now uses the theme's real semantic colour.

* feat(themes): add player-bar title/artist color tokens

New optional --player-title / --player-artist, defaulting to --text-primary / --text-secondary so nothing changes unless a theme overrides them. Lets a theme give the now-playing readout its own colour as a plain token.

* refactor(themes): token-only theme library (flatten, whitelist, one file per theme)

Turn every built-in theme into a single self-contained [data-theme] var block of semantic whitelist tokens (plus the internal --ctp-* palette layer):

- Flatten all themes: drop structural override rules, @keyframes, and global-token overrides (radius / shadow-elevation / transition / spacing / font / focus-ring). Signature player-bar readout colours are preserved via the new --player-title / --player-artist tokens.

- Normalize the var blocks to the semantic whitelist: drop the alternate token vocabulary (nav-active / scrollbar / bg-input / success / border-default / ...); rename --success -> --positive and --border-default -> --border where no whitelist equivalent was set. Migrate the few components that read those tokens to the whitelist equivalents.

- Split multi-theme files so each theme ships as its own file, making the built-in set 1:1 with the per-theme store packaging.

Kept as-is (built-in, not flattened): the two colour-blind-safe accessibility themes, plus the two curated core skins.

* chore(themes): remove seven themes retired after the token refactor

These themes leaned on heavy structural overrides and were dropped rather than flattened. Full removal each: the CSS file(s), the index.css import, the Theme type union, and the ThemePicker entry.

* feat(themes): granular tokens — track lists

Wire track rows to per-region tokens: row hover (--row-hover), the now-playing
row + indicator (--row-playing-bg / --row-playing-text), track title/artist/
number/duration text, column-header text, row dividers, and the resize-handle
active colour. Covers the desktop tracklist, the shared song-row (Tracks hub /
search), and the mobile tracklist. Drop a baked border fallback. Visual no-op.

* feat(themes): granular tokens — cards

Wire album and artist cards to per-region tokens (--card-hover-border,
--card-title, --card-subtitle, --card-placeholder-bg). Visual no-op.

* fix(themes): drop undefined/baked colour aliases

Replace the undefined --bg-surface (resolved to nothing — broken placeholder
backgrounds and filter input) with --card-placeholder-bg / --input-bg, and the
baked-hex aliases --color-error / --color-warning with --danger / --warning so
themes can actually recolour them.

* feat(themes): Spectrum demo theme + trim unused cascade tokens

Add a loud built-in demo theme that gives each region its own hue (sidebar
green, player pink, lists cyan, cards gold, menus red, controls blue) so the
per-region granularity is obvious when you switch to it. Drop two unused
cascade tokens (--sidebar-text-active, --row-active-bg) and the unused
on-media block (those media surfaces stay static by design).

* style(themes): make Spectrum demo brutally loud

Full-saturation neon per region (toxic green sidebar, magenta player, cyan
lists, acid-yellow cards, blood-red menus, electric-blue controls, purple
scrollbar) so the per-region separation is unmistakable. The earlier soft
tints were too subtle to read.

* feat(themes): name the granular demo theme Braindead

* feat(themes): granular per-region tokens — cascade layer + sidebar

Add an optional per-region token layer (semantic-cascade.css) so a theme can
recolour individual regions — sidebar hover, player controls, list rows,
menus, inputs, on-media surfaces — independently of the global tokens. Every
token defaults to its base token (or a media-safe literal), so this is a
visual no-op until a theme overrides one; it only adds control points.

Wire the sidebar region as the first consumer and drop the baked grey
fallbacks (--bg-tertiary, etc.) that no theme could reach.

* feat(themes): granular tokens — controls, menus, scrollbar

Wire inputs, buttons, sliders, the custom-select, context menus, submenus and
modals to per-region tokens, and tokenise the scrollbar. Complete B0 by
dropping the last direct --ctp-* references in the input/button/progress/
scrollbar utility CSS.

Fix three undefined-token bugs that fell back to nothing (so no theme could
reach them): --surface-2 (context-menu hover had no highlight), --bg-surface
(submenu create-input had no background), and the baked grey fallbacks in the
custom-select. Every other new token defaults to today's value — a visual
no-op that only adds override points.

* feat(themes): granular tokens — player bar

Wire the desktop player bar's transport controls, time toggle, and overflow
menu to per-region tokens (--player-control, --player-time-toggle-*, etc.).
Fix the undefined --surface-hover/--surface-active grey fallbacks on the time
toggle. Visual no-op; defaults match today's values.

* chore(themes): remove empty theme stub files

Six theme CSS files were reduced to comment-only stubs by the flatten
sweep but their files and @import lines remained. Five are empty
structural companions of now-flattened themes (morpheus, p-dvd,
aero-glass, luna-teal) and two are orphans of cut themes
(order-of-the-phoenix, pandora). Removed the files and their imports.

* feat(themes): runtime injection foundation for the theme store

Plumbing for installed community themes ahead of the in-app store UI,
nothing user-visible yet:

- installedThemesStore: persisted (localStorage) record of installed
  community themes incl. their CSS text, so an active community theme is
  available synchronously at startup (no flash, fully offline).
- themeInjection: reconcile <head> <style data-installed-theme> elements
  with the store; lightweight defense-in-depth sanitize on top of CI.
- themeRegistry: jsDelivr registry client with a 12h localStorage cache
  and stale-on-error fallback.
- App: inject installed themes before applying data-theme, in both webviews.
- themeStore: widen the Theme type to accept dynamic installed ids.

* feat(themes): dedicated Themes settings tab

Move theme selection and the day/night scheduler out of Appearance into
a new dedicated Themes tab — the future home of the community Theme Store.
Appearance keeps grid columns, visual options, UI scale, font and seekbar.

- ThemesTab: theme picker + scheduler (relocated verbatim).
- AppearanceTab: drop the two relocated sections + now-unused imports.
- Register the tab in settingsTabs (Tab union, resolveTab, search index)
  and Settings (tab bar, render, label map).
- i18n: settings.tabThemes in all 9 locales.

* feat(themes): community Theme Store browse + install

Add the Theme Store section to the Themes tab:

- Fetch the jsDelivr registry (12h cache, stale-on-error fallback).
- Search by name/author/description + filter by light/dark + refresh.
- Per-row CDN thumbnail, name, author, description and actions:
  Install / Apply / Update / Uninstall. Installing fetches the CSS,
  persists it (localStorage) and the runtime injection applies it;
  uninstalling the active theme falls back to the matching core.
- Rating slot left reserved (deferred).
- i18n: themeStore* keys in all 9 locales.

* feat(themes): slim bundle to fixed cores + flat Themes tab

Remove the 86 store palettes (incl. braindead) from the app bundle — the
CSS files, their index.css imports, the Theme union and the picker data —
leaving only the six fixed cores (Catppuccin Mocha/Latte, Kanagawa Wave,
Stark HUD, Vision Dark/Navy). Everything else installs from the store.

Themes tab is rebuilt flat (no collapsible accordions):
- "Your Themes": one card grid of the fixed cores + installed community
  themes; click to apply, uninstall on community ones (active theme falls
  back to the matching core). Catppuccin prefix on Mocha/Latte; a CVD-safe
  pill on the colour-blind-safe Vision themes.
- Scheduler day/night options include installed themes.
- Theme Store: alphabetical order, thumbnail lightbox, and a submit hint
  above the search linking to the themes repository.
- Nav order: Servers, Library, Audio, Themes, Appearance, Lyrics, …
- ThemePicker accordion removed; fixed-theme data moved to fixedThemes.ts.
- i18n for all new strings across 9 locales.

* feat(themes): reset removed-from-bundle themes to a bundled fallback

After slimming the bundle to the six fixed core themes, a profile upgraded
from an older build may have an active or scheduler theme that is now
store-only and not installed — it has no [data-theme] block and would render
as unstyled :root. Reset any theme/themeDay/themeNight that is neither bundled
nor installed to a bundled fallback: Mocha for the main + night slots, Latte
for the day slot. Runs synchronously in runPreReactBootstrap, rewriting the
persisted selection in localStorage before React mounts (no flash; Zustand
rehydrates after first paint). No auto-install and no network — the fallback
is always a bundled theme, so it works offline.

* feat(themes): floating back-to-top button on the Themes tab

The Themes tab can get long (theme grid + scheduler + full store list), so
add a floating back-to-top affordance that appears once the page is scrolled
and smooth-scrolls to the top. It is portalled into the route host and
positioned absolute against it — the main scroll viewport sets contain: paint,
which would otherwise make position: fixed resolve against the scrolling box
and drift with the content. Reusable component (scroll viewport id + threshold
props); i18n common.backToTop added in all nine locales.

* feat(themes): accessibility + state polish for the Theme Store

- Reuse the shared CoverLightbox for the thumbnail preview instead of a
  second inline dialog — gains a visible close button and a focus-managed,
  portalled dialog, and drops duplicated markup.
- Theme cards expose aria-pressed so assistive tech announces the selected
  theme, not just the visual check.
- Transient store messages get live-region roles (loading/empty/install
  failure = status, fetch error = alert).
- Thumbnails degrade gracefully when offline/missing (hide the broken-image
  glyph; the thumbnail button no longer stretches with the row, so its
  background can't show as letterbox bars).

* feat(themes): larger store-row thumbnails (120x75 -> 200x125)

The list previews were too small to make a theme out; bump the display size
(same 1.6 aspect). Thumbnails are now served at 720x450, so the larger
display stays crisp.

* fix(themes): bust thumbnail cache on registry change

jsDelivr serves theme thumbnails with a 7-day max-age, so when a thumbnail
is updated the webview keeps showing its cached old image (the path is
unchanged). Append the registry's generatedAt as a cache-busting query to
the thumbnail URLs (list + lightbox); it changes on every themes push, so a
registry refresh makes the webview re-fetch and reflect the current CDN
image instead of a stale one.

* docs(themes): changelog + credits for the Theme Store

Add the 1.48.0 "Themes — community Theme Store" changelog entry (PR #1009)
and the matching line in the Psychotoxical credits.

* fix(themes): address PR review (uninstall hygiene, validation, polish)

Uninstall/scheduler & validation:
- uninstallTheme() repairs every selection slot (active + day + night), not
  just the manual one, and is shared by both uninstall buttons (dedup).
- Validate theme CSS at install time and skip persisting CSS that won't inject
  (no more "installed/active but renders nothing" with no feedback).
- Harden the runtime validator: exactly one rule, scoped exactly to the
  theme's [data-theme='<id>'] selector (no unscoped/foreign selectors), no
  at-rules, url() only data:, no expression()/javascript:, size-capped.

Tokens & polish:
- Fix three dangling undefined tokens (--surface-2 x2, --bg-surface).
- Finish the warning/success token sweep (--warning / --positive, themeable).
- Apply the active theme synchronously before React mounts (no first-frame
  flash) and inject installed themes up front.
- One-time, dismissible notice when the slim-bundle migration reset a theme.
- Update badge uses semver, not string inequality.
- Offline/stale indicator in the store; cross-window theme sync; drop the now
  dead REMOVED_THEME_REMAP and Card.mode field.

Tests: themeInjection (validator + sync), themeRegistry (cache/force/stale/
malformed), uninstallTheme (slot repair), migration notice. i18n in all nine
locales. Full suite green (1755 tests).

* test(bootstrap): cover startup theme apply + cross-window sync

The review fixes added applyThemeAtStartup / installCrossWindowThemeSync to
bootstrap.ts (a hot-path file) without tests, dropping its coverage to 68.3%
and failing the frontend hot-path coverage gate (>=70%). Add unit tests for
both (and the no-op / malformed-storage paths); bootstrap.ts is back to ~98%.
2026-06-07 02:47:33 +02:00
Frank Stellmacher dc4eef1a97 feat(fullscreen-player): rebuilt static fullscreen player (#1001)
* feat(player): static media-center fullscreen player (v1)

New lean fullscreen player: sharp full-bleed background (artist photo, cover
fallback), no blur / no continuous animations. Bottom-left big cover bottom-
flush with a full-width semi-transparent text bar (title with queue position,
artist, year/genre + rating stars, next-up); control row of transport · center
time · actions; full-width seekbar; live clock. Only the seekbar, time readout
and clock update at runtime, each owning its state (no per-tick re-render).
Reuses FsSeekbar/FsPlayBtn, the cover/artist hooks, idle-fade and queue
helpers. Wired in AppShell; old player kept for A/B.

* feat(fullscreen-player): true waveform seekbar instead of thin bar

Replace FsSeekbar with the real WaveformSeek (cucadmuh's idea). The taller
canvas grows the bottom cluster upward, shifting the info/control rows up.
Height clamped to 32-52px.

* feat(fullscreen-player): up-next popover + control-bar styling

- Queue button opens a semi-transparent 'Up next' popover anchored bottom
  right; clicking a row jumps to that queue item.
- Larger bottom-right action buttons.
- Control row gets its own darker semi-transparent bar (touches the info bar
  above) for contrast; play button matches the plain transport buttons
  (no white circle, same size).

* feat(fullscreen-player): high-res (2000px) background cover

Fetch the full-screen background cover at the 2000px tier via the existing
on-demand fullRes path (same getCoverArt fetch, saved as a high-res WebP
outside the backfill pipeline) instead of the low-res 500px pipeline tier.
usePlaybackCoverArt now forwards a fullRes option to useCoverArt.

* feat(fullscreen-player): scrolling lyrics overlay + control-bar polish

- Lyrics button next to Queue toggles a centered, dark semi-transparent
  scrolling-lyrics overlay (reuses FsLyricsApple).
- Control bar darkened to match the lyrics overlay (0.88).
- Close button sized down to harmonize with the clock.

* feat(fullscreen-player): make rating stars clickable

The rating stars next to the year were display-only. Wire them to
queueSongRating (same path as the player bar / context menu): click to
set, click the current value to clear, with a hover preview. Keeps the
lucide outline look to match the rest of the control bar.

* fix(fullscreen-player): stable, high-res album cover

The foreground cover was keyed per track, so Navidrome's per-track
`mf-<id>` coverArt re-triggered the distinct-disc heuristic and reloaded
the cover on every song change within the same album. Key it on albumId
(via useAlbumCoverRef) so it stays put while the album is unchanged.

It also used the low-res tier; reuse the fullRes 2000px cover already
fetched for the background so the foreground is crisp and both share a
single fetch/decode.

* refactor(fullscreen-player): remove the old player and its settings

The static rebuild is now the only fullscreen player. Delete the old
FullscreenPlayer component, its old-only parts (FsArt, FsPortrait,
FsSeekbar, FsLyricsRail, FsLyricsMenu, useFsDynamicAccent) and its test.
Remove the now-orphaned settings (showFullscreenLyrics, fsLyricsStyle,
showFsArtistPortrait, fsPortraitDim) along with the Appearance
'Fullscreen player' section and its search entry. The new player always
shows the artist photo with cover fallback and has its own lyrics toggle.

Shared building blocks used by the static player (FsLyricsApple,
FsPlayBtn, FsClock, FsTimeReadout, FsQueueModal, useFsArtistPortrait)
are kept.

* i18n(fullscreen-player): translate the new player's strings

Wire the static player's previously English-only strings through i18n
across all 9 locales: now-playing label, track position, up-next label,
queue/lyrics/shuffle controls, and the queue overlay (title, empty,
close). Reuses existing queue.title / common.close keys; adds six new
keys to the player namespace.

* docs(changelog): note fullscreen player rebuild (#1001)
2026-06-05 14:23:47 +02:00
Maxim Isaev 0f5ece6d03 feat(settings): library card grid max columns in Appearance (4–12, default 6)
Persist libraryGridMaxColumns, wire useCardGridMetrics and card grid layout,
add i18n and settings search index; document performance hint in copy.
2026-05-15 02:19:20 +03:00
Frank Stellmacher dc5c64a109 refactor(waveform-seek): H3 — extract renderers + 2 hooks + SeekbarPreview component (#665)
* refactor(waveform-seek): H3.1 — extract helpers + constants + types

* refactor(waveform-seek): H3.2 — extract drawSeekbar + style renderers to utils/waveformSeekRenderers.ts

* refactor(waveform-seek): H3.3 — split renderers into static + animated

* refactor(waveform-seek): H3.4 — extract SeekbarPreview to WaveformSeekPreview.tsx

* refactor(waveform-seek): H3.5 — extract useWaveformHeights hook + hoist constants

* refactor(waveform-seek): H3.6 — extract useWaveformInterpolation hook
2026-05-13 22:04:45 +02:00
Frank Stellmacher afd0786e6c refactor(settings): G.59 — extract AppearanceTab (#624)
Move the Appearance tab body into AppearanceTab: ThemePicker, theme
scheduler (day/night themes + start times, 24h/12h locale-aware),
visual options card (cover art bg, playlist cover photo, bitrate badge,
floating player bar, artist images, Orbit trigger, preloadMiniPlayer,
custom titlebar on Linux non-tiling), UI scale presets, font picker,
fullscreen player portrait + dim slider, seekbar style picker.

The `isTilingWm` state + `is_tiling_wm_cmd` invoke effect, plus the
`useThemeStore` and `useFontStore` hooks, move into AppearanceTab —
no other tab needs them.

Settings.tsx 1761 → 1404 LOC (−357). Drops 9 imports that only the
appearance tab used (ThemePicker, THEME_GROUPS, SeekbarPreview,
useThemeStore, useFontStore, FontId, SeekbarStyle, lucide Clock /
Maximize2 / Type / ZoomIn).

Pure code move — no behaviour change.
2026-05-13 02:30:37 +02:00