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

4.2 KiB

spec, blocked-by
spec blocked-by
core-parser 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

  • WikilinkNode and TransclusionNode share the { target, anchor?, display? } shape and are distinct node types
  • Wikilinks parse with a bare target, with an anchor, and with a display alias; the anchor is passed through verbatim
  • Transclusions parse with a display alias and with a Step Reference anchor (#section:N and #N) passed through as-is
  • render shows wikilinks distinctly (display text, or target when absent)
  • render shows transclusions as their raw source text
  • Core unit tests cover wikilink (bare, anchor, display) and transclusion (bare, display, Step Reference anchor)
  • 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.

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.