Skip to content

Template Composer — Design Brief

Status: Design brief

A comprehensive technical brief, organised around Composer's own architecture. Not yet through /neo-idea/neo-specification/neo-architect; each slice runs through the SDD chain on its own when it's ready to build. This brief states the current position only — it never narrates how a position was reached, reopened, or revised; when a position changes, the old text is replaced, not kept alongside a "later superseded" note. The full discussion history, with the why behind each decision, lives in RETAIL_STUDIO_TEMPLATE_COMPOSER_NOTES.md (same folder as this site).

Last revised 28/08/2026
Linear ticket none yet — created once this work starts

Scope, and what moved out

Composer is the authoring system: Elements (built from primitives), Scenes (ordered sequences of Elements), and Projects (ordered sequences of Scenes), composed, configured, and reused through one Change model, at whatever permission tier the acting user holds.

The rendering foundation Composer is built on — the resolve → measure → solve → animate → place pipeline, the motion graph, layout.mode, pin, flowBox, pinPoint, background, price, cues, and keyframe animation — was carved out of this brief into its own standalone delivery, the Resolver Engine, with its own mkdocs site (resolver-engine-mkdocs-site, same folder). That site is the source of truth for every engine mechanism; this brief only says what Composer takes from it and what it asks of it — see The Resolver Engine Dependency.

Why this exists

Neosframe built Retail Studio — a manifest/Remotion-based template and campaign-editing system — for BSR, its biggest client, against a detailed brief that specified exactly what should be fixed versus editable. Day one after launch, BSR asked to change things that brief said were fixed: add a sticker, add a large background block, change how many lines of text render, add an extra product. The opposite of what they'd briefed.

BSR is currently Retail Studio's only client. Losing them isn't a bad quarter — it's losing the product's only real-world validation. Patching this one request would leave the same failure mode in place for the second client, and the third. The decision is to build a real authoring layer — Template Composer — so a client asking to restructure something isn't an engineering fire drill each time. Getting the underlying model right now, while there's exactly one client to get it right for, is materially cheaper than retrofitting it once there are several.

Summary

Composer is one engine, built on immutable versions, explicit forks, and a single typed Change model, through which Elements, Scenes, and Projects get composed, configured, and reused. It replaces the four separate, ad hoc override mechanisms the current template and campaign system has accumulated with one mechanism reused at three altitudes.

  • One mutation path. Every content edit anywhere is a typed Change, folded against a frozen base by one pure resolver; rebase, enforcement, undo/redo, and a future agent interface all assume nothing else ever mutates document content, and the handful of lifecycle operations that sit outside it are named (The Change Model).
  • Three altitudes, one shape. An Element is placed into a Scene, Scenes are sequenced into a Project; each altitude exposes a field manifest upward the same way a template does today, and no level ever reaches inside the one below (Elements, Scenes & Projects).
  • Everything is a frozen version. Every Element, Scene, Project, and brand style profile has a permanent identity separate from each immutable version it produces; instances reference a version and catch up by explicit rebase, never by silent propagation (Identity, Versions & Schema Versioning, Fork, Rebase & Conflict Resolution).
  • Brand-scoped, DB-native. Elements belong to a brand and, through it, an organisation; Scenes and Projects inherit that scope as an intersection; all of it lives in DB rows, never the bundled path (Brand Scoping & Brand Style, Storage & Versioning).
  • Real authorisation. Today's layoutMode is an affordance gate; Composer needs a server-side per-Change tier check, built on the existing org-scoped capability layer (Permissions & Enforcement).
  • One visual-regression mechanism, two triggers. Render-and-compare at rebase time and on a PR against rendering or mutation-engine code, never on a user's own live edit (Testing, Preview & Incident Response).
flowchart LR
    P["Primitives & components<br/>(engine layer types)"] --> E["Element<br/>spatial unit · no authored size or duration<br/>exposes a field manifest"]
    E -->|"addElementInstance<br/>setInstanceField / setInstanceLayout"| S["Scene / Overlay<br/>sequencing unit · owns duration<br/>exposes a field manifest"]
    S -->|"addSceneInstance / addOverlayInstance<br/>setInstanceField"| PR["Project<br/>ordered list of Scene instances<br/>+ Overlay coverage"]
    PR -->|"setInstanceField only<br/>(campaign permission)"| C["A campaign's filled-in values"]

Design principles

Four laws, not aspirations. Every decision anywhere in this brief should hold up against these; if a new decision seems to need breaking one, that's a signal to find a different answer, not a reason to quietly make an exception.

Single source of truth. Any given piece of data or behaviour has exactly one place that owns it, never two paths that can drift apart. Change[] replacing four separate override mechanisms (The Change Model) is this law in practice: today's campaignPropsSchema / campaignLayoutOverridesSchema / campaignRemovedLayerIdsSchema / campaignAddedLayersSchema split is exactly the failure mode this law forbids — four places a mutation could live instead of one.

One way to do a thing, never several. Not "the best of several ways" — never several at all, for the user and for the developer alike. { value, unit: "targetFraction" } is one mechanism the engine already reuses for pin and for container-relative sizing, and Composer asks it to stretch once more onto the time axis rather than inventing a second unit (The Resolver Engine Dependency tracks that one as an open ask). pin retires attachTo outright rather than the two coexisting. An Element's manifest bindings reuse exactly the pattern a template's bindings already use, rather than a second, Composer-specific exposure mechanism. An Element's exposed manifest fields are the only surface a Change can ever touch from outside; there is no second path that reaches further in, not even a narrower one.

Don't unnecessarily complicate things. Every added flexibility, option, or abstraction has to earn its keep against the complexity it costs everyone who reads, uses, or maintains it after. Dropping min/max sizing on an authored Element (Elements, Scenes & Projects), rather than working out how a clamp composes with the sizing-collapse rules, is this law in practice, not scope cutting for its own sake.

Consistent methods of accomplishing things, for the user and in the code. For the user, a control used for one property shouldn't become a different control for a conceptually similar property elsewhere, and a workflow used for one kind of edit shouldn't demand a different mental model for a similar one. In code, this means a defined interface every implementation is required to conform to, rather than each implementation inventing its own. The engine's motion graph is exactly this: one interface for "a layer's transform composing against whatever it's chained to," implemented once and read by every consumer — renderer and editor selection overlay alike (The Resolver Engine Dependency).

Target deadline

Last confirmed state, not the current one

The alpha target was set at six weeks out from the week of 24 August 2026, i.e. the week of 5 October 2026, by Kat and Haley's conversation with BSR's liaison — a real, live constraint, not an internal aspiration. The notes doc's build-velocity baseline (its §15) found raw throughput is not the limiting factor; scope discipline and priority allocation are.

As of 28/08/2026 that window is no longer confirmed. Per Matthew directly, both the six-week window and the assumption that Retail Studio Templates work would pause to make room for it now appear uncertain; a different approach has reportedly been targeted instead, and Templates work doesn't appear to be paused. Not yet reconciled with the rest of this brief — treat any alpha-window pacing on the Delivery pages as provisional until this is settled. The Resolver Engine has since been decoupled from this schedule entirely and ships on its own timeline.

Non-goals (this round)

  • Everything the Resolver Engine owns. Rendering pipeline, layout modes, price, background, group flow, cues, keyframes — designed and delivered there, consumed here.
  • Real-time collaborative editing — out of scope for this round and not designed for; a lock/checkout model is designed for later (Concurrency & Editing Safety).
  • An MCP / agent interface — a thin, late-bindable wrapper; the discipline that makes it cheap is in scope, the wrapper is not (AI / Agent Forward-Compatibility).
  • PSD import under the Element model — deferred, gets genuinely harder (Editing UI & Canvas).
  • Commercial / pricing framing — belongs to the broader Retail Studio commercial package.
  • Per-brand permission tiers — considered, declined for now; whether a grant ever needs to vary per-Project or per-fork stays open (Permissions & Enforcement).

The pages

Foundations — what everything else assumes:

Composition, Versioning & AccessStorage & Versioning, Fork, Rebase & Conflict Resolution, Permissions & Enforcement.

Product Surface & OperationsEditing UI & Canvas, Testing, Preview & Incident Response.

DeliveryProcess & Organisation, Delivery Sequencing.

Deferred — worked through at full depth so this brief doubles as the planning reference when the work resumes, but none of it is tracked work for the alpha: Concurrency & Editing Safety, Keyframes & Cues, from Composer's side, AI / Agent Forward-Compatibility.