Part of the distilled-docs spec (parent issue). Depends on the rename landing first.
Why
/examples/<name>.md today is a facts block followed by every source file inlined. Nothing names the technique. aquarium is served with an empty description and the single tag transmission, while what actually makes it work is a stencil mask — useMask plus a backside MeshTransmissionMaterial. A reader pays full token price to rediscover that by reading 400 lines of TSX.
Identifiers are data, not prose. Three median-length API names cost 34 characters, which is 28% of the 120-character budget the one-line description gets. They belong in their own field.
Meanwhile notes is dead weight: two uses, both the same string "Updated for MeshTransmissionMaterial in 2023." — maintenance history that belongs in git, rendered into <name>.md where a reader cannot tell it from documentation. And assets[].notes has zero uses and is never rendered by attribution().
What
apis added to schemas/pmndrs.schema.json, optional for now. It becomes required only once every example carries one. A field required before it is populated is a red CI on day one.
Contract for the field:
- Imported identifiers only. Not props — a technique carried by
gl={{ stencil: true }} or frameloop="demand" is said in the prose description instead.
- A short selection, not a list. Typically two or three of the nine an example imports at the median (p90 is 15, max 34). The criterion is what you would have to bring over to reproduce the effect. No number is fixed, but "no ceiling" is not "as many as you like" — the limit is editorial.
render-llms.mjs renders it in the facts block of <name>.md. Not in summaryLine() — the index does not carry it. Adding it there would cost ~34 characters on every line, taking the median from 107 to ~141 and llms.txt from ~19 kB to ~25 kB, on a file read at the start of every question.
notes removed from the schema, from renderExample(), and from the two files that use it (frosted-glass, router-transitions). assets[].notes removed from the schema too.
Lint in bin/validate-pmndrs-metadata.mjs: every apis entry must be imported somewhere in that example's src/. A hard error, not a warning. It goes directly under the existing libraries block, which already cross-checks that field against package.json in both directions.
The check is local and only local. Whether the identifier really exists in drei at the pinned version is out of scope — Rollup already fails the bundle on a missing named export, and the e2e suite then renders the scene. There is no reason for the lint to reprove what the build enforces.
The "stays up to date" property falls out of the local check alone: drei renames an API, someone fixes the import, apis still lists the old name, the lint breaks.
Done when
Tests cover the new render output and the new lint rule, pnpm check passes, and a hand-written bad apis entry fails lint:metadata.
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 #193's branch (or main once it has merged) — it renames the files this PR edits.
git checkout -b catalog-apis-field
/mattpocock-skills:implement #194
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). Depends on the rename landing first.
Why
/examples/<name>.mdtoday is a facts block followed by every source file inlined. Nothing names the technique.aquariumis served with an empty description and the single tagtransmission, while what actually makes it work is a stencil mask —useMaskplus a backsideMeshTransmissionMaterial. A reader pays full token price to rediscover that by reading 400 lines of TSX.Identifiers are data, not prose. Three median-length API names cost 34 characters, which is 28% of the 120-character budget the one-line
descriptiongets. They belong in their own field.Meanwhile
notesis dead weight: two uses, both the same string"Updated for MeshTransmissionMaterial in 2023."— maintenance history that belongs in git, rendered into<name>.mdwhere a reader cannot tell it from documentation. Andassets[].noteshas zero uses and is never rendered byattribution().What
apisadded toschemas/pmndrs.schema.json, optional for now. It becomes required only once every example carries one. A field required before it is populated is a red CI on day one.Contract for the field:
gl={{ stencil: true }}orframeloop="demand"is said in the prosedescriptioninstead.render-llms.mjsrenders it in the facts block of<name>.md. Not insummaryLine()— the index does not carry it. Adding it there would cost ~34 characters on every line, taking the median from 107 to ~141 andllms.txtfrom ~19 kB to ~25 kB, on a file read at the start of every question.notesremoved from the schema, fromrenderExample(), and from the two files that use it (frosted-glass,router-transitions).assets[].notesremoved from the schema too.Lint in
bin/validate-pmndrs-metadata.mjs: everyapisentry must be imported somewhere in that example'ssrc/. A hard error, not a warning. It goes directly under the existinglibrariesblock, which already cross-checks that field againstpackage.jsonin both directions.The check is local and only local. Whether the identifier really exists in drei at the pinned version is out of scope — Rollup already fails the bundle on a missing named export, and the e2e suite then renders the scene. There is no reason for the lint to reprove what the build enforces.
The "stays up to date" property falls out of the local check alone: drei renames an API, someone fixes the import,
apisstill lists the old name, the lint breaks.Done when
Tests cover the new render output and the new lint rule,
pnpm checkpasses, and a hand-written badapisentry failslint:metadata.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 #193's branch (or
mainonce it has merged) — it renames the files this PR edits.implementcommits to the current branch, so the branch has to exist first — otherwise this lands onmain.