Inline style= attributes containing property:value pairs are blocked by
strict style-src-attr CSP. CSS custom properties (--foo:value) set via
style= are treated as data not styles and are not subject to this
restriction. The width is now resolved in the stylesheet via
var(--bar-w, 100%) so CSP cannot interfere.
Timing bars in the status panel and any other dynamically-injected
style= attributes were being silently blocked by the Content Security
Policy. style-src only governs <style> blocks and linked stylesheets;
inline element attributes need style-src-attr separately.
Adding style-src-attr 'unsafe-inline' is the minimal fix — it only
affects attribute-level inline styles, not script execution.
Also removes the temporary debug console.log added in the previous commit.
Tasks run in parallel so any individual task time can exceed the wall-clock
total, causing all bars to render at 100%. Normalise against the maximum
individual task time so bars correctly show relative response times.
Root cause: showSplash() sets display:flex + opacity:1 synchronously,
then dismissSplash() immediately adds the fade-out class (opacity:0).
The browser batches these in the same paint frame so the CSS transition
from opacity:1 -> 0 never starts, and transitionend never fires,
leaving the Promise unresolved and the splash stuck.
Two-part fix:
1. handleLogin: await two requestAnimationFrames between showSplash()
and dismissSplash() so the browser paints opacity:1 first, ensuring
the CSS opacity transition actually runs.
2. dismissSplash: add a 500ms fallback setTimeout that hides the splash
and resolves the Promise even if transitionend is never fired (acts
as a safety net for any future edge cases).
- index.html: checkbox between password field and login button
- app.js: reads #remember-me and passes rememberMe in POST body
- auth.js: rememberMe=true sets 30-day maxAge; false = session cookie
(expires when browser closes)
- style.css: .form-group--checkbox and .checkbox-label styles
Server:
- Add getEmbyUsers(): fetches all Emby users, builds Map of
lowercase/sanitized name -> display name, cached 60s
- Add buildTagBadges(allTags, embyUserMap): classifies each tag
as { label, matchedUser: displayName|null } against the full
Emby user database
- Attach tagBadges[] to every download object when showAll=true
(all 10 construction sites across SABnzbd queue/history and
qBittorrent queue/history blocks)
- matchedUserTag still set to the tag matching the *current* user
for the non-showAll badge
Frontend:
- showAll mode: renders tagBadges[] — unmatched tags (no Emby user)
amber leftmost, matched tags show Emby display name in accent
colour rightmost
- Normal mode: renders matchedUserTag badge only (current user's tag)
- server: add extractAllTags() returning all tag labels for a series/movie
- server: showAll now includes items with ANY tag (not just user-matched);
non-admin path unchanged (must match current user's tag)
- server: replace userTag with allTags[] + matchedUserTag on every download object
- frontend: render all tags in header; unmatched tags amber (left), matched
user tag in accent colour (rightmost); only visible in showAll mode
- css: add --unmatched-tag-bg/color variables to all three themes (light,
dark, mono) and .download-user-badge.unmatched style
- Server tracks each client's refresh rate via query param on /user-downloads
- Active clients expire after 30s of no requests
- Status panel 'Data Refresh' card shows:
- Background poll interval (or Disabled)
- Effective mode: Background if all clients >= poll rate,
Foreground (with rate) if any client is faster, Idle if no clients
- Active client list with per-user refresh rate and last-seen age
- Foreground mode shown with orange badge for visibility
- Client refresh rate sent on every dashboard request
- Each service fetch is individually timed (SABnzbd, Sonarr, Radarr, qBit)
- Status panel shows timing bar chart with ms per task and total
- Shows 'Last Poll' age that updates live
- Shows client refresh rate (1s/5s/10s/Off)
- Status panel auto-refreshes in sync with dashboard refresh cycle
- Changing refresh rate restarts the status panel refresh too
- TTL counters update live on each refresh
- New /api/dashboard/status endpoint (admin-only, 403 for non-admins)
- Returns server info (uptime, Node version, memory usage)
- Returns polling mode and interval
- Returns cache stats: entry count, total size, per-key breakdown
with item count, size in KB, and TTL remaining
- Status button in admin controls header
- Collapsible status panel with grid layout
- Responsive: single column on mobile
- Detect trackedDownloadState=importPending or status=warning/error
- Extract statusMessages and errorMessage from queue records
- Display red 'Import Pending' badge on download card header
- Hover reveals tooltip with the specific issue messages
- Visible to all users (not admin-only)
- Replace 'Login to Emby' heading with sofarr logo and subtitle
- Subtitle reads 'Login with your Emby credentials'
- Login form fades out smoothly before splash screen appears
- Form labels remain left-aligned within centered login box
- Show sofarr logo splash screen while app initialises
- On page load: splash stays visible while checking auth and fetching data
- After login: splash reappears while fetching initial downloads
- Minimum 1.2s display with smooth fade-out transition
- Subtle pulse animation on the logo
- Series title links to Sonarr series page (/series/{titleSlug})
- Movie title links to Radarr movie page (/movie/{titleSlug})
- Links open in new tab, only shown for admin users
- Instance URL preserved through data aggregation for multi-instance support
- Admin users see download path (SABnzbd storage / qBittorrent save_path)
- Admin users see target path (Sonarr series folder / Radarr movie folder)
- Paths displayed in monospace font at bottom of card details
- Non-admin users unaffected (paths not sent in API response)
- Admin users (Emby IsAdministrator) see a 'Show all users' toggle
- When toggled, all tagged downloads are shown regardless of user
- Each download card shows the tagged user's name as a badge
- Non-admin users see only their own downloads (unchanged behavior)
- Backend accepts ?showAll=true query param (admin-only)
- Redesign download cards to be significantly more compact
- Add CSS custom properties for theming
- Add theme switcher (Light, Dark, Mono) with localStorage persistence
- Update README with environment variable Docker deployment docs
- Update Docker Compose example to use environment: instead of volume mount
- Fix seriesMap key (use Sonarr internal id, not tvdbId)
- Fix Sonarr tag resolution (use tag map like Radarr)
- Use sourceTitle for history record matching
- Fall back to embedded movie/series objects when API timeouts
- Add includeMovie/includeSeries params to queue/history API calls
- Add coverArt field to all download responses (TMDB poster URLs)
- Add cover art display to frontend download cards
- Fix user-summary route to use instance config and tag maps