Files
offlineu/OfflineU-project-summary.md
T

108 lines
6.1 KiB
Markdown

# OfflineU — Project Summary (for Claude Code handoff)
## What this is
A fork of the self-hosted course viewer [WhiskeyCoder/OfflineU](https://github.com/WhiskeyCoder/OfflineU)
(Flask app), customized for personal use. Deployed via Docker (Dockhand on a Ugreen NAS),
built locally from a private Gitea repo rather than pulling the upstream image.
## Deployment
- `docker-compose.yml`: `build: .` + `pull_policy: build` (forces a real rebuild every
deploy — without `pull_policy: build`, Compose can silently reuse a stale cached image).
- `network_mode: host`, `PUID=1000`, `PGID=10`, `TZ=America/New_York`.
- Volumes: courses at `/volume1/files/training``/app/courses`,
progress/settings data at `/volume2/docker/offlineu/data``/app/data`.
- Deployed from Gitea via Dockhand's "Deploy from Git" stack type.
## Files touched this session
- `offlineu_core.py` — all backend logic (Flask routes, settings, progress tracking)
- `templates/course_dashboard.html` — main dashboard / library browser
- `templates/lesson_view.html` — individual lesson page
- `templates/settings.html` — new settings page
- `templates/help.html` — new help page
- `static/theme.js` — shared script that applies persisted display settings
## Features added, roughly in order
1. **Library browser** — lazy, drill-down directory browsing instead of typing a full
path. `/library` lists one directory level at a time; a folder is treated as a
"course" once it (or 2+ of its immediate subfolders) contain video/audio files
directly. Starts collapsed at the top level.
2. **PDF preview fix** — lesson pages were fetching PDFs as text and dumping raw bytes.
Now PDFs (and HTML) render in an iframe; `.docx/.doc/.rtf` show a "no preview,
download instead" message rather than garbled text.
3. **Full-width, resizable layout** — removed a hardcoded 800px video cap and 1000px
page cap. Video player supports native corner-drag resizing (`resize: both` +
`object-fit: contain`), and the last-used size persists via settings.
4. **Settings system** (`/settings`, `/api/settings`) — server-side persisted
(`/app/data/settings.json`), covering:
- Theme (dark/light + 10 named presets: Dracula, Tokyo Night, Catppuccin Mocha,
Ayu Dark, GitHub Dark, Atom One Dark, Houston, Night Owl, Dainty, Matcha)
- Accent color (auto-syncs to a theme's signature color on switch, independently
editable after)
- Font family (system/sans/serif/monospace/Monaspace) and size
- Layout width, density, card style, corner radius
- Library root override (point the browser at a specific subfolder)
- Video player size
All applied via CSS custom properties, fetched once per page load by `static/theme.js`.
5. **Header/footer redesign** — branded gradient header, sticky footer holding
Settings + Help links (consolidated from several scattered links). Clicking the
"OfflineU" wordmark anywhere calls `/reset_course` (clears the loaded course)
rather than just `/` (which would keep showing the same course).
6. **Help page** (`/help`) — "How to Use" / "Supported File Types" moved here from
the cluttered main dashboard.
7. **Recently Viewed** — tracks the last 5 lessons viewed across *all* courses
(`/app/data/recent_views.json`), shown only on the no-course-loaded library
screen (not on an individual course's page). Clicking an entry loads that
lesson's course if it isn't already active, then jumps to the lesson
(`/recent/open`).
8. **Watch-progress tracking**`Lesson.duration_seconds` added alongside the
existing `progress_seconds`. Lesson rows in the course tree and in Recently
Viewed show either a green checkmark (completed), an accent-colored
"NN% watched" badge + thin progress bar (in progress), or a plain "○" (untouched).
9. **Library curation** (Settings → Curate Library) — hide individual courses or
entire folders from the browser without touching anything on disk. Persisted in
`/app/data/hidden_paths.json`. Hiding a folder hides everything inside it.
`/library` (normal browsing) filters hidden items out; `/library/manage`
(used by the Settings UI) includes them flagged `hidden: true` so they can be
un-hidden.
## Rough edges fixed this session
- **Single-section course detection** — `_looks_like_course()` in
`offlineu_core.py` now also recognizes a course with exactly one section
subfolder, *if* that subfolder's name reads as a section label ("Section 1",
"Module 2", "Chapter 3", etc. — see `_SECTION_NAME_RE`). This deliberately
doesn't touch the general "publisher folder holding one course" case
(e.g. `Pluralsight/Docker Deep Dive/`), since that subfolder is named after
the course, not a section.
- **Empty folder after hiding all its courses** — added `_contains_visible_course()`,
used by `list_library_directory()` when `skip_hidden=True`, so a folder whose
every course has been individually curated out no longer appears as a
drillable (but empty) directory in normal browsing. The Settings curation UI
(`skip_hidden=False`) is unaffected — it still needs to show those folders so
courses can be un-hidden.
- **Matcha theme accuracy** — replaced the hand-approximated Matcha palette with
real values sourced from lucafalasco/matcha's published VS Code theme JSON.
- **Dainty theme swapped for Nord** — turned out "Dainty" (HotWordland/dainty-vscode)
is a Lab-space theme *generator*, not a fixed palette — there was no single
official hex set to verify against, so approximating it was never really
fixable. Replaced it with Nord, which has a well-documented, fully verifiable
official palette (nordtheme.com). Anyone with `theme: "dainty"` already saved
in `settings.json` falls back gracefully to the `dark` default on next load.
## Known limitations still open
- App is unauthenticated by design (matches upstream) — settings and hidden-path
curation apply app-wide, not per-browser/per-user.
## Workflow that's been in use
Edit locally → `git add . && git commit -m "..." && git push` to the private
Gitea repo → redeploy the stack in Dockhand (which builds from the fresh
`git pull` + `pull_policy: build`).