Stop resetting lesson progress on view, and add Notes Hub search

view_lesson() called update_lesson_progress() with no arguments on
every page load, which defaults completed=False and progress_seconds=0
and silently overwrote whatever was already saved - just opening an
already-watched lesson reset its progress/completed state unless the
client happened to report real values within the next 15 seconds.
Replaced with touch_lesson_accessed(), which only bumps last_accessed.

Also fixes the "Resume from Xs" feature this fed into: it read a value
that was never populated on this route, and even when given a real
one, checked activeMedia.duration synchronously before metadata had
loaded, so it silently never applied. Now waits for loadedmetadata (or
resolves immediately if already available) and defers to an explicit
note-timestamp jump (?t=) when both are present.

Notes Hub gets a live search box filtering by note text, lesson title,
or course name (client-side, no reload), with a "no notes match" state
- useful now that a single category folder can hold dozens of courses'
worth of timestamped notes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-22 22:00:35 -04:00
co-authored by Claude Sonnet 5
parent 393f78206a
commit 3fedf849d3
3 changed files with 75 additions and 17 deletions
+19 -11
View File
@@ -912,12 +912,27 @@
renderNotes();
if (activeMedia && INITIAL_SEEK_SECONDS !== null && INITIAL_SEEK_SECONDS !== undefined) {
const doInitialSeek = function() { activeMedia.currentTime = INITIAL_SEEK_SECONDS; };
// Seek once metadata is available (activeMedia.duration is NaN
// before that, so doing this synchronously on script load - as
// this used to - silently never applied). An explicit jump to
// a note's timestamp (?t=, see INITIAL_SEEK_SECONDS) takes
// priority over resuming general playback progress.
const savedProgressSeconds = {{ lesson_progress_seconds|default(0) }};
function applyInitialSeek() {
if (INITIAL_SEEK_SECONDS !== null && INITIAL_SEEK_SECONDS !== undefined) {
activeMedia.currentTime = INITIAL_SEEK_SECONDS;
} else if (savedProgressSeconds > 0 && savedProgressSeconds < activeMedia.duration - 30) {
activeMedia.currentTime = savedProgressSeconds;
showNotification(`Resumed from ${Math.floor(savedProgressSeconds / 60)}:${String(Math.floor(savedProgressSeconds % 60)).padStart(2, '0')}`);
}
}
if (activeMedia) {
if (activeMedia.readyState >= 1) {
doInitialSeek();
applyInitialSeek();
} else {
activeMedia.addEventListener('loadedmetadata', doInitialSeek, { once: true });
activeMedia.addEventListener('loadedmetadata', applyInitialSeek, { once: true });
}
}
@@ -1047,13 +1062,6 @@
saveProgress(activeMedia.currentTime, true);
markAsCompleted();
});
// Resume from saved position
const savedProgress = {{ lesson.progress_seconds|default(0) }};
if (savedProgress > 0 && savedProgress < activeMedia.duration - 30) {
activeMedia.currentTime = savedProgress;
showNotification(`Resumed from ${Math.floor(savedProgress / 60)}:${String(Math.floor(savedProgress % 60)).padStart(2, '0')}`);
}
}
function saveProgress(progressSeconds, completed = false) {