feat: parse and render Obsidian cross-references (task 0005)
Model `[[wikilinks]]` and `![[transclusions]]` as distinct inline node
types sharing a `{ target, anchor?, display? }` shape, and render them:
wikilinks underlined (display text or target), transclusions as their
raw source text.
remark-wiki-link is added to the pipeline for `[[…]]`. It does not
recognise `![[…]]` embeds or split the `#anchor` from the target, so
transclusions are recovered by scanning text runs and anchors are split
in the translation layer. Its alias divider is set to `|` so Step
Reference anchors like `#rolling:2` survive.
This commit is contained in:
63
.claude/tasks/0005-cross-references.md
Normal file
63
.claude/tasks/0005-cross-references.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
spec: core-parser
|
||||
blocked-by: 0004-richer-blocks-and-inline
|
||||
---
|
||||
|
||||
## What to build
|
||||
|
||||
Add Obsidian cross-references so `[[links]]` and `![[embeds]]` — including KitchenMD Step References — are typed nodes the parser surfaces and the renderer styles.
|
||||
|
||||
In `@kitchen-md/core`, model `WikilinkNode` and `TransclusionNode`, both `{ target, anchor?, display? }`: `target` is the filename without extension, `anchor` is the part after `#` passed through verbatim, `display` is the alias after `|`.
|
||||
The node type — not a boolean flag — discriminates a reference (`[[…]]`) from an embed (`![[…]]`).
|
||||
`TransclusionNode` covers KitchenMD Step References (`![[file#section:N]]`, `![[file#N]]`), whose anchor is passed through as-is; Step Reference resolution is out of scope.
|
||||
Add remark-wiki-link to the pipeline.
|
||||
|
||||
In `@kitchen-md/bin`, render wikilinks distinctly from surrounding prose (underline or distinct colour), using display text or the target when there is no display text.
|
||||
Render transclusions as their raw source text (e.g. `![[file#section:1]]`).
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] `WikilinkNode` and `TransclusionNode` share the `{ target, anchor?, display? }` shape and are distinct node types
|
||||
- [x] Wikilinks parse with a bare target, with an anchor, and with a display alias; the anchor is passed through verbatim
|
||||
- [x] Transclusions parse with a display alias and with a Step Reference anchor (`#section:N` and `#N`) passed through as-is
|
||||
- [x] `render` shows wikilinks distinctly (display text, or target when absent)
|
||||
- [x] `render` shows transclusions as their raw source text
|
||||
- [x] Core unit tests cover wikilink (bare, anchor, display) and transclusion (bare, display, Step Reference anchor)
|
||||
- [x] Renderer unit tests (ANSI stripped) cover wikilink and transclusion rendering
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
All acceptance criteria are met, with no dropped or deferred criteria.
|
||||
The following decisions are worth recording.
|
||||
|
||||
### remark-wiki-link handles only `[[…]]`, so transclusions are recovered separately
|
||||
|
||||
The installed `remark-wiki-link@2.0.1` (landakram) parses `[[…]]` into `wikiLink` mdast nodes but does not recognise `![[…]]` embeds.
|
||||
The leading `!` makes remark attempt an image, which fails and leaves the whole span as literal text.
|
||||
Transclusions are therefore recovered in the translation layer by scanning each text run for `!\[\[…]]` and splitting it into text and `TransclusionNode` parts.
|
||||
This keeps the plugin in the pipeline as the spec asks while still surfacing transclusions as typed nodes.
|
||||
|
||||
### Anchors are split in the translation layer, not by the plugin
|
||||
|
||||
remark-wiki-link leaves the `#anchor` attached to the node's `value` and does not separate it.
|
||||
A shared `splitAnchor` helper splits `target#anchor` at the first `#` for both node types, so the anchor is passed through verbatim and the key is omitted entirely when absent.
|
||||
|
||||
### The alias divider is set to `|`
|
||||
|
||||
The plugin defaults its alias divider to `:`, which would consume a Step Reference anchor such as `#rolling:2`.
|
||||
It is configured with `aliasDivider: "|"` so that only a real Obsidian alias is split off, and a display alias is recorded only when it differs from the node's value.
|
||||
|
||||
### Discriminants mirror the interface names
|
||||
|
||||
Following task 0004's precedent for `codeSpan`, the discriminants are `"wikilink"` and `"transclusion"` — the interface names lowercased — since neither construct has an mdast counterpart to borrow a type name from.
|
||||
|
||||
### Optional keys are omitted when absent
|
||||
|
||||
Consistent with task 0004, `anchor` and `display` are spread in only when present rather than set to `undefined`, so parser output deep-equals the expected node shape without stray keys.
|
||||
|
||||
### Rendering follows task 0003/0004 conventions
|
||||
|
||||
Wikilinks render underlined, showing the display text or the target when there is no display, matching how links render (both are references).
|
||||
When a wikilink has an anchor but no display, only the target text shows — this follows the spec's wording ("display text, or the target when there is no display text") exactly.
|
||||
Transclusions render as their reconstructed raw source (`![[target#anchor|display]]`), unstyled, since resolving the embed is out of scope.
|
||||
The renderer tests assert ANSI-stripped text plus the presence of styling, never particular colours.
|
||||
Reference in New Issue
Block a user