Skip to content

Read before changing

  1. This page · 2. ADR-0019 — rendering, preview, versioning, switching ·
  2. ADR-0027 — freshness, purge on write · 4. ADR-0003 (templates as manifests). The machine contract is this page's context: frontmatter; .claude/rules/domains/setu-card.md is GENERATED from it. Gates: check:setu-card-templates · check:parity (R8) · the apps/web Playwright suite · the run-web driver (the only thing that boots the card route). Your plan must answer: does the change touch the renderer, a manifest, or the cache path; is any vendor content about to live inside a template; which surface previews it. Related domains: web-app · ui-systemic · media · data-seam. Machine reads (the manifest's reads, in order): documentation/portal/design-system/public-setu-card-spec.md · documentation/portal/architecture/adr/0019-setu-card-rendering-preview-versioning-switching.md · documentation/portal/architecture/adr/0027-card-freshness-purge-on-write.md.

Public Setu Card — the sharing surface ​

Status: ready to send · Target: project 633dc069… "QR setu prototype" · QRS-457 · companion to store-catalogue-spec.md, which specified the merchant half

Scope widened 2026-08-09 on the owner's recommendation. This began as a catalogue-and-item-detail spec; the owner asked that the whole journey be validated rather than each half in isolation — "vendor → Store → public Setu Card → end customer" — with a card sample for every persona whose Store we designed. §3.1 accepts that and refines it: three archetype-shaped cards rendered with all eight personas' data, which covers every persona while paying O(archetypes) rather than O(industries).

THIS IS AN R1 DEFECT, NOT A NEW FEATURE

The public Setu Card currently renders no product images at all. catalog_item_media is ordered and carries alt_text, and get_public_catalogue already projects a full ordered image array with key, alt, width and height. The renderer — CatalogBlock.tsx, 38 lines — shows name, description and price in a two-column grid and discards the array. There is also no per-item detail view anywhere on the public card.

The launch vertical is festival_stall. An idol seller sharing a card that reads "Eco Ganesh Idol, ₹1,500" with no photograph is broken in exactly the way an estate agent's listing is broken, and photographs are the whole product for both. All 14 industries are affected.

1 · Why this spec exists, recorded honestly ​

store-catalogue-spec.md specified the merchant surface — where a business owner manages items. It never specified the sharing surface: what a customer sees when the merchant sends them a link. §4.4 of that spec even asks for a "preview as customer" affordance, and the thing being previewed was never designed.

That is the gap in one sentence, and it matters because the sharing surface is what competes with a WhatsApp forward. A merchant who still has to assemble details and photographs elsewhere and then send them by WhatsApp is not being helped by a Setu Card; they are being given a second place to type.

2 · Who this is for — the VISITOR, which is a different person from the merchant ​

Everything in store-catalogue-spec.md §2 described a merchant on a cheap phone in their stall. This surface has a different user: somebody who just scanned a QR code or tapped a WhatsApp link, on an unknown device, on a slow connection, with no account and no app, who will decide in a few seconds whether this business is worth contacting.

Consequences that must drive the design:

  • They arrive from a link with no context. The page has to establish what this is, instantly.
  • They are not logged in and must never be asked to be. A signup wall in front of a scanned card destroys the platform's growth mechanic (CLAUDE.md, category 3, anonymous-first).
  • Their next action is to contact the merchant, not to browse. Call, WhatsApp, enquire.
  • They will share it onward. The share is the growth loop, so it must be one tap.
  • Images are the content. For an idol, a saree, a plate of sweets or a flat, the photograph is the product description.

3 · The data contract — exactly what get_public_catalogue returns ​

Design against these fields and invent none. This is the public allow-list, and it is deliberately narrower than the merchant's view.

Per item: id · name · description · kind · pricing_mode · price_minor · compare_at_price_minor · currency · unit · availability · position · category · images[]

Per image: key · alt · width · height — ordered, first is primary

Plus: categories[] (name, slug), filtered to those carrying a visible item

⚠ Money is integer paise. 150000 is ₹1,500. Indian digit grouping (₹1,50,000).

⚠ price_minor and compare_at_price_minor are NULL when pricing_mode = 'on_enquiry' (QRS-456). The price is withheld by the database, not by the renderer — so "Price on enquiry" is a real state to design, not a display choice.

Not projected today: stock_quantity · attributes · hsn_sac · tax_rate_bp · track_inventory · variants. ⚠ These are NOT all the same kind of absence, and § 3.4 sorts them:stock_quantity is withheld permanently and on purpose (a competitor must not read inventory levels — availability is the public fact, the number is not), while attributes carries the per-industry EVALUATION fields that § 3.4 C establishes do belong on this surface in R1. The design shows them; the storage decision (QRS-452) proceeds separately.

⚠ Variants are not projected, so the public card cannot show the "From ₹1,500" label the merchant sees in their own Store. That is a merchant-vs-public divergence on precisely the seam ADR-0011 warns about, and it is an open decision (QRS-457) — do not design a "From ₹X" price on this surface until it is resolved.

3.1 · THE ARCHETYPE IS THE CARD SHAPE — three shapes, eight personas ​

Owner requirement (2026-08-09): for every industry we design a Store for, design the corresponding public card, so the whole journey is validated rather than each half in isolation — vendor → Store → public card → customer. Agreed, and it is the right instinct: the Store design surfaced four architecture defects, and this side has had none of that scrutiny.

One refinement, and it makes the exercise cheaper and a stronger test. Eight separate card designs would pay O(industries), the thing ADR-0020 exists to avoid. What actually varies is the visitor's primary action, and that is exactly what business_archetypes already encodes — "what you sell":

ArchetypePrimary entityThe visitor's primary actionCard shape
Goods — a stocked itema stocked itemBrowse, then order or enquireA photographed grid. The catalogue is the page
Time — a slota slotBookA service menu with durations and prices; the CTA is a booking, not a browse
Expertise — an enquiryan enquiryEnquirePortfolio-led, prices often absent; the card generates the lead, never the sale

⚠ That correspondence is the finding, and it should be tested rather than assumed: if the archetype is the card shape, then three card designs cover all fourteen industries — and if the exercise reveals a fourth shape, that is a defect in the ARCHETYPE MODEL and far more valuable than a fourth mockup.

So: three card shapes, rendered with all eight personas' data through the same businessProfile switcher Catalogue.dc.html already uses. That yields eight per-persona samples from three designs, and it puts the two halves of the journey side by side in one project where a data mismatch is visible rather than inferred.

The eight, and the shape each must prove:

PersonaArchetypeWhat its card must prove
Festival stall · Ganpati Bappa ArtsGoodsThe R1 launch vertical. Photographs carry everything; seasonal urgency
Boutique · Aarna HandloomsGoodsSize variants and a discount, without variants being publicly projected (§3)
Dairy · Gokul DairyGoodsNon-piece units (litre, g) read correctly next to a price
Cafe · Chai & CharchaGoodsA menu is a catalogue. R2 (Digital Menu) but the shape must hold
Salon · Glow StudioTimeThe CTA is book, not buy. Durations. published:false, so also the unpublished state
Yoga studio · Sahyadri WellnessTimeA course priced per month is not a product with a price tag
Real estate · Sahyadri PropertiesExpertiseR1. Listings, no stock, price often withheld, launch offers (§3.2)
Distributor · Shree Wholesale TradersExpertiseReorder-oriented; a catalogue whose buyer is a repeat customer
Jewellery · Anantha JewellersGoods⚠ The pricing stress test (QRS-465). Weight-and-purity specs, collections, one-of-one pieces, and every price on enquiry — because a jeweller’s price is a formula over the morning’s metal rate, which is R2. Needs no new card shape: it is goods, like the boutique

3.2 · Launch offers are the AGENT's own, and they are R1 ​

Owner requirement: a solo real estate agent must be able to publish a launch offer or a new-launch property independently, with distinct visual treatment rather than appearing as one more listing. This is not a builder-only capability; the builder case is R2 (QRS-455).

✅ The architecture already draws exactly this line, and on the live side of it. ADR-0025 D1 separates two things that look alike:

promo_slot (ADR-0004)offer (ADR-0025)
Whose contentThird partyThe vendor's own
Compliance riskReal — a restricted vertical may not carry itNone — advertising your own service is ordinary commerce
StatusInert, fails closed foreverLive

An agent's launch offer is a first-party offer, so no compliance gate applies. It renders on one card, their own, so it needs no targeting, no tree, and no campaign primitive — which matters, because real_estate's composition is ['catalogue','party','schedule','location'] and does not include campaign.

⚠ The cost of pulling this into R1, stated before anyone discovers it: a TIME-BOUNDED offer is a render-time temporal gate, which ADR-0025 D2 calls "the hard part" because it collides with the edge cache. Recommendation for R1: a manual featured flag with no expiry — the agent turns it on and off. No expiry means no render-time gate, no scheduled purge, no cache problem, and a solo agent with a handful of listings is well served by a toggle. Self-expiring scheduled offers arrive with campaigns in R2, where D2's outbox purge is already the plan.

So the design must show a featured listing with genuinely distinct treatment — a hero position, its own visual weight — and must also show what the section looks like with no featured item, since most merchants will have none.

3.3 · Rich media — video, galleries and before/after (QRS-463) ​

Owner challenge, 2026-08-09: video cards with in-card play and full-screen, left-right swiping between several videos, and before/after or portfolio galleries — across yoga, real estate, salon, educators and wellness.

⚠ These were not excluded. They were never considered, and that is a worse answer than a wrong one. The exclusion sentence in the first draft of §8 listed stock, variants, reviews, checkout and per-industry fields; video and before/after appear nowhere in it, because the spec was written from the Store's field list outward and the Store has no video. A spec derived from what the merchant editor happens to support cannot discover what the customer needs.

What the schema already supports, measured ​

RequirementState
Multi-image gallery, ordered✅ Exists end to end. catalog_item_media is ordered with alt_text; get_public_catalogue projects the full ordered array with dimensions. It is only unrendered (QRS-457)
Video as an uploaded asset~ Nearly free. media.content_type is a free-text MIME column and media.purpose already includes 'gallery', so a video/mp4 row is storable today. Genuinely missing: a duration_seconds, and a poster-frame reference so a card can render a still without downloading the video
Before/after pairing~ One column. It is a labelled role within an existing gallery, which is exactly QRS-458's catalog_item_media.role — widen the closed set to ('photo','before','after','floor_plan','brochure') and the pairing is expressible with no new table
Swipe between several videos✅ Presentation only. scroll-snap, the same zero-JS mechanism §4 already specifies for images

So the cost is one column on media, one widened CHECK on catalog_item_media, and a renderer. None of it is architecture.

⚠ But hosted video and embedded video are different decisions, and one of them fights D7 ​

Uploaded to our storageYouTube / external embed
Storage + egressWe pay, and video is orders of magnitude heavier than imagesFree
TranscodingNeeded for a 60-second clip shot on a ₹8,000 phoneTheirs
Tier 0 (0 KB, renders with JS off)Achievable — a <video> with a poster needs no JS❌ An iframe is third-party JS on the most performance-critical page in the product
Edge cacheOursTheirs, but the iframe blocks our first paint
⚠ Visitor privacyNothing leaves our originA YouTube iframe sends the visitor's IP and sets cookies before they press play

That last row is the one that matters and it is not a performance note. ADR-0019 D7 commits the public card to "counts only — no cookie, no IP, no fingerprint, no visitor identifier of any kind", with zero DPDP exposure and no consent banner. Dropping a YouTube iframe onto that page hands the visitor to Google before they have asked for anything, which forfeits the posture deliberately chosen and potentially the consent-banner-free status with it.

Recommendation: support both, with a click-to-load facade for embeds. Render a poster image and a play control; the third-party iframe is injected only on tap. That keeps Tier 0 at 0 KB, keeps first paint ours, and means no visitor data reaches a third party unless the visitor deliberately asks for the video. For R1, external embeds are the cheaper half (no storage, no transcoding, no bandwidth) and the facade is what makes them acceptable. Hosted upload can follow once the storage decision is taken.

3.4 · The exclusion list, re-sorted — "not R1" is not "never" ​

The owner's framing is the correct one and my single sentence collapsed it: six exclusions under one justification, and they do not share one. Re-sorted honestly:

A · Should NEVER exist on the public card — permanent, principled ​

ExcludedWhy it is permanent
Stock quantity numbersA competitor must not read a vendor's inventory levels. availability is the public fact; the number is not. Already documented in get_public_catalogue's own comment and enforced by the projection
A raw attributes key/value dumpArbitrary merchant JSON rendered verbatim to visitors is unvalidated content on the most public surface. ⚠ Note this is not the same as the typed per-industry fields in C — that distinction is the whole point

B · Not R1, with a named dependency — deferred, not refused ​

ExcludedThe actual dependency
Checkout / paymentA separate release with its own compliance work (Razorpay Route, the RBI PA constraint). Not a design question
Reviews and ratings⚠ My original exclusion had no rationale beyond "not in v2", and the owner is right that they matter for service businesses. The real dependency is moderation: reviews are user-generated content, which brings defamation exposure, a takedown obligation and the IT Rules grievance clock that starts at launch — and the plan already records that the only takedown mechanism today is hand-written SQL against Prod. Shipping UGC before a takedown path exists is the risk, not the schema. Revisit as soon as the report-abuse and takedown work lands
Co-broking / builder distributionThe network layer. Needs the QRS-454 discovery session (QRS-455)

C · I WAS WRONG TO EXCLUDE THESE — they belong in R1 ​

ExcludedWhy the exclusion was wrong
Per-industry evaluation fields (carpet area, configuration, floor, property type, amenities)⚠ The owner's distinction is exactly right and it is the one I collapsed: these are not optional extras like a variant or a stock count, they are the information a buyer needs to EVALUATE the thing. A property listing without a configuration and an area is not a simplified listing, it is an unusable one. And with solo agents now in R1 (QRS-460), this is R1 scope. QRS-459
VideoNever considered. See §3.3
Before/after and galleriesNever considered. Galleries already work and are merely unrendered; before/after is one widened CHECK. See §3.3
VariantsI presented an unresolved projection decision as a principle. catalog_item_variants simply is not in the public RPC today, which is a gap to close or close deliberately (QRS-457) — not a rule

The generalisable lesson, and it is the reason this section exists rather than a quiet edit: an exclusion list must state a REASON PER ITEM. One justification covering six items hides the two that have none, and it took the product owner reading it to find them.

How C is expressible without column sprawl, so the design is not blocked on it ​

The design needs to know which fields to show; the schema needs to decide where they live. Those are separable, so the prompt names a concrete real-estate field set as display-only and says nothing about storage. QRS-452's decision — item_attribute_schema per industry, validated at the write boundary, with expression indexes on the filtered keys — proceeds in parallel.

4 · What the design must deliver ​

4.1 The catalogue section on the card ​

  • Image-led, and the image is the primary element — not a thumbnail beside text. A visitor scanning a card should be reading photographs.
  • Category grouping when categories exist, using the projected categories[].
  • Per-item state that a customer cares about: out of stock, coming soon, discounted. Not draft (never projected), not stock counts (never projected).
  • Discount shown honestly — compare_at_price_minor struck through, with the percentage derived.
  • "Price on enquiry" as a first-class state, not an empty space.
  • It must look finished with JavaScript disabled — Tier 0 is 0 KB (ADR-0019 / D11). This is the most performance-critical surface in the product and it is edge-cached.

4.2 The item detail view — the piece that replaces a WhatsApp forward ​

This does not exist today and is the core of this spec. When a merchant wants to send one specific item to one specific buyer, this is what they send.

  • A gallery of all images, swipeable, with the primary first. scroll-snap, zero JS.
  • The full description, not a truncated line.
  • Price, unit, and availability stated plainly, including the on-enquiry case.
  • A contact action that is the point of the page — call, WhatsApp, enquire. This is what the visitor came to do.
  • A share action that shares this item, because that is what gets forwarded.
  • It must be linkable. A URL that resolves to this item is the deliverable; a modal that cannot be sent is not. Propose the URL shape (/:slug#item-… vs /:slug/item/:id) and say which and why — note that one cache entry per card is the current design and a second route changes that.

4.3 Empty and degraded states, which are the common cases at launch ​

  • An item with no image must look deliberate, never broken. Most merchants will have some.
  • An item with no description.
  • A card with no catalogue at all — the section auto-hides (ADR-0019); confirm nothing is left behind.
  • A slow connection: explicit dimensions, loading="lazy", no layout shift. CLS is the easiest Core Web Vital to fail on a page that is mostly imagery.

5 · Constraints ​

  • DOM web, server-rendered, Stack 1 — React Router v8 on Cloudflare Pages. Not React Native. A customer lands in their phone browser from a QR scan and must never be asked to install anything.
  • Tier 0 = 0 KB. The page renders and looks finished with no JavaScript. Tier 1 ≤30 KB gzipped, deferred after paint, for the share sheet, the gallery and the bottom sheet (ADR-0019 D11).
  • Mobile-first at 360×740, and it must hold on desktop too — a card gets opened on a laptop.
  • WCAG AA contrast on every text pair. accent is a fill token and fails AA as small text. Every image needs its alt, which the data already carries.
  • Semantic tokens only, zero hard-coded colours. The card renders light only in R1, but the palette system requires both schemes to be defined.
  • No em dashes or en dashes in any copy. Gated.
  • The vendor's language, with a correct lang attribute. English, Hindi, Marathi.
  • No account, no login, no signup wall. Anywhere.

6 · What NOT to design ​

⚠ Read § 3.4 first — it sorts these by REASON, and two items the first draft excluded have since been accepted into scope (rich media, per-industry evaluation fields). What remains out: checkout or payment (a separate release with its own compliance work) · reviews or ratings (they matter, but user-generated content needs a moderation and takedown path that does not exist yet) · stock counts (permanent) · variants (not projected — an open decision, not a rule) · per-industry property fields such as carpet area or configuration (undecided contract, QRS-459) · floor plans as a distinct media type (needs QRS-458 first) · builder or channel-partner attribution (R2, QRS-455) · anything requiring a field absent from §3.

7 · How this will be judged ​

  1. Would a merchant send this link instead of a WhatsApp album, and would it look better?
  2. Does the page render and look finished with JavaScript disabled?
  3. Is the visitor's next action — contacting the merchant — unmissable?
  4. Does an item with no image look deliberate rather than broken?
  5. Is every value on screen backed by a field in §3?
  6. Does it hold at 360×740 in Devanagari, and on a desktop window?

8 · The prompt to paste into Claude Design ​

Design the public Setu Card as an end customer sees it, in the existing prototype project. Add new screens under prototype/service-card/ alongside ServiceCard.dc.html, matching that folder's structure and using it as a reference for the card's overall anatomy. Do not modify ServiceCard.dc.html or any other existing file.

This is the PUBLIC, customer-facing web page a visitor lands on after scanning a merchant's QR code or tapping a WhatsApp link. It is a server-rendered browser page, not a mobile app screen. The visitor has no account and must never be asked to create one. Mobile-first at 360x740, plus one desktop view. Light theme.

The user is NOT the merchant. It is somebody who just scanned a code, on an unknown phone, on a slow connection, deciding in seconds whether this business is worth contacting. They arrive with no context, they will not browse for long, their next action is to contact the merchant, and if they like it they will forward the link. Merchants do this over WhatsApp today: if this page is not better than a WhatsApp album they will keep using WhatsApp, and the card adds nothing. Images are the content, whether the item is a hand-made idol, a saree, a haircut or an apartment.

THREE CARD SHAPES, EIGHT SAMPLE BUSINESSES. Use the same businessProfile enum control that prototype/mobile-console/Catalogue.dc.html already uses, with the same eight options, so the merchant's Store and the customer's card can be compared side by side. But do not design eight layouts: what varies is the visitor's primary action, and there are three of those.

  • Goods (Festival stall, Boutique, Dairy, Cafe) — the visitor browses, then orders or enquires. A photographed grid; the catalogue is the page.
  • Time (Salon, Yoga studio) — the visitor books. A service menu with durations; the primary call to action is a booking, not a browse. A course priced per month is not a product with a price tag.
  • Expertise (Real estate, Distributor) — the visitor enquires. Portfolio-led, prices frequently absent, and the card's job is to generate the conversation rather than the sale.

Add a ninth sample business: Jewellery · Anantha Jewellers (Goods). Collections rather than plain categories (Bridal, Daily wear), one-of-one pieces, a weight-and-purity specification block in the same generic per-industry section described below, and every item priced on enquiry — a jeweller’s price is computed each morning from the metal rate, so it is never a stored figure. This is the hardest test of whether the specification block and the on-enquiry state really are generic.

If you find yourself needing a fourth shape, say so explicitly and explain why. That would be a genuinely useful finding rather than a failure.

Design against exactly these fields and invent none. Each item has: name, description, an item type (product, service, package, menu_item, course or listing), a pricing mode of either fixed or on_enquiry, a price in integer paise (150000 is Rs 1,500, shown with Indian digit grouping as Rs 1,50,000), an optional "was" price for discounts with the percentage derived rather than stored, a currency, a unit (piece, kg, g, litre, ml, metre, hour, day, month, session), an availability state (available, out_of_stock, discontinued or coming_soon), a category name, and an ordered list of images each with alt text and known dimensions, the first being primary. Categories come as an ordered list. When the pricing mode is on_enquiry there is NO price at all — the server withholds it deliberately, so "Price on enquiry" is a designed state, not a blank.

NOT on this surface, and the reason differs per item so read them individually. No stock quantity numbers — permanent, because a competitor must not read a vendor’s inventory levels; availability is the public fact and the number is not. No checkout or payment — a separate release with its own compliance work. No reviews or ratings — not because they lack value, they matter a great deal for service businesses, but because user-generated content needs a moderation and takedown path that does not exist yet. No variants — they are simply not in the public data today, which is an open decision rather than a rule, so do not design a "From Rs X" price on this surface.

RICH MEDIA — required, and this is the part the first draft of this brief missed entirely.

  • Multiple images per item, as a swipeable gallery. The data already carries an ordered list with alt text and known dimensions. Use CSS scroll-snap so it works with no JavaScript.
  • Video. An item or the card itself may carry one or more videos. Design an in-card play control and a full-screen viewing state, and left-right swiping between several videos. Yoga demo classes, a property walkthrough, a salon transformation, an educator's sample lesson: for these businesses the video is the proof. ⚠ A video must render as a poster image with a play control until the visitor taps it. Never auto-play, never load a third-party player on first paint. This is both a performance rule (the page must look finished with JavaScript disabled) and a privacy rule (nothing may reach a third party before the visitor asks for it).
  • Before / after pairs. Where a business sells a visible change, two images must be presentable as a deliberate pair rather than two adjacent gallery items: a salon styling result, a fitness or wellness journey, a renovated property. Design the pairing and its label. Do not design a draggable split-screen slider; a simple, labelled, side-by-side or swipe-between treatment is what must work on a cheap phone.
  • A portfolio gallery at CARD level, not only per item. A photographer, an electrician or an agent has work to show that is not attached to any single catalogue item.

PER-INDUSTRY EVALUATION FIELDS — required for real estate, and design them as display-only. A buyer cannot evaluate a property without them, so a listing that omits them is not a simplified listing, it is an unusable one. For the real-estate persona show: configuration (1BHK / 2BHK / 3BHK), carpet or built-up area, floor and total floors, property type (apartment / plot / villa / commercial), property status (ready to move / under construction / new launch), locality, and a short amenities list. Present them as a compact, scannable specification block — not a long form, and not free text buried in the description. Show the same block empty, because many listings will not have every field.

⚠ Design this block as a GENERIC per-industry specification section that happens to be filled with real-estate fields, not as a bespoke real-estate component. Other industries will fill the same block with their own fields (a yoga course's level and duration, a vehicle's make and kilometres). The layout must not assume real-estate labels. FEATURED / LAUNCH ITEMS — required, and the real-estate case is why. A merchant can mark one item as featured, and a solo real estate agent publishing a new launch must be able to do this themselves; it is not a builder-only capability. A featured item needs genuinely distinct treatment — its own hero position and visual weight, not a small badge on a grid tile. Also show the same section with no featured item, because most merchants will have none. Treat it as a simple on/off state with no expiry date.

DELIVER THREE THINGS.

(1) The card's catalogue section, in each of the three shapes. Image-led, with the photograph as the primary element rather than a thumbnail beside text; grouped by category when categories exist; per-item state a customer cares about (out of stock, coming soon, discounted); discounts with the old price struck through; "Price on enquiry" designed; and the featured item given hero treatment.

(2) An item detail view, which does not exist today and is the heart of this request: what a merchant sends when they want one specific buyer to see one specific item. A swipeable gallery of all images with the primary first; the full description rather than a truncated line; price, unit and availability stated plainly; a contact action that is the visible point of the page (call, WhatsApp, enquire); and a share action that shares this item, because that is what gets forwarded. It must be a linkable page, not a modal that cannot be sent — propose the URL shape and say which you chose and why.

(3) The states that will be most common at launch: an item with no image, which must look deliberate and never broken because most merchants will have some; an item with no description; and how the section behaves on a slow connection, with explicit image dimensions and no layout shift.

Hard performance constraint that shapes the design: the page must render and look finished with JavaScript completely disabled. Any interaction layer (share sheet, gallery, bottom sheet) loads after first paint and must stay under 30 KB gzipped. This is the most performance-sensitive surface in the product and it is served from an edge cache. Prefer CSS scroll-snap over scripted carousels.

Visual direction: follow the existing QR setu design system and its tokens, and stay consistent with ServiceCard.dc.html's card anatomy. Apple-style soft corners, 3xl radius on outer shells and 2xl on inner panels. Plus Jakarta Sans for UI, Baloo 2 for display. The QR monogram gradient is reserved for brand emphasis only and must never be a flat accent. Semantic colour tokens only, zero hard-coded colours. Restrained, compositor-friendly motion. This page is a small business's shop window: it should feel considered and trustworthy rather than clever.

Constraints: touch targets at least 44px; WCAG AA contrast on every text pair (the accent token is a fill and fails AA as small text); every image carries alt text, which the data already provides; no em dashes or en dashes in any copy; all copy must work in English, Hindi and Marathi without clipping; and no login, signup or account prompt anywhere on the page.

Return, in light theme, primary artboard 360x740: the catalogue section for a Goods business, populated with about 8 items across 2 categories including one out-of-stock, one discounted, one with no image, and one featured · the same section for a Time business where booking is the primary action · the same section for an Expertise business (real estate) with listings, no stock, a withheld price, and a featured new launch · the same Expertise section with no featured item · the item detail view for an item with several images · the item detail view for an on-enquiry item with no price · the item detail view for an item with one image and a short description · the share affordance · and one desktop view of a catalogue section.

Judge your own output against: would a merchant send this link instead of a WhatsApp album, and would it look better; does it render and look finished with JavaScript disabled; is contacting the merchant unmissable; does an item with no image look deliberate rather than broken; does the featured item read as genuinely more important; does the Time shape make booking the obvious action rather than browsing; is every value on screen backed by a field listed above.

9 · Validation of the returned design (2026-08-09) — measured against the real files ​

Claude Design returned prototype/service-card/PublicCatalogue.dc.html and prototype/service-card/ItemDetail.dc.html. The owner rejected both as below reference quality. Every statement below was checked by reading the actual files, not inferred from the screenshots — which matters, because four of the owner's ten findings are true in substance and inaccurate as stated, and two capabilities reported as "completely missing" are in fact built but unreachable.

9.1 What the reference actually contains, section by section ​

prototype/service-card/ServiceCard.dc.html (390×864, variants Business | Individual, Light | Dark):

#SectionDetail
1Cover image, 208 pximage-slot + a two-stop scrim, brand pill top-left, Heart + Share circular buttons top-right
2Identity88 px squircle avatar overlapping the cover by −46 px, Open now · till 9 PM pill, name + verified badge, tagline, rating + review count, locality
3ActionsFull-width primary CTA (Book appointment / Get a quote), then Call and WhatsApp as a secondary pair
4Offer bandConditional, saffron→coral gradient, gift icon, dashed coupon-code chip
5Services & pricing4 rows: tinted icon tile, name, detail line, price, per-row Book / Enquire pill
6Gallery"Our work", 3-column square grid, 3 photos
7Reviews2 stacked cards + Write a review → Review.dc.html
8Pay via UPIUPI id in mono + Pay now → Pay.dc.html
9Connect5 socials as 48 px tinted tiles (WhatsApp, Instagram, YouTube, Facebook, X)
10Growth banner + Powered byGradient CTA to create a card
11Share sheetBottom sheet with the real QRDisplay component, Copy link / Share card

Eleven sections. The returned PublicCatalogue.dc.html has five, and drops 1, 4, 7, 8 and 9 entirely plus the availability pill, the rating, the verified badge and the primary CTA from 2 and 3. That is the measurable form of "it feels like a regression": it is one, and by roughly half.

9.2 The ten findings, adjudicated ​

#FindingVerdict
1Timeline photo experience with Heart + Share⚠ Substance confirmed, description corrected — and this is the one that must not go into a prompt as written. Heart + Share are real, on the cover; the gallery is a static 3-up grid. The word "timeline" appears zero times in every file in the project (all three ServiceCard variants, the yoga and real-estate cards). See 9.5 — a time-ordered feed is a different and much larger thing than a gallery
2Category tiles need horizontal interaction⚠ Partly wrong. The chips already scroll horizontally (overflow-x:auto). What is missing is that they are bare text chips, not tiles with imagery. Correct ask, wrong diagnosis
3Video completely missing⚠ Wrong as stated, right in effect. ItemDetail has poster → play → full-screen player with left/right chevrons. But it is invisible on the card (a 13 px play badge on two of three shapes), and the prev/next navigation is dead code: the model is item.video, singular, so hasMultipleVideos can never be true. The swipe you asked for is literally unreachable
4Before/After completely missing⚠ Wrong as stated. ItemDetail has it, 4:5 side-by-side with Before/After labels and a Bridal Makeup sample. But it renders below the gallery, one tap deep, and for a salon the transformation is the hero
5"Order on WhatsApp" is wrong✅ Fully confirmed, and worse than described. ItemDetail sets primaryCta = onEnquiry ? 'Enquire on WhatsApp' : 'Order on WhatsApp' with primaryHref = https://wa.me/.... For Goods and Expertise, a wa.me deep link is the only primary action on the page. Only Time routes to Book.dc.html. ⚠ customer-flows/Order.dc.html and Pay.dc.html exist in the same tree and neither generated file links to either — the native flows were available and routed around
6Hero missing social icons✅ Confirmed absent — but note the reference puts Connect near the bottom, and its icons are custom monochrome paths, not brand logos. Both halves of this ask are net-new, not restorations
7Visual quality is a regression✅ Confirmed and quantified — 5 sections against 11, and no cover image at all, which is the single largest cause of "basic"
8Google Maps / location missing✅ Confirmed — locality is text only. ⚠ And the answer must not be an embed; see 9.4
9Availability should be prominent✅ Confirmed, and stronger than stated — it is not buried, it is absent. The reference has it in the identity block
10Reviews should scroll horizontally✅ Confirmed, and stronger than stated — reviews are absent entirely, as is the rating. The reference stacks them vertically, so horizontal is an upgrade rather than a restoration

Score: six confirmed, four correct in substance with the diagnosis off. The central charge — that the output is a catalogue page wearing the name of a Setu Card — is correct and provable.

9.3 ⚠ What the review missed, and the largest item is three designs already in the project ​

uploads/ holds three complete service cards that were never used as reference, and between them they already contain eight of the ten things asked for:

UploadPersonaAlready designed
uploads/ServiceCard.dc.htmlPurohit / Vedic Acharya (a ninth persona nobody listed)Weekly availability with Open now / Closed today / Opens at · Directions + Location + map · Recent work gallery · Save contact (vCard) · Reviews + See all · Add-ons & extras · Verified
uploads/YogaTrainerServiceCard.dc.htmlYoga studioWatch Anytime · Recorded Library ← the video capability, already designed · Weekly Schedule · Memberships & Plans with MOST POPULAR · Live Class Access (Zoom) · Special Workshops & Retreats · Diet & Wellness Plans · Testimonials & Reviews · Frequently Asked Questions · Follow & Stay Connected
uploads/RealEstateServiceCard.dc.htmlReal estate▶ Virtual Tour ← the walkthrough capability · a real https://www.google.com/maps/search/… link with 📍 View on Map · EMI Calculator · nearby landmarks with travel times · RERA display · lead pipeline

Twelve sections in the yoga card against five in the new one. The prompt asked for a card and got a catalogue partly because it pointed at ServiceCard.dc.html alone while three richer, industry-specific cards sat one directory away.

Five further items the review did not raise:

  1. No UPI / Pay block. The reference has one. This is a money regression, not a visual one, and it compounds finding 5.
  2. No offer band. The "featured item" hero replaced it, but a featured item is not a promotional offer — ADR-0025's first-party offer is a separate block with its own time-bounding. Both are needed.
  3. i18n was dropped. All three uploads are bilingual (pick({en, hi, mr}) plus a :lang(hi),:lang(mr) Devanagari rule). The new cards are English-only, against a vendor-language requirement.
  4. ⚠ The uploads were built against design-system project 7d05f391-48e9-4acf-a580-5234ea591884 — a third project, which is neither the design system (37245d93) nor the prototype (633dc069). CLAUDE.md documents two. Their tokens may not exist in ours, so they are prior art for structure and section inventory, never for token names or icon craft (they use emoji: ▶️ 📷 💬 📍).
  5. The "multiple patterns per industry" ask needs bounding. Nine personas × N patterns is a combinatorial explosion, and it contradicts this spec's own §3.1 finding that the archetype is the card shape. The stress test that actually finds gaps is 3 shapes × 3 layout patterns = 9 templates, with the personas as data over them. That is the manifest-plus-palette architecture (ADR-0019 D1) doing its job; per-industry layouts would be the conditional sprawl ADR-0021 D4 bans.

9.4 ⚠ Four constraints the corrective prompt must carry, or the design cannot be built ​

Each of these is a place where a reasonable design instinct collides with a decision already taken.

AskThe collisionThe answer to put in the prompt
Google MapsAn embedded map is a third-party script, hundreds of KB, and it leaks the visitor's IP to Google — against ADR-0019 D7 (no visitor identifier) and the DPDP posture, and it cannot render at Tier 0 (0 KB)A tappable address block plus a View on map link to google.com/maps/search/…, optionally over a static map image. Never an iframe. This is exactly what RealEstateServiceCard already does
VideoA YouTube iframe on first paint is the same problem: third-party, heavy, tracking before consentPoster image is Tier 0 and must look finished with JS off. The player loads only on tap, youtube-nocookie, Tier 1
Heart / SaveWho is saving? An anonymous visitor has no account, and a server-stored per-visitor favourite is a visitor identifier, which D7 forbids. Consumer accounts (category 3) exist in the model but not in R1R1: local to the device, plus an aggregate count. It must never imply a synced favourites list across devices. Say so in the design, or it promises something we cannot honour
Reviews on the cardPublic UGC on a vendor's page is a moderation and defamation surface from day oneDesign the section, and design the empty state, but treat go-live as gated on the report-and-takedown path (QRS-320). Still an open owner decision

And one schema consequence: the current model allows one video per item, which is why the full-screen prev/next is dead code. Swiping between multiple videos requires catalog_item_media to carry video as ordered rows like images do — QRS-458 already owns the role column that makes this expressible.

9.5 ⚠ The one thing that must be resolved before the prompt goes out: "timeline" is ambiguous, and the two readings differ by an order of magnitude ​

Measured: zero occurrences of timeline, feed, post, story or moment in any file in the project. So the word describes something the reference does not have, and the two possible meanings are very far apart:

  • Reading A — the cover-plus-gallery experience (a hero image, Heart and Share on it, and a swipeable photo gallery). This is what the reference has. It is free: catalog_item_media and a card-level media collection already exist, it renders at Tier 0, and it is genuinely reusable across all 14 industries.
  • Reading B — a time-ordered "Updates" feed the merchant posts to. This is a new content type: its own table, ordering, authorship, moderation, freshness (every post purges the card per ADR-0027), and a merchant surface to write posts from. It is also the strongest engagement mechanic on the page, because it gives a customer a reason to come back — the same argument that makes jewellery rates compelling.

Recommendation: build A now and decide B separately. A satisfies "non-negotiable across every card" immediately and at no architectural cost; B is a feature with a spec, an ADR touch and a discovery question, and slipping it in as a design instruction would commit us to it by accident. The prompt below specifies A, and B is filed as QRS-469.

10 · The corrective prompt ​

⚠ This supersedes §8, which produced the rejected output. Two changes in how it is framed, because both failures above trace to framing rather than to execution: it names all four reference files instead of one, and it states the section inventory as a floor rather than a description.

Use the claude_design MCP to work in project 633dc069-6df8-4408-b625-068907c60c33.

Read all four of these before designing anything. The first is the quality benchmark; the other three are approved industry cards that already solve much of what is asked below, and they were missed last time:

  • prototype/service-card/ServiceCard.dc.html — the benchmark for quality, hierarchy and section inventory
  • uploads/ServiceCard.dc.html — Purohit card: weekly availability, Directions, Recent work, Save contact
  • uploads/YogaTrainerServiceCard.dc.html — Watch Anytime · Recorded Library, Weekly Schedule, Memberships, FAQ, Testimonials
  • uploads/RealEstateServiceCard.dc.html — ▶ Virtual Tour, 📍 View on Map linking to google.com/maps/search/…, EMI calculator, nearby landmarks

⚠ Take structure and section inventory from the three uploads. Do not take their tokens or icons: they were built against a different design-system project and they use emoji. All tokens and icons come from _ds/qr-setu-design-system-37245d93-4fa1-42d0-b765-53a7664d5129/ and prototype/service-card/icons.js, as prototype/service-card/ServiceCard.dc.html does.

What went wrong last time, so it is not repeated. PublicCatalogue.dc.html has five sections where the benchmark has eleven. It dropped the cover image, Heart and Share, the availability pill, the rating, the verified badge, the primary call to action, the offer band, reviews, the social links and the payment block. It is a catalogue page, not a Setu Card. The new card must be a superset of the benchmark's sections, never a subset.

Replace prototype/service-card/PublicCatalogue.dc.html and prototype/service-card/ItemDetail.dc.html. Leave ServiceCard.dc.html and everything in uploads/ untouched.

The section floor — every one of these appears on every card, in this order ​

  1. Cover image, full-bleed, with a scrim, the QR setu brand pill, and Heart and Share as floating circular buttons. Heart is a save affordance and shows an aggregate count; it is per-device and must not suggest a synced favourites list.
  2. Identity, overlapping the cover: avatar, business name, verified badge, tagline, rating and review count, locality.
  3. Availability, high on the page: Open now, closing time, or Closed · opens 9 AM, plus a tappable expansion showing the week. Follow the Purohit card.
  4. Primary action, full width, then channels as a secondary row. ⚠ See the action vocabulary below. This is where the last version broke.
  5. Social presence, in or immediately under the hero, using recognisable platform marks for WhatsApp, Instagram, YouTube, Facebook and X. Inline SVG only, no remote images. Keep them monochrome or single-tinted so five saturated brand colours do not fight the vendor palette.
  6. Offer band when an offer is live, with a coupon-code chip. This is separate from a featured item and both can appear.
  7. Category tiles, horizontally scrollable with scroll-snap. Tiles with imagery and a count, not bare text chips — that is the specific gap.
  8. Featured item, given genuinely more visual weight, labelled New launch for real estate and New batch for a course.
  9. Catalogue, in the shape the archetype dictates: Goods a browsable grid, Time a bookable list, Expertise larger enquiry cards.
  10. Photo gallery, horizontally swipeable with scroll-snap, full-screen on tap, Heart and Share on each photo.
  11. Video, as a first-class section and not only a badge: a horizontally swipeable row of video cards, each with a poster and a play control, opening full-screen with left and right swiping between videos. Design for several videos per business. Label it per industry, following the yoga card's Watch Anytime: property walkthroughs, training demos, transformations, product showcases, teaching samples.
  12. Before and After, where it applies (salon, fitness, wellness, home services). Not a generic gallery — pair the images and communicate the change: a draggable reveal or a tight labelled pair. For these industries it belongs high on the page, not buried.
  13. Reviews, as a horizontally scrollable carousel with the rating summary, plus a write-a-review entry point.
  14. Location: address block, distance or landmark line, and a View on map link. ⚠ Never an embedded map, never an iframe, never a third-party script. A static image behind the link is fine.
  15. Payment, following the benchmark's UPI block.
  16. Growth banner and Powered by QR setu, then the share sheet with the real QRDisplay component.

⚠ Action vocabulary — the product correction ​

WhatsApp is a contact channel. It is not how QR Setu transacts. The last version made Order on WhatsApp the only primary action on the page for two of three shapes, while prototype/customer-flows/Order.dc.html and Pay.dc.html sat unused in the same project.

  • Primary actions are QR Setu native: Order (Goods) · Book (Time) · Enquire (Expertise), plus Pay and Chat where they apply. Route them to prototype/customer-flows/Order.dc.html, Book.dc.html, Pay.dc.html.
  • Call and WhatsApp are secondary channels, styled as a supporting pair, exactly as the benchmark does.
  • No string on the card may read Order on WhatsApp, Enquire on WhatsApp or Get rate on WhatsApp. The action and the channel are separate ideas and must look separate.

Layout patterns — bounded deliberately ​

Produce three layout patterns, and vary them by archetype shape, not by industry: one for Goods, one for Time, one for Expertise. Then prove each with three different personas as data over the same layout. Nine industries do not get nine layouts. If a persona cannot be expressed as data over its shape's layout, say so explicitly rather than adding a layout, because that finding is more valuable to us than the design.

Personas to cover, using the existing businessProfile control: Festival stall · Dairy · Boutique · Jewellery · Sweet shop (Goods) · Salon · Yoga studio · Cafe (Time) · Real estate · Distributor · Purohit (Expertise).

Hard constraints ​

  • Renders finished with JavaScript disabled. Every image reserves its box from stored width and height. Interaction is an enhancement layered on top, never a requirement to read the page.
  • No third-party embeds of any kind on first paint: no map iframe, no YouTube iframe, no remote fonts, no analytics script. A video player loads only after a tap.
  • Light and dark, both complete. Every colour from a token.
  • Bilingual, English and Devanagari, following the uploads' pick({en, hi, mr}) pattern and their :lang(hi),:lang(mr) font rule.
  • No em dash or en dash in any copy, label, or example. Use a comma, a colon, parentheses, or two sentences.
  • Every value on screen must be a field a merchant actually fills in. Invent no metric, no follower count, no "trending" badge.

Deliver ​

Each of the three layouts in light and dark, at mobile and desktop, with: a rich card (many photos, several videos, reviews, an offer, before and after) · a sparse card (name, one photo, three items, no reviews, no video) so the design degrades visibly rather than breaking · an item detail view with a multi-image gallery and multiple videos · an item detail for an on-enquiry item with no price · and the share sheet open.

Judge your own output against: is every one of the sixteen sections present or deliberately and visibly absent; is the primary action QR Setu native rather than a WhatsApp link; does the video section demonstrate swiping between several videos; does before and after read as a transformation rather than two photos; does the sparse card look intentional; and is this richer than prototype/service-card/ServiceCard.dc.html on every axis the owner named: hierarchy, imagery, interaction, meaningful sections, premium feel.

Parity status ​

Stack 1 (DOM web, SSR), so the three-surface merchant-app checklist does not apply — this is one web codebase whose desktop/mobile-browser parity holds by construction (ADR-0011). The seam that does apply is the merchant's in-app preview versus this live page: per ADR-0019 the in-app "preview" opens this real URL via expo-web-browser and is never a second renderer, so there is nothing to hold at parity by hand. apps/web's existing Playwright layer (layout-invariants + axe-core) is the gate.

Proactive-value answer ​

This surface is where the merchant's effort turns into a customer action, so its proactive value is measured on the visitor's side rather than the merchant's: the page's job is to make contacting the merchant the obvious next step, and to make forwarding the link a single tap, because the forward is the platform's growth loop. On the merchant's side it closes the loop opened by store-catalogue-spec.md §4.3 — nudges like "3 items have no photo" only earn their place if a photo visibly changes what a buyer sees, and today it changes nothing, because nothing renders it.

Setu Card, Catalog and template architecture (operating-manual text) ​

Provenance — moved from CLAUDE.md on 2026-09-23 (QRS-1288)

This is the verbatim text of CLAUDE.md § "Setu Card, Catalog & template architecture" as of commit 00c1eca, relocated here under the context-architecture programme. Sentences of the form "this said X until [date]" are corrections recorded at the time they were made; the live rule is the corrected one. Retired vocabulary inside those corrections names what was retired and is not a live claim.

Setu Card, Catalog & template architecture [ENFORCED — ADR-0003/0019, designed 2026-08-04] ​

The Setu Card is the platform's core differentiator (see "The core product principle"), not a module — every decision here is designed to hold as the platform grows into orgs/seats/enterprise without a redesign. Full design record: documentation/portal/architecture/adr/{0003,0004,0011,0014,0019}-*.md. ⚠ Parts of this section ARE built — setu_card_templates shipped in a v2 migration and the renderer exists in apps/web; this said "nothing is built yet" until 2026-08-28 — this is Phase 0 documentation, written before the code, per D10 below.

  • ONE renderer, always DOM, always Stack 1. The public Setu Card never has an RN renderer, in R1 or ever. apps/mobile's in-app "preview" is two different products that share one name: a fidelity preview (expo-web-browser opening the real live card URL — already a dependency) and an editing affordance (palette swatches + a publish-time thumbnail, not a renderer, so it cannot drift). react-native-webview is rejected. SetuCardPreview.tsx (the onboarding delight moment) stays deliberately NOT template-aware — a check:parity rule bans importing the card-manifest schema from apps/mobile/**. Full reasoning: ADR-0019.
  • A template is a repo-authored, CI-validated manifest — a closed block vocabulary, never markup. No admin builder UI, no DB-authored content, in R1. Claude Design generates a manifest + palette (JSON data), never code — this is what makes AI-authored templates safe: there is no rendering logic to review, only data to validate. setu_card_templates is a registry table (composite (template_key, version) PK, status, feature_code, — ⚠ this called it card_templates until 2026-08-28, and no such table exists; the feature-scoped-naming rule this file enforces elsewhere applies to its own prose too) archetype_keys) seeded from the manifest files, never authored independently of them. ADR-0003's custom sanitized-HTML escape hatch is cut, not deferred — with no untrusted author it has no user.
    • ⚠ IN THE REPO A MANIFEST IS A .ts MODULE, NOT A .json FILE — there are ZERO card-template JSON files here (verified 2026-08-12; the only manifest.jsons in the tree are PWA manifests and an unrelated email one). The single manifest that exists is packages/schemas/src/setu-card-template-manifests/default.v1.ts, registered by apps/web/src/tiers/public/features/setu-card/setuCardTemplates.ts as 'default/1', and check:setu-card-templates validates it by import()ing the .ts. JSON is the authoring and handoff format on the Claude Design side; TS is the checked-in form, which is why the file header names a default.v1.manifest.json that lives in the design project and not in this repo.
    • The safety argument survives the format change, but only because of a property that must be actively kept: the module exports a literal data structure and nothing else. The moment a manifest contains a function, a conditional or an import beyond types, "there is no rendering logic to review" stops being true and the whole no-untrusted-author argument weakens with it. Treat a manifest .ts as JSON that happens to type-check.
  • Template versioning has three independent locks, because "a live vendor card never breaks" is a guarantee, not a promise: manifests are immutable (a version is never edited, only superseded); a BEFORE UPDATE trigger on card_templates refuses status='retired' while any profile still pins that version; the renderer declares SUPPORTED_SETU_CARD_TEMPLATE_VERSIONS and check:setu-card-templates asserts every non-retired row is in that set. Block props are additive-only within a schema version — a breaking change is a new block type or a new schema version.
  • Palette is a separate axis from Scheme, never a third value. type Scheme = 'light'|'dark' stays closed; theme.css's three css-interop invariants are untouched. On DOM, a palette is CSS custom properties on the card root, so the Catalog section inherits the vendor's palette for free — zero wiring, zero vendor configuration, because the Catalog has no theme of its own to keep in sync. RN gains a palette-aware colour channel (useCardColors(), ~40 lines over the existing imperative hsl() path) for its own small surfaces — never a card renderer. The public card renders light only in R1; every palette still must define both schemes (schema-enforced, contrast-gated) so visitor-selectable dark is additive later, not a migration. ⚠ prefers-color-scheme must never enter packages/tokens/src/theme.css — the public card following the visitor's OS theme is correct there and is the opposite of the in-app rule (QRS-201). It belongs in apps/web's own stylesheet if/when built.
  • Templates are switchable at any time, losslessly, forever — enforced by one invariant (T12, gated in check:setu-card-templates): a manifest block may REFERENCE a field, never CONTAIN vendor content. Switching a template is one UPDATE of the card row's template key/version — ⚠ NOT profiles.card_template_key: profiles was dropped by the ADR-0020 baseline and this cited it until 2026-08-28; no vendor data ever moves, because none was ever stored in a template. evaluateSetuCardTemplateReadiness() (pure, packages/domain, node --test) is a pre-flight courtesy before a switch; graceful degradation (a block with an absent required field hides, never errors) is the actual safety net for a field emptied afterward — testing only the pre-flight is the trap. Seasonal templates (available_from/available_until) gate selection only, never rendering — the same pattern already used for entitlement and for retirement.
  • The ad/sponsorship seam is reserved and INERT, not built. A promo_slot block type exists in the manifest schema; resolvePromo(slotId, ctx) always returns null and fails closed forever, because ad_restricted/compliance_profile does not exist — ⚠ and neither does the business_domains TABLE this cited; it has zero references in the live schema (the v2 model is industries) — and shipping a live resolver before it exists risks a compliance-restricted vertical (doctor, CA, loan agent) carrying an ad it legally cannot. See ADR-0004's 2026-08-04 amendment.
  • Known, currently-true gaps — read as open tracker items, never as "this already works":
    • ⚠ IMAGES STILL DO NOT RESOLVE, BUT THE REASON CHANGED ON 2026-08-28 AND THIS BULLET NAMED THE OLD ONE. It read "THERE IS NO PUBLIC MEDIA URL IN THIS REPO … _shared/r2.ts is a presigner with zero live importers … setPublicMediaBaseUrl() … nothing does yet". Three of those are now false: manage-media is a live Edge Function that imports _shared/r2.ts (index.ts:55) and is declared verify_jwt = true in config.toml; apps/mobile/src/lib/mediaBootstrap.ts calls setPublicMediaBaseUrl() from _layout.tsx at app boot; and apps/web reads the same origin via env.server.ts. ⚠⚠ AND A THIRD WAS ALSO FALSE — I WROTE IT INTO THIS FILE ON 2026-08-28 AND RE-MEASURED IT THE SAME DAY. It read "no bucket or origin is provisioned, so EXPO_PUBLIC_MEDIA_BASE_URL / MEDIA_BASE_URL are unset". A bucket IS provisioned: apps/mobile/.env.development sets EXPO_PUBLIC_MEDIA_BASE_URL=https://pub-…​.r2.dev, .env.production sets https://media.qrsetu.com, and deploy-web.yml:221 passes --var MEDIA_BASE_URL. Mobile images therefore DO resolve. What remains true is narrower and different: get_public_catalogue still projects storage_key rather than a URL, and apps/web never calls setPublicMediaBaseUrl (zero hits) — so the WEB consumer surface composes through an unset module singleton. That is a one-line wiring gap, not an unprovisioned bucket, and the two diagnoses send you to opposite places. The seam fails closed on purpose — a fabricated base puts a torn-page icon on every tile, which a buyer reads as the vendor's photo being bad rather than as a feature that has not shipped. For a Ganapati stall this is still commercially the largest single gap: nobody buys a ₹45,000 idol from a line of text. Read it now as provisioning outstanding, not nothing built. apps/web/src/app/analytics.ts now installs a GA4 sink through setAnalyticsSink, so on the landing route a track() call reaches Google Analytics (G-WF39L6Q5VH, production only — the measurement id is a per-environment var and its ABSENCE is the off switch, so dev and UAT ship no tag, no cookie and no request rather than being filtered). ⚠ It is mounted PER ROUTE, never in root.tsx, because root wraps the public Setu Card and D11 gives that route zero client JS. Everywhere else the original claim still holds — apps/mobile calls setAnalyticsSink nowhere, so every merchant-side track() still reaches the no-op console sink. ⚠ analytics_events, profile_analytics AND qr_codes all have zero references in the live v2 schema — this paragraph described row counts and columns for two tables that no longer exist. profile_analytics has 30 live rows and no writer. qr_codes.scans exists as a column, is never incremented, and nothing in the current tree reads it except legacy/. The card work (M11/QRS-349) gives profile_analytics its first writer and retires qr_codes.scans in favour of a real event stream — until that lands, none of the above should be read as functioning scan/view tracking.
    • ⚠ CACHE INVALIDATION IS WIRED — this bullet claimed the opposite until 2026-08-12 and the claim was false in two directions at once. It read: "No write path invalidates the Cloudflare cache. Verified zero cache/cloudflare/invalidate references in manage-profile, manage-settings, manage-reminder."Two of those three EFs were archived on 2026-08-09 and the third never touches a card, so the evidence cited could not have supported the claim even when it was written. What is actually true: _shared/cardCache.ts (invalidateCardCache) is called by three live Edge Functions — manage-item, manage-media and manage-setu-card (this named only the first and third until 2026-08-28) — it purges Cache-Tag: card-{slug} synchronously, and it is best-effort by design (a purge failure never fails the write). Read this as the general warning about "verified zero X" claims: a grep over the wrong file set is indistinguishable from a grep that found nothing.
      • ⚠ But two real gaps remain, so do not read this as G1/G2 closed. (1) The purge is unverifiable in practice today — CLOUDFLARE_ZONE_ID/CLOUDFLARE_API_TOKEN are unset on Dev (QRS-306, no Cloudflare account provisioned), so purgeByTag fails at getConfig() before any network call and the whole path is exercised only by its failure branch. (2) apps/web was serving no Cache-Tag at all until QRS-569 — the loader set it and React Router dropped it for want of a headers export — so even a working purge had nothing tagged to purge. Both halves had to be true and neither was.
      • The audit trail is the structured log, not a table (QRS-573, fixed 2026-08-12). It used to write to public_page_ops_cache_operations, dropped by the ADR-0020 baseline; a successful purge therefore recorded nothing anywhere while a failure logged correctly. Now card_cache.purged / card_cache.purge_failed / card_cache.purge_threw, each carrying the tag. ⚠ Deliberately NOT on public.outbox, which has a cache.purge topic and is ADR-0027's intended home: nothing drains it (no worker, no cron, no consumer — verified), so enqueuing would trade a working purge for rows nobody processes. It moves with the ADR-0025 campaigns drain worker.