Emirates Ecom CMS
admin@rettermobile.com · approver

Template & data contract v1.2.0

The complete integration surface between this CMS and a site backend. Rendered from the same constants the compiler and gates run on, so this page cannot drift from reality. Decisions behind it: ADR-0005…0008 in docs/adr/.

1 · Slots — logic-free by design

Delivered templates contain no conditionals, loops, or expressions. A filler is mechanical string substitution — nothing more.

Inline product field:   {{p:<SKU>:<field>}}
Inline store field:     {{s:<CODE>:<field>}}
Inline context field:   {{c:<bindingId>:<field>}}   (the entity the host route serves)
Repeated block:         {{#list:<bindingId>}} …inner uses {{f:<field>}}… {{/list}}
                        ({{f:…}} resolves by the binding's entity)

Product fields

title Product title, HTML-escaped

price Formatted price, e.g. "AED 145"

category Catalog category slug

stock "In stock" | "Low stock - N left" | "Out of stock"

image Product image URL (relative catalog path)

Store fields since v1.1.0

name Store name, HTML-escaped

address Street address

city City

phone Phone number

hours Opening hours text

services Comma-separated services

maplink Google Maps URL for the location

2 · Binding types

fixed Explicit SKUs in fixed order (curated campaign features). Selection frozen at authoring; values live at serve time. Requires a fallback policy.

query Rules-based — category / maxPriceAed / sort / limit — resolved fresh on every request. Self-heals when products disappear.

hybrid Pinned SKUs first, a query fills the remainder to a target count (merchandising rails).

static No data — baked content the filler never touches (artwork, editorial copy, countdowns).

context Since v1.2.0 — the single entity the host ROUTE is serving (PDP templates). The filler resolves it from request context; inline access via {{c:<bindingId>:<field>}}. CMS previews use the authoring mock.

external Since v1.2.0 — a list whose membership the FILLER decides (host-driven grids). The template defines item markup; the optional hint states editorial intent. CMS previews use the authoring mocks.

Entities since v1.1.0

product Default. Pinned by skus; queries by category / maxPriceAed / sort / limit.

store Pinned by codes; queries by city / limit. No fallback policies — a missing store fails the integrity gate.

Unavailability policies (required on pinned product bindings)

hide Omit the item; the layout collapses gracefully.

show Render it with its out-of-stock state.

substitute Replace with the first in-stock result of the fallback query not already on the page.

3 · Manifest — the structured half of every page

Ships beside the template in every artifact. Declares every binding the page needs; the filler resolves exactly what is declared, nothing else.

{
  "contract": "1.2.0",
  "meta": { "title": "Rioja Tasting Weekend - MMI", "description": "Three landmark Riojas, 15-31 August." },
  "bindings": [
    { "id": "featured", "type": "fixed",
      "skus": ["MMI-RIO-001", "MMI-RIO-002", "MMI-RIO-003"],
      "fallback": { "policy": "substitute", "query": { "category": "rioja", "maxPriceAed": 120 } } },
    { "id": "grid", "type": "query",
      "query": { "category": "whisky", "sort": "price_desc", "limit": 8 } },
    { "id": "branch", "entity": "store", "type": "fixed", "codes": ["MMI-MARINA"] },
    { "id": "finder", "entity": "store", "type": "query", "query": { "city": "Dubai" } },
    { "id": "product", "type": "context" },
    { "id": "grid1", "type": "external", "hint": "primary category grid, host-curated" }
  ]
}
// Note: in the AUTHORING manifest, context/external bindings also carry a
// "mock" field (sample data for CMS preview). It is stripped from delivered
// artifacts — what you receive is exactly the structure above.

4 · Canonical entity shapes

What the adapter returns and what fill fields derive from (schema.org-aligned naming; see ADR-0007/0018).

product: { "sku": "MMI-RIO-001", "title": "Marques de Riscal Reserva 2019",
           "category": "rioja", "price_aed": 145, "stock": 62 }

store:   { "code": "MMI-MARINA", "name": "MMI Dubai Marina",
           "address": "Marina Walk, Dubai Marina", "city": "Dubai",
           "lat": 25.0772, "lng": 55.1369, "phone": "+971 4 368 2596",
           "hours": "Sat-Thu 10:00-22:00; Fri 14:00-22:00",
           "services": "Click and collect, Chilled room, Tastings" }

5 · Publish gates

Every artifact passes all of these before it can be staged or published. Failures return to the authoring chat.

contract Contract & manifest — Manifest parses, matches the pinned contract version, and every {{#list:ID}} block has a declared binding.

tokens Design tokens only — No hex colors or literal font-family in page code — all color/type through var(--...) tokens, so design-doc changes restyle every page.

a11y Accessibility — Alt text on every image; exactly one h1.

perf Performance budget — Template under 60 KB and at most 12 images.

seo SEO metadata — manifest.meta.title present and description of 20+ characters.

integrity Bindings resolve — Every pinned/query binding resolves against the live catalog: pinned SKUs/codes exist, queries return at least one item. context/external bindings are host-resolved and outside this guarantee (ADR-0019).

js Script & asset policy — Self-contained vanilla JS only — no external scripts, eval, inline handlers, or cross-origin fetch; images only from the app CDN or catalog paths.

6 · Delivery & the filler's duties

Artifacts are immutable and content-hashed. On publish we send a webhook; the site backend pulls /api/delivery/<site>, caches the artifact, and activates it atomically. Rollback on either side is re-pointing at a previous hash.

At serve time the filler must, and may only: resolve each declared binding from the live catalog (for context: the entity the route is serving; for external: the filler's own selection per the hint), substitute slot values (HTML-escaped), apply unavailability policies on pinned items, inject tokensCss, and honor the publish/expiry window in meta (timezone Asia/Dubai).

Conformance: golden pages + fixture data run in both sides' CI (ADR-0014). Green kit = preview fidelity guaranteed.