Create @refrakt-md/vue renderer with ADR-008 component interface
The Nuxt adapter currently renders all runes via renderToHtml() (identity transform only). To support Vue component overrides — where theme or site authors provide custom Vue components for specific runes — we need a @refrakt-md/vue renderer package that dispatches on typeof/data-rune and provides the ADR-008 framework-native interface (props + named slots).
Acceptance Criteria
- New
packages/vue/package exists with@refrakt-md/vuename - Exports a
Renderercomponent that recursively renders aRendererNodetree - Renderer dispatches on
typeofattribute to select registered component overrides (falls back to generic HTML element) - Uses
extractComponentInterfacefrom@refrakt-md/transformto partition children into properties, refs, and anonymous content - Property values passed as named Vue props to component overrides
- Top-level refs passed as Vue named slots
- Original
tagobject passed as prop alongside extracted props (hybrid per ADR-008 Option 4) - Exports a component registry mechanism (similar to
@refrakt-md/svelte's registry) - Exports an
elementsoverride mechanism for HTML element-level overrides (Table, Pre, etc.) - Nuxt adapter can use the Vue renderer instead of
renderToHtml()for pages with component overrides - TypeScript compiles cleanly
- Test suite covers extraction, dispatch, and fallback rendering
Approach
Mirror the architecture of @refrakt-md/svelte:
Renderer.vue— recursive component that walks the renderable treeregistry.ts—typeofname → Vue component mappingelements.ts— HTML element name → Vue component mapping (for Table, Pre, etc.)theme.ts—VueThemeinterface (manifest + layouts + components + elements)
Vue has native named slots, making it the most natural fit after Svelte. The renderer uses <component :is> for dynamic dispatch with v-bind for props and <template #name> for named slot content.
The extraction logic is already framework-agnostic (extractComponentInterface in @refrakt-md/transform). The Vue-specific work is:
- Converting extracted refs into named slot content via Vue's
<template #name>syntax - Passing scalar props via
v-bind - Recursive rendering with
<component :is>for dynamic dispatch
References
- ADR-008 — Framework-native component interface for rune overrides
- WORK-117 — Framework-agnostic extraction logic (done)
- WORK-119 — Svelte renderer extraction (done, reference implementation)
packages/svelte/src/Renderer.svelte— Svelte renderer (reference)packages/nuxt/— Nuxt adapter (consumer of this package)
Resolution
Completed: 2026-04-06
Branch: claude/implement-spec-030-F0LFn
What was done
- Created
packages/vue/with full ADR-008 component interface Renderer.ts— Vue 3defineComponentusingh()render function for recursive tree rendering- Component overrides dispatched via data-rune, receiving:
- Properties as named Vue props
- Named refs as Vue named slots (pre-rendered HTML via innerHTML)
- Anonymous children as default slot
- Original tag as escape-hatch prop
- Element overrides: Table (scrollable wrapper), Pre (rf-codeblock structure)
- Registry + elements + VueTheme type exports
- 20 tests using
@vue/server-rendererSSR rendering
Notes
- Used
defineComponent+h()render functions instead of.vueSFC to avoid needing vue-tsc/vite in the build - Named slots use Vue's native slot mechanism:
h(Component, props, { slotName: () => vnode }) - All 2022 tests pass (20 new)