Add Astro native component override support with ADR-008 named slots
Astro is a special case per ADR-008: it is both an adapter (routing, build integration) and has its own component format with native named slots. Unlike Next.js and Nuxt which need separate renderer packages (@refrakt-md/react, @refrakt-md/vue), Astro component overrides can be handled directly in packages/astro/ using Astro's built-in <slot name="..."> mechanism.
For interactive runes rendered inside Astro islands, the island's framework renderer (React, Svelte, or Vue) handles extraction — that is covered by WORK-123, WORK-124, and WORK-119 respectively. This work item covers static .astro component overrides only.
Acceptance Criteria
packages/astro/supports registering.astrocomponent overrides keyed bytypeofname- Renderer dispatches on
typeofattribute to select registered.astrocomponent overrides (falls back to identity-transformed HTML) - Uses
extractComponentInterfacefrom@refrakt-md/transformto partition children - Property values passed as Astro props (
Astro.props.prepTime, etc.) - Top-level refs passed as Astro named slots (
<slot name="headline" />,<slot name="ingredients" />) - Original
tagobject passed as prop alongside extracted props (hybrid per ADR-008 Option 4) - Component registry mechanism added to
@refrakt-md/astro(e.g.,registry.ts) - Integration hook wires up the registry so components are available at render time
- TypeScript compiles cleanly
- Test suite covers extraction, dispatch, and fallback rendering
Approach
Unlike React/Vue renderers which are recursive components, the Astro renderer operates at build time during SSR. The approach is:
- Component registry — export a
registrymap oftypeofname →.astrocomponent, similar to@refrakt-md/svelte's registry - Render phase — when rendering a node with a
typeofthat matches a registered component, useextractComponentInterfaceto partition children, then render the.astrocomponent with props and named slots - Fallback — unregistered runes render via identity-transformed HTML as today
Astro's native named slots make this particularly clean:
---
const { prepTime, difficulty } = Astro.props;
---
<article class="my-recipe">
<slot name="headline" />
<slot name="ingredients" />
<slot name="steps" />
</article>
The main complexity is integrating the component dispatch into Astro's rendering pipeline, which differs from Svelte/React/Vue's recursive component model.
References
- ADR-008 — Framework-native component interface for rune overrides (see Astro special case in Consequences)
- WORK-117 — Framework-agnostic extraction logic (done)
- WORK-119 — Svelte renderer extraction (done, reference implementation)
- WORK-123 — React renderer (for island components)
- WORK-124 — Vue renderer (for island components)
packages/astro/— Astro adapter (this package gets the additions)
Resolution
Completed: 2026-04-06
Branch: claude/implement-spec-030-F0LFn
What was done
RfRenderer.astro— recursive Astro component that dispatches ondata-runeto registered component overrides- Component overrides receive extracted properties as Astro props, named refs as native Astro named slots (
<Fragment slot="name" set:html={html} />), anonymous children as default slot, and originaltagas escape-hatch prop - Element overrides:
Table.astro(scrollable wrapper) andPre.astro(rf-codeblock structure) registry.ts— ComponentRegistry type + empty default, exported from index.ts- Package.json exports for
./RfRenderer.astroand./elements/*.astro - Removed stale
themeAdapterreference from integration hook (per ADR-009) - 8 tests covering registry, extraction pipeline, and dispatch logic
Notes
.astrocomponents shipped as source (not compiled), matching the existingBaseLayout.astropattern- Actual Astro component rendering can't be tested in vitest — tests cover the TypeScript extraction pipeline that
RfRenderer.astrodepends on - For interactive runes in Astro islands, the island's framework renderer (React/Vue/Svelte) handles extraction per WORK-123/124/119
Relationships
Related
- ADR-008accepteddecisionFramework-native component interface for rune overrides
- WORK-123doneworkCreate @refrakt-md/react renderer with ADR-008 component interface
- WORK-124doneworkCreate @refrakt-md/vue renderer with ADR-008 component interface
- WORK-119doneworkUpdate Svelte renderer to pass props and snippets to component overrides
- WORK-117doneworkImplement framework-agnostic extraction logic for component interface
- ADR-009accepteddecisionFramework-agnostic theme packages