Part of the distilled-docs spec (parent issue). Needs the schema PR and the skill.
Why
The one-line description is what the index can afford. It is not what a reader wants once they have opened the example.
What
The explainer lives in the example's own README.md, under the badges. That file already exists in all 170 examples — badges, degit line, thumbnail, nothing else. GitHub renders it, degit carries it into whatever the reader scaffolds, and it is where a human lands. Writing under the badges is additive: no new file, no new convention.
render-llms.mjs inlines it into <name>.md, under the facts block. This is what finally puts an explanation into the document agents are served. llms.txt is untouched — it keeps its single line. One line to choose, the full explainer once chosen.
Structure is fixed, length is not — the problem, the technique, the pitfalls. A fixed structure resists drift better than a word ceiling, and a demo that needs 200 words should not be padded to 400.
The glossary goes to examples/<demo>/CONTEXT.md, which the multi-context layout in docs/agents/domain.md already provides for.
Where the content comes from
describe-example --explain, which runs /mattpocock-skills:teach in sub-agents (see the skill issue). The teach workspace — MISSION.md, learning-records/, lessons/ — is one person's learning state and stays outside the repo. Only the reference distillation and the glossary come back.
Keeping it from rotting
Prose that explains code goes stale on its own. It gets a mechanism, chosen from how this repository actually changes.
How examples actually change. Seven commits in twenty-four months touched any example's src/:
| Commit |
Files |
Examples |
abdf0770 migrate demos → examples |
1159 |
161 |
3f913a0d pre-commit prettier hook |
593 |
161 |
d05ff3b6 convert the examples to TS |
550 |
158 |
| the other four |
2–51 |
1 to 9 |
Nobody edits aquarium on its own. Change here is either a mechanical sweep across the whole gallery, or a targeted fix on a handful.
So the obvious mechanism is out. A hash of the example's source stored next to its explainer, failing the lint when the source moves, would have fired 161 times on a formatting commit. The only rational response would be to re-stamp 161 hashes without reading a word — training precisely the reflex that makes a tripwire worthless.
What works instead: every identifier the prose names in backticks must still appear somewhere in the example's src/. A concatenation and a search — no import parsing, no package resolution. useMask disappears in a drei migration → the lint breaks on precisely the explainers that named it, and on no others. A prettier sweep never wakes it.
It reaches further than renames: in a declarative r3f scene the technique is the set of components used, so an explainer saying "the spheres are instanced" names Instances, and the day instancing goes the identifier goes with it.
The check is deliberately looser than the one on apis, because it guards prose rather than a data field:
| Field |
Checked against |
Why |
apis |
the example's imports |
a list of library APIs — an entry that is not imported has no business there |
README.md, CONTEXT.md |
anywhere in the example's src/ |
prose may name the demo's own components (Aquarium, Turtle) and its props (stencil in gl={{ stencil: true }}) |
What it gives up is small: an identifier deleted from the code but surviving in a comment still passes.
Second, free: the writing contract. The explainer names the technique and its APIs; it never walks the code, cites no line numbers, no variable names, no "the file X does Y".
Also in this PR
docs/agents/domain.md gains two rules:
teach is a legitimate producer of CONTEXT.md, alongside /domain-modeling
- no demo gets a
CONTEXT.md that merely restates its README
Absence is the normal state. teach is multi-session and interactive; realistically this happens for a handful of examples driven by curiosity, not as a programme across 170. An example with an explainer is better; one without is fine.
Done when
--explain produces an explainer for at least one example, <name>.md carries it, and the backticked-identifier check fails on a deliberately stale identifier.
How to implement
Start from a cleared session — this issue is written to be self-sufficient, and a session carrying the design discussion also carries the options that were rejected.
Branch off main once #194 and #196 have merged — it needs the renderer and the skill.
git checkout -b readme-explainer
/mattpocock-skills:implement #199
implement commits to the current branch, so the branch has to exist first — otherwise this lands on main.
Part of the distilled-docs spec (parent issue). Needs the schema PR and the skill.
Why
The one-line
descriptionis what the index can afford. It is not what a reader wants once they have opened the example.What
The explainer lives in the example's own
README.md, under the badges. That file already exists in all 170 examples — badges,degitline, thumbnail, nothing else. GitHub renders it,degitcarries it into whatever the reader scaffolds, and it is where a human lands. Writing under the badges is additive: no new file, no new convention.render-llms.mjsinlines it into<name>.md, under the facts block. This is what finally puts an explanation into the document agents are served.llms.txtis untouched — it keeps its single line. One line to choose, the full explainer once chosen.Structure is fixed, length is not — the problem, the technique, the pitfalls. A fixed structure resists drift better than a word ceiling, and a demo that needs 200 words should not be padded to 400.
The glossary goes to
examples/<demo>/CONTEXT.md, which the multi-context layout indocs/agents/domain.mdalready provides for.Where the content comes from
describe-example --explain, which runs/mattpocock-skills:teachin sub-agents (see the skill issue). The teach workspace —MISSION.md,learning-records/,lessons/— is one person's learning state and stays outside the repo. Only the reference distillation and the glossary come back.Keeping it from rotting
Prose that explains code goes stale on its own. It gets a mechanism, chosen from how this repository actually changes.
How examples actually change. Seven commits in twenty-four months touched any example's
src/:abdf0770migrate demos → examples3f913a0dpre-commit prettier hookd05ff3b6convert the examples to TSNobody edits
aquariumon its own. Change here is either a mechanical sweep across the whole gallery, or a targeted fix on a handful.So the obvious mechanism is out. A hash of the example's source stored next to its explainer, failing the lint when the source moves, would have fired 161 times on a formatting commit. The only rational response would be to re-stamp 161 hashes without reading a word — training precisely the reflex that makes a tripwire worthless.
What works instead: every identifier the prose names in backticks must still appear somewhere in the example's
src/. A concatenation and a search — no import parsing, no package resolution.useMaskdisappears in a drei migration → the lint breaks on precisely the explainers that named it, and on no others. A prettier sweep never wakes it.It reaches further than renames: in a declarative r3f scene the technique is the set of components used, so an explainer saying "the spheres are instanced" names
Instances, and the day instancing goes the identifier goes with it.The check is deliberately looser than the one on
apis, because it guards prose rather than a data field:apisREADME.md,CONTEXT.mdsrc/Aquarium,Turtle) and its props (stencilingl={{ stencil: true }})What it gives up is small: an identifier deleted from the code but surviving in a comment still passes.
Second, free: the writing contract. The explainer names the technique and its APIs; it never walks the code, cites no line numbers, no variable names, no "the file X does Y".
Also in this PR
docs/agents/domain.mdgains two rules:teachis a legitimate producer ofCONTEXT.md, alongside/domain-modelingCONTEXT.mdthat merely restates its READMEAbsence is the normal state.
teachis multi-session and interactive; realistically this happens for a handful of examples driven by curiosity, not as a programme across 170. An example with an explainer is better; one without is fine.Done when
--explainproduces an explainer for at least one example,<name>.mdcarries it, and the backticked-identifier check fails on a deliberately stale identifier.How to implement
Start from a cleared session — this issue is written to be self-sufficient, and a session carrying the design discussion also carries the options that were rejected.
Branch off
mainonce #194 and #196 have merged — it needs the renderer and the skill.implementcommits to the current branch, so the branch has to exist first — otherwise this lands onmain.