Skip to content

feat: composite figures are a container the server knows - #82

Merged
dereuromark merged 2 commits into
mainfrom
feat/composite-figures-composite-sat-6436a4ec
Aug 15, 2026
Merged

feat: composite figures are a container the server knows#82
dereuromark merged 2 commits into
mainfrom
feat/composite-figures-composite-sat-6436a4ec

Conversation

@dereuromark

Copy link
Copy Markdown
Contributor

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 ::: figure fence parses to a figure_group node. 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 producing semantic tokens, and all 238 tests stayed green.

Input:

::: figure
![one](a.png)
^ (a) One
:::
^ Figure #: Group caption

Before: no fold, no hover, no token on the opener, no token on the caption line.

The control, which must not change:

::: figure "A titled figure div"
![one](a.png)
^ (a) One
:::

That opener is not the composite-figure production at all. It parses to admonition and keeps every behavior it had.

What changed

File Change
src/folding.ts figure_group joins FOLDABLE
src/semantic.ts the opener's reserved kind word is scoped type, and the group caption is collected beside the children
src/hover.ts its own description, naming what separates it from the container it resembles
src/completion.ts ::: offers figure, listed separately from the eight admonition kinds
src/composite-figure.test.ts new

Two 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 figure is offered separately in completion, labelled Composite 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/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. 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.json declares the pin as git+https://, and the generated lockfile recorded git+ssh://git@github.com/ for the same dependency, because this machine's git config rewrites the HTTPS form to SSH via insteadOf. npm ci follows the LOCKED url, so CI would have failed with Permission denied (publickey) on a public repository, before a line compiled, with nothing in the diff to suggest why. Rewritten to HTTPS - which is what tree-sitter-carve's lockfile carries for the same dependency - and verified by deleting node_modules entirely and installing from the lockfile.

Tests

src/composite-figure.test.ts asserts the engine's shape first, so a dependency that stops producing figure_group says 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.

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.
@dereuromark
dereuromark merged commit 9efaca0 into main Aug 15, 2026
1 check passed
@dereuromark
dereuromark deleted the feat/composite-figures-composite-sat-6436a4ec branch August 15, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant