Add Recently Added, bulk rename, time remaining, library stats, backup export
Five more usability features on top of the search/thumbnails batch: - Recently Added: dashboard card of courses by folder mtime, separate from Recently Viewed (watched vs. just showed up on disk). - Bulk find/replace rename across every course/folder name at once, with a mandatory preview step before anything touches disk. Refactored the single-item rename route to share the same validate/apply logic. - Estimated time remaining on the loaded course's stats card, computed only from lessons that have actually reported a duration. - Library-wide stats overview: total courses, lessons tracked, time watched, daily streak - read from each course's small progress file rather than re-scanning course contents. - One-click backup/export zip of settings, hidden-paths, recently-viewed, and every course's progress/notes. Also fixes a real performance bug found along the way: search was routing through list_library_directory, which computes a full recursive file count and thumbnail lookup for every course at every level regardless of match - turning a search into an O(every file in the library) scan. Gave search its own lightweight directory-only walk (iter_all_courses), now shared by Recently Added and the stats overview too, so the expensive per-course work only runs for courses that actually match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -163,6 +163,48 @@ Asked for after brainstorming what could make the app nicer to use — see
|
||||
avoid re-validating/re-scanning an untrusted path). Button lives in the
|
||||
course stats card, behind a confirm prompt.
|
||||
|
||||
## Search perf fix + five more features (this session)
|
||||
|
||||
Also caught: search on the real deployed library was taking 3+ seconds
|
||||
(scanning ~6000+ files across the real course tree). Root cause:
|
||||
`search_library_courses` walked via `list_library_directory`, which computes
|
||||
a full recursive file count (`rglob`) and thumbnail lookup for *every*
|
||||
course at *every* level, regardless of whether it matched. Rewrote it with
|
||||
its own directory-only walk (`iter_all_courses` - now shared by search,
|
||||
Recently Added, and library stats below) so the expensive per-course work
|
||||
only runs for courses that actually match. Verified with a synthetic
|
||||
6000-file library: effectively instant for narrow queries, same cost as
|
||||
before only in the pathological "everything matches" case (unavoidable -
|
||||
you need real data for every result you return).
|
||||
|
||||
1. **Recently Added** — dashboard card showing courses by folder mtime
|
||||
(`get_recently_added_courses`), separate from Recently Viewed (which
|
||||
tracks what's been *watched*, not what showed up on disk).
|
||||
2. **Bulk find/replace rename** — `Settings → Manage Library → Bulk Rename`.
|
||||
Preview (`GET /api/bulk-rename/preview`) before apply
|
||||
(`POST /api/bulk-rename/apply`) is mandatory; apply takes the *exact*
|
||||
list the client saw in preview (not a re-derived pattern match), and
|
||||
continues past individual failures (e.g. a name collision) rather than
|
||||
aborting the whole batch. Scans every directory in the library, course
|
||||
and category/group folders alike (`_iter_all_directories`) - by user's
|
||||
choice, since the naming problem isn't limited to course-level folders.
|
||||
The single-item rename route (`/api/rename-path`) was refactored to
|
||||
share the same validation/apply logic (`validate_new_name`,
|
||||
`perform_rename`) - confirmed byte-for-byte identical error responses
|
||||
after the refactor.
|
||||
3. **Estimated time remaining** — added to `_calculate_completion_stats`;
|
||||
only ever computed over lessons that have actually reported a duration
|
||||
(i.e. been played at least once) - no data, no estimate shown, rather
|
||||
than a misleading "0 remaining."
|
||||
4. **Library stats overview** — total courses, lessons tracked, time
|
||||
watched, daily streak. Reads each course's small progress JSON directly
|
||||
rather than re-scanning course contents, so it stays cheap regardless of
|
||||
how many files live inside each course.
|
||||
5. **Backup/export** — `GET /api/backup`, a `Settings → Backup & Export`
|
||||
button. Zips settings/hidden-paths/recent-views plus every course's
|
||||
progress+notes file (stdlib `zipfile`, no new dependency) - not the
|
||||
course files themselves.
|
||||
|
||||
## Known limitations still open
|
||||
- App is unauthenticated by design (matches upstream) — settings and hidden-path
|
||||
curation apply app-wide, not per-browser/per-user.
|
||||
|
||||
Reference in New Issue
Block a user