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