Acceptance Criteria
RuneConfig.block is optional in packages/transform/src/types.ts, and no config without a block emits rf-undefined, asserted on a fixture config- A block-less config's root carries no
rf-* class and no data-rune-fields; its modifiers render as data-* attributes and its universal attributes and meta blocks render as they would with a block refrakt contracts describes a block-less entry by its data-* modifiers, with no BEM selectorsnpm run seo:baseline:check and refrakt contracts --check report no drift on either contract copy
Resolution
Completed: 2026-10-08
Branch: claude/v040-blockless-config PR: refrakt-md/refrakt#678
What was done
packages/transform/src/types.ts: RuneConfig.block is optional, and its doc comment explains block-less configs.
packages/transform/src/engine.ts:
block is empty when the config has none. Facet modifier classes and BEM element classes are skipped, and an empty class attribute is omitted. Every other step still runs.- Diagnostics (
requiresParent, interactive guest, cover sandbox) name the rune via data-rune (D2a point 5).
packages/transform/src/contracts.ts: RuneContract.block is optional. The new generateBlocklessContract emits:
root: [data-rune="…"];data-* modifiers with no classPattern;- inline styles, universal axes without selectors,
childOrder and variants.
It emits no elements, context modifiers or static modifiers.
validate.ts and the CLI plugin-validate.ts: block is optional, but an empty one is still an error.
Guards for the other block consumers: used-css.ts, i18n-extract.ts, CLI inspect.ts (single audit, full audit, dimension audit), scaffold-css.ts, meta-audit.ts, format.ts, editor deriveChildRunes, the Lumina css-coverage and prominence-reach tests, and the create-refrakt scaffolded coverage test.
Tests: the new packages/transform/test/blockless-config.test.ts compares the same fixture config with and without a block. validate.test.ts was updated.
Docs: site/content/extend/theme-authoring/config-api.md.
Changeset: minor for @refrakt-md/transform and @refrakt-md/cli.
Notes
- Contracts (both copies) and the SEO baseline show no drift, as the milestone table requires.
- The spec held: the unconditional class build was at
engine.ts:318. contextModifiers and staticModifiers are class-only, so they are inert on a block-less config. This matches D2a point 3: theming goes through the primitives' own context modifiers.- The editor's
deriveChildRunes names child runes by block. A block-less child rune is left out of that set. It is not reachable today. - Slot names in block-less contracts are deferred to WORK-622, per the work item.