diff --git a/webui/static/docs.js b/webui/static/docs.js
index 0a8f54d3..a7d8ac32 100644
--- a/webui/static/docs.js
+++ b/webui/static/docs.js
@@ -82,6 +82,9 @@ const DOCS_SECTIONS = [
{ id: 'dash-overview', title: 'Overview & Stats' },
{ id: 'dash-workers', title: 'Enrichment Workers' },
{ id: 'dash-tools', title: 'Tool Cards' },
+ { id: 'dash-retag', title: 'Retag Tool' },
+ { id: 'dash-backup', title: 'Backup Manager' },
+ { id: 'dash-repair', title: 'Repair & Maintenance' },
{ id: 'dash-activity', title: 'Activity Feed' }
],
content: () => `
@@ -104,6 +107,7 @@ const DOCS_SECTIONS = [
Genius
Lyrics, descriptions, alternate names, song artwork
ℹ️Workers retry "not found" items every 30 days and errored items every 7 days. You can pause/resume any worker from the dashboard.
+ Rate Limit Protection: Workers include smart rate limiting for all APIs. If Spotify returns a rate limit (429), a global ban activates — all Spotify calls are suppressed and searches automatically fall back to iTunes. A countdown modal appears showing ban duration, and the worker auto-resumes when the ban expires. You can manually disconnect Spotify from the modal to clear the ban immediately.
+
+
Retag Tool
+
The Retag Tool lets you fix incorrect metadata tags on files already in your library. This is useful when files were downloaded with wrong or incomplete tags.
+
+ - Open the Retag Tool card on the Dashboard
+ - Select an artist and album from the dropdown filters
+ - The tool displays all tracks in the album with their current file tags alongside the correct metadata from Spotify or iTunes
+ - Review the tag differences — mismatches are highlighted
+ - Click Retag to write the corrected metadata to the audio files
+
+
The retag operation writes title, artist, album artist, album, track number, disc number, year, and genre. Cover art can optionally be re-embedded.
+
+
+
Backup Manager
+
The Backup Manager protects your SoulSync database (all library data, watchlists, playlists, automations, and settings).
+
+ - Create Backup — Creates a timestamped copy of the database file
+ - Download — Download any backup to your local machine
+ - Restore — Restore the database from a selected backup (current state is backed up first)
+ - Delete — Remove individual backups
+ - Rolling Cleanup — Automatically keeps only the 5 most recent backups to save disk space
+
+
The system automation Auto-Backup Database creates a backup every 3 days automatically. You can adjust the interval in Automations.
+
+
+
Repair & Maintenance
+
Additional maintenance tools accessible from the dashboard:
+
+ - Quality Scanner — Scans your entire library and flags tracks below your quality preferences. Shows a breakdown of formats and bitrates, and identifies tracks where higher-quality versions may be available on Soulseek.
+ - Duplicate Cleaner — Identifies duplicate tracks by comparing title, artist, album, and duration. Lets you review duplicates and choose which version to keep (typically the higher-quality one). Frees disk space by removing redundant files.
+ - Database Updater — Refreshes your library database by scanning your media server. Incremental mode only adds new content; Full Refresh rebuilds the entire database. Deep Scan performs a full comparison without losing any enrichment data from services.
+ - Metadata Updater — Triggers enrichment workers to update artist photos, genres, styles, biographies, and related metadata from all connected services (MusicBrainz, Spotify, iTunes, Last.fm, Deezer, AudioDB, Genius).
+
+
Activity Feed
The activity feed at the bottom of the dashboard shows recent system events: downloads completed, syncs started, settings changed, automation runs, and errors. Events appear in real-time via WebSocket.
@@ -137,8 +175,10 @@ const DOCS_SECTIONS = [
{ id: 'sync-spotify', title: 'Spotify Playlists' },
{ id: 'sync-youtube', title: 'YouTube Playlists' },
{ id: 'sync-tidal', title: 'Tidal Playlists' },
+ { id: 'sync-listenbrainz', title: 'ListenBrainz' },
{ id: 'sync-beatport', title: 'Beatport' },
{ id: 'sync-mirrored', title: 'Mirrored Playlists' },
+ { id: 'sync-m3u', title: 'M3U Export' },
{ id: 'sync-discovery', title: 'Discovery Pipeline' }
],
content: () => `
@@ -166,9 +206,31 @@ const DOCS_SECTIONS = [
Tidal Playlists
Requires Tidal authentication in Settings. Once connected, refresh to load your Tidal playlists. You can also select Tidal download quality: HQ (320kbps), HiFi (FLAC 16-bit), or HiFi Plus (up to 24-bit).
+
+
ListenBrainz
+
If ListenBrainz is configured in Settings, the Sync page includes a ListenBrainz tab for browsing and importing playlists from your ListenBrainz account:
+
+ - Your Playlists — Playlists you've created on ListenBrainz
+ - Collaborative — Playlists shared with you by other users
+ - Created For You — Auto-generated playlists based on your listening history
+
+
ListenBrainz tracks are matched against Spotify/iTunes using a 4-strategy search: direct match, swapped artist/title, album-based lookup, and extended fuzzy search. Discovered tracks can be synced to your library like any other playlist.
+
Beatport
-
The Beatport tab has three views: Browse (featured content, genre browsing), Charts (Top 100, Hype charts), and My Playlists. Browse 12+ electronic music genres and download directly from chart listings.
+
The Beatport tab provides deep integration with electronic music content across three views:
+
Browse — Featured content organized into sections:
+
+ - Hero Tracks — Featured highlight tracks
+ - New Releases — Latest additions to the catalog
+ - Featured Charts — Curated editorial charts
+ - DJ Charts — Charts created by DJs and producers
+ - Top 10 Lists — Quick top picks across genres
+ - Hype Picks — Trending underground tracks
+
+
Genre Browser — Browse 12+ electronic music genres (House, Techno, Drum & Bass, Trance, etc.) with per-genre views: Top 10 tracks, staff picks, hype rankings, latest releases, and new charts.
+
Charts — Top 100 and Hype charts with full track listings. Each track can be manually matched against Spotify for metadata, then synced and downloaded.
+
ℹ️Beatport data is cached with a configurable TTL. The system automation Refresh Beatport Cache runs every 24 hours to keep content fresh.
Mirrored Playlists
@@ -180,6 +242,11 @@ const DOCS_SECTIONS = [
Each profile has its own mirrored playlists
+
+
M3U Export
+
Export any mirrored playlist as an M3U file for use in external media players or media servers. Enable M3U export in Settings and use the export button on any playlist card.
+
M3U files reference the actual file paths in your library, so they work with any M3U-compatible player. Auto-save can be enabled to regenerate M3U files automatically when playlists are updated.
+
Discovery Pipeline
For non-Spotify playlists (YouTube, Tidal), tracks need to be discovered before syncing. Discovery matches raw titles to official Spotify/iTunes metadata using fuzzy matching with a 0.7 confidence threshold.
@@ -200,7 +267,9 @@ const DOCS_SECTIONS = [
children: [
{ id: 'search-enhanced', title: 'Enhanced Search' },
{ id: 'search-basic', title: 'Basic Search' },
+ { id: 'search-sources', title: 'Download Sources' },
{ id: 'search-downloading', title: 'Downloading Music' },
+ { id: 'search-postprocess', title: 'Post-Processing Pipeline' },
{ id: 'search-quality', title: 'Quality Profiles' },
{ id: 'search-manager', title: 'Download Manager' }
],
@@ -219,15 +288,44 @@ const DOCS_SECTIONS = [
Toggle to Basic Search mode for direct Soulseek queries. This shows raw search results with detailed info: format, bitrate, quality score, file size, uploader name, upload speed, and availability.
Filters let you narrow results by type (Albums/Singles), format (FLAC/MP3/OGG/AAC/WMA), and sort by relevance, quality, size, bitrate, duration, or uploader speed.
+
+
Download Sources
+
SoulSync supports multiple download sources, configurable in Settings → Download Settings:
+
+ | Source | Description | Best For |
+
+ | Soulseek | P2P network via slskd — largest selection of lossless and rare music | FLAC, rare tracks, DJ sets |
+ | YouTube | YouTube audio extraction via yt-dlp | Live performances, remixes, tracks not on Soulseek |
+ | Tidal | Tidal HiFi streaming rip (requires auth) | Guaranteed quality, official releases |
+ | Hybrid | Tries your primary source first, then automatically falls back to alternates | Best overall success rate |
+
+
+
💡Hybrid mode is recommended for most users. It tries Soulseek first (best quality), then falls back to YouTube or Tidal if no suitable results are found. The fallback order respects your quality profile settings.
+
YouTube settings include cookies browser selection (for bot detection bypass), download delay (seconds between requests), and minimum confidence threshold for title matching.
+
Downloading Music
When you select an album or track to download, a modal appears with:
- Album hero — cover art, title, artist, year, track count
- Track list with checkboxes to select/deselect individual tracks
- - Download progress with per-track status indicators
+ - Download progress with per-track status indicators (searching, downloading, processing, complete, failed)
-
After downloading, files go through post-processing: optional AcoustID fingerprint verification, automatic metadata tagging (title, artist, album, track number, genre, cover art), and organized file placement in your library.
+
Downloads can be started from multiple places: Enhanced Search results, artist discography, Download Missing modal, wishlist auto-processing, and playlist sync.
+
+
+
Post-Processing Pipeline
+
After a file is downloaded, it goes through an automatic pipeline before appearing in your library:
+
+ - AcoustID Fingerprint Verification — If AcoustID is configured, the downloaded file is fingerprinted and compared against the expected track. Title and artist are fuzzy-matched (title ≥ 70% similarity, artist ≥ 60%). Files that fail verification are quarantined instead of added to your library.
+ - Metadata Tagging — The file is tagged with official metadata: title, artist, album artist, album, track number, disc number, year, genre, and composer. Tags are written using Mutagen (supports MP3, FLAC, OGG, M4A).
+ - Cover Art Embedding — Album artwork is downloaded from the metadata source and embedded directly into the audio file.
+ - File Organization — The file is renamed and moved to your transfer path following the template:
Artist/Album/TrackNum - Title.ext. For multi-disc albums, a Disc N/ subfolder is automatically created when the album has more than one disc.
+ - Lyrics (LRC) — Synced lyrics are fetched from the LRClib API and saved as
.lrc sidecar files alongside the audio file. Compatible media players (foobar2000, MusicBee, Plex, etc.) will display time-synced lyrics automatically. Falls back to plain-text lyrics if synced versions aren't available.
+ - Lossy Copy — If enabled in settings, a lower-bitrate copy is created alongside the original (useful for mobile device syncing).
+ - Media Server Scan — Your media server (Plex/Jellyfin) is notified to scan for the new file. Navidrome auto-detects changes.
+
+
ℹ️Quarantine: Files that fail AcoustID verification are moved to a quarantine folder instead of your library. You can review quarantined files and manually approve or delete them. The automation engine can trigger notifications when files are quarantined.
Quality Profiles
@@ -271,15 +369,23 @@ const DOCS_SECTIONS = [
-
Discovery Playlists
-
SoulSync generates curated playlists from your discovery pool (50 similar artists refreshed during watchlist scans):
-
- - Popular Picks — Top tracks from discovery pool artists
- - Hidden Gems — Rare and deeper cuts
- - Discovery Shuffle — Randomized mix across all pool artists
- - Genre Browsers — Top tracks filtered by genre
-
-
Each playlist can be downloaded or synced to your media server.
+
Discovery & Personalized Playlists
+
SoulSync generates playlists from two sources: your discovery pool (50 similar artists refreshed during watchlist scans) and your library listening data:
+
+ | Playlist | Source | Description |
+
+ | Popular Picks | Discovery Pool | Top tracks from discovery pool artists |
+ | Hidden Gems | Discovery Pool | Rare and deeper cuts from pool artists |
+ | Discovery Shuffle | Discovery Pool | Randomized mix across all pool artists |
+ | Recently Added | Library | Tracks most recently added to your collection |
+ | Top Tracks | Library | Your most-played or highest-rated tracks |
+ | Forgotten Favorites | Library | Tracks you haven't listened to in a while |
+ | Decade Mixes | Library | Tracks grouped by release decade (70s, 80s, 90s, etc.) |
+ | Daily Mixes | Library | Auto-generated daily playlists based on your taste profile |
+ | Familiar Favorites | Library | Well-known tracks from artists you follow |
+
+
+
Each playlist can be played in the media player, downloaded, or synced to your media server. Genre browsers let you filter discovery pool content by specific genres.
Build Custom Playlist
@@ -306,15 +412,28 @@ const DOCS_SECTIONS = [
icon: '/static/artists.png',
children: [
{ id: 'art-search', title: 'Artist Search' },
+ { id: 'art-detail', title: 'Artist Detail & Discography' },
{ id: 'art-watchlist', title: 'Watchlist' },
{ id: 'art-scanning', title: 'New Release Scanning' },
+ { id: 'art-wishlist', title: 'Wishlist' },
{ id: 'art-settings', title: 'Watchlist Settings' }
],
content: () => `
Artist Search
-
Search for any artist by name. Results show artist cards with images and genres. Click a card to see their full discography with albums, singles, and EPs. From the detail view you can download any release or add the artist to your watchlist.
-
The detail view also shows Similar Artists as clickable bubbles for further exploration.
+
Search for any artist by name. Results show artist cards with images and genres. Results come from Spotify (or iTunes as fallback). Click any card to open the artist detail view.
+
+
+
Artist Detail & Discography
+
The artist detail page shows a full discography organized by category:
+
+ - Albums, Singles & EPs, Compilations, and Appearances
+ - Each release card shows cover art, title, year, track count, and a completion percentage (how many tracks you own)
+ - Filter by category, content type (live/compilations/featured), or status (owned/missing)
+ - Click any release to open the download modal with track selection
+
+
At the top, View on buttons link to the artist on each matched external service (Spotify, Apple Music, MusicBrainz, Deezer, AudioDB, Last.fm, Genius). Service badges on artist cards also indicate which services have matched this artist.
+
Similar Artists appear as clickable bubbles below the discography for further exploration and discovery.
Watchlist
@@ -336,6 +455,19 @@ const DOCS_SECTIONS = [
Stats: artists scanned, new tracks found, tracks added to wishlist
+
+
Wishlist
+
The wishlist is the queue of tracks waiting to be downloaded. Tracks are added to the wishlist from multiple sources:
+
+ - Watchlist scans — New releases from watched artists are automatically added
+ - Playlist sync — Tracks from mirrored playlists that aren't in your library
+ - Manual — Individual track or album downloads go through the wishlist
+
+
Auto-Processing: The system automation runs every 30 minutes, picking up wishlist items and attempting to download them from your configured source. Failed items are retried with increasing backoff.
+
Manual Processing: Use the Process Wishlist automation action to trigger processing on demand. Options include processing all items, albums only, or singles only.
+
Cleanup: The Cleanup Wishlist action removes duplicates (same track added multiple times) and items you already own in your library.
+
ℹ️Each wishlist item tracks its source (watchlist scan, playlist sync, manual), number of retry attempts, last error message, and status (pending, downloading, failed, complete).
+
Watchlist Settings
Per-Artist Settings — Click the config icon on any watched artist to customize what release types to include: Albums, EPs, Singles, Live versions, Remixes, Acoustic versions, Compilations.
@@ -434,6 +566,7 @@ const DOCS_SECTIONS = [
Fire Signal — Emit a custom signal that other automations can listen for
All notification messages support variable substitution: {name}, {status}, {time}, {run_count}, and context-specific variables from the action result.
+
Test Notifications: Use the test button next to any notification then-action to send a test message before saving. This verifies your webhook URL, API key, or bot token is working correctly.
ℹ️Signal chaining lets you build multi-step workflows. Safety features include cycle detection (DFS), a 5-level chain depth limit, and a 10-second cooldown between signal fires.
@@ -516,6 +649,7 @@ const DOCS_SECTIONS = [
Download Missing Tracks
From any album card showing missing tracks, click Download Missing to open a modal listing all tracks not in your library. Select tracks, choose a download source, and start the download. Progress is tracked per-track with status indicators.
+
Multi-Disc Albums: Albums with multiple discs are handled automatically. Tracks are organized into Disc N/ subfolders within the album directory, preventing track number collisions (e.g., Disc 1 Track 1 vs Disc 2 Track 1). The disc structure is detected from Spotify or iTunes metadata.
`
},
@@ -525,7 +659,9 @@ const DOCS_SECTIONS = [
icon: '/static/import.png',
children: [
{ id: 'imp-setup', title: 'Staging Setup' },
- { id: 'imp-workflow', title: 'Import Workflow' }
+ { id: 'imp-workflow', title: 'Import Workflow' },
+ { id: 'imp-singles', title: 'Singles Import' },
+ { id: 'imp-matching', title: 'Track Matching' }
],
content: () => `
@@ -542,7 +678,20 @@ const DOCS_SECTIONS = [
Match tracks — Drag-and-drop staged files onto album track slots, or let auto-match attempt it
Review the match and click Confirm to import — files are tagged, organized, and added to your library
-
The Singles tab handles individual tracks that aren't part of an album.
+
+
+
Singles Import
+
The Singles tab handles individual tracks that aren't part of an album structure. Files in the staging root (not in subfolders) appear here. Search for the correct track on Spotify/iTunes, confirm the match, and import. The file is tagged, renamed, and placed in your library.
+
+
+
Track Matching
+
The import matching system compares staged files against official album track lists:
+
+ - Auto-Match — Attempts to match files to tracks automatically based on filename, duration, and track order
+ - Drag & Drop — Manually drag staged files onto the correct album track slots
+ - Conflict Detection — Highlights when a file matches multiple tracks or when tracks are unmatched
+
+
After matching, the import process tags files with the official metadata (title, artist, album, track number, cover art) and moves them to your transfer path following the standard file organization template.
`
},
@@ -612,12 +761,13 @@ const DOCS_SECTIONS = [
Download Settings
- - Download Source Mode — Soulseek, YouTube, Tidal, or Hybrid (tries one, falls back to another)
- - Download Path — Where files are initially downloaded
- - Transfer Path — Where processed files are moved (should be your media server's monitored folder)
- - Staging Path — Folder for the Import feature
- - iTunes Country — Storefront region for iTunes lookups (US, GB, FR, etc.). Changes apply immediately.
- - Lossy Copy — Optionally create a lower-bitrate copy of every download (for mobile syncing)
+ - Download Source Mode — Soulseek, YouTube, Tidal, or Hybrid. Hybrid tries your primary source first, then falls back to alternates. See Download Sources in the Music Downloads section for details.
+ - Download Path — Where files are initially downloaded and processed
+ - Transfer Path — Where processed files are moved after tagging and organization. Should point to your media server's monitored folder.
+ - Staging Path — Folder for the Import feature (files placed here appear on the Import page)
+ - iTunes Country — Storefront region for iTunes/Apple Music lookups (US, GB, FR, JP, etc.). Changes apply immediately to all searches without restarting.
+ - Lossy Copy — When enabled, creates a lower-bitrate copy (MP3) of every downloaded file alongside the original. Useful for syncing to mobile devices or streaming servers with bandwidth constraints. The copy is placed in a configurable output folder.
+ - Content Filtering — Toggle explicit content filtering to control whether explicit tracks appear in search results and downloads.
@@ -627,10 +777,11 @@ const DOCS_SECTIONS = [
Other Settings
- - Content Filtering — Toggle explicit content allowance
- - YouTube — Cookies browser (for bot detection), download delay, minimum confidence
- - UI Appearance — Custom accent colors with persistent preference
- - API Keys — Generate and manage API keys for the REST API (Bearer token auth)
+ - YouTube Configuration — Select cookies browser (Chrome, Firefox, Edge) for bot detection bypass, set download delay (seconds between requests), and minimum confidence threshold for title matching
+ - UI Appearance — Custom accent colors with persistent preference. Changes apply immediately across the entire interface.
+ - API Keys — Generate and manage API keys for the REST API. Keys use a
sk_ prefix and are shown once at creation — only a SHA-256 hash is stored for security.
+ - Path Templates — Configure how files are organized in your library. The default template is
Artist/Album/TrackNum - Title.ext
+ - WebSocket — Real-time status updates are delivered via WebSocket. All downloads, enrichment progress, scan status, and system events push to the UI without polling.
`
@@ -675,7 +826,8 @@ const DOCS_SECTIONS = [
icon: '/static/settings.png',
children: [
{ id: 'api-auth', title: 'Authentication' },
- { id: 'api-endpoints', title: 'Key Endpoints' }
+ { id: 'api-endpoints', title: 'Key Endpoints' },
+ { id: 'api-websocket', title: 'WebSocket Events' }
],
content: () => `
@@ -704,7 +856,24 @@ const DOCS_SECTIONS = [
POST /api/database/backup | Create a backup |
-
The full API has 90+ endpoints. Use reverse proxy support for external access.
+
The full API has 90+ endpoints covering library, downloads, playlists, automations, settings, and more. Use a reverse proxy (Nginx, Caddy, Traefik) for external access with HTTPS.
+
+
+
WebSocket Events
+
SoulSync uses Socket.IO for real-time communication. The frontend connects automatically and receives live updates without polling:
+
+ | Event | Description |
+
+ download_progress | Per-track download progress (speed, ETA, percentage) |
+ download_complete | Track finished downloading and post-processing |
+ batch_progress | Album/playlist batch download status |
+ worker_status | Enrichment worker status (Spotify, MusicBrainz, Deezer, etc.) |
+ scan_progress | Library scan, quality scan, or duplicate scan progress |
+ system_status | Service connectivity changes (Spotify rate limit, slskd disconnect) |
+ activity | System activity feed entries |
+
+
+
All UI elements that show live progress (download bars, worker icons, scan counters) are driven by these WebSocket events.
`
}
@@ -753,6 +922,26 @@ function initializeDocsPage() {
});
content.innerHTML = contentHTML;
+ // Suppress scroll spy during click-initiated scrolls
+ let _scrollSpySuppressed = false;
+
+ function suppressScrollSpy() {
+ _scrollSpySuppressed = true;
+ clearTimeout(suppressScrollSpy._timer);
+ suppressScrollSpy._timer = setTimeout(() => { _scrollSpySuppressed = false; }, 800);
+ }
+
+ // Helper: get element offset relative to a scrollable ancestor
+ function getOffsetRelativeTo(el, ancestor) {
+ let offset = 0;
+ let current = el;
+ while (current && current !== ancestor) {
+ offset += current.offsetTop;
+ current = current.offsetParent;
+ }
+ return offset;
+ }
+
// Section title click → expand/collapse children + scroll
nav.querySelectorAll('.docs-nav-section-title').forEach(title => {
title.addEventListener('click', () => {
@@ -770,7 +959,8 @@ function initializeDocsPage() {
if (children) children.classList.add('expanded');
}
- // Scroll to section
+ // Scroll to section (suppress scroll spy so it doesn't fight)
+ suppressScrollSpy();
const target = document.getElementById('docs-' + sectionId);
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
@@ -778,9 +968,13 @@ function initializeDocsPage() {
// Child click → scroll to subsection
nav.querySelectorAll('.docs-nav-child').forEach(child => {
- child.addEventListener('click', () => {
+ child.addEventListener('click', (e) => {
+ e.stopPropagation();
nav.querySelectorAll('.docs-nav-child').forEach(c => c.classList.remove('active'));
child.classList.add('active');
+
+ // Keep parent section expanded
+ suppressScrollSpy();
const target = document.getElementById(child.dataset.target);
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
});
@@ -811,20 +1005,30 @@ function initializeDocsPage() {
const docsContent = document.getElementById('docs-content');
if (docsContent) {
docsContent.addEventListener('scroll', () => {
- const scrollTop = docsContent.scrollTop + 100;
+ if (_scrollSpySuppressed) return;
+
+ const containerRect = docsContent.getBoundingClientRect();
+ const threshold = containerRect.top + 120;
let activeSection = null;
let activeChild = null;
+ // Find which section is currently in view using getBoundingClientRect
DOCS_SECTIONS.forEach(section => {
const el = document.getElementById('docs-' + section.id);
- if (el && el.offsetTop <= scrollTop) {
- activeSection = section.id;
+ if (el) {
+ const rect = el.getBoundingClientRect();
+ if (rect.top <= threshold) {
+ activeSection = section.id;
+ }
}
if (section.children) {
section.children.forEach(child => {
const childEl = document.getElementById(child.id);
- if (childEl && childEl.offsetTop <= scrollTop) {
- activeChild = child.id;
+ if (childEl) {
+ const childRect = childEl.getBoundingClientRect();
+ if (childRect.top <= threshold) {
+ activeChild = child.id;
+ }
}
});
}