Acceptance Criteria
renderNodes is exported from @refrakt-md/runes and adopted at the sites matching its exact shapebodyOnly() is exported and adopted by the runes whose content model is exactly that shape- Sites that wrap or vary the cursor construction (e.g.
unwrapParagraphImages(...), a pre-built node array) are left explicit rather than forced through the helper refrakt contracts --check and npm run seo:baseline:check report no driftnpm test passes unchanged
Approach
Purely a spelling change — no behaviour, no output. Adopt only where the shape matches exactly; the 72 cursor constructions that take a different argument (headerAstNodes, allItems, a spread) stay as they are. A helper that has to grow parameters to cover every caller is not worth having.
bodyOnly() returns a fresh object per call rather than a shared constant, so a rune that spreads and overrides it cannot mutate the preset for everyone else.
Resolution
Completed: 2026-10-06
Branch: claude/v0-37-post-release-plan-dc3va1
What was done
packages/runes/src/lib/index.ts: renderNodes(value, config) (wraps asNodes + Markdoc.transform + cast + cursor) and bodyOnly() (fresh body-only sequence model per call); both exported from the package index.- 78 cursor constructions of exactly that shape rewritten across core + plugin tag files (the item estimated 81); now-unused
asNodes / RenderableNodeCursor / RenderableTreeNode / Markdoc imports dropped. - 49 rune content models →
bodyOnly() — matches the item's count; seven were spelled 'sequence' as const or with greedy before optional. - Docs:
authoring-overview.md (hint walkthrough) and patterns.md show renderNodes / bodyOnly().
Notes
- Left explicit: cursor constructions over pre-built node arrays or wrapped inputs, and nested
sectionModel / zoneModel body-only copies (not a rune's content model). refrakt inspect --json over every rune × variant on both sites is byte-identical to main.