Files
formations-app/docs/superpowers/specs/2026-06-10-formations-reader-hub3d-design.md
2026-06-10 20:51:18 +02:00

6.2 KiB

Design: formations reader + 3D atom hub (sub-project 1 of 4)

Date: 2026-06-10 Status: approved (brainstorm with owner, visual companion session)

Context

moodle.saillant.cc (Moodle 4.5 on Tower, restored 2026-06-10) hosts 6 formations, each = 4 books (text-only HTML chapters) + 4 quizzes + forum + customcert certificate. 3 formations are published (esp32-ia, kicad-makers, freertos), 3 hidden (llm-locaux, iot-az, docker-selfhosting). The owner wants to replace the learner-facing Moodle UI with a web app matching the new electron 3D identity of lelectronrare.fr (dark + copper, canvas electron hero shipped 2026-06-10).

Program decisions (brainstormed, apply to all 4 sub-projects)

  1. Full replacement: the app becomes the entire learner experience; Moodle stays as backend (accounts, grades, completion, certificates) and as the author/admin tool.
  2. Certificates: generated by Moodle (customcert); the app celebrates and links to the PDF.
  3. Access model: published course content is readable WITHOUT an account (replaces "preview gratuite", gives SEO); a Moodle account is required for quizzes, saved progression, certificates.
  4. Domain: the app will ultimately TAKE moodle.saillant.cc; the Moodle UI moves to an admin subdomain (sub-project 4). Until then the app lives at formations.saillant.cc.
  5. three.js role: option B "the atom is the table of contents" — each course is a three.js atom: 4 orbits = 4 modules (books), electrons = chapters, click to read. Reading happens in a clean light panel.
  6. Quizzes (sub-project 3): rendered natively in the app; attempts and grades pushed to Moodle via its quiz API.

Sub-projects

  1. Reader + 3D hub (this spec) — public reading experience.
  2. Accounts + progression — login (Moodle token), progression lights up the atom's electrons.
  3. Native quizzes — in-app MCQ/true-false, graded via Moodle API.
  4. Certificates + domain swap — celebration screen, customcert PDF link, Moodle UI → lms-admin.saillant.cc, app takes moodle.saillant.cc.

Sub-project 1 scope

Stack & deployment

  • Astro 6 SSR (node adapter) + Tailwind 4 — same stack as lelectronrare.fr; one Docker container (node:22-alpine multi-stage, same Dockerfile pattern) on Tower next to Moodle, port 8096.
  • Routing: traefik on electron-server (dynamic file formations.yml) → http://100.78.6.122:8096 (Tailscale — the LAN 192.168.0.x is shadowed on ES by a docker bridge) + CF tunnel ingress formations.saillant.cc (API recipe, same as moodle/coder restores).
  • three.js pinned (exact version in package.json, lockfile committed; vendor/fork per the supply-chain HITL policy). It is loaded ONLY on course hub pages, lazily.
  • Repo: electron-rare/formations-app on Gitea (this repo).

Data flow (BFF)

  • The app's server (Astro API routes / loaders) is the only thing that talks to Moodle. Browser → app only.
  • Moodle REST web services with a dedicated read-only service token: core_course_get_courses_by_field (catalogue), core_course_get_contents (course structure: books, chapters), chapter HTML files fetched server-side with the token.
  • Admin prerequisite (done via Moodle CLI during implementation): enable web services + REST protocol, create service user + token with the minimal read functions.
  • Cache: in-memory per-process with TTL (content changes rarely) + stale-while-revalidate: if Moodle is down, serve the last good copy and log; only error pages if the cache is cold.
  • Slug mapping fixed in app config: esp32-ia, kicad-makers, freertos (+ hidden ones listed as "en préparation", never fetched publicly).

Pages

  • / — catalogue: 6 cards (3 open, 3 "en préparation"), dark + copper, consistent with lelectronrare.fr (reuse of the visual language, not the code).
  • /cours/<slug>the atom hub (three.js): nucleus = the course; 4 tilted orbits = the 4 modules (books); electrons on each orbit = that module's chapters. Hover an electron → chapter title tooltip; click → navigate to the chapter. Gentle orbital camera (drag to rotate, scroll zoom within limits). Module labels anchored to orbits. Progression rendering exists but all-grey in v1 (lights up in sub-project 2).
  • /cours/<slug>/module-<n>/chapitre-<m> — reading panel: light background, max-width prose column, book chapter HTML rendered server-side (free reading = indexable), breadcrumb, prev/next chapter, a small static atom position indicator in the margin (current electron highlighted), table of contents drawer.
  • Legacy URLs /course/view.php?id=N answered with 301 → /cours/<slug> (active from day one; becomes load-bearing at sub-project 4).

Progressive enhancement & guard rails

  • The hub page ALWAYS renders a complete HTML table of contents (modules + chapters list) server-side; the three.js atom replaces it visually when JS+WebGL are available. noscript, prefers-reduced-motion, WebGL-less, and low-end mobile users get the HTML sommaire. The 3D is an enhancement, never a wall.
  • Perf budget: three.js chunk lazy-loaded only on /cours/<slug>; reading pages ship no 3D; LCP < 2.5 s on reading pages; the atom scene pauses when the tab is hidden (same discipline as the lelectronrare hero: DPR cap, rAF pause, reduced-motion static).
  • Chapter HTML is sanitized server-side before render (defense in depth — authors are trusted today, but the pipe shouldn't assume it).

Error handling

  • Moodle WS unreachable: stale cache if warm; otherwise a friendly error page on course routes; / still renders (catalogue is app-config).
  • Unknown slug/module/chapter → 404 page (same dark identity).
  • three.js init failure → automatic fallback to the HTML sommaire (try/catch, no blank page).

Testing

  • npm run build green in Docker (node:22-alpine, --network=host on ES).
  • E2E curl: / 200, hub 200 with sommaire HTML present, chapter page 200 with real book content, legacy /course/view.php?id=2 → 301.
  • Manual: atom navigation on desktop + mobile, reduced-motion fallback, Lighthouse on a reading page.

Out of scope (later sub-projects)

  • Login, progression state, quiz rendering/grading, certificates, domain swap, Moodle admin relocation, payment.