Support the figure_group node: outline, panel crossrefs, new lint codes - #83
Merged
Conversation
…tline Two halves of #79, and the first is wider than the ticket reads. CROSSREFS. The ticket asks for panel ids to resolve as the group's number plus a letter. Measuring first showed panels are not the gap - they are one case of it. Every crossref feature walked HEADINGS only, so `</#fig>` naming a plain captioned figure, a construct that predates composite figures entirely, offered no completion, jumped nowhere, and hovered as a heading because no case existed for the reference and the lexical fallback matched the `#` inside it. Implementing the panel rule on top of that would have been building the second floor. `captions.ts` collects the captioned hosts a `</#id>` can name - figure, table, figure_group, and a group's panels - and completion, definition, references and hover all read it. The number is the engine's own resolved `caption_number` rather than a re-derived sequence: PART 9R R5 lives in the engine and a second copy would drift the first time a label sequence changed. Only the panel LETTER is derived here, because no engine API exposes it, and the tests pin it against the anchor text `carveToHtml` writes for the same id - so a change to the letter scheme upstream fails here rather than diverging quietly in an editor. Panels are a group's DIRECT figure and table children, so stray content between them takes no letter and does not shift the letters around it. An unnumbered group registers anchors without crossref text for itself or its panels, matching what the engine renders and what §4c states. OUTLINE. A composite figure is a container an author folds and navigates, so it earns an outline entry the way a heading does: named by its caption, with its panel count as the detail and its panels nested under it. It takes no heading level, so it hangs under the section it appears in and never closes one. A panel with no caption of its own falls back to the letter a crossref would use for it, rather than to a number it does not have. The five §4c lint codes needed no server work - the engine emits them and the server already published them verbatim - but nothing pinned that, so a passthrough that stopped would have been silent. Each now has a test, with a well-formed group as the control.
…answer at the declaration Two findings from the review pass, both real. An unnumbered host is no longer offered for `</#`. Its id is a real anchor and a `[text](#id)` fragment link reaches it, but a CROSSREF to it renders as literal text (PART 9 §4c), so offering it was the one entry in a list of working references that quietly did not work. The "Unnumbered figure" detail string goes with it rather than staying as a branch nothing can reach. Find-references now answers from the DECLARATION as well as from a usage, which is what this module documents for every other family. A heading declares its id on its own line and asking there already worked; a captioned host declares it on the block-attribute line above itself, which sits outside the host's span and so is matched by adjacency rather than by containment. The control is an ordinary line inside the group, which declares nothing.
…ght answer
The declaration lookup tested the line above a host against
`/^\s*\{.*\}\s*$/` before accepting it. That reads like a safety check and is
not one: a host appears in this list only because a block-attribute line gave
it an id, so the line above it IS that line by construction. The pattern
therefore cannot reject a wrong answer, and it does reject a right one -
`> {#fig}` inside a block quote does not match an anchored brace, so asking
for references there answered nothing.
The weak control went with it. "An ordinary line declares nothing" pointed at
a line adjacent to no host at all, so it passed whether the guard was there
or not. Its replacements are the quoted declaration, which the guard broke,
and a host with no id, which is what the lookup actually keys on.
…tion The declaration lookup is matched by LINE rather than by a construct under the cursor, so it answers for every column on that line. Running it inside the heading family put it ahead of the link-reference, citation and wikilink families: a host line carrying a reference image (`![alt][img]`) answered the FIGURE's references for a cursor sitting on `img`, which is a regression on behavior that predates this branch. It moves to the end of the chain, where a family matched by line belongs, and the reason is written where the function is so the ordering is not restored by accident. The control is that same document, asked on the label and then on a column of the line nothing else claims.
Filtering panels out of the group's traversal removed more than the panel entries: a panel can wrap a quote holding headings, and the walk descended through the panel to find them before the group had an entry at all. Those headings vanished from document symbols and from the included-file symbol list with them. A panel still yields no outline entry of its own - it rides on the group's symbol - but the walk descends through it again. The control is a heading inside a quoted panel, which lands under the section the group sits in.
Reusing the heading usage scan for a captioned host reported every `[text][]`
whose slug matched the id. That fallback is heading-only (PART 9R R1): beside
a figure `{#foo}`, `[foo][]` renders as literal text, so the server was
offering a usage the author cannot follow and did not write.
The lookup now says WHAT declares an id, not only where, and the collapsed
scan runs for a heading declaration alone. The other two usage forms - the
crossref and the fragment link - reach both kinds and are unchanged. The
control is the same spelling against a heading, where it still counts.
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.
Closes #79.
What was already done
Re-measured against
mainat 9efaca0 before planning, because part of thisticket had landed. #82 already added folding, the semantic-token case scoping
the opener's kind word and collecting the group caption, the hover description,
and
figurein:::completion - plus the engine pin that unblocked theticket. None of that is repeated here.
The diagnostics bullet also needed no implementation. The engine emits all
five §4c codes and the server passed them through verbatim already; nothing
pinned that, so a passthrough that stopped working would have been silent. Each
of the five now has a test, with a well-formed group as the control.
The schema ingest bullet has no work in this repo: there is no
resources/ast-schema.jsonhere and nothing validates wire payloads. The onlyhits are inside the
tests/specsubmodule, which is the spec repo's own copy.The premise that moved, and what it changed
The ticket reads as though panel crossrefs are the gap. They are not the gap;
they are one case of it. The server resolved no caption crossref at all -
not a group's, not a panel's, and not the plain captioned figure's that predates
composite figures entirely. Measured on the pinned build before any change:
definitionAton that</#fig>returnednull;</#completion offerednothing for it; find-references answered nothing; and hovering it reported
Heading with the
#-through-######blurb, because no case existed for thereference and the lexical fallback matched the
#inside it. The engineresolves it fine -
carveToHtmlwrites<a href="#fig">Figure 1</a>- so thiswas a server gap, not a dependency one.
That changed the shape of the work rather than its size: implementing "a panel
id resolves as the group's number plus a letter" on a server that resolves no
caption ids would have been building the second floor. This PR makes crossrefs
reach captioned hosts generally, with the group and panel rules from §4c as the
cases that motivated it. That measurement is recorded on the ticket alongside
the plan.
What this adds
src/captions.tscollects the captioned hosts a</#id>can name - figure,table,
figure_group, and a group's panels - each with its id, its hostposition, and the text the reference resolves to. Completion, definition,
references and hover all read it.
The NUMBER is the engine's own resolved
caption_number, not a re-derivedsequence: PART 9R R5 lives in the engine, and a second copy would drift the
first time a label sequence changed. Only the panel LETTER is derived here
(panel order among panels, a..z then aa), because no engine API exposes it - and
the tests pin it against the anchor text
carveToHtmlwrites for the same id,so a change to the letter scheme upstream fails here rather than diverging
quietly in an editor.
Panels are a group's DIRECT
figureandtablechildren, so stray contentbetween them takes no letter and does not shift the letters around it. An
unnumbered group registers anchors without crossref text for itself or its
panels, matching what the engine renders and what §4c states - and completion
leaves such an id out rather than offering a reference that renders as literal
text.
The outline gains the group: named by its caption, with its panel count as
the detail and its panels nested under it. It takes no heading level, so it
hangs under the section it appears in and never closes one. A panel with no
caption of its own falls back to the letter a crossref would use for it. A
panel yields no entry itself, but the walk still descends through it, so a
heading inside a quoted panel stays in the outline where it was.
Review findings
codex review --base origin/mainran after each step. Four findings, allverified against the code and all real:
real anchor and a
[text](#id)fragment link reaches it, but a crossref toit renders as literal text - so it was the one entry in a list of working
references that quietly did not work. Filtered out; the "Unnumbered figure"
detail string went with it rather than staying as a branch nothing reaches.
half of what the module documents for every other family. Added - and while
adding it, the shape test I first put on the declaration line
(
/^\s*\{.*\}\s*$/) turned out to be a check that could only reject a rightanswer: a host has an id only because a block-attribute line gave it one, so
the line above it IS that line, and the pattern only rejected spellings of it
such as
> {#fig}inside a block quote. Removed, with the weak control thatpassed either way replaced by two that do not.
family put it ahead of link references: a host line carrying
![alt][img]answered the FIGURE's references for a cursor on
img. It moved to the endof the chain, where a line-matched family belongs.
[text][]whose slug matched acaption id. That collapsed fallback is heading-only (PART 9R R1) -
[foo][]beside a figure
{#foo}renders as literal text - so the scan is now scopedto a heading declaration, with the same spelling against a heading as the
control.
The fifth pass came back clean.