Scope
- Add a
layout attribute to feature (plugins/marketing/src/tags/feature.ts), matching grid | list imported from the canonical const (WORK-466) — no bespoke columns value. - Resolve the default in the transform, not via a config variant: compute
effectiveLayout = attrs.layout ?? derive(mediaPosition) (stacked top/bottom → grid, beside start/end → list) and emit it as a single layout meta. Author override beats the derived default; exactly one data-layout lands on the element. layout is always emitted (both grid and list are styled) — no default-suppression guard (unlike width).- This item only introduces the axis + emission; retiring the old
media-position coupling and moving the CSS onto [data-layout] is a separate, dependent work item.
Acceptance Criteria
feature accepts layout matching grid | list, both imported from the canonical const.- The engine emits a single
data-layout from the resolved layout meta. - When
layout is unset it derives from media-position (stacked → grid, beside → list); an explicit layout overrides that default. - Item arrangement and media placement are independently controllable (media beside content and
layout="grid" is reachable).
Dependencies
- WORK-466 — imports
grid/list from the canonical const.
References
- Spec: SPEC-099 §1 (
layout axis), §4 (transform-level default resolution). plugins/marketing/src/tags/feature.ts, packages/runes/src/tags/common.ts (SplitLayoutModel, buildLayoutMetas).
Resolution
Completed: 2026-06-25
Branch: claude/spec-099-feature-layout-axis
What was done
plugins/marketing/src/tags/feature.ts — added the layout attribute (grid|list via layoutMatches([LAYOUT.grid, LAYOUT.list])); resolve effectiveLayout = attrs.layout ?? derive(media-position) (stacked→grid, beside→list) in the transform and emit a single always-present layout meta.plugins/marketing/src/config.ts — layout modifier (source: 'meta', noBemClass: true) so the meta maps to data-layout.plugins/marketing/test/feature.test.ts — 4 tests: default grid, media-derived list, explicit override (both directions), always-present.
Notes
- Author override and the media-derived default never collide because exactly one value is computed in the transform. Output unchanged for existing content; the CSS move + coupling removal is WORK-468.