Work

Work Items

100 of 100 items
Draft 36
WORK-159 main
Unified RefraktConfig types, loader, and JSON Schema
Promote refrakt.config.json from a site-only file into the unified root config defined in ADR-010. Add type-level support for plugins, plan, site, and sites, normalize the three valid input shapes (flat / singular site / plural sites) into a canonical internal form, and publish a JSON Schema so editors can autocomplete and validate the file.
high moderate
0/10 criteria
WORK-160 main
Add discoverPlugins() helper to @refrakt-md/cli
Replace the lazy runPlugin import-on-demand pattern in packages/cli/src/bin.ts with a first-class discoverPlugins() helper that returns the full set of installed plugins. The helper has four consumers in v0.11.0 — CLI dispatch, refrakt --help, refrakt plugins list, and the new MCP server — and centralizing the logic eliminates three different implementations of the same scan.
high moderate
0/10 criteria
WORK-161 main
Extend CliPluginCommand with inputSchema/outputSchema/mcpHandler
Add three optional fields to the CliPluginCommand interface so plugin commands can declare structured input/output schemas and an MCP-friendly handler that bypasses argv parsing. Existing plugins keep working unchanged — the fields are purely additive.
high simple
0/5 criteria
WORK-162 main
Refactor runPlugin dispatch to use discoverPlugins
Switch the CLI's plugin dispatch in packages/cli/src/bin.ts from blind import('@refrakt-md/<namespace>/cli-plugin') to a discoverPlugins() lookup. This produces a friendlier "did you mean?" error when a namespace is misspelled (since we know the full set of installed plugins) and centralizes plugin loading on the new helper.
high moderate
0/5 criteria
WORK-166 main
Framework adapters accept site option
Update all five framework adapter packages — @refrakt-md/sveltekit, @refrakt-md/astro, @refrakt-md/nuxt, @refrakt-md/next, @refrakt-md/eleventy — to accept a site?: string option. Each adapter resolves its target site via the shared normalizer from WORK-159, then reads contentDir, theme, target, packages, routeRules, icons, backgrounds from the resolved site entry instead of the top-level config. Single-site projects and the legacy flat shape continue to work without changes; multi-site repos pick a target per adapter.
high moderate
0/12 criteria
WORK-167 main
Add inputSchema + mcpHandler to plan commands
Wire the @refrakt-md/plan CLI commands with the new schema fields from WORK-161 so the MCP server exposes them as cleanly typed tools. Each command gets a JSON Schema for its inputs and an mcpHandler that bypasses argv parsing and accepts a structured object directly.
high moderate
0/7 criteria
WORK-168 main
Plan package consumes unified config (init scaffolds, serve/build read paths)
Wire @refrakt-md/plan to the unified refrakt.config.json. plan init scaffolds the config (creating it or extending an existing one with a plan section), and plan serve / plan build / other plan commands read plan.dir from the loaded config instead of relying solely on the --dir flag and defaults. The plan section currently exposes only dir; specs path remains derived from dir (a child folder), and surfacing it as a config field can come later if real-world projects demand it.
high moderate
0/8 criteria
WORK-169 main
Scaffold @refrakt-md/mcp package with stdio entry, auto-detect, and core tools
Create the @refrakt-md/mcp package: stdio MCP server entry point, auto-detection of plan and site contexts from the unified config and the filesystem, and the initial set of core CLI-mirroring tools (refrakt.inspect, refrakt.contracts, refrakt.validate, refrakt.reference, refrakt.package_validate).
high complex
0/9 criteria
WORK-170 main
MCP plugin tools (via discovery) and resources
Wire plugin-contributed tools (notably the @refrakt-md/plan commands) into the MCP server via discoverPlugins(), and implement the read-only resources (refrakt://detect, refrakt://reference, refrakt://contracts, refrakt://rune/<name>, refrakt://plan/*) so agents that prefer pull semantics have first-class URIs.
high moderate
0/8 criteria
WORK-263 main
Collection rune: query engine and built-in layouts
The collection rune core — a sentinel-emitting schema plus a postProcess resolver that queries the registry (filter/sort/group/limit) and renders the built-in layouts, with the pinned $item bound-variable contract and the per-layout body interpretation.
high complex
0/7 criteria
WORK-268 main
entityRoutes config-rules adapter
The built-in adapter (in @refrakt-md/content) that turns site.entityRoutes into contributed pages: selects entities by type + filter, substitutes placeholders, renders an inline render string or a render-template partial per entity with $item bound, and back-fills sourceUrl.
high complex
0/6 criteria
WORK-270 main
Plan entity embeddability (sourceFile + extract)
Make plan entities embeddable so {% expand $item.id /%} renders their bodies in entityRoutes detail pages. The plan register hook currently sets sourceUrl only; add sourceFile and an extract that returns the entity rune's transformed body. This is the prerequisite the SPEC-071 dogfood depends on.
high moderate
0/4 criteria
WORK-272 main
refrakt plan site dogfood
Wire refrakt's own plan/ as a sites.plan site and author its plan-site/ content, proving that entityRoutes + collection replace the bespoke plan serve. This is the milestone's proof-of-practice.
high complex
0/4 criteria
WORK-163 main
refrakt --help lists installed plugins + new refrakt plugins list command
Surface the installed plugin set in two places: the top-level refrakt --help output (so users discover available namespaces without reading docs) and a dedicated refrakt plugins list command (canonical machine-readable output for tooling, including MCP clients debugging their setup).
medium simple
0/6 criteria
WORK-164 main
Add --site flag to site-scoped CLI commands
Site-scoped commands (inspect, contracts, validate, scaffold-css, package validate) need to know which site they are operating on when the project declares multiple. Add a --site <name> flag that selects an entry from the normalized sites map; for single-site projects the flag is optional and resolves to the lone entry.
medium moderate
0/7 criteria
WORK-172 main
Migrate this repo's site config to the unified root config
Move site/refrakt.config.json to a unified refrakt.config.json at the repo root, declaring plugins, the plan section, and sites.main (or whatever name we settle on) for the existing site. Validates the design against our own project and gives us a multi-site-ready structure for any future additions (separate plan dashboard site, blog, etc.).
medium simple
0/9 criteria
WORK-173 main
Update create-refrakt scaffolds for the unified config shape
create-refrakt currently writes a flat-shape site/refrakt.config.json for site projects and (via plan init) no config at all for plan projects. Update both paths to produce the new unified root-shape config — site projects get a sites.main section, plan projects get a plan section, and any combined scaffolds (future) declare both.
medium moderate
0/8 criteria
WORK-174 main
Site docs — configuration section
Document the unified refrakt.config.json at site/content/docs/configuration/. Cover the three valid shapes (flat / singular site / plural sites), each section (plugins, plan, site/sites), the multi-site workflow, and the migration story for existing flat-shape configs.
medium moderate
0/5 criteria
WORK-175 main
Site docs — MCP + plugin authoring update + CLAUDE.md pointer
Document the new @refrakt-md/mcp server (registration, tool reference, resource reference) at site/content/docs/mcp/, update the existing plugin authoring docs to cover the new cli-plugin schema fields (inputSchema, outputSchema, mcpHandler), and add a brief pointer in the root CLAUDE.md so AI agents know the MCP server exists when registered.
medium moderate
0/5 criteria
WORK-264 main
Collection heading-delimited table columns
The rich-table path: a table collection's body uses the sections content model (heading = column separator + label, body = per-cell markdoc template with $item), with collection owning the <table>/<thead> and row alignment.
medium moderate
0/4 criteria
WORK-271 main
create-refrakt plan-site scaffold
Add a plan project type to create-refrakt that scaffolds a complete, runnable plan site: config (plugin + entityRoutes), a seed plan/, and a plan-site/ content dir (layout + dashboards), for a chosen adapter target.
medium moderate
0/5 criteria
WORK-355 main
Generate theme scheme stylesheet from tokens.ts (retire hand-authored dark.css + tint.css palette copies)
The Lumina dark/scheme palette is hand-authored in three places that must be kept in lockstep by hand:
medium moderate
0/6 criteria
WORK-430 main
bg sandbox guest — docs + showcase
SPEC-104 §6 + docs: document the bg guest body, the bare-surface guardrail, the sandbox preset (with the refrakt.config.json example), and the boot-frame layering; ship the music-blog backdrop pattern as a working showcase exercising the layout-cascade + SPEC-006 audio-bridge composition.
medium moderate
0/4 criteria
WORK-441 main
Gallery loads the assembled theme config (not just base + plugins)
The static gallery (the visual-regression harness from WORK-409) transforms pages with merged.config — i.e. coreConfig plus the enabled plugins' configs — but never calls assembleThemeConfig. The site assembles core → plugins → theme so theme-level overrides reach the engine; the gallery does not, so it can only ever render base output.This is a latent gap in the regression net: any override a theme applies via mergeThemeConfig (tints, icon swaps, and — after WORK-425 — per-rune defaultElevation/defaultProminence deltas) is invisible in the gallery. The gallery shows what a plugin ships, not what a theme actually renders.Not a blocker for WORK-425. Under the agreed Option A, base surface defaults live in core + plugin configs, which the gallery already loads, so default data-elevation/data-prominence emission is gallery-verifiable today. This item only matters once a theme overrides those defaults (Lumina currently inherits the base defaults unchanged).
medium simple
0/4 criteria
WORK-605 main
playlist adopts field-level emitTag, keeping a minimal inheritance pass
With WORK-604 landed, playlist's tracks field can declare emitTag: 'track' and receive one homogeneous, document-ordered array instead of a mix of list nodes and track tags.That deletes the interleaving machinery at plugins/media/src/tags/playlist.ts:316-353 — the listCursor consuming tracksData by list length, and the defensive tail for "a shape the resolver collected but this loop did not recognise".It does not delete adoptNestedTrack. See below.
medium moderate
0/8 criteria
WORK-617 main
character, realm and faction drop their transforms for the slot declaration
The second proving set for SPEC-143: the storytelling entity runes whose sections arrive as already-rendered child runes (emitTag). Together with WORK-616 this covers both sides of the emitTag split, which is what proves the mechanism rather than one shape of it.Same gate (D7): no drift in contracts or the SEO baseline, per rune.recipe is the standing proof that the escape hatch is still needed (D3). It is not migrated, and it is not bent to fit.
medium moderate
0/6 criteria
WORK-165 main
Lint cli-plugin shape in refrakt package validate
Add a lint pass to refrakt package validate that checks a package's cli-plugin export for structural issues — missing namespace, missing descriptions, malformed inputSchema, namespace conflicts with already-installed plugins. Catches problems at package-publish time rather than at runtime when the MCP server tries to advertise the broken tool.
low simple
0/7 criteria
WORK-171 main
Add refrakt config migrate command
Add a small migration command that rewrites a flat-shape refrakt.config.json into the nested form (site.* and explicit plugins). Optional for users — the flat shape stays valid indefinitely — but useful for projects adopting the unified config or moving to multi-site.
low simple
0/8 criteria
WORK-266 main
article-card reference card rune
The first core plain-presentational card rune — ordinary attributes, no $item/registry knowledge — shipped as the reference implementation for collection body templates. Proves the "cards are plain runes; the template wires entity fields into attributes" model.
low simple
0/4 criteria
WORK-273 main
Deprecate plan build and serve commands
Mark plan build and plan serve deprecated, pointing users at the site approach, once refrakt's own plan site proves the replacement. Keep the authoring CLI. Removing the bespoke render stack is a later release, out of scope here.
low simple
0/3 criteria
WORK-334 main
Chart renderer provider selection and additional providers
Demand-driven follow-up to the WORK-333 seam: ship renderers beyond the built-in svg (e.g. a charting library) and the SSR capability, on the provider model resolved in SPEC-083.Status: draft — demand-driven (not blocked). The provider model is settled (single rf-chart delegating to an app-registered ChartProvider; selection author → site-default → svg; theme orthogonal via tokens). This item only makes sense once there's a concrete need for a second renderer — building one speculatively is the YAGNI trap SPEC-083 warns against.
low unknown
0/6 criteria
WORK-343 main
Schema-declared achieved status for open-world rollups
The deferred "option B" from the WORK-296 achievement-status discussion. Today plan-progress computes its achieved subset with a hardcoded union of terminal statuses (value="status:/^(done|fixed|accepted|complete)$/", decision C). That is correct and minimal, but it bakes domain knowledge into the plan sugar and relies on achieved-status name uniqueness across types — which holds for the five first-party plan types but is not guaranteed for a third-party "trackable" type that introduces its own status lifecycle.This item makes achievement self-declared on each rune's schema so aggregate can derive the achieved set generically, the same open-world way SPEC-084 handles composability (knowledge lives with the party that has it).
low moderate
0/4 criteria
WORK-354 main
Responsive per-cell bento spans
Deferred granular follow-on to the bento collapse model (WORK-348). Once grid-level collapse + automatic progressive reduction are in place, the remaining need is per-cell responsive control — letting an individual tile declare its span per breakpoint.
low moderate
0/3 criteria
WORK-500 main
Retirement-candidate pass over pending unbuilt runes
Once cancelled / superseded exist, review the plan corpus for items that should be retired rather than left dangling. The main pool is the 13 pending work items, all sourced from SPEC-008 (unbuilt runes): stat, math, concept, exercise, glossary, objective, prerequisite, quiz, partner, job, album, artist, video. A reviewed, per-item judgment pass — not an automated flip.
low simple
0/5 criteria
WORK-576 main
Reconsider the content package's public entry points before 1.0
loadContent takes fourteen positional parameters (packages/content/src/site.ts:697) and is exported from @refrakt-md/content. WORK-575 adds an options-bag overload beside it rather than a fifteenth positional, which is the right move for that change and deliberately not a redesign.This item is the redesign question, deferred to where it belongs: a public function's shape is much cheaper to change before 1.0 than after.
low moderate
0/4 criteria
WORK-629 main
Check the SEO baseline's types and properties against schema.org in CI
WORK-628 ran @adobe/structured-data-validator over contracts/seo-baseline/. Its rich-result layer was mostly noise for refrakt. Its schema.org layer was the opposite: zero false positives. It checks that a type exists and that every property is in that type's domain. Over the baseline's history it caught the NonProfit type and the nested Offer.offers over-match. On today's baseline it caught three defects nobody had recorded (BUG-033), on rows whose comments say nothing checks them.SPEC-145 D25 keeps the ban on user schema because this check is too narrow to stand in for a reviewer. It is still a cheap net under the reviewer for first-party rows. This item adds it to CI as a maintainer-side check. Nothing ships to users.
low simple
0/5 criteria
Ready 24
WORK-261 main
Shared field-match parser
Implement the canonical field:value selector grammar from SPEC-070 as a single shared module in @refrakt-md/runes, consumed by collection, entityRoutes (SPEC-069), and backlog. The existing plugins/plan/src/filter.ts folds into it, gaining glob/regex, url resolution, and case-consistency while preserving backlog behavior.
high moderate
0/7 criteria
WORK-262 main
Deferred-body capture for per-entity templates
Implement the deferBody mechanism SPEC-070's prototype validated: a catalog flag plus a pre-transform pass in the content loader that captures a rune's pristine body as a source string and empties it, so postProcess can re-parse and transform it per entity with a bound variable. Capture must happen before the page transform — by schema-transform time Markdoc has already resolved the body's $item interpolations to undefined.
high moderate
0/5 criteria
WORK-267 main
contributePages pipeline phase
Add the Plugin.contributePages hook and a contribution phase in the content loader (after file-page registration, before aggregation) that collects ContributedPage[] and runs them through the normal pipeline — the underlying primitive both the entityRoutes adapter and third-party plugins build on.
high complex
0/6 criteria
WORK-340 main
Token hygiene: phantom-token reconciliation + vocabulary cleanup
Lumina's CSS references several colour tokens that are never defined, so each paints a stale literal fallback (a sky-blue from an old default theme, or a cool Tailwind gray) that doesn't track light/dark. This is the real cause of the "out of place" blue (e.g. the typography specimen background) and the cold-gray muted text in dark mode. Fix the drift at the source, reconcile the vocabulary, and keep the configurable shapes (presets, generator, docs) in correspondence.
high moderate
0/9 criteria
WORK-392 main
Multi-site support in refrakt edit
refrakt edit cannot launch in a multi-site project: EditOptions is just port/contentDir/devServer/noOpen, and resolveSite() throws when multiple sites are declared, telling the user to pass --content-dir — which bypasses config resolution entirely, losing plugins, tints, icons, and route rules. Refrakt's own repo (sites main + plan) hits this. Add a --site flag and an in-editor site switcher so the editor works first-class against multi-site configs.
high moderate
0/5 criteria
WORK-393 main
Registry-aware block preview in the editor
In block mode, registry-resolved runes — collection, relationships, aggregate, xref, expand, and data-bound sandbox — render as unresolved sentinel shells with no explanation. They resolve in core postProcess against coreData.registry, which the client-side block renderer never runs; RUNTIME_ONLY_TYPES in block-renderer.ts only placeholders nav*. The server already holds the registry and resolvers (the full-page iframe preview uses them), so render these blocks server-side; where that's not possible, show an honest placeholder instead of an empty shell.
high moderate
0/5 criteria
WORK-265 main
Shared markdoc formatter functions
Register a small set of author-facing markdoc functions — currency, date, number, join — in @refrakt-md/runes as the shared value-formatting layer, usable anywhere markdoc transforms run: collection cells, body templates, and entityRoutes render strings. Keeps formatting out of fields and out of any bespoke projection DSL.
medium simple
0/4 criteria
WORK-269 main
embed() embeddability contract
Generalize SPEC-066's sourceFile + extract into the embed() contract: an entity is embeddable if it has embed() or (sourceFile + extract). expand and the entityRoutes adapter accept either, so external plugins can make entities embeddable without a source file on disk.
medium moderate
0/4 criteria
WORK-341 main
Dark-mode token parity audit
Make dark-mode token coverage deliberate. Most "dark looks off" reports trace to the phantom tokens fixed in WORK-340 (a phantom can't have a dark value); this item closes the genuine remaining gaps and records the intentional shared-token decisions.
medium simple
0/4 criteria
WORK-350 main
Rebuild index composability section as a bento showcase
Capstone + real-world validation. Replace the current split-feature composability section on the site index (a juxtapose of two sandboxes) with a richer bento grid where each cell is a different live rune composition — the section demonstrates composability by being a composition. It doubles as the hero example atop the new Compositions docs category (WORK-346) and as the dogfood proof that the bento substrate works end to end.Now that the v0.20.0 surface model has shipped, the showcase can flex the new surface vocabulary too — so the grid demonstrates both composition axes (a guest in a media zone, and the surface treatment around it).
medium moderate
0/7 criteria
WORK-351 main
Theme-aware global chrome: selection, scrollbars, color-scheme
Three pieces of browser chrome currently ignore the theme. Make them token-driven and mode-aware.
medium simple
0/4 criteria
WORK-352 main
Focus-visible and reduced-motion consistency
Accessibility polish across the theme. Today only ~5 of ~90 rune CSS files define :focus-visible, and only 2 honor prefers-reduced-motion — so keyboard focus is inconsistent and animations don't degrade for motion-sensitive users.
medium moderate
0/4 criteria
WORK-391 main
group-order attribute on collection, aggregate, relationships
Implements SPEC-095: an explicit group-order attribute so authors can sequence groups over a non-enum field (e.g. category, plugin, edge kind), which today render in arbitrary insertion order.
medium moderate
0/7 criteria
WORK-394 main
Close editHints coverage gaps across rune packages
editHints drive click-to-edit in the block editor; every rune without them is one where clicking a section silently does nothing. Coverage today is 93/130 runes (71.5%): business, design, docs, places, and storytelling are at 100%, but core is at 58% (35/60), plan at 45% (5/11), media at 60% (3/5), learning at 67% (2/3), and marketing at 80% (12/15). Close the gaps using the established SPEC-009 playbook (visible elements as refs/data-name, hints declared in the rune's config).
medium moderate
0/5 criteria
WORK-395 main
Surface pipeline warnings in the editor
The engine grew a rich soft-lint system (posture demotion, escape-hatch lints, nesting validation, entity-id collisions) but the editor actively swallows it: runPreviewPostProcess in packages/editor/src/preview.ts collects PipelineWarnings into an array and drops it, and hook errors hit a bare catch {} ("degrade silently"). Authors only discover problems at build time. Pipe warnings through to the UI as a validation rail.This is the warning channel that SPEC-098's inline lints build on.
medium simple
0/5 criteria
WORK-397 main
Relocate bento and bento-cell from marketing to core runes
Execute ADR-019: move bento + bento-cell from @refrakt-md/marketing to core (@refrakt-md/runes + @refrakt-md/lumina) as Layout primitives, with a back-compat re-export shim from marketing. Not a blocker for WORK-350; target a future minor.
medium moderate
0/6 criteria
WORK-439 main
Visual-regression compare-against-base CI job
Deferred from WORK-409 (its final acceptance criterion). The harness runs end-to-end locally and in the pinned Playwright container; what's left is the CI automation, which needs a browser-backed runner to develop against — hence its own item, unassigned to a milestone until a browser CI environment is in place.
medium moderate
0/3 criteria
WORK-573 main
Make error-severity pipeline diagnostics load-bearing
WORK-554 established, by planting one and observing, that an error-severity PipelineWarning causes nothing to happen anywhere:
medium moderate
0/6 criteria
WORK-583 main
Emit sequence ordinals as nodes, not CSS counters
A numbered sequence renders its ordinal through CSS:
medium moderate
0/8 criteria
WORK-585 main
Name the Steps item container, and normalise Playlist onto refs
Every rune that declares sequence: names the container holding its items — except one. Steps builds its <ol> at plugins/marketing/src/tags/steps.ts:166 and pushes it straight into children two lines later, so the element carries no data-name, gets no BEM element class, and cannot be addressed from a layout tree.
medium trivial
0/6 criteria
WORK-586 main
Resolve routeRules layout names and entityRoutes types in the config layer
WORK-578 built refrakt validate's config layer and deliberately left two of its four checks partial. This finishes them.
medium moderate
0/7 criteria
WORK-607 main
plan migrate ids treats an ID present on the base ref as fixed
runMigrateIds sorts colliding claimants by path and keeps the first (plugins/plan/src/commands/migrate-ids.ts:165), so on a branch it can renumber the entity already on main and leave the local draft holding the ID.The safety argument in the code — that it only acts when nothing references the ID — rests on a check that reads plan files only (migrate-ids.ts:170-182). Plan IDs are referenced from four published CHANGELOGs (runes 130, transform 120, lumina 80, content 51), where a renumber is permanent and invisible.
medium simple
0/6 criteria
WORK-332 main
Remove the legacy data-field engine machinery (Tier 2)
Internal-only cleanup that lands the rest of WORK-323 once WORK-331 has stopped emitting the dual-emit metas. Remove the now-dead legacy read/strip path so data-rune-fields is the engine's only field-data input. No external/contract benefit beyond Tier 1 — this is for a single internal representation and a smaller engine.
low moderate
0/4 criteria
WORK-574 main
Declare the four retype-and-wrap collections as lists (D6)
accordion's mainEntity, how-to's step and tool, and recipe's recipeIngredient and recipeInstructions are collections that still serialise as a bare object when they hold one item and an array when they hold two.Every other collection in the catalog has been declared: pricing's offers, playlist's track / hasPart, breadcrumb's and timeline's itemListElement. These five are the remainder.
low trivial
0/5 criteria
In Progress 1
WORK-584 main
Settle the three couplings ADR-029 assumes
ADR-029 records three couplings as open questions rather than asserting them. Each is answerable by reading code and running existing tooling; none needs a design. This item answers all three and writes the answers back into the ADR, so the implementing spec is built on measurements rather than expectations.It is a research item. The deliverable is three findings, not a code change.
high simple
1/5 criteria
Review 0
Done · 366 archived 39
WORK-595 main
Edge coverage — the documents frontmatter field and prose path extraction
WORK-594 establishes the index against the one edge class whose extraction is already trustworthy, and finds almost nothing — because embedded source reaches 28 of this repository's 237 content pages. A page can be entirely about a subsystem without ever naming a file in it.This item is the three classes that between them reach the pages that matter.
high moderate
16/16 criteria
WORK-596 main
The refrakt_stale MCP tool and the touching query
This is the phase where the feature stops being retrospective. It is placed after WORK-595 only because an impact lookup that knows about snippet invocations but not about the guides is a lookup that misses the pages most worth updating.The CLI answers "what is stalest across the corpus?" — a survey, run occasionally. An agent editing code has a different and better-timed question: "I am about to change these files. What documents them?"
high moderate
9/9 criteria
WORK-598 main
Drop inert metas from rune children arrays
100 metas across 24 tag files are mapped in properties and emitted into children, where createComponentRenderable filters them straight back out (packages/runes/src/lib/component.ts:127-131). 133 other metas already omit that emission and render identically.Delete the emission. Nothing else.
high simple
5/5 criteria
WORK-599 main
Remove the unreachable schema.org channel from createComponentRenderable
schema, typeof and schemaOrgType on TransformResult / InlineTransformResult have zero callers across all 121 runes. SPEC-130 moved every rune to the declarative table on createContentModelSchema({ schema }); the imperative surface it replaced was never deleted.So schemaTags (component.ts:57-61) is always empty, isSeoMeta (:84) is always false, emptySeoMetas (:110-125) is always empty, and roughly 40 lines of the helper are unreachable.
high simple
6/6 criteria
WORK-604 main
Pass authored tags through a mixed list|tag:x field with emitTag
resolveSequence's emit branch skips anything that is not a list, then replaces the field with only the emitted nodes (packages/runes/src/lib/resolver.ts:194):
high simple
5/5 criteria
WORK-609 main
Stop findChildren retyping author-nested runes of a colliding name
SPEC-146 Problem 1, the present-tense half. findChildren (packages/runes/src/lib/schema-table.ts) matches a schema children key against data-rune as well as data-name / data-field, descends the whole subtree and applies every match. So playlist's children: { track } retypes an author's own {% track %} nested in an unrelated container as one of the playlist's tracks, and because the child row is applied with the author's node as root, the row's own properties resolve inside it. The SPEC records the result in the JSON-LD graph, not only in RDFa: an unrelated recording published as a track.The colliding rows are a closed set: playlist's musicRow/spokenRow and breadcrumb's breadcrumb-item carry their own properties and reach the graph; recipe/howto (step), pricing (tier), timeline and accordion carry text/generated only and mis-stamp RDFa.
high moderate
9/9 criteria
WORK-611 main
Add the registers declaration and its core registration hook
SPEC-144's mechanism. A rune declares that it registers a named entity or an edge, and one core pipeline participant performs the registration in the existing Phase 2 (register) / Phase 3 (aggregate) slots. The registry, the relationship graph and the query runes (collection, relationships, aggregate) are unchanged. This only gives them a declarative way in.
high complex
9/9 criteria
WORK-614 main
Add the slot declaration and generate a rune's transform from it
SPEC-143's mechanism, without migrating any rune. That happens in WORK-616 and WORK-617.createContentModelSchema gains an emits declaration carrying the renderable's identity (rune, tag, property), its properties in SPEC-140's data form (fieldMetas, which already takes no function values), and its slots:
high complex
16/16 criteria
WORK-618 main
Resolve preprocessors in one tree-order walk
SPEC-141's main change. Replace the three whole-AST passes (walkAndReplaceIncludes, walkAndReplaceSnippets, walkAndReplaceData) and their hand-written order in config.ts with one walk that dispatches per tag, and move each preprocessor into the tag module that owns it:
high complex
12/12 criteria
WORK-619 main
The identity guard is path-granular
Implement SPEC-158. IDENTITY_FIELDS (packages/transform/src/identity-fields.ts) becomes a list of paths, and findReservedFields resolves a wildcard segment. This adds three protections:
high simple
10/10 criteria
WORK-620 main
A rune config may omit block
SPEC-145 D2a. A composed rune has no BEM block and ships no CSS, but it still needs an engine config: its modifiers, universal attributes and meta blocks have to render. Today RuneConfig.block is required. The engine builds ${prefix}-${config.block} unconditionally (packages/transform/src/engine.ts), so a config without one emits rf-undefined. A rune with no config at all leaks data-rune-fields into the HTML.Make block optional:
high moderate
4/4 criteria
WORK-621 main
data-owner and data-slot survive a primitive's transform
SPEC-145 D10a, landed ahead of composition so it can be proved inert.Markdoc keeps only the attributes a node's schema declares. A marker set on a node before a primitive transforms it is therefore dropped, unless every node and tag schema declares it. Declare data-owner and data-slot once, at config assembly, on every node and tag schema, never per rune. Teach the schema-table resolvers (packages/runes/src/lib/schema-table.ts) to admit a node past a boundary by data-owner plus data-slot. A primitive's own data-name on the same node is left alone.In the output, releaseOwnedNodes strips data-owner and keeps data-slot (D10a step 4, D10c).Nothing sets either attribute yet, so the gate is that nothing moves. The survival test runs the markers through every rune in D12's placement set. That set is "no peer schema, no requiresParent". Any rune that drops either attribute is a finding, named by the test.
high moderate
5/5 criteria
WORK-622 main
A rune can be defined by a composition template
The mechanism at the centre of SPEC-145. A definition is frontmatter (the input declaration: tag, attributes, content model, schema, registers) plus a Markdoc body (the output template). Slot names are the join between them.This item builds the definition → PluginRune path and the template's render. It covers only the vocabulary the two slices need:
high complex
17/17 criteria
WORK-623 main
A composition definition is checked at construction
Every rejection SPEC-145 decides at definition load or schema construction, in one place, so that a bad definition fails with its rune and the offending name rather than rendering silently wrong. Between them, D11 and D26 match every content-model field to exactly one slot.The bulk of this item is the error messages. Each check names the rune and the thing it rejected, and the tests assert the message as well as the throw.
high moderate
11/11 criteria
WORK-624 main
Slice 1 — bond as a composed rune
The first composed rune: SPEC-145's small worked example, the ~17-line definition that replaces plugins/storytelling/src/tags/bond.ts. The slice uses:
high moderate
5/5 criteria
WORK-625 main
Slice 2 — character as a composed rune, against the SEO baseline
SPEC-145's full worked example and its spike criterion. It is the first composed rune with a schema row: Person, which a first-party definition may declare under D25. It is also the first that uses sections with each and $each.heading, and a preamble with an image slot.character is the case WORK-617 was cancelled for. Its transform failed SPEC-143's D4 family test, so it is replaced rather than declared. The composed version drops the character-section child rune entirely.Like WORK-624, it ships beside the plugin (SPEC-147 D1). The reference is the storytelling character fixture in contracts/seo-baseline/. The gate is that every difference from it is explained, not that there are none (SPEC-147 D2). Two differences are already decided:
high complex
8/8 criteria
WORK-630 main
A composition template places a declared meta block with {% metablock %}
SPEC-145 D7. A composed rune has no layout, and the engine renders a declared blocks entry only through layout. So a composed rune can declare metaFields and blocks in full and get nothing, with no diagnostic.WORK-622 landed the generated config carrying both but left its "meta blocks render" criterion unchecked for this reason (#683). character's blocks.metadata (role and status as a definition list) needs it for WORK-625. Pulled into v0.40.0 on 2026-10-08 rather than dropping the block from the composed character. Placing a badge or deflist rune instead is the channel SPEC-145's authoring note rules out for attribute values.
high moderate
11/11 criteria
WORK-631 main
Content no content-model field matches is reported, not silently dropped
Found by WORK-625, the composed character. A {% hint %} an author wrote before the first section was matched by no preamble field and dropped, with no error and no warning. The composed definition works around it with a catch-all body preamble field. SPEC-145 records the finding beside the character worked example.D11 promises against this silent loss but checks the other direction. It requires that every declared field is placed by a slot. Nothing checks that every authored node is matched by a field. So content can still vanish between the author's file and the content model, by another route.This applies to any content model, not just compositions. A tree-owning rune with a narrow preamble loses unmatched content the same way, so the fix belongs in the content-model resolver, not in the composition layer. First measure how many shipped runes drop content today, so the change can be staged as a warning before an error if needed.
high moderate
5/5 criteria
WORK-633 main
A plugin ships composed runes from a declared rune directory
SPEC-153 implementation note 3, the plugin side. In v0.40.0 a composed definition reached the pipeline only as a string passed to defineComposedRune. A plugin now declares runeDir (package-relative, resolved the way fileRoots resolves, D2). Every <rune>.md in it is loaded as a composed rune.Decisions:
high complex
8/8 criteria
WORK-634 main
A project defines its own composed runes in runes.dir
SPEC-153 implementation note 4, the project side, and the step that opens composition to users. A project declares runes.dir in refrakt.config.json, following contentDir's shape, with a recommended default of runes (D4). The loader discovers every <rune>.md in it.Decisions:
high complex
7/7 criteria
WORK-637 main
The authoring guide for composed runes
Composition ships to users in this milestone, and SPEC-153 D9 makes the definition file the user-facing authoring surface. Today the only documentation is a PluginRune note in rune-authoring/authoring-overview.md. Add a page under site/content/extend/rune-authoring/ written from SPEC-145's "template vocabulary, in one place" table. The guide is the spine of that table rather than a summary of it.It covers:
high moderate
6/6 criteria
WORK-638 main
Plan runes keep the content their content models drop, then content-unmatched becomes an error
WORK-631 added content-unmatched: validation reports an authored node that no content-model field matches, because the transform drops it. It shipped as a warning because the measurement found that four shipped runes drop real content today, all in the plan plugin. The plan site renders each entity through its rune ({% expand %}), so the loss reaches plan.refrakt.md.Measured over this repo's plan/ directory (907 files), as of WORK-631:
high moderate
5/5 criteria
WORK-600 main
Drop redundant undefined guards in refs and properties literals
createComponentRenderable opens both slot loops with if (v === undefined) continue (component.ts:77 and :98), so inside refs and properties:
medium trivial
4/4 criteria
WORK-601 main
Consolidate extractText and adopt the exported textContent
Two families of duplication, with different fixes.AST side — six byte-identical copies. extractText(node: Node) is defined identically in packages/runes/src/tags/form.ts, plugins/places/src/tags/map.ts, plugins/design/src/tags/{palette,typography,spacing}.ts and plugins/marketing/src/tags/comparison.ts.Renderable side — textContent is already exported and reimplemented anyway. @refrakt-md/runes exports it (seo.ts:23), and plugins/plan/src/pipeline.ts + plugins/storytelling/src/pipeline.ts define extractTextContent, while plugins/media/src/tags/track.ts defines a Tag-flavoured extractText.
medium simple
4/4 criteria
WORK-602 main
Add renderNodes and the bodyOnly content-model preset
Two spellings, each repeated past the point of being idiom.renderNodes(nodes, config) → RenderableNodeCursor. The full form appears 153 times, 81 of them as exactly new RenderableNodeCursor(Markdoc.transform(asNodes(resolved.X), config) as RenderableTreeNode[]). There are 160 as RenderableTreeNode[] casts in the tag files.bodyOnly() — { type: 'sequence', fields: [{ name: 'body', match: 'any', optional: true, greedy: true }] } is the entire content model for 49 of the 94 declared in the tag files, in four whitespace variants.
medium simple
5/5 criteria
WORK-603 main
Add fieldMetas and groupByHeading
The two utilities with actual design in them, as opposed to the spellings in WORK-602.fieldMetas(attrs, spec) collapses the declare-then-name-again cycle into one declaration, returning a Record<string, Tag> usable directly as properties. plugins/plan/src/tags/work.ts goes from 36 lines of plumbing to about 13:
medium moderate
10/11 criteria
WORK-606 main
Project itemModel and render emitTag and template in the rune reference
For a rune whose primary syntax is a Markdown list, refrakt reference documents that a list is accepted and nothing about what goes in it. Three facts are lost at two points:
medium moderate
6/7 criteria
WORK-608 main
Promote the three styles transform helpers into the named-transform enum
RuneConfig.styles[…].transform is one of only two function-typed fields in the whole of RuneConfig (packages/transform/src/types.ts:301; the other is postTransform, which stays imperative by design). Unlike postTransform, it does not need to be a function: a declarative sibling already ships, and the function form is used by a closed set of three helpers.
medium simple
8/8 criteria
WORK-610 main
Let findAllByName resolve owner-marked nodes across a rune boundary
SPEC-146 Problem 2. findAllByName stops dead at any nested data-rune, which is right for every rune today and wrong the moment content is placed inside a primitive on another rune's behalf (SPEC-145). Only node-sourced values are affected: properties from a node, text, and the node half of entities. Attribute-sourced values resolve through the field bag and are untouched by nesting.
medium simple
7/7 criteria
WORK-612 main
Migrate storytelling's entity and bond registration onto registers
The motivating case for SPEC-144. plugins/storytelling/src/pipeline.ts registers character, realm, faction, lore and plot through a generic walk and two field-list switch statements (extractEntityName, extractEntityData), expands character aliases in aggregate, and registers bond as an edge. All of that becomes a registers block on each rune.postProcess stays exactly as it is. Declaring sentinel resolution is out of scope for this milestone (see WORK-611).
medium moderate
6/6 criteria
WORK-615 main
Remove snippet's standalone figure wrapper
SPEC-141 D5/D6, shipped first and on its own. snippet runs in two phases: preprocess, and a postProcess pass, wrapStandaloneSnippets in packages/runes/src/config.ts, which wraps a standalone <pre> in <figure class="rf-snippet">. The wrapper adds no information: data-source and data-lines are already on the <pre>, and .rf-snippet's CSS only resets the margins the figure itself introduced.Removing it makes snippet single-phase, so WORK-618 moves one concern into the tag module rather than half of one.Breaking output change. Downstream CSS or tooling selecting .rf-snippet or [data-source-path] must move to pre[data-source]. Authors who want chrome compose it: {% codegroup title="…" %}, or {% figure %} once BUG-028 lands in this milestone.
medium simple
7/7 criteria
WORK-616 main
work, bug and decision drop their transforms for the slot declaration
The first proving set for SPEC-143: the plan entity runes whose sections arrive as resolved entries (no emitTag). After SPEC-081 they build no structure; what is left is properties, title → header, description → blurb (omitted when empty), sections → body. That is exactly what the slot declaration expresses.
medium moderate
5/5 criteria
WORK-626 main
Every plugin package exports its manifest and publishes what it declares
SPEC-153 implementation note 1: the packaging prerequisites everything else in that spec rests on. All three failures are invisible in the monorepo, so this step must not be assumed. Measured in SPEC-153:
medium simple
5/5 criteria
WORK-627 main
An npm pack → install → load harness for plugins
SPEC-153 implementation note 2. It is the only gate that catches the measured breakages, and nothing like it exists today. A monorepo test resolves packages through workspace links, so it passes whatever the tarball contains.The harness packs a plugin, installs the tarball into a temporary fixture project outside the workspace, and loads it through the same loadPlugin() path a site uses. The first target is the plugins as they ship today. That proves the harness on known-good input, and proves WORK-626's fix against the tarball rather than the workspace. Shipping composed runes in a tarball is a later SPEC-153 step that reuses this harness.
medium moderate
4/4 criteria
WORK-628 main
Spike: @adobe/structured-data-validator over the SEO baseline
SPEC-145 D25 bans schema in user composition definitions until a mechanical check exists, and names the candidate:
medium simple
3/3 criteria
WORK-632 main
A by-attribute schema table's rows must cover the attribute's matches
SPEC-145 D27 (c). validateSchemaTable (packages/runes/src/lib/schema-table.ts) checks two things: that by names a declared attribute, and that a fallback exists. It never compares rows keys with that attribute's matches. So a misspelt row (podcasts:) or a newly added enum value silently selects the fallback, and {% playlist type="podcast" %} would publish MusicAlbum.D15 already requires an exact two-way correspondence for template variants. The schema by gets the same check. It needs no composition, and it applies to every hand-written table today (playlist, track, organization and others).
medium simple
5/5 criteria
WORK-635 main
lore as a composed rune
SPEC-147 lists lore as the simplest storytelling rune. It has a title, a body, an Article schema row (title → headline, category → articleSection) and a registered entity. It follows the pattern WORK-624 and WORK-625 set:
medium simple
5/5 criteria
WORK-636 main
realm and faction as composed runes
SPEC-147: realm (Place) and faction (Organization) share character's shape, sections plus a preamble, and each has a -section child rune that composition removes. Their media split maps to {% mediatext %}. Both follow WORK-625's pattern:
medium moderate
7/7 criteria
WORK-613 main
Migrate design's registration onto registers
The second reachable plugin in SPEC-144's reach table. plugins/design/src/pipeline.ts (92 lines) has the same shape as storytelling's, only smaller. Move its registration onto registers declarations and delete the hooks.Under the same D4 gate as WORK-612: a registry snapshot taken before, asserted after, with order included.
low simple
3/3 criteria