What's New
- Build version in sidebars — The dashboard and admin sidebars now display the running build version so you can confirm which release is deployed. (#76)
- Same-tab Plex OAuth redirect mode — Optional flow that keeps Plex sign-in in the current tab instead of a popup. Friendlier for password managers and automation. (#60)
Bug Fixes
Sign-in & sessions
- Logging out no longer 500s when you hit the URL directly; you're reliably redirected to the landing page from both admin and user areas.
- Plex PIN polling is no longer caught by the strict auth rate limiter, so sign-in completes cleanly on slow networks.
- Periodic re-check against Plex so revoked server access logs the user out promptly. (#49)
- Session re-checks are suspended during first-run onboarding to avoid spurious logouts.
Wrapped slideshow
- Fixed a race that could leave the slideshow on a blank slide after rapid navigation.
- The "Close" button on the wrapped page no longer strands you on about:blank; it falls back to the home page when there's no history to return to.
- Progress bar now uses your active theme color instead of always rendering red.
- Durations no longer show "9h 60m" — values roll over correctly to the next hour.
- Personal wrapped headings now say "Your Content Mix" instead of "YOU'S Content Mix".
- Tapping the very first pixel of the left edge now reliably goes to the previous slide instead of skipping forward.
- Your position in the slideshow is preserved in the URL (#slide=N) so refresh or back-forward keeps your place.
- Reduced-motion preferences are respected when animating progress.
- A personal wrapped page with no history now shows a clear empty state instead of misleading fun facts.
- Marathon-day stats are capped at 24 hours so duplicated history records can't produce impossible totals.
- Keyboard navigation on the final summary slide no longer accidentally activates buttons when mashing arrow keys.
Sharing & privacy
- When the server rejects a privacy setting (e.g. below the admin-enforced floor), the radio now snaps back visually and a toast explains why — no more silent resets.
- Privacy modes below the admin's floor render as visibly disabled with an inline explanation instead of looking available.
- Invalid or expired share links now show a message that actually explains expiry/invalidation instead of a bare "not found".
- Share modal is flicker-free and properly announces the selected option to screen readers.
- Private-OAuth denial pages now tell the viewer they need to sign in to Plex and be a member of this server.
Admin
- "Delete All History" and "Clear Cache" now surface a toast if the preflight check fails, instead of appearing to do nothing.
- "Clear All" on the Logs page now refreshes the table immediately — no reload required.
- Date-range filters and active filter pills on logs are now reactive, and streaming log entries honour the active filters.
- "Export JSON" on the logs page actually downloads a file now.
- Added the missing "Slides" link to the admin sidebar and a proper empty state on year selectors when no history exists.
- Cron schedule input validates as you type and blocks save on invalid expressions.
- "Next sync" no longer shows a stale time when the scheduler is paused.
- OpenAI API key can now be cleared from settings; empty save deletes the stored value.
- Custom slides render Markdown as HTML (previously shown raw) and the delete confirmation now includes the slide's title in an inline prompt instead of a native dialog.
- Toggling "Can control" on a user now respects the configured global default share mode rather than hardcoding public.
- Destructive actions (Clear All Logs, bulk user-control apply) use a consistent confirm dialog.
- Active admin tab is written to the URL so Settings pages are bookmarkable.
- Users table and logs actions have proper mobile styles.
Onboarding
- Fixed CSRF token handling that could block the onboarding footer form and stall navigation between steps. (#65, #66, #67)
- Added a Cancel button on the sync step so you can back out of a long import.
- The AI fun-facts step now accepts an OpenAI API key, base URL, model, and persona with a Test Connection button that verifies credentials before saving; admin settings mirror the same Test Connection, with an SSRF safeguard that refuses to forward the stored key to a different base URL. AI features default to off — you opt in explicitly. (#77)
- Hidden submit buttons use an accessible sr-only pattern instead of off-screen positioning.
Plex integration
- Servers identified by hostname URL are now matched using /identity machine identifier. (#74)
- Plex friend payloads with null fields no longer crash account sync; plain-host server URLs are recognised. (#73)
Errors & rate limiting
- New friendly error pages for 404 / 403 / 429 / 5xx.
- 429 responses now include a Retry-After header.
- Username lookup rejects whitespace-only input.
Security
- Plex auth tokens and OpenAI API keys are no longer embedded in the page load payload sent to the browser. Logins complete server-side; secrets stay on the server.
- CSP is tailored to the app (allows Plex thumbnails via plex.tv / *.plex.direct and Google Fonts; denies frame-ancestors).
- Early 403 / 404 responses from CSRF and request-filter handlers now carry the same security headers as normal responses, and use the same request-filter matchers in dev and production.
Maintenance
- Refactored the code-quality workflow into tiered jobs for faster CI. (#64)
- Thumbnail proxy now forwards ETag / Last-Modified and handles If-None-Match / If-Modified-Since for proper 304 caching.
- Cleaned up verification checks and test isolation. (#62)
- Updated Vite to v8, TypeScript to v6, Biome to 2.4.12, marked to v18, @lucide/svelte to v1, plus smaller bumps and config migrations.
Full changelog: 0.1.10...0.1.11