Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
114 commits
Select commit Hold shift + click to select a range
e782ae5
feat(blocks-engine): scaffold package with exact-pinned deps + versio…
borkweb Jun 22, 2026
a8819d8
test(blocks-engine): freeze DLA golden fixture contract
borkweb Jun 22, 2026
628baf9
test(blocks-engine): capture golden fixtures from current DLA output
borkweb Jun 22, 2026
87ad859
test(blocks-engine): freeze /wp public contract
borkweb Jun 22, 2026
32cb41e
feat(blocks-engine): port WP-backed /wp operations
borkweb Jun 22, 2026
f6a6a5f
test(blocks-engine): freeze worker pool contract
borkweb Jun 23, 2026
e706b2b
feat(blocks-engine): add forked worker pool
borkweb Jun 23, 2026
817a342
fix(blocks-engine): resolve in-flight batches to sentinels on stop()
borkweb Jun 23, 2026
fa9a305
test(blocks-engine): freeze compose contract
borkweb Jun 23, 2026
215c38c
feat(blocks-engine): port pure helper modules
borkweb Jun 23, 2026
3744d10
feat(blocks-engine): extract sanitize helper
borkweb Jun 23, 2026
fb848b9
feat(blocks-engine): port catalog recipe table seam
borkweb Jun 23, 2026
9bf16bd
feat(blocks-engine): add compose precedence
borkweb Jun 23, 2026
9435e2f
feat(blocks-engine): share raw convertible unwrap list
borkweb Jun 23, 2026
1a86bc8
fix(blocks-engine): avoid domhandler type dependency
borkweb Jun 23, 2026
8ead5ff
feat(blocks-engine): wire convert and default entry
borkweb Jun 23, 2026
e17dc02
fix(blocks-engine): restore injection scan+throw in default htmlFallback
borkweb Jun 23, 2026
e3e8cda
fix(blocks-engine): resolve worker-child path in the bundled dist layout
borkweb Jun 23, 2026
29d81c6
chore(blocks-engine): consume as tsx source (keep tsup for publish) +…
borkweb Jun 23, 2026
785d47a
refactor(blocks-engine): plain re-export barrel entry (drop runtime t…
borkweb Jun 23, 2026
2a55e34
feat(blocks-engine): export island constants + scanForInjection + ser…
borkweb Jun 23, 2026
8784f67
feat(blocks-engine): port block-markup validation unit (validateBlock…
borkweb Jun 23, 2026
c362110
fix(blocks-engine): restore trust-source posture in ported validateBl…
borkweb Jun 23, 2026
5f94c63
feat(blocks-engine): freeze async convert contract
borkweb Jun 24, 2026
7d67d24
feat(blocks-engine): wire async main convert
borkweb Jun 24, 2026
ece767b
feat(blocks-engine): freeze internals export contract
borkweb Jun 24, 2026
e62a5d6
feat(blocks-engine): curate public barrel exports
borkweb Jun 24, 2026
966410c
feat(blocks-engine): freeze cli package contract
borkweb Jun 24, 2026
5cb6cce
feat(blocks-engine): add one-shot cli
borkweb Jun 24, 2026
051842a
docs(blocks-engine): freeze example contracts
borkweb Jun 24, 2026
ddafd5f
docs(blocks-engine): add package usage guide
borkweb Jun 24, 2026
37fb647
feat(theme): freeze site-to-theme contracts
borkweb Jun 24, 2026
bf3db75
feat(theme): add site-to-theme stub exports
borkweb Jun 24, 2026
f2c5467
test(theme): freeze P0-2 ingest contracts
borkweb Jun 24, 2026
20992fe
feat(theme): implement browser-free P0-2 extraction
borkweb Jun 24, 2026
14711fe
test(theme): freeze P0-3 site-to-theme paths
borkweb Jun 24, 2026
86a8cd6
feat(theme): implement P0-3 site-to-theme skeleton
borkweb Jun 24, 2026
2182ea5
feat(theme): freeze P1-1 asset contracts
borkweb Jun 24, 2026
7a12649
feat(theme): implement P1-1 asset stage
borkweb Jun 24, 2026
c085c42
test(theme): freeze P1-2 font network contract
borkweb Jun 24, 2026
6905035
feat(theme): self-host network fonts via fetchImpl
borkweb Jun 24, 2026
36ed439
Wire site-to-theme assets stage
borkweb Jun 24, 2026
90378e1
Fix honest site-to-theme asset rewrite
borkweb Jun 24, 2026
2b193db
Freeze chrome split contract
borkweb Jun 24, 2026
0fb8c21
Implement browser-free chrome split
borkweb Jun 24, 2026
61fbeae
Freeze chrome signature contract
borkweb Jun 24, 2026
53cadd1
Implement chrome signature variants
borkweb Jun 24, 2026
6db7e5d
Freeze chrome parts contract
borkweb Jun 24, 2026
c74e1bf
Implement chrome part stage
borkweb Jun 24, 2026
9f6a629
Wire chrome parts into site theme assembly
borkweb Jun 24, 2026
e62b305
Freeze template plan contract
borkweb Jun 24, 2026
f1e33c2
Implement front page template plan
borkweb Jun 24, 2026
3426a1e
Freeze section coverage contract
borkweb Jun 24, 2026
a6c0829
Implement section coverage measurement
borkweb Jun 24, 2026
9c0f7dc
Freeze coverage island contract
borkweb Jun 24, 2026
fdc18a5
Implement coverage island fallback
borkweb Jun 24, 2026
b4b7479
Wire section coverage fallback
borkweb Jun 24, 2026
14210c4
Export chrome signature parity helpers
borkweb Jun 24, 2026
668afea
Add converted section coverage parity
borkweb Jun 24, 2026
c04f2e6
Freeze html fallback parity surface
borkweb Jun 24, 2026
1f10947
Add html fallback parity helpers
borkweb Jun 24, 2026
046c980
Freeze source assets parity surface
borkweb Jun 24, 2026
e19eb58
Add source assets parity tests
borkweb Jun 24, 2026
1ad09b9
Add deterministic font helper parity
borkweb Jun 25, 2026
109de7e
Freeze segmentPage parity surface
borkweb Jun 25, 2026
1f21e59
Add segmentPage parity surface
borkweb Jun 25, 2026
f417466
Freeze chrome parts builder parity surface
borkweb Jun 25, 2026
7fea7ff
Add chrome parts builder parity surface
borkweb Jun 25, 2026
f3593d9
chore(blocks-engine): freeze page reconstruct helper surface
borkweb Jun 25, 2026
bb06168
Add page reconstruct helper parity surface
borkweb Jun 25, 2026
fd76e2c
feat(blocks-engine): align reconstruct converted gate with DLA
borkweb Jun 25, 2026
e83b769
chore(blocks-engine): freeze form blocks emitter surface
borkweb Jun 25, 2026
2858202
feat(blocks-engine): port DLA Jetpack form emitter
borkweb Jun 25, 2026
e8b4acf
chore(blocks-engine): freeze fallback diagnostic surface
borkweb Jun 25, 2026
90d8329
feat(blocks-engine): port DLA fallback diagnostics
borkweb Jun 25, 2026
99256f8
fix(theme): preserve layout offset wrapper class
borkweb Jun 25, 2026
b567e12
fix(theme): keep main-sibling layout rail past content-landmark gate
borkweb Jun 25, 2026
825a676
feat(theme): accommodate the WP admin bar for logged-in viewers
borkweb Jun 25, 2026
1972f90
feat(theme): freeze source CSS carry contract
borkweb Jun 25, 2026
203668b
feat(theme): carry source css by default
borkweb Jun 25, 2026
bf0ef6c
docs(theme): map page reconstruct lift
borkweb Jun 25, 2026
0139f9f
feat(theme): freeze native reconstruct contracts
borkweb Jun 25, 2026
0692235
feat(theme): freeze native low-level helper surface
borkweb Jun 25, 2026
bfbe622
feat(theme): port native low-level helpers
borkweb Jun 25, 2026
9d0973c
feat(theme): freeze native text renderer surface
borkweb Jun 25, 2026
f9f8861
feat(theme): port native text renderers
borkweb Jun 25, 2026
542ebf4
feat(theme): freeze native section renderer surface
borkweb Jun 25, 2026
413d96b
feat(theme): port native section renderers
borkweb Jun 25, 2026
0fa8291
feat(theme): freeze native renderer dispatcher surface
borkweb Jun 25, 2026
85ea74e
feat(theme): port native renderer dispatcher
borkweb Jun 25, 2026
5a8720b
feat(theme): freeze native reconstruct contract
borkweb Jun 26, 2026
86f39fd
feat(theme): wire native reconstruct decisions
borkweb Jun 26, 2026
683d81e
feat(theme): freeze site render options surface
borkweb Jun 26, 2026
cb0c8f5
feat(theme): thread site render options into reconstruct
borkweb Jun 26, 2026
bad8ec9
feat(theme): freeze variation hoist surface
borkweb Jun 26, 2026
f5b9194
feat(theme): hoist recurring block style variations
borkweb Jun 26, 2026
591fd1f
test(theme): freeze region audit diagnostic surface
borkweb Jun 26, 2026
85b17f0
feat(theme): add region audit diagnostics
borkweb Jun 26, 2026
d48fa05
feat(cli): default to whole-site theme builds
borkweb Jun 26, 2026
3091407
feat(cli): default theme output to ./_block-theme
borkweb Jun 26, 2026
b8e7e3d
fix(cli): harden path errors
borkweb Jun 26, 2026
d40cba7
chore: Add ADR docs, move .gitignore, add CONTEXT.md
borkweb Jun 26, 2026
5eafd20
Merge remote-tracking branch 'origin/trunk' into feature/site-to-theme
borkweb Jun 26, 2026
2fa557b
feat(theme): export reconstructNativeAggregate harness entry
borkweb Jun 26, 2026
5b1fddf
feat(theme): freeze reconstruct strategy contracts
borkweb Jun 26, 2026
b7351b0
test(theme): freeze strategy seam default output
borkweb Jun 26, 2026
57f71df
feat(theme): add reconstruct section strategy seam
borkweb Jun 26, 2026
592447d
feat(theme): add preserve DOM strategy core
borkweb Jun 26, 2026
85e2682
test(theme): prove preserve DOM lib-i parity
borkweb Jun 26, 2026
9b395f4
docs(blocks-engine): restructure README with Install and Quick Start
borkweb Jun 29, 2026
c6d1859
build(blocks-engine): prepare package for public npm publish
borkweb Jun 29, 2026
11e0b88
build(blocks-engine): add per-package release tooling
borkweb Jun 29, 2026
5c867a1
feat(blocks-engine): polish developer experience from DX review
borkweb Jun 29, 2026
067772c
ci(blocks-engine): add typecheck step to the JS workflow
borkweb Jun 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
name: Bug report
about: Report a problem with a package in this repository
title: ''
labels: bug
assignees: ''
---

**Package and version**
Which package (e.g. `@automattic/blocks-engine`) and what version?

**What happened**
A clear description of the bug, including any `BlocksEngineError` `code` and `hint` you saw.

**Reproduction**
Minimal steps or code/CLI command that triggers the problem. Include sample input if relevant.

```
# command or code
```

**Expected behavior**
What you expected to happen instead.

**Environment**
- Node version:
- OS:
- Install method (npm/npx/pnpm):
48 changes: 48 additions & 0 deletions .github/workflows/js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: JS Transformer

on:
pull_request:
paths:
- ".github/workflows/js.yml"
- "packages/blocks-engine/**"
push:
branches:
- trunk
paths:
- ".github/workflows/js.yml"
- "packages/blocks-engine/**"

jobs:
validate:
name: Typecheck, build, and test
runs-on: ubuntu-latest

defaults:
run:
working-directory: packages/blocks-engine

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "20"

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Typecheck
run: pnpm typecheck

- name: Build
run: pnpm build

- name: Run tests
run: pnpm test
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.claude/worktrees/
.superpowers/
dist/
docs/superpowers/
node_modules/
47 changes: 47 additions & 0 deletions CONTEXT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# blocks-engine (JS engine)

The deterministic engine for WordPress blocks, shipped as one pluggable package (`@automattic/blocks-engine`) with a React-free default entry and an opt-in in-process `/wp` entry. It owns two generic, consumer-agnostic capabilities — **HTML→blocks translation** and **deterministic theme assembly** (static directory → block theme) — and exposes extension seams; consumers (the first being the data-liberation-agent static-site importer) plug their own choices, including non-deterministic quality steps, into those seams. Sibling to the existing `php-transformer`, with which it does **not** target output parity.

(Identity note: the engine's scope deliberately expanded from "translation only" to "translation + theme assembly" — see ADR 0004, which supersedes the translation-only framing of ADR 0001.)

## Language

**Engine**:
The translation + theme-assembly core plus its extension seams. Owns generic logic; knows nothing about any one consumer.
_Avoid_: importer (that's the consumer)

**Theme assembly / Assembler**:
The engine's multi-**stage** process that turns a static site directory into a block theme on disk (ingest → foundation → section-extract → reconstruct → chrome → assets → plan → assemble). Run by the `siteToTheme` convenience command or by composing the stages directly.
_Avoid_: pipeline (reserved for the **consumer's** end-to-end run, e.g. DLA's). The engine has an *assembler*, the consumer has a *pipeline*.

**Stage**:
One isolated unit of the assembler with a frozen input→output contract (e.g. `foundation`, `sectionExtract`, `assemble`). Stages are public and composable; `siteToTheme` chains them.

**Hook**:
An optional async seam at a named stage (`onFoundation`/`onSection`/`onAssets`/`onRefine`) where a consumer injects a **non-deterministic** quality step (visual polish, asset triage, repair). Absent hook = deterministic identity (byte-identical to no hook).
_Avoid_: plugin, middleware.

**SectionSpec**:
The engine's shared contract describing one visual section (structure + style facts) consumed by reconstruction. Produced deterministically two ways: the engine's browser-free **cheerio** `sectionExtract` (best-effort, from static HTML), or **injected** by a consumer that captured richer computed-style specs (e.g. DLA's Playwright `extractFull`). Injected via the `sections` data input — NOT a hook (it is data, not a non-deterministic step).

**ThemeModel**:
The pure in-memory result of assembly (`styleCss`, `themeJson`, `templates`, `parts`, `patterns`, `assets`). Materialized to disk by `writeTheme`. Keeps the assembler snapshot-testable without disk.

**Converter**:
A pluggable unit (`(html, context) → block markup | null`) the engine applies to translate an HTML fragment. Built-ins ship with the engine; consumers may supply their own.
_Avoid_: recipe (a consumer's word for its platform-specific converters)

**ConversionContext**:
The context object threaded through conversion (e.g. source URL, media-URL map). Carries only generic fields — never consumer-specific state.
_Avoid_: BlockRecipeContext (the old consumer-coupled name)

**Fallback block**:
The block the engine emits for a fragment it can't convert. Defaults to `core/html`; a consumer overrides it via `htmlFallback` (a block name, or an emitter function).
_Avoid_: island (a consumer's term for its own fallback shape)

**Canonicalize**:
Normalizing block markup through `@wordpress/blocks` so WordPress's parser/validator accepts it on the way in. One of the two `@wordpress/blocks`-backed operations (with the rawHandler conversion); both are process-isolated because they pull in React.
_Avoid_: fix, normalize (use "canonicalize")

**Transformer**:
The PHP sibling package (`php-transformer`) — a separate engine with its own `TransformerResult` contract. Referenced only to say the JS engine does not mirror it.
34 changes: 34 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Contributing

This repository is a monorepo of WordPress block tooling. Each package is developed and released independently:

- `packages/blocks-engine` — `@automattic/blocks-engine` (JS/TS engine + CLI)
- `php-transformer` — `automattic/blocks-engine-php-transformer` (Composer)
- `figma-transformer`

## Reporting bugs

Open an issue using the bug-report template. Include the package affected, the version, a minimal reproduction, and what you expected versus what happened.

## Developing `@automattic/blocks-engine`

```sh
cd packages/blocks-engine
pnpm install # install dependencies (uses the committed lockfile)
pnpm build # build dist via tsup
pnpm test # run the vitest suite
```

The worker pool forks Node child processes, so a development environment must support `child_process` fork + IPC. When running tests or tools against the built output, run `pnpm build` first so the worker child file is present in `dist`.

## Pull requests

- Branch off `trunk`.
- Keep changes scoped to one package per PR where practical; CI runs per-package on paths under that package.
- Use Conventional Commits (`feat`, `fix`, `docs`, `build`, `test`, `refactor`, `chore`) with a package scope, e.g. `fix(blocks-engine): …`.
- Add or update tests for behavior changes. The suite must pass before review.
- Public API and `BlocksEngineError` `code` values are a stable contract — call out any change to them in the PR description and the package `CHANGELOG.md`.

## Releasing

Releases are tagged per package (`blocks-engine-v*`, `php-transformer-v*`, `figma-transformer-v*`). See the package's own release notes/tooling for the current process.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ Blocks Engine is a collection of tools for generating, transforming, and materia

## Packages

- [`@automattic/blocks-engine`](packages/blocks-engine/) - JavaScript primitives for transforming content into WordPress-native block outputs.
- [`php-transformer`](php-transformer/) - PHP primitives for converting HTML, Markdown, and generated website artifacts into WordPress-native block outputs.
- [`figma-transformer`](figma-transformer/) - PHP primitives for converting Figma `.fig` archives and Figma-derived scenegraphs into static HTML website artifacts with parity diagnostics.
15 changes: 15 additions & 0 deletions docs/adr/0001-engine-owns-translation-and-composition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# blocks-engine owns HTML→blocks translation and composition behind a pluggable extension API

blocks-engine owns the generic work of translating HTML into WordPress blocks — the `@wordpress/blocks` rawHandler core, the block-markup canonicalizer, the deterministic converter catalog, **and** the precedence that composes converters in order. Consumer-specific choices are not baked in; they are supplied through three extension seams: a `ConversionContext`, an ordered list of consumer **Converters** (functions or declarative `selector→block` recipe tables), and an `htmlFallback` override (default `core/html`). The data-liberation-agent is the first consumer but won't be the only one, so the boundary is: *generic translation logic + composition + extension seams → engine; the specific things plugged into those seams → consumer.*

## Considered Options

- **Engine = low-level primitives only; each consumer composes.** Rejected: duplicates the generic catalog and the recipe-table mechanism in every consumer, leaving "the hard work" outside the shared engine.
- **Engine = generic conversion only; consumers own precedence/composition.** Rejected: a second consumer would have to bring its own recipe-table engine and precedence logic; the reusable extensible surface stays trapped in the first consumer.

## Consequences

- The first consumer's types (`AdapterBlocks` / `BlockRecipe` / `BlockRecipeContext`) become, or alias, the engine's `Converter` / recipe-rule / `ConversionContext` types; the consumer passes its platform recipes as **data**.
- "Behavior-preserving / byte-identical" constrains engine **output**, not interface — the type and precedence relocation is deliberate; golden fixtures pin the emitted markup.
- The `htmlFallback` seam must be threaded through the engine's **internal recursion**, not just applied at the outer boundary — otherwise nested fallbacks lose the consumer's chosen block.
- The consumer's fallback marker (DLA's `PIPELINE_ISLAND_OPENER`) and its install-time gate (`validateReplicaInputs`) stay in the consumer, supplied via `htmlFallback`.
20 changes: 20 additions & 0 deletions docs/adr/0002-composable-operations-and-worker-pool.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Engine API surface: composable operations + a worker pool

The engine exposes three **composable operations** rather than one monolithic `convert()`:

- `compose(html, ctx, { converters, htmlFallback })` — **pure, synchronous**, React-free. Runs consumer converters (functions or recipe tables) → built-in generic catalog → `htmlFallback`. No `@wordpress/blocks`.
- `rawConvert(html)` and `canonicalize(markup)` — **async**, backed by a package-managed **pool** of `fork()`ed worker processes (default `min(cores, 4)`, configurable, dies with the parent). These load `@wordpress/blocks`, isolated in the pool.

A convenience `convert()` chains `rawConvert → compose → canonicalize` for consumers who just want "HTML in, blocks out."

(Packaging of these operations into one package with entry points is ADR 0003.)

## Considered Options

- **One monolithic `convert()` owning rawHandler internally.** Rejected: it would force consumer **function** converters (e.g. DLA's imperative Squarespace walk) through the worker IPC boundary, where functions can't be serialized — breaking function-converters under isolation. It would also force the consumer's currently-synchronous composition path to become async.
- **Single forked child instead of a pool.** Rejected: throughput regression versus the current `cluster(min(cores, 4))` on whole-site runs, which is exactly the workload the parallelism exists for.

## Consequences

- Consumer **function** converters run in the pure, in-process `compose`; only HTML/markup **strings** cross the worker boundary. Serialization is never an issue.
- The first consumer (DLA) deletes its bespoke HTTP server + cluster client and consumes the package's worker pool; it keeps wiring the three ops at its own pipeline stages.
16 changes: 16 additions & 0 deletions docs/adr/0003-single-package-with-entry-points.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Single package with entry points (not a pure/wp package split)

Ship the engine as **one** package — `@automattic/blocks-engine` — with entry points rather than two packages:

- **default** (`.`) — React-free: `compose` + helpers + `createWorker()` (the pool client). Never loads `@wordpress/blocks` in the importing process.
- **`/wp`** — opt-in, **in-process** sync `rawConvert` / `canonicalize` / `convert` that load `@wordpress/blocks` in the current process; this is also what the forked worker child runs.

## Considered Options

- **Two packages (`@automattic/blocks-engine` pure + `@automattic/blocks-engine-wp`).** Rejected. The React isolation consumers need comes from the worker **process**, not a package boundary, so the split doesn't earn it. Its only real benefit — letting a helpers-only consumer skip the `@wordpress/*` + jsdom install — serves a largely hypothetical consumer: the package exists to do HTML→blocks, which needs rawHandler, and the cheerio-based pure code isn't browser-ready, so there's no edge/browser story either. The split also puts the headline name on the weaker half (the pure layer alone is a best-effort converter, not the engine).

## Consequences

- One version, one publish pipeline; no cross-package version coupling.
- React isolation is a property of the worker `fork()`, reachable from the single package's default entry — the importing process never loads React 18.
- A consumer with no React conflict can `import … from '@automattic/blocks-engine/wp'` for a synchronous in-process engine.
43 changes: 43 additions & 0 deletions docs/adr/0004-engine-owns-deterministic-theme-assembly.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Engine identity expands to deterministic theme assembly

The engine's scope expands from **HTML→blocks translation only** to **translation +
deterministic theme assembly**: a static site directory → a block theme on disk
(`theme.json`, templates, parts, patterns, `style.css`, carried/rewritten assets),
exposed as a `siteToTheme` convenience command over composable, public **stages**.

This supersedes the framing in ADR 0001 / `CONTEXT.md` that treated a
pipeline/importer as exclusively the **consumer's** job and the engine as a pure
translator. The engine still knows nothing about any one consumer; "theme assembly"
is generic, consumer-agnostic work — but it IS more than translation.

Non-deterministic quality (visual-parity polish, decorative-asset triage, repair) is
explicitly NOT engine code. It enters through optional async **hooks**
(`onFoundation`/`onSection`/`onAssets`/`onRefine`); absent hook = deterministic
identity. The engine owns the deterministic skeleton; consumers inject judgment.

## Considered Options

- **Keep the engine translation-only; theme assembly stays in the consumer (DLA).**
Rejected: the deterministic theme-assembly logic is generic (not DLA-specific) and
other implementers want it; leaving it in DLA blocks reuse, which is the whole point.
- **Make theme assembly a separate sibling package.** Rejected: it shares the
translation core, the worker pool, and the React-isolation machinery; a package
split re-introduces the cross-package version coupling ADR 0003 avoided.
- **Engine does its own browser capture to reach DLA-grade fidelity.** Rejected:
violates the browser-free posture and ADR 0003. Instead, `SectionSpec` is a shared
input contract — the engine ships a browser-free cheerio extractor for a best-effort
static path, and consumers inject richer captured specs for full fidelity.

## Consequences

- `CONTEXT.md` gains: Theme assembly / Assembler, Stage, Hook, SectionSpec, ThemeModel.
"pipeline" stays reserved for the consumer's end-to-end run; the engine has an
*assembler*.
- The browser-free **static-dir** path is a NEW, lower-fidelity capability with its own
golden fixtures. It is NOT held to DLA-output parity.
- The migration definition-of-done for lifting DLA's logic is: **engine + DLA-injected
specs/aggregates/hooks == DLA-today, byte-identical** (proves the lift preserved
behavior). Static-path output is governed by golden fixtures, not DLA parity.
- DLA eventually deletes its deterministic copies and imports the engine, wiring its AI
skills into the hooks and feeding its captured `SectionSpec`s via the `sections` input.
The Studio/WP install stays in DLA.
Loading
Loading