Appearance
Setu Card
Status: 🟡 designed, not yet built (Phase 0 complete 2026-08-04; M1-M14 pending) · ADR:ADR-0003, ADR-0019 · Tracker: QRS-341–QRS-356
What this feature is
The universal public identity every QRSETU user gets — qrsetu.com/<slug> — rendered from a vendor-chosen, versioned template (a validated JSON manifest, never markup) applied over the vendor's own business data. It supersedes the earlier "Service Card" naming and is the platform's core differentiator, not a module: the first thing a customer sees when they scan a QR code, and the primary growth/virality surface (WhatsApp/social share previews, SEO).
Parity status
Android native · iOS native · Web PWA — N/A, this feature has no merchant-app-native surface to verify. The public card renders exactly once, in DOM, on Stack 1 (React Router v8 SSR, ADR-0011). There is deliberately no RN renderer for it — see ADR-0019 Decision 1. The only merchant-app-side surfaces this feature touches are the fidelity preview (expo-web-browser, opens the real card URL — verified on all three surfaces per the three-surface pass, since expo-web-browser behaves differently on Android Custom Tabs / iOS SFSafariViewController / RNW window.open) and the editing affordance (palette swatches + a publish-time thumbnail — a small RN-rendered surface, verified like any other apps/mobile/src/ui primitive). Neither is built yet.
Proactive-value answer
What action does it prompt? Two, both timely and derived from stored state, never fabricated:
- Publish momentum — an incomplete card (missing hours, no cover image, catalog empty) surfaces as a home-screen action item naming exactly what's missing, using the same
evaluateSetuCardTemplateReadiness()machinery built for template switching (ADR-0019 Decision 4) — one readiness check serves both "should I switch templates" and "is my card actually finished". - Seasonal refresh — once a vendor has published, an upcoming seasonal template's
available_fromwindow (e.g. Ganesh Chaturthi) becomes a dismissible nudge to refresh the card's look, entirely config-driven (QRS-356, 26.0.2+).
Where the intelligence comes from: setu_card_templates' own availability windows + the vendor's current profiles.card_template_key — no ad-hoc query, no fabricated claim. Neither nudge requires new data collection.
The model in one paragraph
A template is a repo-authored, CI-validated JSON manifest — never a database-authored row, never markup. setu_card_templates is a registry (composite (template_key, version) PK) seeded from the manifest files, carrying only metadata: status (active/deprecated/retired), feature_code, archetype_keys, available_from/available_until. A profile pins an exact (key, version) via card_template_key/ card_template_version — never "latest", because upgrades are vendor-initiated. Palette is a separate axis (card_palette_id), never a third theme scheme. Full reasoning: ADR-0019.
Why the old templates table was not reused
A templates/template_selections pair already existed in the baseline schema, with 5 placeholder stub rows and zero references anywhere in the tree — free to replace with no migration cost. It was not adopted, because it implements exactly what ADR-0003's own analysis had already rejected: html_structure text NOT NULL, css_styles text NOT NULL, javascript_code text — arbitrary string interpolation as the storage mechanism, which cannot even hold a JSON manifest (the HTML columns are NOT NULL). Dropped outright in the QRS-341 cleanup, not migrated.
Guarantees, and what enforces them
| Guarantee | Enforcement |
|---|---|
| A live vendor card never breaks | Manifests are immutable; a BEFORE UPDATE trigger refuses status='retired' while any profile is pinned; the renderer declares SUPPORTED_SETU_CARD_TEMPLATE_VERSIONS |
| Switching templates loses nothing | T12 (check:setu-card-templates) — a block may reference a field, never contain vendor content; evaluateSetuCardTemplateReadiness() pre-flight + mandatory graceful degradation |
| Every template is light+dark, contrast-safe | T2 + T3, schema-enforced and measured, not eyeballed |
| No PII reaches an anonymous template render | T9 — the manifest field allow-list mirrors ADR-0014's public projection exactly |
| The Catalog matches the vendor's palette | Free — CSS custom-property cascade on DOM, zero vendor configuration (ADR-0019 Decision 2) |
Known gaps this feature must close before it ships to Prod
- QRS-350 (G1) — no write path invalidates the Cloudflare cache today. A vendor edit with no visible effect reads as a broken product. Not deferrable.
- QRS-351 (G2) — the payments kill switch can be defeated by a cached card still showing a live pay button.
- QRS-349 — analytics (views/scans/shares) has no writer anywhere in the repo today; this feature gives
profile_analyticsits first one.
Related
- Catalog — the business-owned data this feature renders; owns the item data, this feature owns presentation only.
- ADR-0009 — archetype compatibility is config (
setu_card_templates.archetype_keys), not a redesign per vertical. - Template authoring specification — how a new template is generated and what CI validates.