feat(learner): web shell, library, lesson views (LessonView), decks, code files, exercises, notes, account and admin pages

Tasks 10-12 of the WP-A plan in one commit (the route modules import each other):
- header extras portalled into the Phase 0 header: course switcher, search, Reading/Work, account
- course frame with outline sidebar (mobile overlay, Escape closes), progress, gating locks
- library: featured course, continue row, New and collection rows, course list, filters, search
- LessonView: text (track switcher, folds, ordered hint tiers), video (resume, done near the end,
  timestamp notes, transcript), companion decks rendered with pdf.js (thumbs, arrows, fullscreen,
  per-slide notes), code files (tree, tabs, copy, zip, command), exercises (Web Worker runner or
  local command with optional pasted evidence), floating action bar
- notes list, account (email/password, providers, SSO by email) and owner admin pages
- pdf.js worker served from /api/learner/pdf.worker.mjs
- browser entry @stc/feature-learner/web (the package index also carries the server half)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-30 04:53:36 +01:00
co-authored by Claude Opus 5.5
parent 99c0e4929f
commit 89c51f013d
27 changed files with 2994 additions and 1 deletions
@@ -0,0 +1,20 @@
import type { Feature } from "@stc/contracts";
/** The pdf.js worker from the installed pdfjs-dist, for client-side deck rendering. */
export function pdfWorkerPath(): string {
return Bun.resolveSync("pdfjs-dist/build/pdf.worker.min.mjs", import.meta.dir);
}
export function assetRoutes(): Feature["routes"] {
let path: string | null = null;
return {
"/api/learner/pdf.worker.mjs": {
GET: async () => {
path ??= pdfWorkerPath();
return new Response(Bun.file(path), {
headers: { "Content-Type": "text/javascript; charset=utf-8", "Cache-Control": "public, max-age=86400" },
});
},
},
};
}
@@ -8,6 +8,7 @@ import { lessonRoutes } from "./lesson";
import { userDataRoutes } from "./userdata";
import { codeRoutes } from "./code";
import { transpileRoutes } from "./transpile";
import { assetRoutes } from "./assets";
/**
* The learner feature: auth, library, reader, notes, progress and exercises.
@@ -32,6 +33,7 @@ export function learnerFeature(ctx: ServerContext): Feature & { auth: AuthServic
...userDataRoutes(ctx),
...codeRoutes(ctx),
...transpileRoutes(),
...assetRoutes(),
};
// Every handler waits for the learner migrations (a no-op once Phase 2 runs them in migrate()).
for (const methods of Object.values(routes)) {