docs: reader + 3D atom hub design spec

This commit is contained in:
electron-rare
2026-06-10 20:51:18 +02:00
commit 2067bc6c83
@@ -0,0 +1,129 @@
# 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.