This repository has been archived on 2026-07-29. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
kitchen-md/.claude/tasks/0005-cross-references.md
alexion b0fdc22165 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.
2026-07-29 20:05:35 -04:00

64 lines
4.2 KiB
Markdown

---
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.