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
+24 -5
View File
@@ -1446,6 +1446,23 @@ class ProgressTracker:
ProgressTracker.save_progress(course, progress)
@staticmethod
def touch_lesson_accessed(course: Course, lesson_path: str):
"""
Record that a lesson was opened, without touching its saved
completed/progress_seconds/duration_seconds - update_lesson_progress
is for the client reporting real playback progress, and calling it
(with its completed=False, progress_seconds=0 defaults) just for
viewing a page would silently reset an already-watched lesson back
to 0% every time it's opened, before the client gets a chance to
report anything real.
"""
progress = ProgressTracker.load_progress(course)
entry = progress.setdefault(lesson_path, {})
entry['last_accessed'] = datetime.now().isoformat()
progress['last_accessed_path'] = lesson_path
ProgressTracker.save_progress(course, progress)
@staticmethod
def _notes_from_entry(entry: Dict[str, Any]) -> List[Dict[str, Any]]:
"""
@@ -2340,15 +2357,16 @@ def view_lesson(lesson_path: str):
if current_index < len(all_lessons) - 1:
next_lesson = all_lessons[current_index + 1][0]
# Update last accessed
ProgressTracker.update_lesson_progress(current_course, lesson_path)
# Update last accessed without touching saved progress/completed state
ProgressTracker.touch_lesson_accessed(current_course, lesson_path)
# Record for the cross-course "Recently Viewed" list on the dashboard
record_recent_view(current_course.name, current_course.path, lesson_path, lesson.title)
# Read notes directly from the progress file rather than the Lesson
# object - apply_progress_to_tree (which populates Lesson fields) isn't
# called on this code path, only on the dashboard's tree render.
# Read notes and progress directly from the progress file rather than
# the Lesson object - apply_progress_to_tree (which populates Lesson
# fields) isn't called on this code path, only on the dashboard's tree
# render.
lesson_progress = ProgressTracker.load_progress(current_course).get(lesson_path, {})
seek_seconds = request.args.get('t', type=int)
@@ -2358,6 +2376,7 @@ def view_lesson(lesson_path: str):
lesson=lesson,
lesson_path=lesson_path,
lesson_notes=ProgressTracker._notes_from_entry(lesson_progress),
lesson_progress_seconds=lesson_progress.get('progress_seconds', 0),
initial_seek_seconds=seek_seconds,
outline_topic_id=lesson_progress.get('outline_topic_id', ''),
outline_topic_name=lesson_progress.get('outline_topic_name', ''),