mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Learn: per-lesson URLs, a :::data shared-dataset construct, and a linked-views deep dive
This commit is contained in:
@@ -2,7 +2,10 @@
|
||||
|
||||
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.
|
||||
teach the grammar's dormant power and funnel readers into the app. It is pitched past the
|
||||
basics — fundamentals are left to the official Vega-Lite docs (linked from the index), so
|
||||
the section leads with advanced cases (interaction, composition, transforms) rather than
|
||||
fundamentals.
|
||||
|
||||
## A marketing surface, like the landing
|
||||
|
||||
@@ -14,6 +17,12 @@ teach the grammar's dormant power and funnel readers into the app.
|
||||
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.
|
||||
- **An index plus a page per lesson.** `/learn/` is the index (a card per lesson); each
|
||||
lesson is its own URL `/learn/<slug>/` — a separate indexable document with its
|
||||
frontmatter-derived `<title>`/`<meta>`. The per-lesson HTML shells are generated from the
|
||||
lesson frontmatter by `scripts/learn-pages.ts` (run from `vite.config` on every dev/build,
|
||||
so "drop a file" still holds; the shells are git-ignored). The single `src/learn` entry
|
||||
renders the index or one lesson from `location.pathname`.
|
||||
|
||||
## Lessons are markdown; the renderer is general
|
||||
|
||||
@@ -21,23 +30,31 @@ A lesson is a `.md` file in `src/learn/lessons/`, discovered with `import.meta.g
|
||||
**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) |
|
||||
| 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) |
|
||||
| `:::data` wrapping a `{ name: rows }` JSON object | — metadata | injected into specs at render |
|
||||
|
||||
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 following fenced `vega-lite` block → spec. A `:::data` block names datasets once for the
|
||||
whole lesson; specs reference them with `{ "data": { "name": … } }` and `injectDatasets`
|
||||
merges the rows in at render time — so a shared dataset isn't repeated per stage, and the
|
||||
source pane keeps a stage's grammar legible instead of burying it under data.
|
||||
|
||||
## 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).
|
||||
`lessons/*.md` → `import.meta.glob` + `parseLesson` (`src/learn/lessons.ts`, using
|
||||
`core/lesson-parse`) → `LESSONS`. The `src/learn` entry reads the path: `/learn/` →
|
||||
`LearnIndex`, `/learn/<slug>/` → `LessonView`, both inside `LearnLayout` (shared
|
||||
header/footer/theme). `LessonView` dispatches each block → `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). `parseLesson` also returns `datasets` (the `:::data`
|
||||
blocks); `LessonView`/`SpecProgression` call `injectDatasets` so only the _rendered_ spec
|
||||
carries the rows — the displayed-and-diffed spec keeps its by-name reference.
|
||||
|
||||
## Rules
|
||||
|
||||
@@ -52,3 +69,7 @@ highlights what the stage changed with `changedLines` (`core/spec-diff`, an LCS
|
||||
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.
|
||||
- **Lesson charts render through `LandingChart` with `fitMode: 'width'`** — which sets
|
||||
`width: "container"` and drops fixed heights on every view, and container width only works
|
||||
for a single or layered view, not side-by-side. A multi-view lesson is therefore a
|
||||
`vconcat` (a stacked column), not an `hconcat` dashboard, which would fight the sizing.
|
||||
|
||||
Reference in New Issue
Block a user