Skip to content

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:

  1. 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".
  2. Seasonal refresh — once a vendor has published, an upcoming seasonal template's available_from window (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 ​

GuaranteeEnforcement
A live vendor card never breaksManifests 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 nothingT12 (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-safeT2 + T3, schema-enforced and measured, not eyeballed
No PII reaches an anonymous template renderT9 — the manifest field allow-list mirrors ADR-0014's public projection exactly
The Catalog matches the vendor's paletteFree — 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_analytics its first one.
  • 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.