Many course platforms (Pluralsight, Packt, ...) open with a generic
branded bumper animation running 10-20s before the actual course
title card. The 8s candidate window landed squarely inside that
bumper and grabbed the platform logo instead of anything
course-specific. Candidates now spread across the first ~25s.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Concurrent requests for the same course's thumbnail (e.g. the
Precompute background scan overlapping an ordinary page load) shared
fixed candidate filenames, so one request's cleanup could delete a
temp file the other was still using, crashing with ENOENT. Candidate
filenames are now unique per call.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Auto-play next lesson on end, with a cancelable countdown and a
Settings toggle (default on).
- Grid-view course cards show a small progress ring (checkmark at
100%) instead of a separate bar.
- Duplicate Lesson Files: scans within each course/folder for media
files that look like the same lesson downloaded twice, with per-file
delete and a shared ignore list with Duplicate Courses.
- Auto-generated cover art now samples a few early candidate frames
and keeps the largest JPEG, favoring an intro title card over a
blank fade-in or a plain presenter frame.
- Settings -> "Regenerate Thumbnails" re-runs that logic for every
course with an auto-generated thumbnail (never touches manual
covers), so already-cached thumbnails can pick up the improvement.
- Add .gitignore for __pycache__/.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Favorites: star toggle on course rows, the course page, and a new
dashboard card; rebased on rename/move, backed up/restored, and
caught by stale-reference cleanup (also fixed a pre-existing gap
where favorites.json wasn't in the backup file list).
- Storage Usage: click a folder row to drill into its contents one
level at a time, with a Back button.
- Lesson player: added ,/. (speed step), [/] (prev/next lesson), and
F (fullscreen) keyboard shortcuts.
- Settings: a CHANGELOG.md-backed "What's New" list, since VERSION
alone only ever shows the current build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The resize observer was persisting any rendered size change, including
incidental reflow from the page's own responsive max-width clamping on
window/viewport resize - not just deliberate corner-handle drags. This
let the stored size drift into a badly non-16:9 box over repeated views
on different window sizes, pushing the actual resize grip far below the
fold. Now only persists while the user has mousedown on the video, and
caps rendered height at 80vh as a backstop against already-drifted values.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_build_category_index() was the one tree-scanning function never
wrapped in the app's existing 5-minute cache - it ran fresh on every
File Management page load and Sort Unsorted scan (not a manual "this
may take a moment" action like Duplicate Courses or Storage Usage), so
the cost was invisible until it was already slow. Measured ~1.0-1.1s
consistently against the live NAS-mounted library at 161 courses,
confirmed via profiling that a single call makes dozens of iterdir()
round-trips - each one a network hop over SMB. Now shares the same
cache_get_or_compute pattern and invalidate_cache() call sites as
get_all_course_dirs(), so no new invalidation logic was needed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Manage Library search: find any course/folder by name anywhere in
the tree instead of expanding levels one by one; results carry the
same Move/Rename/Hide actions.
- Bulk select: every row gets a checkbox, so items found via search or
expanded across different tree levels can be hidden, shown, or moved
to the same destination together in one batch.
- Undo last action: a "Last action: ... [Undo]" bar appears after any
move/rename (Sort Unsorted apply, Bulk Rename apply, a Manage
Library move/rename, a bulk move) and reverses the whole batch.
Deliberately never covers Hide/Show (already a one-click toggle) or
Delete (permanent by design) - only ever move/rename, which are
trivially reversible. Re-checks each item before reversing it, so a
partial failure reports exactly what did and didn't reverse.
- Storage Usage: disk usage per top-level library folder, largest
first, with a simple proportional bar; a manual scan since it reads
every file's size.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previous approach baked git rev-parse --short HEAD into the image
at Docker build time, but Dockhand's build context doesn't reliably
have .git available, so it silently fell back to "unknown" in
production even though the build itself succeeded. Replace it with a
plain VERSION file committed to the repo (a timestamp + short
description, updated by hand alongside each commit) that ships via the
same COPY . . as everything else - no git access needed inside the
build at all.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>