Skip to content

Catalog ​

STALE — read current architecture state first (2026-08-08)

The Catalog's data layer is being replaced. M8 shipped against profile_items owned by profiles; the baseline moves it to catalog_items owned by workspaces, adds media, catalog_categories, variants, location-aware stock, tax and unit-of-measure. The editor UI is largely salvageable; the data layer is not. See current state §4 for exactly what must land first.

Status: 🟡 designed, not yet built (Phase 0 complete 2026-08-04; M8/M13 pending) · ADR:ADR-0019 · Tracker: QRS-341, QRS-355

What this feature is

The list of what a vendor sells — products, services, packages, listings — owned entirely by the vendor, independent of any Setu Card template. It reuses profile_items (already live on Prod, already populated with an archetype-variable metadata jsonb field bag) rather than a new business_items table, and it renders as a section inside whichever template the vendor has chosen, auto-hiding while empty.

Parity status ​

Android native · iOS native · Web PWA — not yet verified; the editor has not been built (M8). The editor is a standard apps/mobile merchant-app screen (list/add/edit over Card variant="list" + Sheet + TextField, reusing ReminderRow.tsx's row idiom) with no platform-divergent seam of its own — no camera, no native storage, no push. Verification, once built, follows the standard three-surface checklist in CLAUDE.md's "Cross-platform feature parity" — there is no anticipated divergence to flag at G0.

Proactive-value answer ​

What action does it prompt? An empty Catalog is a home-screen action item ("Add your first item so customers can order") rather than a passive empty state the vendor might never discover — CLAUDE.md's core product principle explicitly rejects a screen that only renders state. There is deliberately no "enable Catalog" toggle: the section exists the moment a vendor adds one item, and auto-hides on the public card while empty, so there is no configuration step between "I have things to sell" and "customers can see them."

Where the intelligence comes from: profile_items row count, read directly — no fabricated claim, no external data source.

The model in one paragraph ​

The Catalog is business data, not presentation. It is owned by the vendor independent of any template choice, which is what makes template switching lossless by construction (ADR-0019 Decision 4) — no vendor data ever lives inside a manifest, so switching templates never touches a catalog row. profile_items already carries item_type (product/service/menu_item/course/package), price/original_price/currency, category, tags[], availability_status (models finite inventory — out_of_stock/discontinued/ coming_soon), and an archetype-variable metadata jsonb (already populated in practice with shapes like {level, duration, max_students} for yoga or {servings, prep_time, vegetarian} for a restaurant). The only genuinely new column is position — an ordering the vendor controls. attributes jsonb was considered and rejected: it would duplicate the already-populated metadata jsonb, the exact QRS-249/284/287 duplicate-source-of-truth bug class.

Theme inheritance — why the Catalog needs zero configuration ​

The Catalog section renders inside the card's palette-scoped wrapper. On DOM, CSS custom properties inherit for free — bg-surface already compiles to hsl(var(--surface)) — so the Catalog picks up whichever palette the vendor chose for their card with zero wiring, zero vendor configuration, and no separate "Catalog theme" to keep in sync. There is no second theme, because the Catalog was never given one of its own.

  • Setu Card — the presentation layer that renders this section; owns the template, not the data.
  • ADR-0019 — the switching invariant (T12) that keeps Catalog data safe across template changes.
  • Tracker: QRS-341 (the rejected templates engine's vcard_customizations/ service_customizations tables are dropped, not reused, for the same reason profile_items is extended rather than replaced).