Commit Graph

14 Commits

Author SHA1 Message Date
Psychotoxical 209dd61442 refactor(lib): consolidate generic infra into src/lib
Move domain-agnostic, feature-free helpers out of the flat utils/ and store/
roots into src/lib/ (plan M4, §3 lib/ layer):
- lib/format/: formatBytes, formatClockTime, formatDuration, formatHumanDuration,
  relativeTime (pure format/date/byte/clock helpers)
- lib/i18n.ts: i18next bootstrap (global app infra)
- lib/util/: sanitizeHtml, platform, dedupeById, safeStorage (pure helpers +
  the zustand storage adapter)

playbackScheduleFormat stays in utils/format (runtime usePlayerStore dep =
audio-core coupling, not generic). formatClockTime keeps a type-only
@/store/authStoreTypes import (erased, no runtime inversion — accepted per the
deviceSync precedent).

Pure move via deep @/lib/* specifiers (no barrel → no mock-collapse surface).
tsc 0, lint 0/0, full suite 319 files / 2353 tests green.

Tooling note: lib_move.py regex extended to also rewrite side-effect imports
(import './i18n') and vi.importActual paths — both were silent gaps.
2026-06-30 08:07:04 +02:00
cucadmuh 5167d8f49e feat(ui): themed startup splash with deferred window show (#1030)
* feat(ui): add themed startup splash with deferred window show

Show a theme-aware loading splash before the Vite bundle mounts, hide the
native window until it paints, and use per-theme logo gradient colors.

* docs: note startup splash in CHANGELOG and credits for PR #1030

* docs: attribute PR #1030 startup splash to cucadmuh
2026-06-08 12:59:07 +03:00
Frank Stellmacher e0ff596a02 refactor(styles): split tracks.css into per-section files (#659)
tracks.css (539 LOC) → 8 per-section files in src/styles/tracks/ +
an index.css. Same mechanic as the theme/components/layout splits.
Concatenating reproduces the original byte stream (+1 trailing newline).

Generated via /tmp/split-tracks-css.mjs.
2026-05-13 19:22:13 +02:00
Frank Stellmacher 18bf3adb1f refactor(styles): split layout.css into per-section files (#658)
layout.css (3209 LOC) → 29 per-section files in src/styles/layout/ +
an index.css that imports them in original cascade order.

Same mechanic as theme.css + components.css splits: top-level sections
detected by single-dash or 3+ dash header decoration. Concatenating in
@import order reproduces the original byte stream (+1 trailing newline,
cosmetic).

Generated via /tmp/split-layout-css.mjs.
2026-05-13 19:15:40 +02:00
Frank Stellmacher 4b4cf42167 refactor(styles): split components.css into per-section files (#657)
components.css (14205 LOC) → 84 per-section files in src/styles/components/ +
an index.css that imports them in original cascade order.

Same approach as the theme.css split: top-level sections are detected by
single-dash or 3+ dash header decoration (/^\/\* ─(?: |─{2,})/), 2-dash
sub-sections stay inside their parent. Concatenating in @import order
reproduces the original byte stream (+1 trailing newline, cosmetic).

Each section is now self-contained — touching Tracklist, Modal, Hero,
Sidebar, etc. only opens one focused file.

Generated via /tmp/split-components-css.mjs.
2026-05-13 19:10:11 +02:00
Frank Stellmacher 45a6a18849 refactor(styles): split theme.css into per-theme files (#656)
theme.css (16138 LOC) → 122 per-section files in src/styles/themes/ +
an index.css that imports them in original cascade order.

Concatenating all files via index.css reproduces the original byte stream
(+1 trailing newline, cosmetic).

Each top-level section header in theme.css (matching /^\/\* ─{3,}/)
becomes its own file, slugged from the header text (or the [data-theme]
selector found in the body when the header was a banner). Pure-separator
headers fold into the previous section so they don't create empty files.

Generated via /tmp/split-theme-css.mjs.
2026-05-13 19:00:50 +02:00
Frank Stellmacher 0cd8998dc9 refactor(app): Phase B.1 — extract pre-React bootstrap into src/app/ (#555)
main.tsx shrinks from 56 -> 17 LOC. New module surface:

  - src/app/windowKind.ts: cached getWindowKind() detector,
    replaces the global __PSY_WINDOW_LABEL__ string everywhere
  - src/app/bootstrap.ts: pushUserAgentToBackend +
    pushLoggingModeToBackend + runPreReactBootstrap orchestrator

App.tsx + playerStore.ts now read getWindowKind() instead of poking
window.__PSY_WINDOW_LABEL__ directly. Behaviour-preserving.
2026-05-12 01:39:39 +02:00
Maxim Isaev 39f4d03da5 feat(player): queue undo/redo with hotkeys and playback-aware restore
Snapshot queue, current track, time, and pause before queue mutations.
Ctrl+Z/Cmd+Z restores prior state; Ctrl+Shift+Z/Cmd+Shift+Z reapplies undone
edits when the redo stack is non-empty; new edits clear redo.

Resync the Rust audio engine only when the current song identity changes
from the pre-apply state so reorder/enqueue-style edits keep live playback.

Register the document capture listener from main.tsx after the window label
is set. Mini player forwards undo/redo via mini:undo-queue and mini:redo-queue.
2026-04-27 00:17:10 +03:00
Maxim Isaev 4b60495e38 feat(playback): loudness bind rules, analysis seeding, and normalization UI
Resolve integrated loudness from SQLite only at decode bind; keep pre-trim
until a row exists, then allow provisional gain from live updates. Pass
DB-stable hints from the web app into play and gapless preload.

Default pre-measurement attenuation -4.5 dB with an icon reset in Settings;
drop redundant normalization copy and shorten pre-trim descriptions.

analysis_cache seed_from_bytes returns an outcome and skips redundant
waveform work on cache hits; wire callers and related frontend/backend glue.
2026-04-26 02:29:54 +03:00
Frank Stellmacher e3aabd98b7 feat(tracks): add Tracks library hub page (closes #299) (#300)
New /tracks route with three sections:

- Hero "Track of the moment" — random pick with play / enqueue / reroll
- Random Pick rail — 18 song cards, rerollable; hero song deduped
- Browse all tracks — virtualized list (@tanstack/react-virtual),
  paginated 50 at a time

Browse uses Navidrome's native /api/song?_sort=title&_order=ASC for
proper A-Z order (no Subsonic equivalent), with automatic fallback
to search3 on non-Navidrome servers. Search input drives search3
with 300ms debounce. Bearer token cached module-level, re-auth on 401.

Play button on rows + cards calls a new enqueueAndPlay() helper that
appends to the existing queue (skip if duplicate) and jumps to the
song — different from playSongNow which replaces the queue. Enqueue
button stays opaque (no hover-only).

i18n keys for sidebar.tracks + tracks.* namespace in all 8 locales.
New AudioLines sidebar icon. Sidebar entry inserted between
"All Albums" and "Build a Mix".

Performance: cover thumbnails dropped (uniform layout instead),
RAF-throttled scroll prefetch, hover transforms removed from cards
(WebKitGTK compositing-friendly).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 14:23:15 +02:00
cucadmuh bac0afe6ae fix(subsonic): sync Rust HTTP UA with main webview UA (#235)
Use the main window WebView user agent as the runtime source for Rust-side HTTP clients and refresh the audio engine client when the UA changes. This keeps backend and WebView requests aligned while preserving a simple startup sync flow.
2026-04-21 19:04:04 +02:00
Psychotoxical cef2db92cb feat(mini-player): floating mini window — early alpha (#162)
A second webview window (label "mini") with a compact player: album art,
title, artist, prev/play/next, progress bar, pin-on-top toggle, expand
back to main, close. Main minimizes on open and restores when the mini
is hidden or closed. Spacebar toggles, arrow keys skip tracks.

Cross-window sync: main window subscribes to playerStore and pushes
`mini:sync` via emitTo on track / play-state change. Audio progress
already broadcasts to all windows via `audio:progress`. Control actions
(prev/next/toggle) come back via `mini:control`; main-window restore
goes through a direct Rust command because WebKitGTK pauses JS in a
minimized webview.

Tiling-WM detection reused from existing `is_tiling_wm()` — always-on-top
is skipped on Hyprland/Sway/i3 since it's ignored there anyway.

Early alpha: no queue expand, no lyrics, no EQ, no drag-snap. Scope kept
deliberately tight for v1; follow-ups planned.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 23:32:20 +02:00
Psychotoxical e23280c013 feat(sidebar): add split Mix navigation mode with separate sidebar entries
Extract ALL_NAV_ITEMS to src/config/navItems.ts and add randomMix/randomAlbums
nav items. A new toggle in Settings switches between a single "Build a Mix" hub
and separate "Random Mix" / "Random Albums" sidebar entries (randomNavMode in
authStore). Fixes reorder crash caused by hidden items being overwritten with
undefined during the merge step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-15 22:27:53 +02:00
Psychotoxical 65459e53f1 Initial release with i18n and theming 2026-03-09 19:02:57 +01:00