This release is a full code audit ahead of v2.0.0 — 11 subsystems reviewed, 22 fixes shipped, every finding tracked in AUDIT.md with severity and status (FIXED / OPEN / ACKNOWLEDGED / DEFERRED). Rolls up v1.13.3 through v1.13.17 into one shipping build.
What was the audit looking for?
Disconnects, half-finished features, places where the same concept was handled three different ways. User-reported bugs ("the trailer is supposed to be disabled but it's still playing on Jellyfin", "the preview shows a Toy Story file that doesn't exist on my Windows install", "the countdown finished but the schedule didn't apply") consistently traced back to parallel implementations drifting apart.
Headline fixes
Trailer "disable" now actually disables (NEXUP-1, PLUGIN-2, SEQUENCING-1)
Toggling a NeX-Up trailer off used to run preroll.enabled = trailer.is_enabled against a column that didn't exist on the Preroll model. SQLAlchemy silently accepted it as a transient attribute and never persisted. Disabled trailers correctly disappeared from nexup_trailers sequence blocks but kept playing through random blocks, category schedules, and the Jellyfin/Emby plugin. Added the enabled column with migration, now respected by every scheduler path AND the manual sequence-apply endpoint AND the plugin resolver.
Backup/restore now lossless (BACKUP-1)
The JSON backup payload was missing many fields. A round-trip silently dropped:
- Categories:
plex_mode,apply_to_plex,is_system(the last protecting NeX-Up system categories from being deleted post-restore) - Prerolls:
duration,file_size,enabled,community_preroll_id,exclude_from_matching,file_hash - Schedules:
fallback_category_id,sequence,color,blend_enabled,priority,exclusive,holiday_name,holiday_country— sequence schedules came back as plain category schedules; blend/exclusive/priority all reverted to defaults - Holiday presets:
start_month/start_day/end_month/end_day,is_recurring— date-range holidays collapsed to single point
Now lossless. Backup payload carries schema_version: 2; older v1 backups still restore (defaults for new fields).
Scheduler honors intent over Plex reply (SCHEDULER-6)
All three scheduler apply branches used to only update setting.active_category / active_schedule_id / last_run if Plex apply returned True. So an empty category, a brief Plex outage, broken paths, or any other apply failure left the dashboard tile permanently stale. Now state reflects intent (which schedule is the winner) regardless of Plex result. The 5-minute periodic verifier still retries Plex sync.
Dashboard preview shows reality, not Plex-stored ghosts (PREVIEW-1, PREVIEW-3)
- The dashboard preview button used to query Plex for "what's currently set" and try to resolve those paths locally. If Plex held a leftover path from a different host (e.g. a Docker container's
/data/prerolls/...after switching to a Windows install), preview rendered "Toy Story" instead of what NeXroll thinks is active. Now resolves from NeXroll's intent first. - Per-preroll preview used to build
static/prerolls/{category.name}/{filename}URLs and 404 for uncategorized prerolls. NewGET /prerolls/{id}/videoendpoint streams by ID — works regardless of category folder structure.
Primary-category UI retirement (CATEGORIES-7)
The v1.13.0 work fixed the Categories page but four primary-flavored UI surfaces lingered. All removed:
CategoryPickerno longer renders a "Primary" chip or "Make primary" star — all selected categories are equal chips- "Set as Primary (moves files)" checkbox in Add-Prerolls-to-Category — gone
- Bulk "Apply to N Selected" no longer moves files; just adds the m2m tag
- Internal:
?set_primary=true|falseno longer sent
"Sea of thumbnails" → folder picker (IMPORT-1)
Adding prerolls to a category used to be a flat thumbnail grid with no way to grab a whole folder. New affordances:
- Folder filter dropdown derived on the fly from each preroll's path (
Christmas (24),Halloween (12), etc.) - Select All (N) button picks every preroll matching the current filter
- Each thumbnail's caption shows source folder name
Pick "Holiday" → click Select All → click Add. Done.
Scheduler tile countdown clarity (SCHEDULER-7)
User reported: "the countdown finished for 1135 Test but it didn't apply, instead reset to 1h 40min." Root cause: when one schedule's window opens, the countdown's "next up" target jumps to the next schedule on the same React render — both label and time flip simultaneously, looking like a reset. The countdown was actually correct; the display was ambiguous. Now shows:
Now: <currently active schedule>line above the countdownNext: <upcoming> @ <target time>(target timestamp on the same line)
When the target schedule flips, the timestamp visibly changes too.
Cross-cutting cleanup
- Deleted
_apply_schedule_win_lose_logic(~410 lines): a parallel reimplementation of scheduler logic that lingered after v1.13.5 rerouted its call site. - Consolidated 7 parallel
_prerolls_for_categoryhelpers into one canonicalprerolls_for_category_query(db, category_id)inscheduler.py. This was the audit's most architecturally dangerous finding — theenabledfilter had to be added to each duplicate independently, and the manual-apply and Jellyfin/Emby paths were each caught only after testing surfaced the regression. Future fixes here land once, not seven times. _paths_equal+_find_preroll_for_trailerhelpers for trailer↔preroll linkage. Tolerant of case and forward-vs-backslash separator drift on Windows.
Deferred (tracked in AUDIT.md for future passes)
- SCHEDULER-2 — Apply-branch elif chain readability (no behavior change)
- SCHEDULER-5 — No visible "why this schedule wins" badge in the UI
- CATEGORIES-4 — Sequence-import dedupe edge case
- BACKUP-2 —
Schedule.preroll_idsID remap on restore (rarely used field) - BACKUP-3 — File-bundle ZIP backup path is unaudited
- NEXUP-3 — Sync in-progress detection uses module dict (TOCTOU; safe for single-process uvicorn)
API changes
- New:
GET /prerolls/{id}/video,POST /prerolls/rescan(already shipped),GET /categories/{id}/delete-impact(already shipped) - Changed shape:
DELETE /categories/{id}response now returnsremoved_m2m/cleared_primary/disabled_schedules/cleared_fallback_schedules/removed_holiday_presetsinstead of the v1.12.20 reassignment counts - Backup payload: new
schema_version: 2, additional fields per model (see BACKUP-1 above). v1 payloads still restore. - New schema column:
prerolls.enabled(Boolean, default True). Idempotent SQLite migration on startup. Older builds reading this DB just see the new column as defaulted-true.
Install
Windows: download NeXroll_Installer.exe below.
Docker: rebuild your image from this tag.
Existing installs will see the prerolls.enabled column added automatically on first launch — no manual migration needed.