Appearance
ADR-0003 · Template system scalability
Status: 🟡 Proposed · Depends on: — · Drives: ADR-0004
Context
Template-first is a locked product decision: core, but greenfield — templates/template_selections exist in the baseline schema but have zero references in src/; the live app is 100% dedicated per-feature UIs. That prior decision named this "the #1 architecture initiative to sequence before it drives feature design."
The prototype's Templates.dc.html (thoroughly reviewed across two revalidation passes, see Templates review) is a genuinely strong reference for what the real engine needs: a real plan-aware entitlement model (PLANRANK/TPLMETA), a real lifecycle state machine (draft → review → scheduled → published → paused → archived → deprecated with a version log), and — as of the fix just revalidated — a real widget-type security model: five named, schema-typed widget types (widget-calculator, widget-booking, widget-map, widget-leadform, widget-payment) plus one sanitized Custom HTML escape hatch gated behind an independent pending_security_review state, separate from the template's own lifecycle.
Two real gaps remain, unaffected by today's revalidation:
- No Claude Designs → real-template import/export contract (Templates Finding 3) — a screen becomes a real template today only by hand-rebuilding it; there's no JSON-schema export/import path.
- The block library has no ad/sponsor-slot concept (surfaced during the AdManager review) — AdManager defines a
servicecard.footerad placement with no template-side block type that could ever render it.
This ADR is the actual "what does adopting template-first mean, mechanically" decision the prior product-vision memory flagged as needed before templates drive feature design further.
Options considered
A — Server-rendered JSON-schema block model
Templates stored as an ordered JSON block array ({type, props, plan}[]). A single BlockRenderer React component maps type → component. Entitlement and security are enforced twice: at save-time (an Edge Function validates the prop schema against the block type + plan) and at render-time (a component tree, never raw HTML string interpolation). WIDGETSCHEMA's existing shape (five named widget types with typed props) generalizes directly into this model.
- For: the security boundary is structural — a
BlockRenderercan only ever render known component types with validated props, so there's no string-interpolation surface to sanitize in the first place, for every block except the one deliberate Custom HTML escape hatch. - Against: more up-front schema design work than a string-template approach.
B — Handlebars/string-template engine
Templates as Handlebars (or similar) strings with registered helper functions — the approach the greenfield product-vision memory named as a "design-toward target."
- For: simpler to stand up initially; very flexible authoring.
- Against: reopens exactly the Custom-HTML-sanitization problem at the engine's core — arbitrary string interpolation is the mechanism, not an escape hatch — instead of solving it once, in one contained block type, the way the just-revalidated fix already does.
C — Hybrid: JSON-schema blocks (A) + one sanitized Custom HTML escape hatch
Most blocks are JSON-schema/component-based per A. Exactly one block type (custom) allows raw HTML, sanitized at write-time with the disclosed policy the prototype now already implements (scripts, event-handler attributes, iframe, object/embed, and external stylesheets stripped before render), gated behind the independent security-review state machine already designed.
- For: this is already what the prototype converged on, through two rounds of revalidation — a named widget-type system plus one deliberately-contained, sanitized escape hatch. Adopting C is formalizing what's already been designed and verified, not inventing a new approach.
- Against: none identified beyond A's up-front schema cost.
Recommendation
C. This isn't a new architectural bet — it's naming and generalizing the pattern the Templates screen already arrived at across its security fix and this ADR's own review process. The real engine should define a portable TemplateDocument schema (ordered blocks, each {type, props, plan}, matching WIDGETSCHEMA's shape) consumed by both the real render layer and the save-time validation Edge Function.
Closing the import/export gap (Finding 3)
Define the TemplateDocument JSON schema first — before building any "Import from Claude Designs" UI. Once it exists, a Claude Designs prompt can ask the design tool to emit that JSON schema directly for a finished screen, rather than the real app trying to parse arbitrary .dc.html markup and inline JS (renderVals(), data-props, tsType hints) after the fact — asking the source system to emit a known target schema is far more robust than reverse-engineering a design-tool-specific format.
Closing the ad-slot gap (surfaced post-AdManager review)
Add a type:'sponsored' block to BLOCKLIB/WIDGETSCHEMA itself — a first-party, schema-typed slot ({slotId, fallback}) that calls the same getPromo(slotId, workspace) contract AdManager/manifest.js already use elsewhere (e.g. mobile-console/Home.dc.html's getPromo('home.banner', ws)). This turns "servicecard.footer is a defined slot with no consumer" into "any template can declare a sponsored block, and the Service Card template does" — templates and the ad system become one connected model instead of two.
Consequences
- The real Edge Function that saves a template must validate the full block array against
TemplateDocument(per-block prop schema + plan gate) — this is the "primary write-enforcement layer" the data-access rule already requires, applied to templates specifically. - ADR-0004 depends on the
sponsoredblock type existing, so any template (not just AdManager's currently-fixed slot list) can carry a sponsored placement. - Templates Finding 2 (the "AI-assisted" creation stub) and Finding 4 (this same ad-slot gap, now addressed here) are otherwise unaffected by this ADR.
Open question for product
Is a template marketplace (users buying/selling templates — raised in the original Claude Designs open questions) in scope for this same phase, or fully roadmap? This affects whether entitlement needs a "purchased" tier in addition to plan-rank, which changes TemplateDocument's save-time validation rules.
Related
- Tracker: "Templates block library has no ad-slot concept..."
- Templates review (Findings 1, 3, 4 and both revalidation passes)
- Product-vision decision #5 (template-first: core but greenfield)