Skip to content

Templates (admin panel)

File: prototype/admin-panel/Templates.dc.html (145 KB → 156 KB → 152 KB after the follow-up fix) · Status: 🟢 Finding 1 fully resolved, Findings 2-4 unchanged · Last reviewed: 2026-07-19 (re-fetched fresh for a second revalidation pass; full file read and grepped directly from the Claude Designs project all three times)

This screen is the reference implementation for template scalability per the design project's own CLAUDE.md. It genuinely delivers on the entitlement model (PLANRANK, TPLMETA) and the lifecycle state machine (draft → review → scheduled → published → paused → archived → deprecated, with a version log [ver, note, date, status, author]). The findings below are gaps against that same standard, not a rejection of the approach.

Finding 1 — Custom HTML / Dynamic data widget blocks have no security model

Severity: blocker (this is a live-XSS-risk-in-waiting, not a polish item) · Status: 🟢 fully resolved, see Revalidation below

BLOCKLIB includes {type:'custom', label:'Custom HTML', ...} and {type:'dynamic', label:'Dynamic data widget', desc:'Live feed / API bound', ...}, both gated plan:'business', cat:'Enterprise'. A full-file grep for sanitiz, sandbox, iframe, dompurify, xss, csp returned zero matches. There is no sanitization, sandboxing, or CSP story attached to either block type anywhere in the file.

Evidence this isn't hypothetical: the real-estate template's own version log reads ['v4','EMI + lease-yield calculators, locality guides','2026-07-01','published','Sameer T.'] — an EMI calculator is already referenced as a shipped feature, almost certainly implemented via one of these two generic block types, with no security review visible anywhere in the design.

Design-fixable — copy-paste prompt for Claude Designs

markdown
In prototype/admin-panel/Templates.dc.html, the block library (BLOCKLIB) has two escape-hatch block types:
'custom' (Custom HTML) and 'dynamic' (Dynamic data widget). Neither has any sanitization, sandboxing, or CSP
story in the design. Before this goes further:

1. Split 'dynamic' into a closed set of first-party, named widget types (calculator, booking, map,
   payment, lead-form) instead of one generic "API bound" block. Each widget type should show a defined prop
   schema in its inspector (Content tab), not free-form config.
2. For 'custom' (Custom HTML), design the actual admin experience for a sanitization boundary: what the admin
   sees when their HTML is stripped of scripts/event handlers, an explicit warning state ("this block renders
   in a restricted, sanitized context"), and make clear in the UI that Custom HTML is a last resort, gated more
   restrictively than plan alone (e.g. requires an explicit approval step separate from the normal
   draft->review->published lifecycle).
3. Do not remove the escape hatches. Design the guardrail around them, since removing them entirely would be
   a regression against what the reference EMI-calculator template already needs.

Keep every existing token, radius, and motion convention. Do not invent new colors.

Revalidation — 2026-07-19

Re-fetched Templates.dc.html fresh from the Claude Designs project (152 KB → 156 KB, confirming a real edit landed) and re-ran the same checks.

Resolved, thoroughly:

  • type:'dynamic' is gone from BLOCKLIB entirely. In its place, a real WIDGETSCHEMA object defines five named, typed widget types exactly as asked: widget-calculator (with a formula select offering "EMI (loan)", "GST (tax)", "Discount", "Area / unit price" — directly answering the real-estate template's own EMI-calculator need), widget-booking, widget-map, widget-leadform, widget-payment.
  • Custom HTML now discloses its exact sanitization policy in the UI itself: "Renders in a restricted, sanitized context. Scripts, event-handler attributes (onclick, onload...), iframe, object/embed, and external stylesheets are stripped before render. Only static markup and inline non-JS styling survive."
  • A genuine independent approval gate exists: pending_security_review → approved ("Approved for live use") / rejected ("Rejected - revise and resubmit"), with real actions (Submit for security review, onCustomApprove) and a toast confirming the separation: "Sent for security review, separate from template approval." This is exactly the "independent of the template's own draft → review → published status" ask, confirmed verbatim in the file's own copy.
  • The escape hatches were not removed, as asked.

Two regressions were introduced by this same fix (6 hardcoded hex colors, 2 new em-dash prose violations) — a follow-up prompt was written and sent; see the second revalidation below confirming both are now fixed.

Revalidation #2 — 2026-07-19 (follow-up fix)

Re-fetched Templates.dc.html again (156 KB → 152 KB) and re-checked both regressions specifically.

  1. Hex colors — resolved. #1f2937 is gone from the file entirely (0 occurrences). The remaining five (#0b1220, #3a1e00, #5a3400, #7a3b00, #8fd6a0) are no longer raw literals — every one is now inside a var(--token, #hex) fallback, e.g. var(--code-surface, #0b1220), var(--preview-ink-strong, #3a1e00), var(--preview-ink-soft, #5a3400), var(--preview-ink-brand, #7a3b00), var(--code-text, #8fd6a0). This is the same "token with a documented fallback" pattern already confirmed compliant elsewhere in this file (see Confirmed compliant below) — a real named token now backs each one, with the literal only as a fallback.
  2. Em dashes — resolved. Both flagged sentences were rewritten: "Custom HTML is a last resort — reach for a Dynamic widget block first." → "Custom HTML is a last resort. Reach for a Dynamic widget block first.", and "...typed and validated — there is no free-form config." → "...typed and validated, with no free-form config." Both now read as intended without an em dash.

Note in passing, not a new regression: the file's pre-existing version-log seed data ('Paused — reworking batch schedule block', 'Deprecated — merged into Freelancer template') also uses an em dash and technically sits outside the established '—'-as-empty-value-placeholder exception. Confirmed present in the very first 141.8 KB fetch of this file, i.e. it predates this finding and this fix — flagged here for completeness, not folded into Finding 1's resolved verdict. Low priority; worth a single follow-up prompt only if/when this file is touched again for something else.

Verdict: Finding 1 is fully resolved — both the original security gap and the two regressions it introduced along the way.

Finding 2 — "AI-assisted" template creation is a stub, not real generation

Severity: minor (documentation/expectation gap, not a UX defect as designed)

The creation flow's fourth start option calls aiGenerate(), confirmed by reading the method: it sets blocks:['cover','identity','actions','about','services','offers','gallery','r...'] — a fixed, hardcoded block list, not any data-driven generation. This is fine as an interaction placeholder, but nothing in the screen discloses that to a viewer, so it currently reads as a working AI feature.

Design-fixable — copy-paste prompt for Claude Designs

markdown
In prototype/admin-panel/Templates.dc.html, the 'AI-assisted' creation start (aiGenerate()) currently seeds a
fixed block list with no visible indication that it's a placeholder rather than a working generation call.
Please make this explicit in the design: either (a) label it clearly as a preview/mock of the intended AI flow
(e.g. a small "Preview" or "Coming soon" tag on that start option), or (b) if AI generation is meant to be
demoed as real in this prototype, design the actual input (a prompt or a set of business-type questions) that
would drive different block-list outputs, rather than always returning the same list regardless of input. Do
not silently leave it implying a live AI call.

Finding 3 — No import path from a Claude Designs screen into Template Management

Severity: major (this is scope, not a defect — see the tracker note below)

Template creation has exactly four starts, confirmed by grep: 'blank', 'duplicate', 'inherit', and AI-assisted (Finding 2). There is no fifth "Import from Claude Designs" option, and no JSON-schema export/import contract exists anywhere in the project.

Architecture-gated — not a Claude Designs prompt yet (tracked as QRS-045)

This needs an export/import contract designed first (what gets extracted from a .dc.html file: block boundaries, renderVals() prop shapes, tsType hints, token references). Once that contract exists, a design-fixable prompt can ask Claude Designs to add the "Import from Claude Designs" creation start that consumes it. Sending this straight to Claude Designs today would just produce another UI mockup of an import button with nothing real behind it. See ADR-0003 for the proposed TemplateDocument schema this import contract should target.

Finding 4 — Ad architecture

See the cross-cutting Ad architecture note on the admin-panel index page. Templates.dc.html's block library has zero ad/sponsor/ad-slot references; this screen isn't where that gap gets fixed until the product decision on the admin-panel index page is made. ADR-0003 proposes closing this with a first-party type:'sponsored' block type.

Confirmed compliant (do not re-flag)

  • Semantic tokens only: 0 hardcoded hex values found outside var(--x, #fallback) fallback patterns.
  • No native <select> anywhere; uses the dsSelect DS-native dropdown convention as mandated.
  • Version log shape matches the documented [ver, note, date, status, author] format exactly, with real seed data.
  • Plan-aware entitlement gating (PLANRANK, TPLMETA, per-block plan) is real and matches CLAUDE.md's description, not just described in prose.
  • Smart collections are implemented as real predicate functions (e.g. test: t => (t.adoption||0) >= 1500), not hardcoded lists.