Skip to content

Consumer build — end-to-end implementation plan (client + backend) ​

APPROVED BY THE OWNER 2026-09-04 — THIS PAGE IS THE PLAN OF RECORD

The owner approved this plan in full and asked for end-to-end implementation and delivery of the consumer side, with a report at every switch between client work and backend work. The recommended defaults in §12 are adopted unless the owner overrides one at the phase that needs it. It supersedes the morning draft of this page and the twelve waves in release-reconciliation.

Context ​

The owner asked for one plan that covers the entire Consumer side of QR Setu, reconciled against the latest Claude Design prototype and the current repository, precise enough that implementation can proceed without missed screens, architectural drift or rework. This page is the plan of record; every row below becomes a parity-contract row or a tracker id as its phase starts.

How this was produced (so every claim can be checked). The design was re-pulled live from the prototype project (633dc069-6df8-4408-b625-068907c60c33) on 2026-09-04: SCREENS.md, consumer.prompt.md, biodata.spec.md, every consumer artboard and module, the two public biodata and intro card pages, platform/meeting-providers.js. The repo was measured by four independent inventories (mobile tier, backend, shared packages and web, the design files) plus three read-only gates (check:screens, check:design-parity, the conformance ledger). Where the design and the repo disagree, both facts are stated. Where something could not be measured it is marked unverified.

Owner constraints carried unchanged. WhatsApp OTP on Meta only, no third-party messaging or auth provider · no assumed integration or dependency (stop and ask) · no effort estimates · consumer data never indexed · organisation developer accounts only · designs always fetched, never cached · splash intact · no tokens in output · existing-account recognition post-OTP · default theme light · marketplace OFF at launch (D-a) and OFF means ABSENT · Orders is being replaced by Meetings, build nothing new on Orders.


0 · Baseline, measured 2026-09-04 ​

FactMeasured valueConsequence
Design registrySCREENS.md round 40 (Home), 39 (bar restored), 38 (biodata audit closed), 35 (Meetings)The plan enumerates from this registry, not from the 2026-08-13 transcription
Consumer artboards in the design25 in prototype/consumer/ (+ BiodataPage, the intro-card public-page artboard in setu-card/, 3 documents in my-qrsetu/)Section 1 lists every one with a disposition
Screen ledger (check:screens)39 approved · 29 built · 1 stale · 2 unimplemented · consumer section: 11 rows, transcribed 2026-08-13⚠ The ledger is green and wrong: it has no row for MyQRSetu, Biodata, BiodataView, Meetings, MyProgress, intro card, the four makers or EmblemTrial. Re-transcription is a P0 item (R24)
Home parity contractconsumer_home 21 / 39 pass · 10 gap · 8 blocked · 0 route-verified (pull 2026-09-03)Home implements the retired round-16 stack (QRS-1006)
Consumer routes10 files under src/app/consumer/, flat Stack inside AppLockGate; no tab bar, no centre button, no /consumer/scanThe chrome is new development, not wiring
Consumer features6 (account · browse · chat · collection · home · notifications), no hooks/ anywhere, 9 test files / 79 testsEvery screen calls @qrsetu/data singletons inline
Data seams27 seams · 13 Supabase-backed. Consumer-relevant: consumer REAL · auth REAL · onboarding REAL · chat STUB (reads real RPC shapes, writes stub) · collection · consumerActivity · consumerPrefs · account STUBFour consumer seams have no backend behind them
Backend for consumersTables: users, media, conversations/messages/*, feature_grants, orders.buyer_user_id, slugs.user_id. None for biodata, meetings, notifications, push, rate limits, saved, progress, makers. RPCs: get_my_context, get_my_slug, resolve_slug_status (authenticated only), get_my_conversations, get_conversation_messages, 5 anon feed RPCs. EFs: manage-account (update_email · change_password · delete_account (HARD) · claim_slug), manage-media (workspace-only), send-auth-otp, whatsapp-webhook. No chat EF, no consumer upload path, no scheduler, no outbox drain, no pushSections 3 and 4 name every gap by object
anon/authenticated table privilegesZero (asserted by pgTAP). RLS is defence in depth; RPC + EF is the only access pathEvery consumer read is an RPC, every write an EF
Packages@qrsetu/domain: consumer/*, chat/*, auth/* exist; no biodata, meetings, scan, progress. @qrsetu/schemas: no consumer schema. @qrsetu/i18n: 8 consumer namespaces, none for biodata / meetings / makers / progressThe design's pure modules are ports still to be written
Native deps presentexpo-camera, expo-notifications, expo-local-authentication, expo-image-picker, expo-image-manipulator, expo-secure-store, react-native-svg, qrcodeAbsent: expo-clipboard, expo-sharing, react-native-view-shot, react-native-webview (each needs a size callout before install)
Deep linksscheme: qrsetu only; no intentFilters, no associatedDomains, no assetlinks.json, no AASAA scanned qrsetu.com/... opens the browser today
Analytics18 typed events; sinks only in apps/web (GA4; card beacon to an archived EF, QRS-734); no mobile sinkInstrumentation ships, delivery is a decision
Release recordreleases/26.0.1/release.json: status scoped, target date 2026-08-15 (passed), 121 change records, 0 builds, 0 approvalsThe consumer release needs its own record or a re-based 26.0.1
Open tracker rows ≥ QRS-90057, of which these bind this plan: 908/981 keystore · 909 hard delete · 914 person slug resolves as shop · 917 account type never reaches server · 920 Dev media transforms · 921 no rate limiting · 923 watermark · 924 Marathi review · 936 phone + prefix · 952 no SMS channel · 986 slug oracle · 990/1006 Home · 1007 retired name · 1008 scan dead link · 1009 UPI maker · 1010 PDF export · 1011 read state · 1012 media outlive account · 1013 Meta templates · 1014 ZoomEach is referenced where it bites

Dispositions used throughout: LAUNCH (in the consumer release) · HONEST-EMPTY (ships with the design's own empty state and no producer) · OFF (marketplace, absent by capability) · LATER (post-release phase P9, named) · SUPERSEDED (design says do not implement). Work classes: FIT (current architecture, wire only) · EXT (extend an existing component, named) · NEW (a component that does not exist) · FUTURE (design it now, build later).


1 · The complete consumer surface, from the live design ​

Every artboard and module in prototype/consumer/, prototype/my-qrsetu/, the two consumer public pages in prototype/setu-card/, and the platform modules the consumer screens import.

#Design fileRoundDesigned states (verbatim prop enums)Repo todayDisposition
S1ConsumerHome.dc.html40homeState: First run no location · Located anonymous · Registered nothing published · Registered no activity · Registered with activity · Launch variant one industry live · Loading · No businesses in this area · Error · marketplace: Follow platform control · On · Off · tab: home · saved · orders · account. 14 sections, 11 sheetsBuilt on the retired round-16 stack; 21/39 rowsLAUNCH, rebuild (F2)
S2MyQRSetu.dc.html35, 39Open · In discussion · Concluded · Nothing activated · Address just claimed · Loading · ErrorNot builtLAUNCH (F3)
S3Biodata.dc.html36, 38view content · people; contentState ×10 (Empty and starting · Partly complete · Complete · Editing a live profile · Validation on a required field · Saving · Saved · Loading · Could not save · Read only for a co-manager); accessState ×8 (Nobody yet · A basic link shared and opened · A request waiting · Released people with expiry dates · An expiry approaching · A release withdrawn · A subject removal request · Concluded, ready to reopen); subject Sister · Myself. 13 sheetsNot builtLAUNCH (F4, F5)
S4BiodataView.dc.html39viewer released · basic · owner; screenState Ready · Loading · Network error · Link withdrawn · Search concluded; familyLayout from profile · tree · vine · cards · list; subject ×2Not builtLAUNCH (F6)
S5setu-card/BiodataPage.dc.html38view Recipient page · WhatsApp link preview; scenario ×9 (Basic one cropped photograph · Basic text only · Released photographs · Request submitted and pending · Link expired · Link withdrawn · Concluded · Removal requested (subject view) · Loading); profileStatus Open · In discussion; devicePrompt auto · iPhone · Android; subject ×2; familyLayout ×4Not built (no consumer route in apps/web)LAUNCH (F7, web)
S6Account.dc.html35accountState Registered · Guest · Loading · Error; view hub · details · areas · notifs · appearance · privacy · help · abouthub + notifs built against a stub; other views are rowsLAUNCH (F11); areas OFF
S7Notifications.dc.html35demoState Default · EmptyBuilt against a stub; read state per deviceLAUNCH (F10)
S8ScanVerify.dc.html35demoCode Scanning + 9 payloads (shop sticker · that shop's UPI · unknown UPI · cancelled sticker · lookalike · closed account · ordinary website · unsecured pay page · plain text); cameraState Allowed · DeniedNot built; the ledger's one missing row; /consumer/scan is a dead href (QRS-1008)LAUNCH (F9)
S9Meetings.dc.html35view Calendar · Meeting · New meeting · Meeting account; connection Not connected · Authorizing · Connected basic · Connected paid · Could not connect; scenario Default · Nothing scheduled · Loading · Error · Offline; notification None · New invitation · 15 minutes beforeNot built; zero hits for "meetings" in the tierLAUNCH participant half, HONEST-EMPTY; hosting LATER (F12, C1)
S10Chats.dc.html23, 24view list · thread; listState Default · No conversations yet · No custom lists yet · Six custom lists; threadState Default · New from an item · Waiting for first reply · Closed now away reply · Offline · Failed message · Registration required · Blocked; mediaState Default · Uploading · Upload failed · Waiting for connection · Not downloaded yet · Microphone blockedList built; reads real; every write stub, no chat EFLAUNCH text chat; media/voice/forward LATER (F16, D-m)
S11OrderCode.dc.html35account The buyer · The relativeBuilt, route gated off (QRS-708); ledger says built and reachableOFF (marketplace) (F17)
S12WifiQR.dc.html40none (theme only)Not builtLAUNCH (F13)
S13LinkQR.dc.html40noneNot builtLAUNCH static, dynamic re-pointing LATER (F13, D-s)
S14UpiQR.dc.html40noneNot builtLATER, decision D-d / QRS-1009
S15PhotosQR.dc.html40noneNot built; needs an album page and mediaLATER (F13)
S16the intro-card editor and public-page artboards (their filenames carry the retired name, QRS-1007)40none (theme)Not built; revives the retired product name (QRS-1007)LATER, decision D-l (F14)
S17MyProgress.dc.html40scenario Default · First use · Nothing logged yet · Not shared · LoadingNot built; no entry point in CONSUMER_TABS, ACCOUNT_GROUPS or the More sheet; the coach read lives on a merchant screenLATER, decision D-j (F15)
S18Featured.dc.html22props cards · heading · hideHeading · cardWidth · intervalMsBuilt (FeaturedBlock.tsx)OFF at launch (mounted only by vendor view)
S19–S22ItemFeed · VendorFeed · ItemView · VendorView16–22as documented in the registryBuilt against real anon RPCsOFF (marketplace), kept reachable from the registry only
S23EmblemTrial.dc.html10report pagen/aDocument, not a screen
S24–S26ConsumerHome_v1 · Biodata_v1 · MySessions_v1———SUPERSEDED, do not implement
D1–D3my-qrsetu/Direction · Integration · BiodataAudit1, 2R, 37/38documents—Read as decisions; the audit's 15 items are closed in the design
M1consumer-data.js40CONSUMER_TABS, consumerBarTabs, CONSUMER_MORE_TOOLS, consumerMoreGroups, HOME_SECTIONS + homeSections(present,{marketplace}), PERSONAL_KINDS (5), CONSUMER_QR_MAKERS (15), FREQUENT_MAKERS (4), ACCOUNT_GROUPS, NOTIF_CHANNELS/TOPICS, QUIET_HOURS, APP_LANGUAGES, PRIVACY_ITEMS, HELP_TOPICS, APP_META, NOTIFICATION_KINDS (8), notificationsFor, actionItems, creationMeta, personalStories, LIFE_STATES@qrsetu/domain/consumer/* ports the round-16 subset (CONSUMER_HOME_SECTIONS, CONSUMER_QR_TOOLS, notifications, preferences)Port to @qrsetu/domain/consumer/* (F1, F2)
M2biodata-core.js3966 constants, 109 functions; FIELDS = 60 (about 11 · work 6 · family 10 · community 4 · kundli 9 · expect 12 · talk 4 · contact 4; basic 24 · released 32 · private 4; required 12); TIERS, PLAN {subjectCap 1, personLinkCap 6}, RELEASE_DAYS 90, EXPIRY_WARN_DAYS 10, REMOVAL_HOURS 72, MAX_PHOTOS 4, MAX_PEOPLE 10, MAX_CUSTOM 8, MAX_LEAD 3, MAX_OPENING 3, AGE_FLOOR 18, AGE_CEILING 75, THEMES (12), ORNAMENTS (3), FAMILY_LAYOUTS (4), LANGUAGES (mr hi en), LANG_METRICS, ENUM_FIELDS (12), NON_TRANSLATIONS (8), EMBLEMS (28, 13 seeded), LIFT_CONTRACTNothingPort to @qrsetu/domain/biodata/* (F4)
M3biodata-family.js39tree · vine · cards · list, one node model, caller's tier filterNothingPort as one shared component (RN + DOM) (F4, F6, F7)
M4qr-registry.js35TYPES order: order · item · order_receipt · biodata_share · biodata · identity · setu_card; RESERVED; parse/resolve/isForAudience; setOwnerKindLookup; STORES → web app + APK; deepLinkNothingPort to @qrsetu/domain/scan/registry.ts (F9)
M5qr-verify.js35VERDICT verified · unknown · caution · danger; parsePayload (upi · url · text · empty); lookalikeOf; verify(raw, ctx) with business · codeStatus · reports · vpaOwner lookupsNothingPort to domain; lookups from an RPC (F9)
M6meetings-mine.js + mobile-console/meetings-core.js + platform/meeting-providers.js35rule + exceptions; per-occurrence invite state; VIEW_MODES; invitations/upcoming/nextUp; NEEDS_ACCOUNT; PROVIDERS; joinRoute; shareChannels; NOTIFY_KINDS; fromAuthResponseNothingPort to @qrsetu/domain/meetings/* (F12)
M7progress-core.js40FIELDS weight/waist/hip, HABIT_SUGGESTIONS, GRANT_SCOPES, streak, sharedView, deleteEverythingNothingLATER (F15)
M8chat-core.js, chat-media.js, chat-media-engine.js, voice-notes.js23, 24shared with the merchant half@qrsetu/domain/chat/* ports organisation, messages, presence, searchText chat LAUNCH; media LATER (F16)
M9brand-qr.js, emblem-art.js, image-slot.js, qr-toast.js, confirm-delete.js—brand QR encoder with the Akaya plate; drawn Om; toast and destructive confirm@/ui has Toast, ConfirmSheet; QR is QRDisplay-style with qrcodeBrand QR = one @/ui primitive (design-first, ADR-0015) (F8)

⚠ Two design-internal contradictions, carried as decisions rather than resolved silently. (1) consumer.prompt.md and every inline registration sheet (ItemView, VendorView, Chats) still say email or Google, then a 6-digit code by email; the product and the onboarding registry (rounds 11–14) are WhatsApp OTP first, Google a permanent fallback, email never (decision D8). The consumer sign-in spine already built governs; the marketplace sheets are OFF at launch, and the Chats registration gate must reuse the real spine (QRS-912). (2) meeting-providers.js marks Zoom live while meetings-core.js says the connected path is designed and not built; see C1 and QRS-1014.


2 · Architecture the plan builds on, and what it extends ​

LayerFIT (reuse as is)EXT (extend, justified)NEW (does not exist)FUTURE (design now, build later)
Identity and sessionauth.users + public.users (display_name · avatar_media_id · locale · timezone · primary_context · status), handle_new_user, slugs registry with user_id, get_my_context, get_my_slug, claim_slug, WhatsApp OTP spine, PIN gate + AppLockGatemanage-account: soft delete (status='deleted', QRS-909), set_avatar, mark_notifications_read, export_data, report_code; primary_context set at sign-up (QRS-917); E.164 + on auth.users.phone (QRS-936)—global sign-out and session revocation (I2 of the auth review)
Data accesspackages/data seam idiom; RPC SECURITY DEFINER + REVOKE ALL FROM PUBLIC; EF writes with required idempotencyKey; TanStack Query; Zod in packages/schemasreal impls behind chat, consumerActivity, consumerPrefs (server-side prefs), accountseams biodata, meetings, scan, qrMakers (client-only)progress, introCard, photoAlbum
Mediamedia table, presigned R2 upload via manage-media, _shared/r2.ts, expo-image-manipulator, expo-image-pickermedia.owner_user_id + three-way XOR and the second constraint media_purpose_matches_scope widened (R28); purposes avatar for a user, biodata_photo, biodata_emblem; manage-media accepts an owner scope; private bucket wiringderivative pair (C4); tombstoning on delete (R8, QRS-1012)media.sweep drain (needs a scheduler, X4)
Messaging (WhatsApp)_shared/whatsapp.ts + _shared/communication.ts + communication_messages ledger; send-auth-otp proves the pathtwo utility templates (biodata subject notice; meeting invite/change), Meta review (QRS-1013)—campaigns dispatcher (ADR-0029)
Chattables conversations · messages · conversation_states · labels · reports, read RPCs, @qrsetu/domain/chat/*, realtime policy—manage-chat EF (send text · mark read/unread · set state · labels · block · report) — the write path the RLS comment names and nothing implementschat media (chat_* purposes exist, no presign path), voice, forward, star
Entitlementsfeature_grants with user_id scope, limit_value/period, useFeature three axesrows for consumer.biodata (subject cap 1, person-link cap 6); a num_nonnulls XOR CHECK on the seven scope columns (R27, core entity → architecture proposal page first)—plan rows for locked personal kinds (Invitation, Birthday)
Notificationsderived in-app list (consumerNotifications), expo-notifications local schedulingkinds biodataRequest · biodataRemoval · biodataExpiry · meetingInvited · meetingChanged · meetingCancelled; per-account read state table (D-c)localNotifications.ts port (tier-agnostic)push (no token table, no sender, entitlement stripped)
Rate limitingEF-side counts over idempotency_keys (per user); OTP budgets—public.rate_limits bucket + _shared/rateLimit.ts for anonymous reads and access requests (QRS-921)WAF / Turnstile in front of apps/web
WebReact Router v8 SSR, headers export, Cache-Tag purge, Worker runtime, THEMED_ROOT_SEGMENTS already reserves consumerRESERVED first segments (biodata, intro, photos, o, order, item, c, app, help, legal, download) kept in one domain constant and asserted equal to is_slug_reserved's seed (R30)apps/web/src/tiers/consumer/ (biodata public page, D5 greeting)intro card page, album page, report-abuse queue (Apple 1.2)
Gatescheck:design-parity P1–P7 (reachableFrom), check:screens, check:sql, check:rpc, pgTAP, Deno, jest-expo, device suitere-transcribe the ledger from round 40 (R24); a contract per new screendomain test asserting registry RESERVED == SQL reserved seedjourney walk over the consumer routes (QRS-630)

Rules binding every phase: anonymous-first (no session gate on browse; AppLockGate is a no-op without a session) · EF is the write-enforcement layer, RLS is defence in depth · every RPC name is a *_RPC constant · nothing in apps/mobile branches on plan, persona or industry · no privacy reassurance at the point of use (QRS-549) · never a store CTA in the app (ADR-0002) · Marathi first, the family's content never translated · the design's pure modules are ported once into @qrsetu/domain and consumed by the app and the web alike (the design's own "cannot drift" rule).


3 · Feature by feature ​

Each card answers the same questions: design scope → what fits today → built and needs wiring → client development → backend that exists → backend to modify → backend missing → schema/RLS/storage → state, navigation, data flow → auth/authz → supporting infra → browser vs native → states → security and privacy → tests → dependencies and sequence.

F1 · Chrome and navigation (tabs, centre button, More sheet, header) — LAUNCH, NEW ​

  • Design. CONSUMER_TABS = home · chats (badge) · saved · meetings (page) · orders (header:true) · account (header:true); consumerBarTabs() splits 2 + centre + 2; the centre button opens the More sheet ("Make and do") with three groups from consumerMoreGroups(): Make something (personal kinds except contact) · QR tools (15 makers) · Tools (Scan a code · My order code · Recently scanned · Share my contact). Header: bell with unread count, reminders clock, avatar → Account (never a tab). Same bar on Home, Chats, Account, MyQRSetu, BiodataView, feeds.
  • Fits today. Expo Router Tabs (used once, merchant), PressableScale centre action pattern, ActionLauncher sheet pattern, @/ui Sheet, sessionStore, AppLockGate host.
  • Built, wire only. Nothing: the tier is a flat Stack with 7 push sites, all from Home.
  • Client. consumer/_layout.tsx → AppLockGate → Tabs (Home · Chats · Saved · Meetings) with the centre slot rendered as a non-navigating action; ConsumerMoreSheet fed by the domain port of consumerMoreGroups(); ConsumerHeader (bell, reminders, avatar); consumerBarTabs() port is the only place the split is decided (design round 39 rule); ?tab= and ?sheet= deep links. Marketplace OFF: Saved renders the design's honest empty state, Orders stays header-only and reachable from Account (design), the QR-tools group shows only makers that have a screen (see F13).
  • Backend. None for the chrome. Bell count reads F10; reminders read F12.
  • State / nav. Tab state is route state; sheet state is local; unread badge = totalChatUnread from the chat seam. Tab screens stay mounted (the driver caveat in CLAUDE.md applies to tests).
  • Auth. Bar and sheet render for anonymous users; items that need a session (My order code, contact card, Make something) open the sign-in spine, not a wall.
  • Browser vs native. Identical (RNW). Web /app/consumer/... mount per ADR-0028.
  • States. Badge 0 / n / 9+; sheet loading none (data is static).
  • Tests. jest: bar renders consumerBarTabs() output, centre button opens the sheet, header avatar routes to Account, anonymous render; contract consumer-chrome.json with reachableFrom.
  • Depends on. Nothing. Unblocks every other feature's reachability. Phase P3.

F2 · ConsumerHome, the identity hub (round 40) — LAUNCH, rebuild (EXT of the existing screen) ​

  • Design. Section stack from homeSections(present, {marketplace:false}): identity (pinned; greeting, permanent address with brand QR and copy, pulse chip, Share my card / Confirm my number, My QR setu; guest variant claims the address; registered-nothing-published shows the three-step "make a marriage profile") · stories (rings per activated experience, state from personalStories(); dashed Make new) · focus "Waiting on you" (snap carousel from ownerPrompts(), count chip, dots) · today (sessions accepted, check-in due, unread announcement) · quickMake (Wi-Fi · UPI · Link · Photos) · progress (journey() ring, four milestones, next step) · yourCards (creations with creationMeta(): access facts for a biodata, never scan counts) · create (lead card + tiles from PERSONAL_KINDS) · weekRecap. The six market:true sections are ABSENT. 9 homeStates; sheets scan · code · more · create · recent · contact · reminders (the area · filters · reg · cats sheets are marketplace-only). Bell, avatar.
  • Fits today. IdentityCard.tsx + ClaimAddressSheet.tsx (claim flow real: get_my_slug, claim_slug, checkSlug), sessionStore, the D1 identical-availability copy, useFeature.
  • Built, wire only. Identity card and claim sheet (keep; extend with pulse chip and My QR setu).
  • Client. Replace the round-16 stack (search, categories, near you, QR row) with the round-40 stack; every section self-hiding; present computed from one overview query; carousels with snap and dots; the three-step first run; the reminders sheet reads meetings; Recently scanned and Share my contact sheets. Domain port: HOME_SECTIONS, homeSections, personalStories, creationMeta, creationsFacts, LIFE_STATES.
  • Backend exists. get_my_slug, get_my_context, claim_slug.
  • Backend new. get_my_biodata_overview() (record + access rollup + journey inputs; F4), get_my_consumer_activity() (F10), get_my_meetings() (F12). Today and weekRecap read progress check-ins and coach announcements that have no schema; both stay absent by the self-hiding rule and are contract rows blocked with the reason (R15).
  • Schema. None of its own.
  • State / data. One useConsumerHome() hook composing three queries with distinct staleTime (identity 5 min, overview 30 s, meetings 60 s); optimistic nothing; offline renders cached data with the design's offline treatment; error is an error, never an empty state.
  • Auth. Anonymous sees identity in guest mode (claim CTA) and nothing else that needs an account.
  • Infra. Bell count from F10; analytics home_viewed (id only).
  • Browser vs native. Copy address: expo-clipboard (absent, D-i) or the web clipboard API; brand QR via react-native-svg on both.
  • States. All 9 homeStates + loading + error; every section's absent state; carousel with 1 / n cards; guest vs registered identity.
  • Security. Address hardcodes qrsetu.com today (QRS-992): read the origin from config per env.
  • Tests. Contract consumer-home.json re-enumerated from round 40 (currently 21/39), every section row reachableFrom; jest per homeState; device cases for the copy control and carousels.
  • Depends on. F1, F3/F4 overview RPC (against the stub first). Phase P3, finished in P5.

F3 · My QR setu, the identity home — LAUNCH, NEW ​

  • Design. 7 states. Address card (initials, name, mono address, copy, brand QR 60 px, address note, Share my card sheet with a 150 px code and copy, gear → Account). Intro card, your bio link row (see F14). Activated: the biodata card with status chip + life chip, three access facts (waiting · released · fields never shared, or concluded facts), four card actions (People and access · Open the profile · See it as a family sees it · Open the page in a browser), concluded variant (See the concluded page · Reopen the search) with the reopen note. Nothing activated: the three start steps and Make a marriage profile. Available: Invitation · Birthday card, visibly locked, "On a plan", "Managed on qrsetu.com" (fifth rule, never a purchase CTA). Your identity rows → Account views (address · language · what reaches you · privacy). Footer: nothing here is indexed. Bar + centre inherited.
  • Fits today. Identity data (get_my_slug, get_my_context), useFeature for the locked kinds, Account routes, Sheet.
  • Client. New feature tiers/consumer/features/identity with MyQrSetuScreen; ShareCardSheet; LockedCapabilityRow from @/ui (fifth rule presentation).
  • Backend exists. get_my_slug, get_my_context, feature_grants (availability/entitlement rows for consumer.invite, consumer.birthday = not shipped).
  • Backend new. get_my_biodata_overview() (shared with F2).
  • Auth. Session required (a destination, reached from three places; guest sees the claim path).
  • States. 7 designed + guest.
  • Security. Contact card by QR is name + number only: the address page (D5 greeting) must render byte-identical for every state and never reveal the number to an anonymous visitor without the owner's action; the "scan gives your name and number" promise needs the greeting page to render the number only when the owner has enabled it (decision D-t).
  • Tests. Contract my-qr-setu.json (7 states, 4 card actions, locked rows); jest.
  • Depends on. F1, F4 overview. Phase P5.

F4 · Marriage biodata, the editor (content view) — LAUNCH, NEW ​

  • Design. Hub: live-editing warning, progress arc + headline from journey(), four thresholds, one weighted recommendation, a card per section (9 groups; status done / started / N needed / not started; stake required / recommended / optional; the tier that mostly reads it), see-it-as-they-see-it card, content-language chip, tier control in the section head, URL + copy + branded QR, the 12-theme How it looks strip, the publish declaration, liveness footer (live dot, last change, hand-off to Share; Publish only for first publication). Section screen: head, why a family cares, who sees it, one tip, fields, custom fields, the photograph block (About), the family-layout picker (Family), All sections / Next. Field sheet built from the field declaration: hint, tier, what an empty field means, choice · suggest · multi · toggle · list · date (year→month→day, AGE_FLOOR 18, AGE_CEILING 75) · derived (age), per-field visibility switch (required fields have none), clear, save. Custom fields: 6 types, 5 presentations, max 8, label ≤ 28, duplicate and standard-field conflict refused, up to 3 lead, live preview, 8 suggestions, reorder. People sheet: structured people (relation · name · detail · seniority · order), max 10, validatePerson. Photos: up to 4 slots, cover = first shown, show/hide, move, remove; 4:5 geometry; honest claim (PHOTO_CLAIM, no download control, no screenshot claim). Opening composer: collection (28 emblems, 13 seeded, tags filter never gate) · words (10 presets) · own picture (upload rules) · off; kuldaivat offered first; ≤ 3 emblems, ordered, each with optional words. Themes: 12 records (wash · pair · accent · ornament), no accent ink. Languages: UI in mr/hi/en; content never translated; formatValue only for ENUM_FIELDS; Devanagari metrics as tokens. States: 10 content × 2 subject; sheets 13.
  • Fits today. media + manage-media presigned upload (after EXT), expo-image-manipulator (derivative), feature_grants, Sheet, ConfirmSheet, Toast, TextField, ChoiceGrid, PillSelect, Calendar.
  • Client. Feature tiers/consumer/features/biodata with BiodataHubScreen, BiodataSectionScreen, FieldSheet, CustomFieldSheet, PeopleSheet, PhotoSheet, OpeningComposerSheet, ThemeStrip, TierSheet, ShareSheet, ConcludeSheet, ReopenSheet; consumerDraftStore (persisted, cleared on save). Domain port @qrsetu/domain/biodata/*: FIELDS, GROUPS, TIERS, INPUTS, ENUM_FIELDS, VALUE_MAPS, PLACES, LANG_METRICS, journey, sectionState/Status, nextAfter, completeness, validatePerson, validateCustom, conflictFor, publishedCustom, leadCustom, formatValue, isShippedOption, dobRange/ageFrom, photoSet/photoOrder/visiblePhotos, openingOf/openingEmblems/kuldaivatOffer/moveEmblem, look/ornament, words (owner vs subject copy), shownFields/isShown/canHide. Schemas: packages/schemas/biodata.ts generated from FIELDS.
  • Backend exists. Nothing for biodata.
  • Backend modify. manage-media: owner-scoped upload (owner_user_id, private bucket, purposes biodata_photo, biodata_emblem), key u/{userId}/{purpose}/{uuid}; feature_grants rows for the caps.
  • Backend new. manage-biodata EF: create · update (version) · set_photos · set_opening · set_custom · set_theme · set_family_layout · publish · unpublish · conclude · reopen · retire; get_my_biodata(id); get_my_biodata_overview(); reference minting (D6 Feistel permutation over marriage_biodata_ref_seq, key in an EF secret); biodata_templates (D7 sibling) + a default/1 manifest in packages/schemas (R16).
  • Schema / RLS. §4.1: biodata_subjects, marriage_biodatas (typed gate columns + jsonb values · field_tiers · hidden · people · photos · opening · custom_schema · custom_values, version, reference, template_key/version, family_layout, theme_id), one active per subject (partial unique index); owner RLS owner_user_id = auth.uid(); revoke all; COMMENT ON everywhere; citext slug FK to slugs.
  • Data flow. useBiodata(id) (30 s), useMutation per action with the read version; 409 → reload or overwrite; drafts persisted locally; every save recomputes journey() client-side from the same domain module the server validates with (R4: the EF rejects unknown keys and any field_tiers entry that loosens a private field).
  • Auth. Owner only; co-manager read-only is a later grant model (the design state exists; ships as blocked until a co-manager relationship exists in schema, decision D-u).
  • Infra. D3 subject notice over WhatsApp on publish (utility template, QRS-1013) with a view link and a removal link; publish returns notice: sent | failed (R17); analytics biodata_published.
  • Browser vs native. Editor is native/RNW only; photos via expo-image-picker (permission refused = designed state); image manipulation identical on both.
  • States. 10 content states, Could not save (what survived), Loading skeleton, Read only; field validation on required; every sheet's empty state; caps reached.
  • Security / privacy. Private fields unreleasable by construction (tierSees); the reference is never sequential; photographs private-bucket only; derivative watermark is the primary control (QRS-923); no PII in analytics; the family's words never translated or normalised; no picker for religion or caste (design's refusal, kept).
  • Tests. node --test for every port (both directions, including the Feistel bijection); pgTAP RLS (owner · other consumer · anon · merchant) and projection; Deno per action incl. version conflict and idempotent replay; jest per content state; contract biodata.json; device cases (picker, keyboard, Devanagari input).
  • Depends on. P1 schema, media EXT, feature rows. Phase P4 (backend) → P5 (client).

F5 · Biodata, people and access — LAUNCH, NEW ​

  • Design. 8 access states. The waiting request (what they asked, number confirmed by one code, how they used the link, what they see today, the honest "QR setu checked nothing else" line; Release / Decline), the forwardable link as a count + city with its limit stated, per-person shares with tag, expiry chip, note; Release more opens the release sheet with expiry choice; withdraw (destructive confirm); extend; the subject removal card the owner cannot refuse (REMOVAL_HOURS 72); conclude sheet → in-app celebration; reopen sheet restating both promises (zero releases, nobody returns). Plan line: free plan 6 person links, forwardable link not counted.
  • Backend new. biodata_shares (token hash only), biodata_access_requests, EF actions mint_share · release · withdraw · extend · answer_request (release | decline) · request_removal (signed token from the D3 message, no auth); get_my_biodata_shares(id); access rollup in the overview RPC.
  • Rules in schema/EF. expires_at = released_at + 90 days; lapse at read time; reopening after concluded withdraws every person share; the person-link cap is a feature_grants row; a withdrawn or expired token resolves to the closed page, never 404 (R2).
  • Notifications. kinds biodataRequest · biodataRemoval · biodataExpiry (in app) and the Today strip rows (actionItems() port); WhatsApp to the requester on release is a later channel (no template yet; state it).
  • States. 8 designed; celebration; removal countdown copy computed from removal_requested_at.
  • Security. Removal honoured by stopping service immediately (C3), erasure by runbook (X4); request phone numbers are E.164-validated (R7) and never shown beyond the owner.
  • Tests. pgTAP: share hash lookup, expiry at read time, one active per subject; Deno: every action; contract rows for all 8 states.
  • Depends on. F4. Phase P4 → P5.

F6 · Biodata, in-app reader (BiodataView) — LAUNCH, NEW ​

  • Design. Roles released · basic · owner; states Ready · Loading · Network error · Link withdrawn · Search concluded. Recipient: standing block (who shared, tier, lapse), photo carousel
    • lightbox (cover only on a forwardable link, reason stated), identity line, the family's own paragraph, work, expectations, community, family drawn in the creator's layout, kundli (released), place map (area, never a pin), who to talk to (call + message inside QR setu), footnotes, same page in a browser (QR + copy), growth CTA. Owner preview: tier switch (Anyone / Released) with the design's sentences, Edit and People in the band. Menus: recipient Keep this profile / Remove from my profiles, Report this profile (family not told who); owner Edit / People and access. Ask to see more is a signed request with three asks (family · community and kundli · the rest of the photographs) + note; pending state. Share sheet: code · copy · more apps; forwards the basic address only.
  • Backend. Reads get_public_biodata(p_slug, p_token) through the biodata-read EF wrapper (rate limit → RPC → record the open via service role, R1); access_request with requester_user_id set; keep needs a per-user list → biodata_kept (user_id, biodata_id, share_id), new, small; report needs a moderation destination → biodata_reports (append-only, admin-read later; QRS-803), new.
  • Message inside QR setu → a conversation with the family (a user, not a workspace): today conversations.workspace_id is NOT NULL and the consumer-to-consumer case has no model. This is a real gap (R18b). ⚠ SETTLED 2026-09-05 by ADR-0032, which locks the chat identity model. A person is reachable in-app by capability, and a conversation is between PRINCIPALS rather than between a workspace and a consumer, so person-to-person messaging is a modelled destination rather than a permanent redirect. R1 still routes the talk action to WhatsApp on the number the family released — the schema change is sequenced before manage-chat and the in-app path follows it, not this screen.
  • States. 5 designed × 3 roles; galleryLocked for basic readers.
  • Tests. Contract biodata-view.json; jest per role/state; the shared family component tested once in domain.
  • Depends on. F4, F5, F16 (D-v). Phase P5.

F7 · Biodata, the public page and the address greeting (apps/web) — LAUNCH, NEW ​

  • Design. /<slug>/biodata (basic) and /<slug>/biodata/<share> (per person), noindex; 9 scenarios incl. lapsed / withdrawn / concluded (never a 404), removal confirmation (subject view), loading; the WhatsApp link preview (first name, neutral line, wordmark, no photograph at any tier); language pills mr/hi/en (UI only); opening block; photo hero carousel with lightbox and attribution strip; family layout param from the record; theme via three custom properties; access-request form (name · relationship · phone · asks); who to talk to (call, WhatsApp help line); share pill + end-of-reading share card; app offer (web app or signed APK, no store); growth CTA; In discussion status note. D5 greeting at /<slug> for a person: byte-identical for claimed / unclaimed / private.
  • Fits today. SSR route family, headers export (noindex, no-store, Cache-Tag: biodata-{id}), the card's OG pattern, setu-card-redirect for merchants.
  • Client (web). apps/web/src/tiers/consumer/features/biodata/ routes :slug/biodata, :slug/biodata/:share, plus :slug → resolve_slug_owner_kind (merchant → 301 to the card, person → greeting); the same domain module and the shared family component; report abuse control (Apple 1.2, never cut).
  • Backend. get_public_biodata (tier-projected, private never), biodata-read EF wrapper (rate limit per token and per source hash, fail closed), access_request (anon, rate-limited), resolve_slug_owner_kind (anon-grantable, answers only workspace | user | null, identical for reserved / released / never claimed, R9, R23), presigned derivative URLs minted inside the wrapper for seconds.
  • States. All 9 + preview + greeting states.
  • Security. Enumeration-safe greeting; bearer share tokens compared by hash and state-checked on every read; rate limits; no-store; no photograph in any OG; report path.
  • Browser only. This surface is DOM only, by ADR-0011/0019.
  • Tests. run-web driver asserts headers on the built server; vitest per scenario from a fixture; axe; pgTAP tier projection never leaks private; the oracle byte-identity test.
  • Depends on. F4/F5 backend, P0 production 500 fix (X1). Phase P7 (routes) after P4.
  • Design. One brand QR encoder (brand-qr.js: matrix + Akaya "QR" plate, light plate in both themes) used by the identity card, My QR setu, biodata hub and reader, order code, meetings share, makers; share sheets always: code first · WhatsApp · copy · the OS share sheet; makers add Download image (PNG with caption). qr-registry.js deepLink() and STORES (web app + APK, no store listing). Sharing is by pointer, never by copy; no PDF or image export of a biodata (D-e / QRS-1010).
  • Fits today. qrcode (matrix), react-native-svg, expo-web-browser, setuCardShare.{native,web} pattern, QRDisplay design-system component.
  • Client. One @/ui BrandQr primitive (systemic surface → design pull + drift-ledger row, ADR-0015) consumed by every consumer surface; ConsumerShareSheet with the fixed channel order; a useShareByPointer(target) hook wrapping Share/navigator.share; PNG export on native needs a raster (no canvas): react-native-view-shot (absent, size callout) or server rendering. ⚠ Server rendering is rejected for the Wi-Fi and UPI makers: their payload (a router password, a UPI id with amount) would leave the device (R19). On-device capture only.
  • Deep links. Android app links need the release signing fingerprint (after QRS-981); iOS associated domains need the Apple team (D-U-N-S pending); apps/web serves assetlinks.json and AASA from public/; intentFilters in app.json; until then the web page's Open in app uses qrsetu:// and the design's honest APK/web copy.
  • Backend. None. Infra. Analytics share_minted, card_shared (ids only).
  • Browser vs native. Web: navigator.share with file support detection, clipboard API; native: OS share sheet, expo-clipboard (D-i).
  • Tests. Domain: deepLink, shareTarget; jest: sheet order; device: share on Android and iOS.
  • Depends on. BrandQr design pull. Phase P3 (primitive) → P7 (links).

F9 · Scan and verify + the QR registry — LAUNCH, NEW ​

  • Design. Camera viewfinder (dark surface in both themes), camera-denied full screen with the way through; every payload → registry first (order · item · order_receipt · biodata_share · biodata · identity · setu_card); a QR setu code opens after a half-second interstitial naming what was scanned (no verdict sheet), except that a setu_card/item still passes the verdict engine (cancelled sticker, unknown slug) and a slug the app cannot render refuses honestly; wrong audience (an order token) is refused plainly; non-QR-setu payloads → the four-verdict sheet with signals, payee block for UPI, business block (name · category · area · since; no scan count, QRS-576), scope note, actions (Open · Continue in my UPI app · Report this code · Scan another); report sheet with five reasons → moderation; scan history kept (last 25). Recently scanned sheet on Home reads it.
  • Fits today. src/ui/scanner/ shell (built for this, zero importers; expo-camera is its only importer, parity R9), getUserMedia fallback on web.
  • Client. Feature scan-verify on the scanner shell; domain ports scan/registry.ts (types as data, parse/resolve/isForAudience, RESERVED) and scan/verify.ts (verify(raw, ctx) with injected lookups); scanHistoryStore (device, capped); the opening interstitial; deep-link handoff to VendorView / ItemView / OrderCode / BiodataPage / MyQRSetu.
  • Backend exists. get_public_setu_card (business facts), resolve_slug_status (authenticated).
  • Backend new. resolve_slug_owner_kind(p_slug) anon (R23); verify_qr_lookups(p_slug, p_code, p_vpa) returning business · codeStatus · reports · vpaOwner as one read (the design's ctx), with codeStatus unknown until a minted-code registry exists (QRS-576) and vpaOwner null until vendor VPAs are stored, both stated in the response; manage-account report_code → a qr_reports append-only table (community reports are the other half of the safety layer; the count feeds the reported signal). Rate-limited (§2.6).
  • Browser vs native. Camera: native expo-camera; web getUserMedia with the denied state; the UPI action (upi:// intent) is native-only, on web it shows the payee and copy.
  • States. Scanning · denied · opening · wrong audience · 4 verdicts × signal sets · report form · reported; offline (lookups fail → unknown with the honest scope note, never verified).
  • Security. Fail closed: no lookup means no green tick; the registry order (biodata before setu_card) is asserted by test; the owner-kind oracle is identical for all three unknowns.
  • Tests. Domain: the 9 demo payloads reproduce the design's verdicts; jest; device (camera).
  • Depends on. F1 (permanent Scan entry), resolve_slug_owner_kind. Phase P6.

F10 · Notifications — LAUNCH, EXT ​

  • Design. Bell → list grouped Today / Earlier, derived by notificationsFor() from stored activity; 8 kinds (orderReady · orderConfirmed · paymentFailed · message · newItems · biodataRequest · biodataRemoval · biodataExpiry) plus meetings (invited · reminder · changed · cancelled); each links to its object; reading clears; Mark all read; empty state; Notification preferences → Account › notifs. Read state per account on lift (D-c, QRS-1011).
  • Built, wire only. NotificationsScreen + @qrsetu/domain/consumer/notifications.ts (5 kinds).
  • Client. Add kinds; swap consumerActivityService stub for the real impl; optimistic mark-read.
  • Backend new. get_my_consumer_activity() (biodata requests/removals/lapses, meetings invited / changed / cancelled, unread chats; orders only when the marketplace is on); consumer_notification_reads (user_id, notification_id, read_at); manage-account mark_notifications_read. No notifications table (the list stays derived).
  • Infra. In-app only at launch; local reminders are F12's; push absent (stated).
  • States. default · empty · loading · error · offline (cached list).
  • Tests. pgTAP for the reads table; Deno; jest; contract consumer-notifications.json.
  • Depends on. F4/F5 kinds, F12 kinds. Phase P6.

F11 · Account (profile and settings) — LAUNCH, EXT ​

  • Design. 8 views. hub: guest banner (Confirm your number) or Edit profile; Saved · Orders · Scans counters; ACCOUNT_GROUPS (Personal: My QR setu · Personal details · Your areas · Your contact code; Your activity: Saved · Orders · Recently scanned; Preferences: Notifications · Appearance and language · Privacy and data; Support: Help · About); merchant pitch card; Sign out (confirm, toast). details: change photo, full name, mobile (validation), email, Save, Delete my account (confirm). areas: marketplace. notifs: channels (In app locked on · WhatsApp · SMS · Email), topics (orders and payments locked on with why · chats · saved · offers · seasonal), quiet hours. appearance: theme Light · Dark · System, language mr/hi/en, rupee note. privacy: saves and recent scans (Clear) · history (Clear) · Download my data (Request). help: FAQ accordion, message support. about: wordmark, line, version, legal.
  • Built, wire only. AccountScreen hub + notifs against the stub; useSignOut (merchant) reuse; themeStore, localeStore.
  • Client. Remaining views; consumerPrefs real impl (server-side prefs on users or a consumer_prefs jsonb column so the phone and the web agree, decision D-w); details save; avatar via owner-scoped media; delete flow; counters (Scans from the device history; Saved/Orders absent with the marketplace).
  • Backend exists. set_my_display_name, update_email, delete_account (hard), get_my_context.
  • Backend modify. delete_account → soft delete (users.status='deleted', QRS-909) that retires biodatas, revokes shares, tombstones media, releases the slug (trigger exists), and stops purging the undeclared profile-pictures bucket; set_avatar; update_phone goes through the phone-change OTP (a full verification, not a field write); export_data (request recorded, delivery later); set_prefs.
  • Honesty of channels (D-p). Only In app and WhatsApp have a sender; SMS (QRS-952) and email do not exist. Render SMS and email absent (availability axis), never as toggles that do nothing.
  • Auth. Guest sees the hub with the banner; every write needs a session; delete requires the confirm and, per the app lock, a fresh PIN/biometric when set (decision D-x).
  • States. Registered · Guest · Loading · Error × 8 views; validation on details; toasts.
  • Security. Deletion cascade documented and tested (R8); export request logged; no privacy reassurance copy at the point of use (the design's privacy intro is a statement of what is stored, allowed).
  • Tests. Contract consumer-account.json (8 views × 4 states); jest; Deno for the EF actions; pgTAP for the soft-delete cascade.
  • Depends on. F1, media EXT, manage-account EXT. Phase P6.

F12 · Meetings, participant half (LAUNCH, HONEST-EMPTY) and host half (LATER, C1 / QRS-1014) ​

  • Design. Calendar view: Day · Week · Month · Upcoming as four reads of one timeline, week strip, month grid, the invitation queue above (accept / decline in place, one card per meeting, answer written to the next occurrence), notification banner (New invitation · 15 minutes before), sticky host action; Meeting view: host line (person vs business), when, live / imminent / ended, description, who is coming (Coming · Not coming · Invited; "N not on QR setu got the link"), Join (in app where the provider supports it, fresh link on open), Accept / Decline, Share (send in QR setu · copy · QR · other), reminder toggle (does not decline), host Cancel; New meeting: connect gate (Connect → Authorize with scopes in words → connected / could not connect), four-field form (title with suggestions, day, time, length with plan cap read from the account, repeat none/daily/weekly, invitees, More options); Meeting account: what the integration reported (not reported ≠ unlimited), Disconnect. Scenarios Default · Nothing scheduled · Loading · Error · Offline.
  • Client. meetings.tsx with ?view=; domain ports meetings/* (occurrencesFor, timeline, inviteState, occurrenceState, monthGrid, weekStrip, invitations, upcoming, nextUp, reminderPlan, joinRoute, shareChannels, NEEDS_ACCOUNT, PROVIDERS, fromAuthResponse); localNotifications.ts (7-day horizon, top-up on open, reopens the meeting, never carries the link); the create and account views render Not connected honestly with the connect gate present but the authorize step disabled until P9 (fifth rule: availability, "not ready", never a dead control).
  • Backend new (participant). §4.2 meetings, meeting_participants (server reminder prefs, R6), meeting_invite_states, meeting_joins; get_my_meetings(from, days), get_meeting_join(id) (fresh); manage-meeting answer · set_reminder · record_join. Producer: none in scope (C1): the pipeline is proven by pgTAP, Deno and one seeded meeting on Dev.
  • Backend later (host, P9). Zoom Meeting SDK for web in a QR Setu-hosted page inside a WebView (react-native-webview, size callout) + Server-to-Server OAuth on QR Setu's own account (H1), provider connections table with encrypted tokens, meeting-provider connect · create_room, meeting-join-token EF; the host's own account (H2) after Marketplace review. Decision QRS-1014.
  • Infra. In-app kinds; WhatsApp invite/change/cancel to numbers (utility template, QRS-1013); reminders local only; a web reader has no local notifications (stated).
  • Browser vs native. Calendar identical; local reminders native only; Join in app is the WebView page (native) or the same hosted page in a new tab (web).
  • States. 5 scenarios × 4 views × 5 connection states; empty invitation queue; offline shows the link we last saw.
  • Security. Join links read fresh; notifications never carry the link; invitee phone numbers E.164, indexed, erasure query documented (R5).
  • Tests. Domain (recurrence, DST, occurrence state, reminder budget), pgTAP, Deno, jest; contract consumer-meetings.json; device: a scheduled local reminder fires.
  • Depends on. F1 tab, P1 schema. Phase P6; hosting P9.
  • Design. CONSUMER_QR_MAKERS (15): wifi · upi · link · place · event · photos · doc · social · profile · feedback · poll · survey · assignment · learning · sos, each with cta and three steps; four have screens (WifiQR, UpiQR, LinkQR, PhotosQR); FREQUENT_MAKERS = wifi · upi · link · photos on Home; the More sheet lists all 15 opening the create sheet on that kind. Maker screens: inputs (Wi-Fi: SSID, password show/hide, WPA/WEP/Open with the WIFI: escaping rule; UPI: VPA validation, name, amount, note → upi://pay payload; Link: URL validation + scheme rule, optional label; Photos: title → album slug + three slots), branded QR preview (not-ready placeholder), Download image, share sheet (WhatsApp preview mock, more, download), footer copy.
  • Client. qr-makers feature with WifiQrScreen, LinkQrScreen (LAUNCH); the create sheet handoff; domain makers/payloads.ts (Wi-Fi escaping, UPI payload, URL normalisation) tested both ways; the More sheet shows only makers with a screen (availability axis), the other eleven are absent, not "coming soon" tiles.
  • Backend. None for Wi-Fi and Link as static codes. ⚠ The Link maker's copy promises a re-pointable code ("Change the address later and the printed code keeps working"), which needs a redirect record (personal_links, /<slug>/l/<id> web route) that does not exist (R20). Decision D-s: launch static with a design copy correction, or build the redirect (LATER). Photos needs an album table, a public album page, owner media and moderation (LATER). UPI is decision D-d / QRS-1009.
  • Browser vs native. PNG export: on-device raster (F8); web: canvas as designed.
  • Security. Wi-Fi password and UPI payload never leave the device and are never logged; makers store nothing server-side at launch.
  • States. Not ready · ready · invalid input · download success/failure · share fallback text.
  • Tests. Domain payload tests against the design's own strings; jest; device: download on Android.
  • Depends on. F8 BrandQr. Phase P6.
  • Design. the intro-card editor artboard (avatar, name, bio, links with presets Setu Card · WhatsApp · Instagram · YouTube · Facebook · any link, reorder, remove; quick actions Message me on QR setu, Let people book time, Pay me by UPI (VPA), Save me as a contact (phone); page QR, preview, share with download) and the intro-card public-page artboard at /<slug>/intro (links, action sheets: chat with a first-message gate, book request, UPI QR, vCard download, share, growth CTA).
  • Why later. Needs its own table (intro_cards, links, actions), a public SSR route, chat and booking request writes (F16, and booking has no consumer model), UPI (D-d), vCard generation, and a name: the design file revives the retired product name (QRS-1007); the product name is Intro card and the route /intro. Nothing in the repo may be called intro card (check:docs group #1).
  • Design it now. Reserve intro in RESERVED and is_slug_reserved; keep the My QR setu row as the design draws it but rendered as not ready (availability) until built.

F15 · My progress — LATER, decision D-j ​

  • Design. Self-entered record (weight, optional waist/hip), habits and a non-shaming streak, private before/after photos, a revocable grant to one business with scopes shown before confirming, settings (nudge, stop sharing, delete everything), first-use notice with [legal copy pending review]. Data on the phone until shared; the coach reads on the merchant lead detail while the grant stands.
  • Why later. No entry point in the round-40 navigation data (not in CONSUMER_TABS, ACCOUNT_GROUPS or the More sheet; back goes to Account); the coach read is a merchant surface outside this plan; the legal copy is a placeholder; the grant needs a progress_grants table and a sync of local entries at grant time. Home's today and weekRecap sections stay absent.
  • Design it now. Nothing in schema; note the dependency in the tracker.

F16 · Chats (consumer half) — text LAUNCH (NEW write path), media and interactions LATER (D-m) ​

  • Design. List (search over name, category, message text; derived pills All · Unread · Favourites · Archived + user lists; pins capped at 3; category chip from industry; media glyph), thread (day groups, per-message state incl. queued/failed with retry, typing, business-hours status, away system line once per closed period, shared item card with sold state, reply-to, New messages separator, jump to latest, composer that blocking disables), per-conversation actions (Pin · Favourite · Archive · Read/Unread · Mute · Add to list · Open their Setu Card · Report · Block), registration gate holding the draft; round 23 voice, photos, documents (transfer and cache states, permission refused, viewer); round 24 swipe reply, message info, long-press actions (Reply · Copy · Forward ≤ 5 · Star · Save · Info · Delete for me).
  • Built, wire only. ConsumerChatsScreen list; @qrsetu/domain/chat/*; read RPCs get_my_conversations, get_conversation_messages; RLS, block trigger, reports table, states and labels tables exist.
  • Backend new (LAUNCH). manage-chat EF: send (text | item, client_key idempotency) · mark_read · mark_unread · set_state (pin cap 3 enforced server-side · favourite · archive · mute) · create_label · delete_label · set_label_member · set_blocked · report; conversations.updated_at touch trigger and conversation_states.updated_at trigger (measured missing); the away reply emitted as a system message by the EF from business hours (once per closed period).
  • Client (LAUNCH). Thread screen; the registration gate reusing the WhatsApp OTP spine (the design's email steps are stale, QRS-912); optimistic send with client_key; realtime subscription through the existing realtime.messages policy.
  • LATER. chat media presign for chat_* purposes (a consumer has no upload path today), voice notes engine, viewer, forward/star/info (message_states exists).
  • States. 4 list × 8 thread states; media states later.
  • Security. Block is bidirectional and enforced by trigger; report goes to conversation_reports; no last-seen anywhere.
  • Tests. Deno per action incl. the pin cap and block; pgTAP already covers grants and blocks; jest; contract consumer-chats.json.
  • Depends on. F1. Phase P4b (EF) → P6 (thread).

F17 · Order code and the marketplace surfaces — OFF at launch ​

  • OrderCode (buyer / relative, grants, re-minted token) stays gated (QRS-708): its token must be server-minted (HMAC in an EF) and its grants need a table; both arrive with the marketplace.
  • ItemFeed, VendorFeed, ItemView, VendorView, Featured, Saved tab content, Orders tab content: absent by capability. Their routes remain for the registry's deep links (a scanned Setu Card still opens VendorView, as the design requires), and their inline registration sheets are not touched.
  • Rows in the ledger stay built; the parity contract marks marketplace rows blocked: D-a.

F18 · Auth, session and the app lock (dependencies, mostly FIT) ​

  • Exists: WhatsApp OTP spine, PIN gate with entry=open reachable via AppLockGate (P7 route-verified), 30-second foreground grace, clear-PIN-on-sign-out, resolveEntryRoute consumer branch.
  • Modify before biodata: primary_context='individual' must reach the server at sign-up (QRS-917, handle_new_user else-business fallback QRS-935); auth.users.phone + prefix (QRS-936); account created on OTP send, not verify (QRS-998) — a consumer who never verifies leaves a phantom row that could claim nothing but must not count; global sign-out (I2).
  • Security note: the PIN is device-local (no server state, measured); a lost unlocked phone has no remote remedy until session revocation exists (stated).

4 · Data model and backend catalogue ​

4.1 Tables ​

TableStatusNotes
usersFIT / EXTstatus='deleted' written by soft delete; avatar_media_id set by set_avatar; optional prefs jsonb (D-w)
slugsFITuser_id owner; resolve_slug_owner_kind reads it
mediaEXTowner_user_id; XOR num_nonnulls(workspace_id, conversation_id, owner_user_id) = 1; media_purpose_matches_scope widened for avatar(user), biodata_photo, biodata_emblem (R28); derivative_key; tombstone on delete
feature_grantsEXTrows consumer.biodata (subject cap 1 · person links 6); XOR CHECK over the 7 scope columns (R27) via the architecture-proposal protocol
biodata_subjectsNEWowner, relation, owner_is_subject, first_name, subject_phone (E.164), content_language
marriage_biodatasNEWtyped gates + jsonb bags (§F4), version, reference (D6), template_key/version (D7), family_layout, theme_id, status, life, timestamps incl. removal_requested_at; one active per subject
biodata_sharesNEW`kind basic
biodata_access_requestsNEWshare_id, requester_user_id, name, relationship, phone, asked, state
biodata_keptNEWrecipient's kept profiles (F6)
biodata_reports, qr_reportsNEWappend-only; admin read later (QRS-803)
biodata_templates + marriage_biodata_ref_seqNEWD7 sibling of setu_card_templates; D6 sequence
consumer_notification_readsNEWper-account read state (D-c)
meetings, meeting_participants, meeting_invite_states, meeting_joinsNEW§F12; rules + deltas, never expanded rows
rate_limitsNEWfixed-window bucket, service role only
conversations, conversation_statesEXTmissing updated_at triggers
LATERmeeting_provider_connections, personal_links, intro_cards, photo_albums, progress_*, order_grantsnot in the release

Every table: RLS on, revoke all from public, anon, authenticated, owner policies by relationship never by role, COMMENT ON for purpose and rules, expand-contract, citext via extensions..

4.2 RPCs (reads) ​

RPCGrantStatus
get_my_context, get_my_slug, get_my_features, get_my_conversations, get_conversation_messages, get_public_setu_card, 5 consumer feed RPCsas measuredFIT
resolve_slug_owner_kind(text) → `workspaceusernull`
get_my_biodata_overview(), get_my_biodata(uuid), get_my_biodata_shares(uuid)authenticatedNEW
get_public_biodata(text, text)called by the biodata-read EF only (service role)NEW (R1)
get_my_consumer_activity(), get_my_notification_reads()authenticatedNEW (the check:rpc forward-declared allowance is retired by this)
get_my_meetings(date, int), get_meeting_join(uuid)authenticatedNEW
verify_qr_lookups(text, text, text)anon (rate-limited through an EF)NEW

4.3 Edge Functions (writes) ​

EFActionsStatus
manage-accountupdate_email, change_password, claim_slug (FIT) · delete_account → soft (EXT) · set_avatar, update_phone (OTP), set_prefs, mark_notifications_read, export_data, report_code (NEW actions)EXT
manage-mediaowner scope, private bucket, biodata purposes, derivative pairEXT
manage-biodatacreate · update · set_photos · set_opening · set_custom · set_theme · set_family_layout · publish · unpublish · conclude · reopen · retire · mint_share · release · withdraw · extend · answer_request · request_removal · access_request (anon)NEW
biodata-readrate limit → get_public_biodata → record open → presign derivativeNEW
manage-chatsend · mark_read · mark_unread · set_state · create_label · delete_label · set_label_member · set_blocked · reportNEW
manage-meetinganswer · set_reminder · record_join (participant) · create · update_occurrence · cancel · share (P9)NEW
send-auth-otp, whatsapp-webhookas is; the utility templates are registry rowsFIT

⚠⚠ CORRECTED 2026-09-10 — THIS SAID IDEMPOTENCY KEYS ARE SCOPED (user_id, action, key) AND THEY ARE NOT, AND A CLIENT WRITTEN AGAINST THE FALSE VERSION 409'd EVERY BIODATA PHOTOGRAPH (QRS-1241). public.idempotency_keys has key as its SOLE PRIMARY KEY and no action column; _shared/idempotency.ts's own header states that "reusing one across operations is detectable instead of silently allowed" and refuses the second call with a 409. So a key is globally unique and every step of a multi-call flow needs its own: issue keeps the caller's key (a duplicate there leaves an orphan pending row), and confirm mints a fresh one, exactly as manage-media's uploadImage already documented. The measured cost of the wrong sentence: photographs uploaded successfully to R2 and never became visible, because the confirm was refused and the row stayed pending. Every action logs a structured event.

4.4 Storage, secrets, templates, config ​

  • R2 private bucket for owner media (bucket name from the row, never env, X1 of the WhatsApp review); CORS file updated for the private bucket's origins.
  • EF secrets: BIODATA_REFERENCE_KEY (D6), BIODATA_REMOVAL_SIGNING_KEY (C3), later Zoom S2S secrets.
  • Meta utility templates: qrsetu_biodata_subject_notice, qrsetu_meeting_update (QRS-1013), seeded in whatsapp_message_templates with used_by.
  • config.toml: the Send SMS hook is dashboard-only today (measured); record it in the promotion runbook as an auth_setting change class with a live probe, since no script can see it.

5 · Client architecture ​

Routes (apps/mobile/src/app/consumer/): _layout.tsx (AppLockGate → Tabs) · index.tsx Home · chats.tsx (+ ?convo=) · saved.tsx (honest empty) · meetings.tsx (?view=) · my-qr-setu.tsx · biodata.tsx (?view=content|people, ?new=1) · biodata-view.tsx (?viewer=) · account.tsx (?view=) · notifications.tsx · scan.tsx · makers/wifi.tsx, makers/link.tsx · existing order-code.tsx, vendor.tsx, items.tsx, item.tsx, vendors.tsx (registry-reachable only).

Features (tiers/consumer/features/): home (EXT), identity (NEW), biodata (NEW), scan-verify (NEW), meetings (NEW), qr-makers (NEW), notifications (EXT), account (EXT), chat (EXT), browse, collection (untouched). Each with screens/ · hooks/ · README.md — the hooks/ layer the tier README promises and no feature has today.

State. Server state in TanStack Query with one queryKey family per seam; client state in Zustand (sessionStore, appLockStore unpersisted, themeStore, localeStore, consumerPrefs mirrored from the server, consumerDraftStore persisted, scanHistoryStore capped). Optimistic updates only for mark_read, answer, chat send. Offline: cached lists with the design's banner; drafts survive a kill; no write dropped silently.

Domain ports (@qrsetu/domain): consumer/* (extend), biodata/*, meetings/*, scan/*, makers/*, all node --test, both directions, consumed by apps/mobile and apps/web.

i18n. New namespaces consumerIdentity, biodata (labels from LABELS.ui, NON_TRANSLATIONS kept verbatim), meetings, scan, makers; Marathi is the reference and human-reviewed (QRS-924); Devanagari metrics as tokens; the family's content is never passed through t().


6 · Security and privacy ​

SurfaceControl
All new tablesRLS by relationship; revoke all; pgTAP asserts a consumer with zero memberships reads nothing merchant-owned and nothing biodata-owned by another user, through the RPC surface (the existing tests grant table privileges first, which is the opposite of production, R36)
Share tokens128-bit, hash stored, state-checked on every read, closed page never 404
Public biodata readtier projection, private unreachable, rate-limited, no-store, opens recorded by the EF not the RPC
Enumeration/<slug> greeting byte-identical; owner-kind oracle identical for all unknowns; slug availability stays the D1 vocabulary
Mediaprivate bucket, presigned for seconds, derivative only at basic tier, no download control, no screenshot claim
Removalsigned single-purpose token, immediate stop of service, 72 h erasure by runbook
Deletionsoft delete cascade (biodata retired, shares revoked, media tombstoned, slug released), R2 sweep by runbook until a drain exists
Concurrencyversion on the biodata; 409 → reload or overwrite
Chatblock trigger, report table, no last-seen, EF-only writes
Scanfail closed, no tick without a lookup, report path, scan history device-local
Makerspayloads never leave the device
PIIphones E.164 and never beyond the owner; Sentry scrubs; analytics ids only
Rate limitsanonymous reads fail closed; authenticated owner actions fail open; timeouts allow-once-and-log (R10)
Store complianceno purchase CTA in the app; report abuse on every public page (Apple 1.2)

7 · Notifications, analytics, webhooks, observability ​

  • In-app: derived list + per-account reads; the Today strip is the proactive surface (no second prompt component).
  • WhatsApp: D3 subject notice (publish), meeting invite/change/cancel; both need utility templates through Meta review (critical path, QRS-1013); sent synchronously by the EF through the existing core; ledger row before send; publish reports notice outcome (R17).
  • Local reminders: meetings only; 7-day horizon; server prefs per participant.
  • Push: none (no tokens, no sender, entitlement stripped). SMS and email: none.
  • Webhooks: whatsapp-webhook records delivery statuses for the two templates (exists).
  • Analytics: vocabulary biodata_published · share_minted · release_granted · access_requested · meeting_answered · scan_resolved · maker_exported (ids and kinds only); the mobile sink is a decision (X7); the web card beacon points at an archived EF (QRS-734) and must not be copied.
  • Observability: Sentry DSN set for the release build; every EF action logs action + object id + outcome; a daily watchdog query for failed subject notices beside the payments one.
  • Scheduler: none exists (X4). Nothing in this plan depends on a cron: expiry is decided at read time, erasure is a runbook, reminders are local.

8 · Browser versus native ​

CapabilityAndroid / iOS nativeWeb (RNW /app)DOM web (apps/web)
Camera scanexpo-cameragetUserMedia + denied staten/a
UPI intent from a verdictupi://copy payee, no intentn/a
PNG export of a maker codeon-device raster (react-native-view-shot, size callout)canvasn/a
Clipboardexpo-clipboard (D-i)clipboard APIclipboard API
Share sheetOS sharenavigator.share / fallback copynavigator.share / WhatsApp link
Local remindersexpo-notificationsnone (stated)none
App lock / biometricsexpo-secure-store, expo-local-authenticationPIN onlyn/a
Photo pick + derivativeexpo-image-picker, expo-image-manipulatorfile input, same manipulatorn/a
Public biodata page, greeting, intro (later)opens in app via registrysamethe only renderer (SSR, noindex)
Join a meeting (P9)WebView on a hosted pagenew tabhosted page
Deep linksapp links after keystore / Apple teamn/aassetlinks.json, AASA, Open in app

Parity: every feature ships on Android, iOS and RNW together; native modules are proven only on the device suite; no exception path (CLAUDE.md).


9 · Testing, regression and validation gates ​

LayerProves
node --test (@qrsetu/domain)every port both directions: homeSections with marketplace off, consumerBarTabs split, journey, tier projection helpers, validatePerson/Custom, Feistel bijection over 10⁹, recurrence and occurrence state, verify reproducing the 9 design payloads, registry order (biodata before setu_card), RESERVED == SQL reserved seed, maker payload strings
pgTAPRLS isolation through the RPC surface; private never projected; one active per subject; share hash lookup and expiry; oracle byte-identity; rate-limit windows; soft-delete cascade; reads table; meetings deltas
Deno (test:ef)every action of manage-biodata, manage-chat, manage-meeting, biodata-read, manage-account: success · validation · authz · idempotent replay · version conflict · safe 500 · notice outcome
jest-expohooks against stubs; each screen per designed state; the bar renders consumerBarTabs(); AppLockGate no-op without session; drafts survive; route tests for src/app/consumer/* (none exist today)
Parity contractsone per new screen, enumerated from the design props, every route row reachableFrom; check:design-parity green; ledger re-transcribed from round 40 so check:screens counts these screens
apps/webvitest per public scenario from a fixture; axe; run-web driver asserts noindex, no-store, Cache-Tag on the built server
Device suitenew cases per screen appended to guides/device-test-suite-consumer-auth.md, run on Android and iOS: camera, clipboard, share, picker, local reminder, app lock over every new route
Static gatescheck:sql, check:rpc (allowances retired), check:naming, check:docs (no retired names), check:screens, check:design-parity, check:claims, lint, type-check

Regression rule: the journey walk (QRS-630) over the consumer routes runs on any change to src/ui/**, packages/** or the consumer _layout.


10 · Distinguished Principal Solution Architect review (independent, adversarial) ​

R1–R17 from the plan of record are carried unchanged and remain folded in (public read that wrote → EF wrapper; bearer tokens → state check; no concurrency → version; jsonb registry → server validator; fourth phone location → E.164 + erasure query; per-device reminders → server prefs; subject notice to a stranger → confirm step; media outlives account → tombstones + runbook; oracle surface; rate-limit SPOF; client derivative accepted; capability constant accepted; no meetings producer; idempotency scope; hidden Home dependencies; template manifest; notice failure alarm). The following are new, found against the measured repo and the full design surface.

#FindingSeverityAmendment
R18There is no chat write path at all. The RLS comment names "the chat Edge Function" as the enforcement layer; no EF touches conversations or messages; every consumer write is a stub. Chats is a tab and the biodata reader promises message inside QR setu.highmanage-chat EF in P4b (text, states, labels, block, report); media and voice stay P9 (D-m).
R18bConsumer-to-family messaging has no model. conversations.workspace_id is NOT NULL; the design's talk block opens a chat with a person. ⚠ Measured 2026-09-05: business-to-business is unrepresentable for the mirror-image reason (consumer_user_id is NOT NULL), so only one of four communication flows has a model.highCLOSED by ADR-0032. A conversation is between PRINCIPALS; the assessment and migration approach are in the proposal, sequenced BEFORE manage-chat so no code is discarded. Every chat table holds zero rows on Dev, which is what makes the change cheap now and expensive later. R1 keeps the WhatsApp route on the released number.
R19Maker PNG export cannot be server-rendered without leaking a router password or a UPI id.mediumOn-device raster only; the dependency is called out before install.
R20The Link maker's copy promises a re-pointable code that static encoding cannot deliver.mediumDecision D-s; never ship the copy against the behaviour.
R21A consumer cannot upload anything today (manage-media is workspace-scoped; the chat_* purposes are unreachable by any code). Avatar, biodata photos and emblems all depend on this.highmanage-media owner scope is a P1 item, before any client photo work.
R22The ledger is green and stale: check:screens transcribes 2026-08-13 and has no row for twelve designed consumer screens, so the third rule's own instrument understates the gap.high (process)Re-transcribe in P0; check:claims picks up the count; every new screen gets a row in the same change as its code.
R23resolve_slug_status is authenticated-only; Scan and the D5 greeting need an anonymous owner-kind answer.mediumresolve_slug_owner_kind anon, three-valued, oracle-safe (R9), rate-limited via the EF wrapper.
R24feature_grants has seven polymorphic scope columns and no XOR CHECK; the biodata caps rely on a rule that is a comment.mediumAdd the CHECK in P1 through the architecture-change protocol (core entity).
R25Provisioning correctness precedes the feature: primary_context never reaches the server at sign-up (QRS-917, fallback business), accounts are created on OTP send (QRS-998), phones lack + (QRS-936). A biodata owner provisioned as a merchant is the wrong category in every policy.highP1 fixes all three before the first biodata row exists.
R26Stale design premises are load-bearing in three places: email/Google inline registration sheets, MySessions, and consumer.prompt.md's five tabs. A builder reading the artboard alone re-implements the wrong sign-in.mediumThe onboarding registry (rounds 11–14) governs; QRS-912 write-back to the design project; contracts cite the governing artboard.
R27media_purpose_matches_scope was missed by the first plan (only the XOR was widened); the migration would fail on the second constraint.mediumBoth constraints in §4.1.
R28Account notification channels imply senders that do not exist (SMS, email). Toggles that persist a preference nothing reads are a promise.mediumD-p: render absent by availability; only In app and WhatsApp are channels.
R29delete_account purges a Supabase Storage bucket no migration declares and hard-deletes the auth row; the soft-delete rule is finalised and unimplemented (QRS-909).highP1 soft delete with the full cascade; the stray bucket purge removed.
R30Reserved first segments live in two places (the registry's RESERVED and is_slug_reserved's seed) with no test binding them; intro and photos are new reservations.mediumOne domain constant, a test asserting equality with the SQL seed, both updated in P1.
R31The centre-button "QR tools" group lists 15 makers and the design opens the create sheet for all of them; eleven have no screen. A grid of eleven dead tiles is the fifth rule's exact failure.mediumShow only makers with a screen; the rest absent by availability (stated in the contract).
R32MyProgress has no entry point in the design's own navigation data and its coach read is a merchant screen; building it now creates a floating surface.lowLATER (D-j), noted in the tracker.
R33Three retired-vocabulary collisions in the design (two artboard filenames and one store key) would fail check:docs the moment they are transcribed.lowProduct name Intro card, route /intro; QRS-1007 write-back.
R34get_my_context carries no slug, features or avatar URL; My QR setu and Account need three reads on open.lowThe overview RPC returns identity + slug + biodata rollup in one call; get_my_context unchanged.
R35Consumer isolation is tested only by granting table privileges first; the production path is RPC-only and untested for cross-user reads.mediumpgTAP through the RPC surface in P1 (R36 above).
R36Version and migration: the biodata registry will change (rounds 7–9 added fields); the jsonb bags absorb additions but a renamed field id is a data migration.lowField ids are frozen at P1; renames are new ids + a backfill migration; the EF ignores unknown keys but logs them.
R37Performance: the hub renders 60 fields, 9 sections and a journey on every keystroke in the design.lowjourney() is pure and memoised per section; saves are per sheet, not per keystroke; the overview RPC is one round trip.
R38Operational: the two utility templates and the Zoom decision are the only external items; both have no SLA.high (schedule)Submitted in P0; the plan ships honest-empty if they slip, and says so.

Verdict. Implementable on the current architecture for P0–P8 with the components named in §2 as EXT/NEW; the two external queues and the confirmations in §12 are the only items that can move the date. R18, R21, R25 and R29 are defects in the previous plan of record and are folded in above.


11 · Phase roadmap, dependencies, acceptance, gates ​

Sequenced by irreversibility first, then by what unblocks the most. Backend phases run against Dev in parallel with client phases against stub seams.

PhaseScopeDepends onAcceptance (a command produces it)Gate
P0 · GroundProd card route 500 (X1) · upload keystore (QRS-908/981) · confirmations §12 · submit the two Meta utility templates · re-transcribe the screen ledger from round 40 (R22) · write the consumer release record—curl the production card → 200/404 not 500 · check:screens prints the true unimplemented count · template ids in the registry seed · check:release greenowner sign-off
P1 · Irreversibles (backend)provisioning fixes (R25) · soft delete cascade (R29) · media owner scope + both constraints (R21, R27) · feature_grants XOR via proposal (R24) · biodata schema + reference + templates + grants · shares/requests/kept/reports · notification reads · meetings schema · rate limits · resolve_slug_owner_kind · reserved-segment parity (R30) · pgTAP through the RPC surface (R35) · erasure runbook (R8)P0 decisions C2, C3test:db green incl. negatives · check:sql green · Dev read-back of every object · list_migrations after the first apply · check:arch-proposal greencheck:release change records
P2 · Seams, schemas, domain portspackages/schemas biodata/meetings · packages/data seams (stub and real, barrel binds real) · @qrsetu/domain ports for consumer, biodata, meetings, scan, makers · check:rpc allowances retiredP1node --test green with both-direction tests · check:rpc 0 new allowances · check:namingtype-check
P3 · Chrome, Home, BrandQrF1 tabs + centre + More sheet + header · BrandQr design pull + drift row · F2 Home round-40 stack against the stubP2 (stub)contracts consumer-chrome.json, consumer-home.json re-enumerated, every route row reachableFrom · check:screens builtcheck:design-parity
P4 · Biodata backend + chat write pathmanage-biodata all actions · biodata-read · overview/shares RPCs · derivative path (C4) · D3 notice · manage-chat (R18) · missing updated_at triggersP1, P0 templatestest:ef green · a publish on Dev delivers the notice to a handset · released token reads released, basic never sees private · a consumer sends a text message on Dev and the merchant thread receives itpgTAP tier tests
P5 · Biodata client + identityF3 My QR setu · F4 editor (10 states, 13 sheets, 60 fields) · F5 people (8 states) · F6 reader (3 roles × 5 states) · share sheet · celebration · Home finished against real dataP3, P4contracts my-qr-setu.json, biodata.json, biodata-view.json all rows verdicted · draft survives a kill · device cases on Android and iOScheck:design-parity
P6 · Account · Notifications · Scan · Meetings participant · Makers · Chat threadF11 8 views · F10 kinds + reads · F9 scanner on the shell · F12 calendar/detail/account honest · F13 Wi-Fi + Link · F16 thread with the OTP gateP1, P3, P4contracts per screen · resolve_slug_owner_kind proven on Dev · a seeded meeting is answerable, schedules a local reminder, fetches its link fresh · the 9 design payloads produce the design's verdictsdevice cases
P7 · Web + cross-cuttingF7 routes + headers + preview + report abuse · D5 greeting · assetlinks.json + intentFilters (after keystore) · i18n three languages (Marathi reviewed) · analytics vocabulary + sink decision · Sentry DSN · default/1 biodata manifestP4, P0run-web asserts noindex, no-store, Cache-Tag · i18n-catalogs.test.ts parity · an Android app link opens the app · check:setu-card-templates covers the biodata manifestcheck:docs, e2e
P8 · Release readinessdevice suite appended and run on Android and iOS · all unit/db/ef/lint/type gates · signed APK + download page · privacy policy, terms, grievance (launch-blocking) · promotion checklist with pasted command output · release record completeeverythinga generated readiness table (screens × contract × journey × open rows) · the six pre-promotion lines each answered by a commandowner go / no-go
P9 · Post-releaseMeetings hosting (QRS-1014 H1) · chat media/voice/forward · Intro card (D-l) · Photos/UPI/dynamic Link makers (D-d, D-s) · MyProgress (D-j) · co-manager read-only (D-u) · the in-app person-to-person path (D-v, model locked by ADR-0032) · push · marketplace ON + order code · data export deliverydecisions——

Critical path: P0 templates → P4 → P5 → P8. Parallel tracks: P1/P2/P4 backend beside P3/P5/P6 client against stubs. Can move the date: the two Meta templates, the Zoom decision (P9 only), Marathi review, the keystore.

Release-readiness criteria (all produced by commands, none asserted) ​

  1. Every consumer screen in §1 marked LAUNCH has a built ledger row, a parity contract with every row verdicted, and every route row reachableFrom; check:screens and check:design-parity green.
  2. test:db, test:ef, npm test, lint, type-check, check:sql, check:rpc, check:naming, check:docs, check:claims green, output pasted.
  3. Device suite cases for every LAUNCH screen pass on Android and iOS, including camera, clipboard, share, picker, local reminder and the app lock over every new route.
  4. A real family journey on Dev: sign up by WhatsApp OTP → claim address → create a biodata → publish (notice delivered) → mint a person share → open it in a browser (basic) → request → release → read released in the app → withdraw → concluded page; each step observed, not inferred.
  5. run-web proves noindex + no-store + Cache-Tag on the built server for both biodata routes and the greeting.
  6. Pre-promotion checklist (sixth rule): declared scope = build; migrations applied on the target both directions; EF set equals the repo's; secrets present by name; client points at the intended project; workflows pass against the target; trace to approved scope by id.
  7. Privacy policy, terms and grievance route live; report-abuse reachable on every public page.
  8. The release record (release.json) carries every change record, targets and builds; approval bound to SHA + manifest hash.

12 · Decisions and confirmations owed (blocking the phase named) ​

#DecisionRecommendedBlocks
C1Meetings hosting in the release?Defer to P9 (H1 on QR Setu's own account, QRS-1014); ship the participant half honest-emptyP6 scope
C2Biodata URL /<slug>/biodata[/<share>]Confirm the design's answerP1
C3Removal honoured howStop service immediately; 72 h erasure by runbookP1
C4Photograph derivativesClient-produced pairP4
D-cNotification read statePer account (table)P1
D-dPersonal UPI maker vs the no-UPI rule (QRS-1009)Decide; recommended LATERP6
D-ePDF / image export of a biodata (QRS-1010)None, follow the later designP5
D-fBiodata URL= C2P1
D-gMinimum ageAGE_FLOOR 18 as in the designP4
D-hModeration of an emblem or photoPost-report, remove the emblem never the profileP4
D-iexpo-clipboard (size callout)InstallP3
D-jMyProgress in the release?LATER (no entry point, coach read is merchant-side, legal copy pending)P6
D-lIntro card (personal links page): scope and nameLATER; name Intro card, route /intro, reserve nowP1 (reservation)
D-mChat scope at launchText send/read/state/block/report via manage-chat; media and voice P9P4
D-pNotification channels shownIn app + WhatsApp only; SMS and email absentP6
D-sLink maker: static or re-pointableStatic at launch with a design copy correction; dynamic P9P6
D-tIdentity greeting and the contact card: does /<slug> ever show the number?Only when the owner enables it; default off; byte-identical otherwiseP7
D-uCo-manager read-only (design state)blocked until a co-manager relationship exists; P9P5
D-vIn-app message the family✅ DECIDED 2026-09-05 — ADR-0032 locks the identity model. Conversations become principal-based, so a person is a first-class counterparty. R1 still routes this action to WhatsApp on the released number; the in-app path follows the schema change, which lands before manage-chat.P5
D-wConsumer prefs storageServer-side (users.prefs jsonb or consumer_prefs) so phone and web agreeP1
D-xRe-authenticate (PIN/biometric) before delete accountYes when a PIN is setP6
X7Mobile analytics sinkDecide, or "instrument now, sink later"P7

Once C1–C4, D-c, D-w and D-l's reservation are answered, P1 starts; the rest fall before the phase they block.


13 · Verification of this plan itself (what to run when it is approved) ​

bash
# the ledger and contracts this plan will change
npm run check:screens
npm run check:design-parity
npm run check:claims
# the measured baselines quoted in §0
node -e "const j=require('./documentation/portal/design-system/screen-conformance.json');console.log(Object.values(j.screens||j).filter(r=>r.section==='consumer').length)"
grep -rn "manage-chat\|owner_user_id\|resolve_slug_owner_kind" supabase/functions supabase/migrations | wc -l   # 0 today

On approval the plan is written back as the portal plan of record, each LAUNCH screen gains a tracker row and a parity contract, and P0 begins with the ledger re-transcription so that the first measured number the owner sees is the true one.