feat: composite figures are a container the server knows - #82
Merged
Conversation
PART 9 section 4c makes a BARE `::: figure` fence - the fence, its separator,
the kind word, and nothing else - ONE figure of ordered panels, normalized to
a `figure_group` node. An opener carrying a quoted title or a `[label]` is
not that production and stays an admonition.
The server switches on `node.type` in four places, each with a default that
does nothing, so a node type the engine grew and the server never learned is
INVISIBLE rather than a type error: the container simply stopped folding,
hovering and tokenizing, and all 238 tests stayed green. That is the shape of
this change - four cases, not a mechanism.
- folding: `figure_group` joins FOLDABLE, so a long group collapses like
every other fenced container.
- semantic tokens: the opener's reserved kind word is scoped `type`, the
same as an admonition's, because the two are the same shape on the line
and a client colouring one should colour the other. The caption is
collected HERE beside the children rather than from any one of them,
because the group's caption sits after the CLOSING fence - the one
placement section 4c adds.
- hover: its own description, naming what separates it from the generic
container it looks like.
- completion: `::: ` offers `figure`, listed separately from the eight
admonition kinds and labelled "Composite figure". Folding it into that
list would say it is a ninth admonition, which is the confusion the
clause exists to prevent.
THE DEPENDENCY MOVES TO A GIT PIN. `@markup-carve/carve` was pinned to the
published `0.1.3`, which predates the node - `figure_group` is not in its
type union, so a case for it would not have compiled. Mid-development a git
pin is the correct pin, and it moves back to a version range at the next
release.
Tests assert the engine's shape first, so a dependency that stops producing
`figure_group` reports that plainly instead of four unrelated feature
failures. The titled and labelled openers are the control throughout: they
differ from the bare one only in the tail of one line, so a reading that
fires on the kind word alone would call them groups and nothing else would
notice.
`package.json` declares the carve-js pin as `git+https://`, and the lockfile recorded `git+ssh://git@github.com/` for the same dependency. That is not a disagreement npm invented: this machine's git config rewrites the HTTPS form to SSH via `insteadOf`, so the install that generated the lock resolved over SSH and wrote what it used. `npm ci` follows the LOCKED url, not the declared one. Anywhere without a GitHub SSH key - CI first among them - it would have failed with `Permission denied (publickey)` before a line compiled, on a public repository, and nothing in the diff would have suggested why. Rewritten to the HTTPS form, which is also what tree-sitter-carve's lockfile carries for the same dependency. Verified by removing node_modules entirely and running `npm ci` from the lockfile, then building and running the suite.
This was referenced Aug 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Carries the composite-figures vocabulary into the server. Part of markup-carve/carve-grammars#222; spec PART 9 §4c, landed as markup-carve/carve#1215, design markup-carve/carve#1122.
What was wrong
A bare
::: figurefence parses to afigure_groupnode. The server switches onnode.typein four places, each with a default that does nothing, so a node type the engine grew and the server never learned is invisible rather than a type error: the container simply stopped folding, hovering and producing semantic tokens, and all 238 tests stayed green.Input:
Before: no fold, no hover, no token on the opener, no token on the caption line.
The control, which must not change:
That opener is not the composite-figure production at all. It parses to
admonitionand keeps every behavior it had.What changed
src/folding.tsfigure_groupjoinsFOLDABLEsrc/semantic.tstype, and the group caption is collected beside the childrensrc/hover.tssrc/completion.ts:::offersfigure, listed separately from the eight admonition kindssrc/composite-figure.test.tsTwo details are deliberate. The caption is collected on the GROUP rather than from any child, because it sits after the CLOSING fence - that placement is the one thing §4c adds to §4, and reading it from a child yields no token at all. And
figureis offered separately in completion, labelledComposite figure: folding it into the eight would say it is a ninth admonition, which is the confusion the clause exists to prevent.The dependency moves to a git pin
@markup-carve/carvewas pinned to the published0.1.3, which predates the node -figure_groupis not in its type union, so a case for it would not have compiled. It now tracks the carve-js commit that carries composite figures. Mid-development a git pin is the correct pin; it moves back to a version range at the next release.A codex pass caught the consequence, fixed in the second commit.
package.jsondeclares the pin asgit+https://, and the generated lockfile recordedgit+ssh://git@github.com/for the same dependency, because this machine's git config rewrites the HTTPS form to SSH viainsteadOf.npm cifollows the LOCKED url, so CI would have failed withPermission denied (publickey)on a public repository, before a line compiled, with nothing in the diff to suggest why. Rewritten to HTTPS - which is whattree-sitter-carve's lockfile carries for the same dependency - and verified by deletingnode_modulesentirely and installing from the lockfile.Tests
src/composite-figure.test.tsasserts the engine's shape first, so a dependency that stops producingfigure_groupsays that plainly instead of reporting four unrelated feature failures. The titled and labelled openers run as the control throughout: they differ from the bare one only in the tail of one line, so a reading that fires on the kind word alone would call them groups and nothing else would notice.