Abstract
Most web content management systems fail slowly. They ship fine, then degrade over years as content, structure, styling, and behavior grow entangled in the same files. The symptoms are familiar: a "brand refresh" becomes a content migration; editors wait on engineering for changes that are visually trivial; a redesign means touching every page; and the system becomes impossible for multiple roles to work on without colliding.
Composer is a small rendering engine that resists this entropy by construction. It splits a page into four artifacts — a semantic content document, a declarative pattern, plain CSS, and an optional browser-side finisher — each owned by the role best equipped to maintain it, with content flowing in one direction toward a finished page on every request.
That split yields a property the rest of the category lacks: because the pipeline is reversible and machine-verifiable, a static prototype can be turned into CMS-ready, authorable artifacts with no human in the loop — and with no unintended delta from the approved design. Reproducing a design runs unattended; a human's time is spent only on deciding where to improve on it, which is the half worth paying a person for. This paper treats that capability as the architecture's headline benefit, then situates the others around it, and supports the claims with evidence from two independent runs of that loop — a real multi-page site (Experience Managed) built and iterated entirely on Composer, and a deliberately adversarial re-run against a third-party prototype by an agent with no prior exposure to the system. It also states the architecture's deliberate tradeoffs honestly, because a white paper that only lists upsides is a brochure.
The problem: entanglement is the default
Templating systems tend to blur the line between what a page says and how it looks:
-
JSX inlines content, structure, styling hooks, and behavior in one file.
-
Handlebars-style templates put presentation structure into the content source.
-
CMS page-builders pour all three into a single drag-and-drop blob.
Each blur has the same long-run cost. Once content knows about presentation, you can no longer change one without risking the other. The consequences compound:
-
Redesigns become migrations. If markup structure lives next to the words, restyling means rewriting the words' container — across every page.
-
Editors depend on engineering. Adding a second card or rearranging a column touches code, so it enters the release train.
-
Brand integrity erodes. Authors reach for
class="featured-red-thing"or inline styles, and bespoke presentation drifts page to page. -
Roles collide. Editor, developer, and designer edit the same file; every change ripples across concerns none of them own.
What these have in common is that each is a problem generated by
an earlier choice. Build tooling exists because JSX must be
compiled. Markup rewriting exists because the platform injects its own
wrappers. Image-optimisation expertise exists because the platform
hands an author a bare <img>. The trade between
authoring comfort and design fidelity exists because the two are
checked at different times by different people. Composer's response to
most of these is not a better solution but a refusal of the premise:
the pattern is the DOM, so nothing needs rewriting; patterns
are literal values, so nothing needs compiling; managed media
negotiates its own formats, so nobody needs to know how. The problems
do not get solved so much as declined.
Nothing in the parts list that follows is novel, and that is the claim rather than an apology for it. XSLT transformed a source tree into an output tree declaratively in 1999. CSS Zen Garden made the case for semantic content and swappable presentation in 2003. Snapshot testing has diffed rendered output against a stored target for a decade. Every headless CMS validates content against a schema. Taken separately these are ordinary, well-understood parts.
Judged separately, every constraint Composer adopts also loses. A render node with five reserved keys is less capable than JSX. A grammar with no expression language is less capable than Handlebars. Literal values only means two patterns cannot share a constant, so repetition replaces abstraction — worse engineering by any ordinary reading. Argued one at a time, on technical merit, each of these is a downgrade, and anyone who wants to dismiss the architecture can do it in a sentence.
They are not separable from what they buy.
zum validate can exist because a pattern is
literal values that are parsed and never executed: the rendered page
is a pure function of document and pattern, so it can be re-derived
and diffed. A template language with an evaluator cannot make that
offer — not because the tool hasn't been built, but because its output
is not recoverable that way.
Expressiveness and verifiability trade against each other,
and this architecture spends the first to buy the second.
Surface area is the same decision from another angle: the entire
grammar fits on a page, which is not a pleasant consequence of
minimalism but a precondition for §3, since a more powerful grammar
offers more ways to be quietly wrong than any gate can check.
So the thesis is not that four artifacts are technically superior. It is a bet about process and ownership: constrain the system until the two questions a senior human normally adjudicates — is this faithful, and is this authorable — can be answered by a command, and the role that adjudicated them stops needing to exist. What changes is not how a page gets built, but who builds it and how many people have to agree before it ships.
The architecture in one direction of flow
| Artifact | Owner | What it holds |
|---|---|---|
Semantic document
(content/<page>.html)
|
Editor |
Plain semantic HTML. One wrapper class per block, plus
row/col for layout. No styling, no
presentation.
|
Pattern
(patterns/<name>/<name>.js)
|
Developer | A literal JS object describing the output tree: which slots pull from which source positions, what tags/classes/attributes the output gets. |
Pattern CSS
(patterns/<name>/<name>.css)
|
Designer | Plain CSS. No tooling, no preprocessor required. |
| Finisher (default export in the pattern file) | Behavior author | Optional. Runs in the browser after render for anything declarative can't express. |
Composer joins them deterministically on every request — the same library in local dev, at the edge, or in a build step. The split is the product; the rest of this paper is its consequences.
The hidden power: reproducing a design stops being a human job
The benefits in §4 are real, but the architecture's most consequential property is one that's easy to miss because it concerns process, not features: an LLM can take a finished static prototype and produce the CMS artifacts that reproduce it — authorable, lint-clean, and faithful to the design — without a human hand-crafting the translation.
Two scoping notes, stated here rather than buried, because a claim that hides its shape invites the reader to go hunting for the catch. The loop needs a target to diff against, and the unattended part is reproduction: matching a target converges without supervision, while deciding where to depart from it is a judgement call and stays one. Both are examined in §3.5.
A target is not a prerequisite, and the choice of whether to have one is the most consequential decision in the whole process. Three modes, with genuinely different verification:
-
Target supplied. A prototype exists.
zum validateis a true fidelity oracle: the loop terminates when output matches the approved design. -
Target manufactured. Only flat mocks exist, so the agent writes the prototype first and runs the loop against it. The gate then proves the patterns reproduce that page, not that the page matches intent — so a reviewer checks one static page against the mocks, once, and everything downstream of that judgement is machine-checked.
-
No target. An existing site is re-platformed with deliberate improvement and nothing is reproduced element for element.
zum validatedoes not apply;zum lintstill proves authorability, and fidelity claims become measurements — contrast ratios, payloads, Lighthouse — where each is checked against what it actually asserts.
The authorability translation tax
In a conventional CMS, a prototype is the end of design and the start of a manual, senior, error-prone job: deciding what the design means as authorable content. A human looks at the rendered page and answers, by hand, questions the tooling can't see for them:
-
Which regions are blocks? Which parts of them are editable?
-
What may an author change, and what is fixed chrome?
-
How is a repeating row modeled? A variant? A media embed?
-
What gets sacrificed because the block library can't express it?
This translation is where the cost lives. It is slow, judgment-heavy, and — critically — it has no automatic check that the result still matches the design. The fidelity of the shipped page to the approved prototype rests on a human eyeballing two browser tabs.
The compromises that follow
Because the translation is manual and unverified, teams compromise to "make the CMS happy," and the compromises rhyme across every project:
-
Presentation drift. "The CMS can't do that, so we'll approximate." The shipped page quietly diverges from the approved design, and the design review that signed it off becomes fiction.
-
Long cycle times. The translation is a project unto itself; every prototype revision re-triggers it, and design and implementation fall out of step.
-
Poor authoring ergonomics. Authorability gets shaped by whatever was easy to wire up, not by what the editor actually needs — or, inversely, the design is mangled to fit a rigid block library.
-
Duplicate code. With no signal that a refactor still matches the target, developers copy-paste rather than factor; deduplication feels risky, so it doesn't happen.
-
Two sources of truth. The prototype and the CMS implementation drift apart, and nobody can say which is canonical.
The root cause is identical in every case: the loop is open. A human is the only thing closing it, and humans are slow, expensive, and inconsistent at diffing markup.
Two machine-checkable gates close the loop
Composer's contribution is to make the two questions a human normally adjudicates mechanically checkable, so the loop can close without one:
-
zum lint— authorability legality. Does the semantic document obey the authoring grammar an editor is bound by? Therow/colscaffolding, the six-column ceiling, no infrastructure tags (<link>/<script>/<style>), no misplaced structural primitives, legal block children. This answers "is this legal for an editor to hold?" — with no human and no taste required. It deliberately does not answer "is this a good authoring model?". Four cards modelled as four columns in one row and as four single-column rows both pass; choosing between them is design, and design is what the reviewer is left to do. The gate removes the tedious half of the judgement, not the interesting half. -
zum validate— presentation fidelity. Does the rendered output match the target prototype, element for element, class for class, attribute for attribute? This answers "did we preserve the design?" — and returns every deviation as a structured, reviewable entry, with a machine-readable--jsonform and exit codes built for scripting. It compares DOM, not pixels, and that is the point rather than a shortfall: when the output tree matches the prototype's tree, the prototype's own stylesheet applies to it unchanged. In both runs of §3.5 visual fidelity was carried by transplanting that stylesheet wholesale — in run two, 1,318 lines across eleven files with zero rule edits. A platform that injects its own wrappers cannot make that offer, whatever it diffs.
Together they bracket the two questions the manual translator was answering by eye: is this legal to author, and is it faithful? The third — is it well modelled? — remains a human's, and is the only one worth a human's attention. Neither gate needs judgment to run; both are deterministic. So an agent can iterate draft → compose → lint → validate → fix to convergence, unattended.
One limit of that guarantee, named rather than left to be found. A
slot whose selector matches nothing is skipped silently —
required: false is the default, and correctly so, since
optional content is ordinary. Validation catches the case that
matters: a drifted selector drops content the target contains, and
that surfaces as a missing-child diff without any flag. What it
cannot catch is a slot that matched nothing at the moment the
target was written — see below — or a project with no target at
all. zum dev --debug names every zero-match and is worth
running before any target is generated.
The inversion: fidelity becomes the pass condition
The deepest change is which way the compromise runs. Conventionally, fidelity is sacrificed to authorability — the design bends until the CMS stops complaining. Under Composer, fidelity is the pass condition: the loop does not terminate until rendered output matches the target. Both constraints are satisfied simultaneously rather than sequentially, and both are machine-verified. "The page in the CMS is the page in the design" stops being an aspiration and becomes a build gate.
What that does not mean is that the two stop interacting, and the difference is measurable. [case study] Compare run two, built to a pixel-perfect target, against a third port — an existing live site re-platformed with no target at all. Run two's patterns carry 5 to 27 slot selectors each, up to 22 of them positional; the untargeted port's carry 4 to 11, at most 7 positional — its most complex pattern is simpler than run two's median. The naming diverges the same way: reproduction yields blocks named for where they sat on a page, composition yields blocks named for what they are.
Both passed zum lint, which is exactly the point — lint
proves legality, not quality. So the honest statement is narrower than
"no trade": matching a target does not force authorability to
fail, but it does constrain its shape, and the constraint is
paid in positional selectors and unrolled repetition. Pixel fidelity
is worth buying when a design has been signed off and must survive
contact with a CMS. It is worth declining when the goal is a
well-composed site, and the difference between those two goals should
be a deliberate decision at the start rather than a default.
A precise word on "no delta," because honesty matters: the validator
does not magically erase differences — it makes
every difference explicit. The loop converges to zero
unintended delta. Where the rendered DOM legitimately differs
from a hand-built prototype — a separator authored as
<mark> rather than a styled
<span>, gradients moved to a CSS
:nth-child rule, a purely decorative element deliberately
dropped — each divergence shows up in the diff, gets a reason, and is
signed off. There is no silent drift. That is a categorically
stronger guarantee than "looks about right," which is all the manual
process ever offered.
Worth knowing what happens next, because the gate changes character
the first time you improve on the prototype. Any deliberate departure
— swapping a CSS background-image for managed media,
collapsing three hand-rolled breakpoints into one responsive scaffold
— means re-baselining the target from composed output. From that point
validation no longer demonstrates fidelity to the original design; it
demonstrates that nothing has drifted from the version you approved.
Both are worth having. They are not the same claim, and the first is
only available once.
The sharper hazard is that re-baselining can encode a defect as the
standard. A slot whose selector matched nothing at the moment a target
was written bakes that absence into the target, and every run
afterwards is green on missing content. This is where a single page
misleads worst: one hero without an eyebrow cannot tell you whether
the block never takes one or whether that instance simply omitted it,
and both readings — deleting the slot, or marking it
required — are overconfidence drawn from a sample of one.
Only real content variation settles it, which is why
required: true is a signal applied after migration rather
than inferred during one. Reverse-engineering a prototype does not
supply that context, and no gate can invent it.
Do nothing and it is faithful; do a little more and it is good
The useful way to hold the previous section is the way Apple framed Mac Catalyst: do nothing and you have a decent app; put in a little extra work and you have a great one. Run the loop unattended and you get a faithful, lint-clean, authorable site — a real floor, and higher than most projects reach by hand. The distance from there to good is the work a person chooses.
That work divides in two, and knowing which half you are in is the
whole skill. Some improvements are free, and provably
so: pulling shared selector prefixes forward onto a parent slot,
replacing a font stack with self-hosted woff2 and
font-display: optional, moving a render-blocking
stylesheet out of the head. These change the artifacts and not the
output, so validation stays green and proves it.
[case study] One refactor restructured a footer's
semantic document from a single column into four and rewrote the
pattern to match; validation passed untouched, because the rendered
tree was byte-identical. The gate does not merely permit authorability
refactors — it certifies them.
The rest are trades, and they are the ones worth a
human. Moving images onto managed media, collapsing three hand-rolled
breakpoint pictures into one responsive scaffold, lifting card
photography out of a CSS background-image so it can be
both optimised and authored — each changes the DOM, each requires
re-baselining, and each should be a decision someone makes on purpose
with a measurement in hand. That is why the earlier framing holds:
reproducing a design is unattended work, and improving on it is where
judgement is worth paying for. The floor is automatic. The ceiling is
a choice.
What this looked like in practice
The loop has been run twice, on deliberately unalike inputs. The second run was set up to strip away the advantages the first enjoyed, because a process claim tested only under favourable conditions is a demo.
Run one — Experience Managed
[case study] The four pages of this paper's case
study were converted from static prototypes to CMS-ready artifacts by
an LLM running exactly this loop. No human hand-crafted the
authorability; the human's role was to
review the diff and adjudicate the deliberate divergences,
not to perform the translation. Each page reached a
zum lint-clean semantic document and a
zum validate result that was either empty or a short,
fully-explained set of intentional divergences — the
accepted-convention diffs (a landmark <nav>,
:nth-child gradients,
<mark> separators) plus, on one page, a single
flagged design decision (dropped decorative color swatches the grammar
couldn't interleave).
Run two — a hostile prototype and an agent with no prior context
[case study] The second run took a Figma-derived
prototype of an Adobe PDF & Productivity page — 2,687
lines, a single 1,318-line <style> block, and some
500 lines of inline JavaScript — and gave it to an agent with no prior
exposure to Composer, working only from the published skill,
AGENTS.md, and the runnable examples. The prototype had
not been written with a CMS in mind: no <main> or
<header> landmarks, card photography carried as
inline background-image, eighteen uncompressed
.otf faces totalling 3.2 MB, a developer-only grid
ruler in the markup, dead rules in the stylesheet, and fourteen
orphaned assets including an unreferenced 18.3 MB PNG. Its
behaviour was not simple either: a scroll-scrubbed hero whose video
grows from a bounded box to a full-bleed plate on two different
curves, two carousels — one of them infinite, with clone slides — a
page-wide reveal system, and a shared smooth-scroll instance.
The output was eleven patterns and three semantic documents: the page
itself plus header and footer chrome authored as their own template
documents. All three were zum lint-clean, and all three
returned an empty zum validate on the first compose, with
no ignore flags. The prototype's 1,318 lines of CSS were split across
global.css and ten pattern files with
zero rule edits — the only change was rewriting a
font url() whose file had moved.
That phase took about forty minutes, wall-clock,
from the prototype landing in the project to three empty validations —
a figure bounded above by the timestamp on the first asset upload that
began the next phase. A meaningful share of it was the agent reading
the skill, AGENTS.md, and the runnable examples before
writing anything. The comparison worth drawing is not against a team's
calendar weeks, which contain meetings, handoffs and waiting; it is
against the days of senior developer time the translation step alone
normally consumes.
A second phase then traded fidelity deliberately, and measurably, for
delivery: authored images moved onto managed media, taking page
imagery from 26.6 MB to 1.1 MB; the font set moved to
self-hosted woff2 with
font-display: optional, from 3.2 MB to 173 KB
actually fetched. The deployed page scores 100 on Lighthouse desktop
(LCP 0.5 s, CLS 0, TBT 0 ms) and 98 on mobile, with no
remaining opportunity above 50 ms.
What the two runs together show
| Run one | Run two | |
|---|---|---|
| Prototype origin | Built in-house for the site | Third-party export, no CMS in mind |
| Agent's prior context | Iterating alongside the system's authors | None beyond published documentation |
| Scope | Four page templates, ~21 patterns | One page plus chrome, 11 patterns |
zum lint |
Clean, all four documents | Clean, all three documents |
zum validate |
Empty, or briefly and explicitly divergent | Empty on first compose, all three |
| What it surfaced | Two refactors | Three defects in the tooling |
The grammar is learnable from the documentation. Run one's agent had the system's authors to hand; run two's had a skill file and five examples. That is the condition the §3 claim actually rests on — that the translation does not require someone who already knows the system — and run two is the run that tests it.
Prototype quality decides what gets negotiated, not whether
the loop closes. Run one's purpose-built prototypes produced
a handful of convention divergences. Run two's produced none at all,
and then a series of departures taken on purpose, each carrying a
number: a backdrop served three ways at 7.9 MB collapsed to
67 KB; four card photographs moved out of CSS
background-image, where they could be neither optimised
nor authored, into managed media where they could be both. Under a
manual translation a defect in the source is either reproduced
silently or corrected silently, and no record survives of which.
Between two gates it becomes a decision with a measurement attached.
One grammar limit, two symptoms. Run one dropped decorative colour chips because source content and pattern-owned decoration cannot interleave inside a single element. Run two unrolled six navigation links, three tabs and three pricing cards because a render node holds only literal values and so cannot vary a class or a child count across iterations of a loop. These look like different problems and are the same one: a pattern describes assembly, and per-iteration variation is not assembly. Worth stating plainly rather than discovering twice.
Both runs independently reached the same conclusion about cross-cutting behaviour. The cautionary tale below — fourteen byte-identical reveal finishers consolidated back into one observer — was rediscovered in run two, where a page-level reveal system, a shared smooth-scroll instance and a footer wipe were placed in a single module from the outset rather than distributed across the patterns that appear to own them. Two agents converging on the same placement from opposite directions is better evidence than either arriving there alone.
Where the human's time actually went. Run two divides
cleanly in two, and the division is the point. The first phase —
prototype to CMS-ready artifacts at full fidelity — took a
four-question briefing and nothing else: how faithful the port should
be, how much to convert, whether chrome stayed global or became page
content, and whether to carry across a developer-only grid overlay.
After that the agent ran unattended until it reported three
zum lint-clean documents and three empty validations.
Nobody adjudicated a diff, because there was no diff. It also solved
its own environment problems along the way, including symlinking the
asset tree so that images resolving through the content path and fonts
resolving through the code path could share one directory.
The second phase — improving on the prototype rather than reproducing it — was collaborative from end to end: which departures from the design were worth taking, when to re-baseline the validation targets, and one performance diagnosis the agent had examined and wrongly dismissed. That is the honest shape of the claim, and it is the better half of it. Reproducing a design runs unattended; beating it is where a human's judgement is worth paying for. No one wants to spend three weeks confirming a border radius. Deciding whether a 7.9 MB backdrop should become 67 KB, and what that costs in fidelity, is an hour well spent.
The economic shape of that is the headline. A task that is normally a multi-day, senior-developer, fidelity-risking manual translation became an unattended loop whose output a reviewer checks rather than produces. The compromises in §3.2 aren't merely mitigated — they stop being structurally necessary. Cycle time collapses because the loop runs at machine speed; authoring ergonomics improve because the grammar, not expedience, dictates the shape; duplication falls because refactors are safe when a diff confirms "still matches"; and presentation drift goes to zero-by-default because matching the design is the termination condition.
Benefits, with evidence
The running case study is
Experience Managed (em2), a publication
site of four page templates — a homepage, an article, an
about/colophon page, and a pillar/category landing — built on Composer
across ~21 patterns. A second, narrower case study — the Adobe page
port described in §3.5 — supplies the delivery and portability
numbers, since it was measured end to end in production. Where a claim
is backed by something observed on either, it is marked
[case study].
Content that outlives redesigns
Because the semantic document carries no presentation, a redesign rewrites the pattern and the CSS while the content sits still. The "refresh the brand" project becomes an actual brand refresh, not a content migration. The inverse also holds: when the content model changes, patterns update once and content migrates mechanically, while CSS and finishers are untouched.
[case study] Across the four pages, the chrome
(navigation and footer) and the about-callout block are authored
identically and rendered by a single shared pattern each. Changing the
structural treatment of any of these is a one-file change in
patterns/, not a sweep through four content files.
A minimal grammar that resists feature creep
A pattern's render node has just five reserved keys —
select, tag, children,
required, delegate — plus open HTML
attributes and string children. There are no conditionals, no loops
over computed data, no expression language. When a pattern needs
something the grammar can't express, the answer is never "grow the
grammar" — it's export default function (root) {…}, a
finisher with the full power of the browser, where runtime logic
belongs.
The discipline pays off in predictability: a developer reading a pattern is reading a description of assembly, never hidden control flow.
Statically analyzable end to end → portable by default
Pattern files are parsed and never executed — only literal values are accepted. A pattern can therefore be inspected, validated, serialized, shipped to a Worker, or rendered by any runtime that speaks the schema, with no transforms in between. The dev server, an edge Worker, a build-time renderer, and a CI validator all consume the same files. This is the quiet structural reason the system has "one runtime, three deployment targets" rather than an SSR/CSR split to keep in sync.
Reversibility is the property underneath §3
The no-human-in-the-loop loop of §3 rests on one property worth naming
on its own: the pipeline is reversible.
transform(source, patterns) is deterministic, so
zum validate
can run it and diff the result against a target — returning tag,
class, attribute, and child mismatches as structured data. Most
template systems can't do this, because their templates aren't
reversible; you take the correspondence between source and output on
faith.
[case study] The diff signal earns its keep beyond
first authoring. When the engine changed to carry a swapped link's
href forward as data-href,
zum validate immediately flagged the new attribute on the
exact elements, on the exact pages — a regression check no one had to
write.
Brand integrity by construction
The semantic document allows only a tight set of classes: the block
name (optionally plus a keyword for a block-level variant), plus
row and col. Visual variation is signaled by
meaning, not style — an author marks emphasis with
<strong>, a per-instance variant in a repeating
structure with <em> or <mark>,
and the pattern's CSS decides what those look like. Authors write
content; they never pick colors.
[case study] Two patterns recurred and held the line
cleanly: card "·" separators authored as <mark> and
tinted in CSS, and a category page's "current pillar" link signaled by
wrapping its label in <em> (targeted via
a:has(em)), with no class="disabled"
leaking into the source.
Composition without growing the grammar
Composition of authored elements needs no primitive at all. Every registered pattern is matched against the whole source tree, so a link the editor wrote is claimed by whichever pattern most-specifically matches it — a host pattern just positions it with a bare slot and the widget pattern owns the output.
The one gap is an element the pattern constructs. It
is created during rendering, after the match pass, so nothing can see
it. delegate: true closes that gap: a constructed leaf is
offered to the registry exactly as an authored element would be — same
matching, same specificity, no second class of element. Depth is
capped at one hop (a pattern may delegate; a delegated pattern may
not), because a chain's intermediate element appears in neither the
source nor the output and would be invisible from both ends.
[case study] The site's social-icon lockup appears in
three places (nav, footer, end-of-article share). Initially each host
pattern inlined the four SVG icons — twelve path
declarations duplicated across three files. Refactored into a single
social-lockup widget matched by a sentinel link
(a[href="…/social"]), the icons collapsed to
four declarations in one file. Where
the sentinel is editorial (nav, footer) each host is a bare slot;
where it is pure template furniture (end-of-article share) the host
constructs it with delegate: true and the editor authors
nothing at all.
Performance and operations: no framework, no hydration
There is no React/Vue/Svelte at runtime, no virtual DOM, no reactivity system, no router. The page arrives finished — no build step between writing and seeing, no hydration delay between loading and using. The only client-side code is per-pattern finishers, and they run after render, never blocking first paint. For a site of mostly-stateless patterns and a few interactive ones, this is a dramatically smaller surface to keep healthy over five years than a framework upgrade cycle.
[case study] The §3.5 port measures the claim: a page carrying an autoplaying video, two carousels and a scroll-scrubbed hero scores 100 on Lighthouse desktop and 98 on mobile, with a cumulative layout shift of 0 and no total blocking time on either. The only client-side JavaScript is the finishers and one shared scroll module.
[case study] Scroll-reveal — the site's one cross-cutting behavior — lives in a single global observer rather than per-pattern JavaScript. (See §6 for how it briefly didn't, and what that taught.)
Source maps that survive into the DOM
In dev, every rendered element carries a
data-source selector resolving to its exact source
position. Walk up the DOM, collect the selectors, and any rendered
element maps back to the line that produced it — enabling
click-to-edit, source-rooted analytics, and visual debugging. It's
opt-in, so production payload stays clean.
Role separation as the organizing principle
The deepest organizational benefit is that each artifact has an owner, and the boundaries let owners work without stepping on each other:
-
An editor edits content without touching code.
-
A developer rewrites a render tree without touching content.
-
A designer iterates on CSS without touching either.
-
A behavior author rewrites a finisher without touching anyone's work.
The usual complaint about a framework component is that four concerns share one file. That complaint is weak — colocation is a defensible choice, and a team that likes it is not wrong. The real cost arrives one step downstream, where nobody is looking: the component's prop signature becomes the content model.
A link is one thing to the person writing it. They copy it and they
paste it, the way they have in a word processor for twenty years.
Give a component the signature
<CTA href label target variant /> and that link
arrives at the author as four fields to fill in, because a headless
content type was modelled on a function's parameters. Nothing about
that decomposition serves the person typing. It serves the prop
passing — and it is routinely sold back to the business as
"structured," "portable," "omnichannel," which inverts who the
benefit belongs to.
variant is the worst of the four, and a different
category of mistake from the rest. The others are pointless
decomposition; that one is presentation entering the content model
through the back door, asking an author to make a design decision
because a component happens to take a prop. It is precisely what §4.5
exists to prevent, arriving under a new name.
The premise underneath all of it is that structured content and HTML
are opposites. They are not.
HTML is already a structured content format — thirty
years old, universally supported, semantically rich, and already
emitted by every tool an author touches. <a
href="…">Label</a> is the structure, and
target has been an attribute since 1996. Splitting it
into JSON fields adds nothing; it replaces structure that already
existed with structure shaped like a parameter list, and bills the
author for the conversion.
A fair line to draw, since decomposition is sometimes right: a field
is legitimate when the content has it, not when the component
wants it. A product has a price — that field exists regardless of
rendering technology, and you will want to query on it. The test is
whether the field would survive a change of front-end framework.
A price would. variant would not.
The same inversion, in markup rather than fields, is what block
libraries do. Gutenberg wraps authored content in
wp-block-* containers and alignment classes; AEM's core
components emit their own cmp-* structure; Edge Delivery
wraps every block in section and wrapper layers. Class-based selectors
often survive that. Structural ones do not — every
:nth-child, every flex or grid parent-child relationship,
breaks the moment a layer is injected between two elements. That is
why a prototype's stylesheet has to be rewritten against those
platforms rather than transplanted onto them, and why §3.5's
zero-rule-edit result is not a small detail.
Composer doesn't ask roles to coordinate through discipline; it removes most of the need to coordinate at all. And as §3 shows, one of those "roles" can now be an agent that produces the developer and content artifacts under machine-checked supervision.
Honest tradeoffs
These are conscious choices. A white paper that hides them is untrustworthy.
-
Positional selectors are brittle by design. Slots address source by position (
:scope > .row:nth-of-type(2) > .col). Move a row, dependent selectors must update. The mitigation is to prefer bare descendant selectors wherever the structure is unambiguous and reserve positional addressing for genuine collisions — a refinement that measurably reduced selector noise across the case-study patterns. -
Patterns can't share constants or factor common patterns. "Only literal values" means repetition is the price of static analyzability. [case study] The article-card visual recurs in four blocks and its CSS is duplicated rather than abstracted; before the social-lockup refactor, the icon SVGs were duplicated too. Keeping patterns small bounds the cost, but the friction is real.
-
Source-child interleave isn't expressible. A slot pulls a whole source element, not just its text, so
[icon] [source text] [arrow]inside one<a>can't be assembled declaratively. [case study] The colophon's palette entry interleaves decorative color-chip<span>s with color-name text; with no clean declarative path, the chips were dropped (names/hex kept) and flagged for a finisher follow-up. The grammar gap is genuine and known. -
The three-class rule is enforced by convention, not (yet) the library. A careless author can still sneak a class into the source; today code review and the editor's toolbar are the backstop, and long-term the editor schema should make it structurally impossible.
-
The pattern owns the rendered tag, so accessibility/landmark decisions live there. A misconfigured pattern can produce structurally valid but semantically wrong HTML. External tooling (axe-core, etc.) can catch some of it; the library doesn't. [case study] Run two rendered an authored
<img alt="Adobe">as an SVG sprite; the tag swap droppedalt, leaving a link with no accessible name. Nothing flagged it — it was caught by reading the engine's attribute filter, and fixed by placing anaria-labelon the enclosing link. Filed as a defect against the carry-forward rules, but the general point stands: the pattern layer can quietly lose semantics the author supplied. -
The declarative/finisher ratio tips with complexity. ~80% declarative is realistic for content-heavy patterns; dashboards and stateful apps push more into the finisher, where pressure accumulates.
A cautionary tale that proves the model
[case study] Each revealing block was initially given its own scroll-reveal finisher — fourteen of them, all byte-identical, each a decomposed copy of one page-level observer the prototypes had carried as a single inline script. Consolidating them back into one global observer didn't just remove redundancy; it restored the prototypes' original design. The lesson generalizes: when the architecture tempts you to decompose a cross-cutting behavior per pattern, that's a smell — cross-cutting behavior wants one home, and the finisher escape hatch should be reserved for behavior that is genuinely per-pattern.
Where Composer fits — and where it doesn't
Composer is not a framework, a templating language, or a build tool. It does one thing: turn semantic content into a finished page on every request, fast enough that nobody thinks about it. It is an excellent fit for content-heavy, editorially-driven, long-lived sites that must survive redesigns and be authored by multiple roles — publications, marketing sites, documentation, brand sites — and especially for teams that want to generate those sites from design prototypes under machine verification rather than hand translation (§3).
It is a deliberately poor fit for highly stateful, app-like surfaces where most of the page is interactive and reactive — there, the finisher would carry most of the weight and the declarative core would buy little. The architecture is honest about this: the grammar is for assembly, and apps are not mostly assembly.
Conclusion
The chronic failures of long-lived web content systems — painful redesigns, editor/engineering bottlenecks, brand drift, role collisions, and the slow, lossy, hand-built translation of a design into a CMS — share a root cause: content entangled with presentation, and a loop that only a human can close. Composer's contribution is to make that entanglement structurally hard by splitting the page into four artifacts with crisp owners and a one-way flow, and then to make the pipeline reversible and machine-verifiable so the loop can close without a human in it.
None of the parts are new, and each constraint is a downgrade on its own terms — which is why the architecture is easy to dismiss in a sentence and hard to dismiss in practice. The constraints are what make the verification possible, and the verification is what removes the adjudicating role. The change is to process and ownership, not to what a page can be made to do.
The headline benefit follows directly: a static prototype can be turned into authorable, faithful, CMS-ready artifacts with no human hand-crafting the authorability — and no unintended delta from the approved design. Scoped precisely: reproducing the design runs unattended, and improving on it is where a human's judgement goes. That is the better half of the bargain, not the fine print on it. The supporting benefits — content that outlives redesigns, brand integrity by construction, composition without grammar growth, and framework-free delivery — are not a feature list. They are what necessarily follows from taking the separation seriously and making it verifiable. The case-study evidence suggests the architecture delivers on that promise in practice, tradeoffs included.
Plain assembly. Clean separations. Verified fidelity. Speed by default.
Appendix: further reading
-
README.md— architecture overview, public API, deliberate tradeoffs. -
MARKETING.md— audience-facing framing of the same split. -
AUTHORING.md— writing patterns by hand (developer guide). -
AGENTS.md— dense reference for agents reverse-engineering HTML into the four artifacts. -
examples/— five runnable mini-projects covering the core patterns. -
skills/zumify/— the assisted-authoring skill that drives the draft → compose → lint → validate → fix loop.
Draft notes (to expand before publication)
-
Quantify the loop: wall-clock and review-time for a prototype → CMS conversion under Composer vs. a hand-built baseline; number of fix iterations to convergence.
-
Performance numbers are partly supplied by §3.5 (Lighthouse scores, imagery and font payloads). Still missing: TTFB at the edge, and a payload comparison against a framework baseline.
-
Neither run tests pattern reuse across pages of one design system — run one built four templates that share chrome, run two built a single page. A second page drawn from the same system, reusing the first page's patterns unchanged, is the experiment that would show whether the authoring model generalises or was fitted to one layout.
-
Write up the untargeted port referenced in §3.4 as a full third case study — live site to Zum with no prototype, verified by lint and measurement rather than by diff. It is currently doing real argumentative work off a single comparison.
-
Add a side-by-side code comparison (same block in JSX vs. the four-artifact split).
-
Expand §4.1 with a true before/after redesign on a single page to demonstrate "content unchanged" concretely.
-
Consider a short section on the editor schema (ProseMirror target) once it firms up.