Learn: a /learn/ deep-dive section — markdown lessons as before/after spec progressions

This commit is contained in:
2026-06-25 12:02:39 +03:00
parent c6ca988e40
commit c19857b0a7
22 changed files with 1291 additions and 13 deletions
+54
View File
@@ -0,0 +1,54 @@
# 11 — The Learning Section (`/learn/`)
An interactive deep-dive into Vega-Lite, served at `/learn/`: a marketing surface separate
from the app, where each lesson walks a spec from an 80%-naive version to a polished one to
teach the grammar's dormant power and funnel readers into the app.
## A marketing surface, like the landing
`/learn/` is a third Vite entry (`learn/index.html``src/learn/`) alongside the landing
(`/`) and the app (`/app/`), under the same marketing-surface rules:
- Reuses **`src/core` and the landing's `LandingChart`** only — never stores, modals,
orchestration, or app components. Vega is lazy-loaded through `LandingChart`, so the entry
stays light.
- **Out of PWA scope.** The service worker is scoped to `/app/`, so the learning pages stay
uncontrolled, always-fresh, and indexable — the point for organic-reach content.
## Lessons are markdown; the renderer is general
A lesson is a `.md` file in `src/learn/lessons/`, discovered with `import.meta.glob` — so
**adding a lesson is dropping a file**, with no registry to edit. A lesson is a _free-form
document of ordered blocks_, not a fixed template:
| Authored as | Block | Rendered by |
| ---------------------------------------------------- | ------------- | --------------------------- |
| plain markdown | `prose` | `Markdown` |
| `:::progression` wrapping `##` stages + fenced specs | `progression` | `SpecProgression` |
| a bare fenced `vega-lite` block | `chart` | `LandingChart` |
| `:::name … :::` | `callout` | `Markdown` (styled by name) |
Inside a `:::progression`, each `##` heading is a stage: heading → tab label, prose → note,
the following fenced `vega-lite` block → spec. Inline data repeats per fenced block — there
is no shared-data construct.
## The pipeline
`lessons/*.md``import.meta.glob` (in `LearnPage`) → `parseLesson` (`core/lesson-parse`) →
`LessonBlock[]``LearnPage` block dispatch → `Markdown` | `SpecProgression` | `LandingChart`.
`SpecProgression` renders each stage's spec with `formatSpec` (`core/json-format`) and
highlights what the stage changed with `changedLines` (`core/spec-diff`, an LCS line-diff).
## Rules
- **The engine consumes plain data, so the authoring surface is swappable.**
`SpecProgression`, `spec-diff`, and `formatSpec` take parsed blocks and specs and know
nothing about markdown — the parser is the only thing coupled to the `.md` format. The
authoring format can change without touching the widget.
- **Parsing, diff, and formatting are pure and live in `core`** (tested hardest); rendering
and the markdown library live in `src/learn`. **Core stays dependency-free**`marked` is
imported only by `src/learn/Markdown`.
- **`dangerouslySetInnerHTML` renders first-party lesson files** (repo content authored by
us), never user input — not an XSS surface.
- Lesson specs are fenced JSON parsed with `JSON.parse`; the source pane re-formats them with
`formatSpec` so the shown JSON matches the editor's house style.