Production-readiness pass before merging the Studio Reference redesign
to master.
Audio (backend):
- Reset AGC `_spectrum_ref` envelope on `start()` so a long silent gap
between sessions doesn't make the first new transients clip at the
ceiling. Annotated the trade-off (loud transient lifts reference for
a few seconds afterwards — the price of real loudness).
- Add `tests/test_audio_analyzer.py` with 10 cases: bin-edge layout,
AGC attack/release asymmetry, lifecycle reset. Skips numpy-dependent
cases when numpy isn't installed; CI has it.
Vinyl mode dead code removed:
- The toggle button was dropped during the sleeve refactor but the JS
state, 2 s `setInterval`, `beforeunload` handler, and `applyVinylMode`
call (commented out in app.js) all stayed. Now properly excised from
player.js + app.js + window.* exports.
- Stripped the matching `.album-art-container.vinyl*` CSS block and its
`vinylSpin` keyframes (~95 LoC).
Sleeve + tonearm fixes:
- Removed the duplicate `.now-playing .vinyl-stage` / `.vinyl-label` /
`.tonearm` block that was overriding the new `.vinyl-stage` rules by
source order — the uncommitted tonearm geometry never took effect
because the stale clone won the cascade.
- Tightened tonearm to 36% × 36% at right:-6%, top:26% so the SVG
bounding box stays right of the sleeve (sleeve right edge ~68%).
Needle now lands on the visible disc grooves at both rest and
playing rotations and never overlaps the cover.
- Removed sleeve `transform: rotate(-2.5deg)` + the matching mobile
`-1.8deg` override; sleeve now sits flat and squared-off.
- Removed the 1px inset hairline on the sleeve and the 1px outline +
inset highlight on the album art — cleaner, no semitransparent
border noise.
- Album art inset 5% to expose a cardstock margin around the print
(using explicit width/height — `inset` shorthand triggered the CSS
replaced-element rule that uses the image's intrinsic size and blew
out the grid track).
Mobile + misc:
- Removed mobile tonearm overrides at 720px and 420px — they were
calibrated for the pre-sleeve geometry and put the needle back over
the cover on phones; desktop geometry is proportional and works.
- Added `<meta name="mobile-web-app-capable">` alongside the legacy
Apple variant to silence the deprecation warning in Chromium.
- Replaced the "PRIMARY" badge on display cards with a copper star
icon (translation key still drives title + aria-label).
- `.gitattributes` with `* text=auto eol=lf` so Windows checkouts stop
nagging "LF will be replaced by CRLF".
Annotations:
- "REF · 24" record-label catalogue mark marked as intentional non-i18n
decoration in index.html.
CI: ruff clean, pytest 7 passed + 3 numpy-skipped (all 10 run on CI).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
VU needle (animated)
- Synthetic wobble bounded by current volume runs only while
state==='playing'. Two combined sines + jitter make it look
like a real analog needle reacting to peaks.
- Settles back to the static volume-mapped position when paused.
Spectrum (real audio)
- Now driven by the same frequencyData the visualizer canvas
uses. Each visual bar averages a chunk of frequency bins.
- Spans are now JS-injected (60 bars) instead of hardcoded so
the bar count is no longer baked in.
- Spectrum spans full width of the masthead column, height
bumped to 56px for presence.
- CSS animation pauses (sets via `body.audio-spectrum-live`)
when JS is driving heights so the keyframes don't fight.
- Synthetic CSS animation remains as the fallback when audio
capture isn't available.
Visualizer auto-enable
- On first install with loopback support, visualizer is
enabled so the spectrum is alive out of the box.
Dynamic background
- Lower max opacity (1 → 0.45 dark, 0.35 light)
- sepia + saturate filter + hue-rotate keep it palette-aligned
with the copper editorial tones instead of fighting them
- mix-blend-mode screen (dark) / multiply (light) blends into
the page background instead of overlaying
Update + connection banners
- Fully restyled: glassy card with copper hairline accent,
mono uppercase text, copper hairline-border CTA buttons,
minimal close button. Matches the rest of the editorial
palette instead of the old solid-green-bar look.
Tabs
- Replace SVG icons with mockup-style numeric badges (01..05)
- Hide the legacy .tab-indicator (was rendering as a long copper
bar above the strip); active tab gets its own copper underline
via .tab-btn.active::after
- Numbers turn copper on the active tab
Player view
- Volume control restored: mute button + slim copper slider live
inside the VU cluster, on the right of the readout, separated
by a hairline. Slider is the existing #volume-slider so all JS
hooks (bidirectional sync, drag, etc.) keep working.
- Track title font scaled down (clamp 34..64) and clamped to 3
lines with ellipsis so long YouTube-style titles don't dominate
the masthead. Adds word-break + overflow-wrap.
- #artist:empty and #album:empty are now display:none so blank
rows don't leave a gap when the source provides no metadata.
Mini player
- Forced display: grid with 4 columns: track / controls / progress
/ volume. Was inheriting legacy display:flex which pushed elements
into a single non-aligned row.
- Position locked: position: fixed, bottom: 0, left/right: 0 with
!important. The strip is firmly anchored to the viewport bottom.
- Top edge progress (::before) painted in copper with glow.
- Responsive collapse: hide progress at <=880px, hide volume at
<=540px, leaving track + controls on phones.
i18n
- tab.player default text aligned to "Now Spinning" (matches
existing en/ru values added earlier).
Wholesale replacement of the player view markup + a verbatim mockup
CSS block scoped to .now-playing. Previous approach kept restyling
legacy classes which left a layered, inconsistent result. This is a
clean snap: same DOM structure as the mockup, same CSS rules, mapped
onto existing JS-touched IDs.
Markup
- One <section class="now-playing"> with vinyl-stage on left and
track-masthead on right
- Vinyl spins via data-playstate and contains album art as the
circular center label (existing #album-art img preserved)
- SVG tonearm pivots in/out by data-playstate
- Track masthead: .kicker (copper italic mono), large italic-serif
.track-title, italic .track-byline, mono .track-album
- Meta grid: 2 cells (State / Source) with mono labels + italic
serif values
- 30 .spectrum bars between metadata and transport
- .transport: progress-row (timecode + .progress-track + timecode)
and .controls (3 .btn-trans buttons + .vu-cluster)
- Volume slider, mute button, visualizer toggle moved to a
.visually-hidden block — they remain functional for JS / a11y
but no longer compete for visual real estate. Volume control
happens via the always-visible mini player.
VU cluster (mockup-faithful)
- 140x60 VU meter with conic-gradient grid background, copper
needle, "VU" label
- Stacked readout: "OUT <strong>SYS</strong>" / "VOL <strong>72%</strong>"
- Click anywhere on cluster toggles mute (calls toggleMute())
- When muted: needle turns rust, readout strong turns rust,
OUT label switches to MUTE
JS hooks
- updatePlaybackState already sets :root[data-playstate] (drives
spin + tonearm)
- Volume tick now updates #vu-vol and #vu-out
- updateMuteIcon updates #vu-out + .vu-cluster.muted class
Scoping
- All new CSS is .now-playing-prefixed so other tabs and dialogs
are untouched
- Legacy .progress-bar:hover scaleY and ::after scale(0) are
defeated with !important inside .now-playing
- Meta-grid reduced from 4 cells to 2 (State / Source). Elapsed and
Length were duplicates of the timecodes already flanking the
timeline. Added .meta-grid-2 modifier with a 2-column layout.
- Timeline was distorted by legacy rules: .progress-bar:hover did
scaleY(1.4) (inflating the 2px hairline on hover) and the
progress-fill::after handle defaulted to scale(0). Both are now
forced via !important to keep the hairline flat and the copper
handle always visible.
- progress-row uses minmax(0, 1fr) for the bar cell so it shrinks
cleanly inside the grid.
- Removed unused meta.elapsed / meta.length keys from en.json + ru.json.
- Dead JS lookups for #meta-elapsed / #meta-length stay (cheap if-checks,
no-op when DOM elements gone).
Side-by-side comparison surfaced several layout regressions vs. the
mockup. This commit lands all of them at once.
Header
- Restore centered "Media Server / Studio Reference Edition" wordmark
in italic Fraunces
- Move folio marks to fixed page corners (visible on every tab):
TL = green pulse + "Connected · Local 8765"
TR = "Vol. I — Studio Reference · v0.x.x"
- Replace boxed version-label badge with copper mono inline in folio.tr
- Reduce header-to-content gap (container padding-top 28→56 with the
folio now anchored above)
Player view
- Spectrum bars: smaller height (32px), centered with max-width so
they don't span the whole right column
- Spectrogram canvas: hidden by default (opacity 0); reveals only when
visualizer toggle is active. No more leaking into bottom-left.
- VU cluster volume controls: strip legacy box (background, padding,
border-radius); compact stacked layout with thin slider, small mute
button, mono "VOL · XX%" readout
- Disable legacy applyVinylMode() — the .vinyl class added a SECOND
rotation animation on top of the structural .vinyl-stage spin,
causing visible compounding. Vinyl is now purely structural.
Toggles
- Remove vinyl mode toggle button (vinyl is always on)
- Keep audio visualizer (spectrum vis) toggle — still shown by JS
when supported
Mini player
- Force always-visible on non-player tabs regardless of scroll, by
short-circuiting setMiniPlayerVisible when activeTab !== 'player'
i18n
- New keys: header.connected, header.volume, header.edition,
header.edition_sub
- Removed unused: player.folio_left, player.folio_right
- en.json + ru.json updated
Restructures the player tab DOM to actually look like the editorial
mockup, not just inherit new fonts. The previous commit only swapped
tokens & typography on the legacy Spotify-clone layout.
DOM additions (all preserve existing JS-touched IDs):
- Vinyl stage: rotating vinyl wrapping the existing #album-art as a
circular center label; spins only when state=playing via CSS hook
- SVG tonearm: pivots in/out based on data-playstate
- Kicker line: copper italic mono header above the track title
- Editorial 4-cell metadata grid: State / Source / Elapsed / Length
- Decorative spectrum bars (30, CSS-only animation, paused when idle)
- VU meter cluster: needle visual driven by volume %, alongside the
preserved volume slider for a11y
- Folio marks: top-left and top-right of the player container
JS hooks (small, additive):
- updatePlaybackState now sets :root[data-playstate] for CSS
- progress tick mirrors timecode into meta-grid cells
- volume update rotates the VU needle
- folio-version mirrors the version label
i18n:
- new keys: player.kicker, player.modes, player.folio_*, meta.*
- added to both en.json and ru.json
Restored: media_server/static/redesign-mockup.html (Studio Reference
visual reference; deleting it in the prior commit was a mistake).
- Root folder cards with hero-style layout and SVG icons
- Full-width thumbnails with aspect-ratio grid items
- List view column headers (Name, Bitrate, Duration, Size)
- Modernized breadcrumb with pill segments and overflow handling
- Proper skeleton shimmer replacing emoji hourglass on thumbnails
- Pagination shows "Showing X-Y of Z" item count
- Refined hover effects, animations, and visual hierarchy
- Download button revealed on row hover in list view
- Type badges hidden by default, shown on hover
- Localized new keys in en.json and ru.json
- Abstract ReleaseProvider protocol for platform-agnostic version checking
- GiteaReleaseProvider implementation using stdlib urllib
- UpdateChecker service with periodic background checks and WS broadcast
- Persistent dismissible banner in Web UI when a new version is detected
- Health endpoint now returns cached update info
- Configurable via update_check_enabled and update_check_interval settings
- i18n support (EN/RU)
- WebGL shader background with flowing waves, radial pulse, and frequency ring arcs
- Reacts to captured audio data (frequency bands + bass) when visualizer is active
- Uses page accent color; adapts to dark/light theme via bg-primary blending
- Toggle button in header toolbar, state persisted in localStorage
- Cached uniform locations and color values to avoid per-frame getComputedStyle calls
- i18n support for EN/RU locales
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Service worker, manifest, and SVG icon for PWA installability
- Root /sw.js route for full-scope service worker registration
- Meta tags: theme-color, apple-mobile-web-app, viewport-fit=cover
- Safe area insets for notched phones (container, mini-player, footer, banner)
- Dynamic theme-color sync on light/dark toggle
- Overscroll prevention and touch-action optimization
- Hide mini-player prev/next buttons on small screens
- Updated README with PWA and new feature documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix CORS: set allow_credentials=False (token auth, not cookies)
- Add threading.Lock for position cache thread safety
- Add shutdown_executor() for clean ThreadPoolExecutor cleanup
- Dedicated ThreadPoolExecutors for script/callback execution
- Fix Mutagen file handle leaks with try/finally close
- Reduce idle WebSocket polling (0.5s → 2.0s when no clients)
- Add :focus-visible styles for playback control buttons
- Add aria-label to icon-only header buttons
- Dynamic album art alt text for screen readers
- Persist MDI icon cache to localStorage
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Registry of 17 popular media apps (browsers, players, streaming)
- Substring matching resolves raw process names to friendly names
- Brand-colored SVG icons displayed inline next to source name
- Russian locale support for Yandex Music (Яндекс Музыка)
- Unknown sources fall back to .exe-stripped name
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Unified header-toolbar container with border and rounded corners
- Consistent header-btn styling for all action buttons
- Compact locale select, separator before logout icon
- Header links integrate as part of the toolbar with divider
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Save vinyl rotation angle before flipping vinylMode flag off
- Wrap vinyl + visualizer buttons in .player-toggles container
- Move margin-left:auto from individual buttons to group
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- New audio_analyzer service: loopback capture via soundcard + numpy FFT
- Real-time spectrogram bars below album art with accent color gradient
- Album art and vinyl pulse to bass energy beats
- WebSocket subscriber pattern for opt-in audio data streaming
- Audio device selection in Settings tab with auto-detect fallback
- Optimized FFT pipeline: vectorized cumsum bin grouping, pre-serialized JSON broadcast
- Visualizer config: enabled/fps/bins/device in config.yaml
- Optional deps: soundcard + numpy (graceful degradation if missing)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Merge Scripts/Callbacks/Links tabs into single Settings tab with collapsible sections
- Rename Actions tab to Quick Access showing both scripts and configured links
- Add prev/next buttons to mini (secondary) player
- Add optional description field to links (backend + frontend)
- Add CSS chevron indicators on collapsible settings sections
- Persist section collapse/expand state in localStorage
- Fix race condition in Quick Access rendering with generation counter
- Order settings sections: Scripts, Links, Callbacks
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add LinkConfig model and links field to settings
- Add CRUD API endpoints for links (list/create/update/delete)
- Add Links management tab in WebUI with add/edit/delete dialogs
- Add live icon preview in Link and Script dialog forms
- Show MDI icons inline in Quick Actions cards, Scripts table, Links table
- Add broadcast_links_changed WebSocket event for live updates
- Add EN/RU translations for all links management strings
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- New display service with DDC/CI brightness and power control via screen_brightness_control and monitorcontrol
- New /api/display/* endpoints (monitors, brightness, power)
- Display tab in WebUI with per-monitor brightness sliders and power toggle
- EDID resolution parsing to distinguish same-name monitors
- Throttled brightness slider (50ms) matching volume control pattern
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Vinyl mode: album art as center label with grooves, spindle hole, vignette
- Smooth transition between normal and vinyl modes
- Accent color picker with 9 preset colors in header
- Fix progress bar hover layout shift (use scaleY instead of height)
- Fix glow position jump when toggling vinyl mode
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Compact header and footer (reduced padding, margins, font sizes)
- Remove separator borders from header and footer
- Fix color contrast on hover states (white text on accent backgrounds)
- Fix album art not updating on track change (composite cache key)
- Slow vinyl spin animation (4s → 12s)
- Replace Add buttons with dashed-border add-cards
- Fix dialog header text color
- Make theme toggle button transparent
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Extract bitrate alongside duration in browse_directory via get_media_info
- Display bitrate in large card view metadata (duration · bitrate · size)
- Replace Audio/Video type badge with bitrate column in list view
- Remove Play All button text, keep icon only
- Add formatBitrate helper function
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Search bar appears when browsing inside a folder
- Client-side filtering with 200ms debounce
- Clear button and search icon
- Hides at root level, resets on navigation
- Localized placeholder (en/ru)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Convert stacked sections to tabbed interface (Player, Browser, Actions, Scripts, Callbacks) with localStorage persistence
- Add in-memory directory listing cache (5-min TTL) with nocache bypass for refresh
- Defer stat()/duration calls to paginated items only for faster browse
- Move mini player from top to bottom with footer padding fix
- Always show scrollbar to prevent layout shift between tabs
- Add tab localization keys (en/ru)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add refresh button to browser toolbar to re-fetch current folder
- Cache "no thumbnail" results to avoid repeated slow SMB lookups
- Fix list view fallback icon sizing for files without album art
- Fix view toggle button hover (no background/scale on hover)
- Skip re-render when clicking already-active view mode button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add Play All button with M3U playlist generation (local temp file with absolute paths)
- Replace folder combobox with root folder cards and home icon breadcrumb
- Fix compact grid card sizing (64x64 thumbnails, align-items: start)
- Add loading spinner when browsing folders
- Cache browse items to avoid re-fetching on view mode switch
- Remove unused browser-controls CSS
- Add localization keys for Play All and Home (en/ru)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add browser UI with three view modes (grid, compact, list) and pagination
- Add file browsing, thumbnail loading, download, and play endpoints
- Add duration extraction via mutagen for media files
- Single-click plays media or navigates folders, with play overlay on hover
- Add type badges, file size display, and duration metadata
- Add localization keys for browser UI (en/ru)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add version label next to Media Server header (fetched from /api/health)
- Move connection status dot before title, remove status text
- Move logout button into header after language selector
- Return 204 instead of 404 for missing thumbnails (eliminates console errors)
- Show "Title unavailable" when media is playing but title is empty
- Add player.title_unavailable translation key for en/ru locales
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Refactored index.html: Split into separate HTML (309 lines), CSS (908 lines), and JS (1,286 lines) files
- Implemented media browser with folder configuration, recursive navigation, and thumbnail display
- Added metadata extraction using mutagen library (title, artist, album, duration, bitrate, codec)
- Implemented thumbnail generation and caching with SHA256 hash-based keys and LRU eviction
- Added platform-specific file playback (os.startfile on Windows, xdg-open on Linux, open on macOS)
- Implemented path validation security to prevent directory traversal attacks
- Added smooth thumbnail loading with fade-in animation and loading spinner
- Added i18n support for browser (English and Russian)
- Updated dependencies: mutagen>=1.47.0, pillow>=10.0.0
- Added comprehensive media browser documentation to README
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add real-time volume updates while dragging slider
- Throttle updates to 50ms for smooth, responsive feedback
- Prevent overwhelming server with excessive API calls
- Update volume immediately on mouse release for final value
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
UI improvements:
- Add icon-based Execute/Edit/Delete buttons for scripts and callbacks
- Add execution result dialog with stdout/stderr and execution time
- Add favicon with media player icon
- Disable background scrolling when dialogs are open
- Add footer with author information and source code link
Backend enhancements:
- Add execution time tracking to script and callback execution
- Add /api/callbacks/execute endpoint for debugging callbacks
- Return detailed execution results (stdout, stderr, exit_code, execution_time)
Server management:
- Add scripts/start-server.bat - Start server with console window
- Add scripts/start-server-background.vbs - Start server silently
- Add scripts/stop-server.bat - Stop running server instances
- Add scripts/restart-server.bat - Restart the server
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add CSS to hide page content during translation load (opacity transition)
- Hide dialogs explicitly until opened with showModal()
- Hide auth overlay by default in HTML (shown only when needed)
- Prevents flash of English text, dialogs, and auth overlay on page load
- Smooth fade-in after translations are loaded
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Reduce command column max-width from 300px/400px to 200px for better table fit
- Change command input from single-line text to multiline textarea (3 rows)
- Apply changes to both script and callback dialogs
- Prevents table overflow and makes editing long commands easier
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Backend improvements:
- Add execution_time tracking for script execution
- Add execution_time tracking for callback execution
- Add /api/callbacks/execute/{callback_name} endpoint for debugging callbacks
Frontend improvements:
- Fix duration display showing N/A for fast scripts (0 is falsy in JS)
- Increase duration precision to 3 decimal places (0.001s)
- Always show output section with "(no output)" message when empty
- Improve output formatting with italic gray text for empty output
Documentation:
- Add localization section to README
- Document available languages (English, Russian)
- Add guide for contributing new translations
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add dirty state tracking for script and callback forms
- Add backdrop click event listeners to detect clicks outside dialogs
- Add unsaved changes confirmation when closing dialogs with modifications
- Reset dirty state when opening dialogs or after successful save
- Add localized confirmation messages (EN/RU) for unsaved changes
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Features:
- Runtime script CRUD operations (create, update, delete)
- Thread-safe ConfigManager for YAML updates
- WebSocket notifications for script changes
- Web UI script management interface with full CRUD
- Home Assistant auto-reload on script changes
- Client-side position interpolation for smooth playback
- Include command field in script list API response
Technical improvements:
- Added broadcast_scripts_changed() to WebSocket manager
- Enhanced HA integration to handle scripts_changed messages
- Implemented smooth position updates in Web UI (100ms interval)
- Thread-safe configuration updates with file locking
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add "Quick Actions" section to display configured scripts
- Load scripts from /api/scripts/list on connection
- Display scripts in responsive grid layout
- Execute scripts with single click via /api/scripts/execute
- Show toast notifications for execution feedback
- Visual feedback during script execution
- Auto-hide section if no scripts configured
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add static file serving to FastAPI application
- Create responsive web interface with real-time updates
- Features:
- Real-time status updates via WebSocket
- Album artwork display with automatic updates
- Playback controls (play, pause, next, previous)
- Volume control with mute toggle
- Seekable progress bar
- Token authentication with localStorage persistence
- Dark theme and responsive design
- Auto-reconnect WebSocket support
- Update README with Web UI documentation
- Zero dependencies (vanilla HTML/CSS/JavaScript)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>