Skip to content

Biodata: design-to-implementation audit (2026-09-28) ​

Asked by the owner on 2026-09-28, after finding the photo viewer blank, "Request more details" throwing an error, People and access not following its journey, and the WhatsApp share arriving as a plain link. The request was explicit: establish the complete gap list against the latest approved design first, and do not patch individual issues on assumptions. This page is that baseline. No product code was changed to produce it.

THE SHORT ANSWER

Three of the owner's four named flows are broken end to end, not cosmetically, and each break was reproduced:

  1. Photos: the viewer opens off-screen. It is position: fixed inside a section whose entrance animation leaves a transform behind, so it is sized to that section (2,400-4,000 px tall) and the photograph lands ~1,200 px below the screen while the page is locked from scrolling.
  2. Request more details: the form is shown on the forwardable link, where the route refuses every submit (no_token) before the backend is called. Where a request can be made, there is no WhatsApp code step, the family is never notified, and releasing it mints a link nobody receives.
  3. People and access: after minting a person's private link, "Send on WhatsApp" and "Copy" hand the family the public forwardable address instead. No one ever holds a person link, so the rest of the journey (Release more, a request, an answer) cannot happen through the app.

And the fourth: the WhatsApp preview is served correctly (every tag, a 200 image); what differs from the design is that the app shares a bare URL where the design shares a message and the link.

1 · The source of truth used ​

ProjectClaude Design "QR setu prototype" (633dc069-…), pulled fresh on 2026-09-28 through DesignSync
Round57 is the latest (the registry SCREENS.md names nothing later)
Filessetu-card/BiodataPage.dc.html (Parichay), setu-card/BiodataChitra.dc.html, setu-card/BiodataPatrika.dc.html, consumer/BiodataView.dc.html (in-app reader), consumer/BiodataDesigns.dc.html (picker), consumer/Biodata.dc.html (hub, editor, People and access), consumer/MyQRSetu.dc.html, consumer/biodata-core.js, biodata-view.js, biodata-templates.js, biodata-publish.js, consumer-data.js, my-qrsetu/biodata.spec.md, SCREENS.md
Side by sidethe artboards render on localhost:8090 (the mirror, refreshed today), the built web page on localhost:4175, the app PWA on localhost:8080

ONE SOURCE COULD NOT BE READ WHOLE (QRS-1399)

consumer/Biodata.dc.html is larger than DesignSync's 256 KiB read limit. The pull came back truncated: true and stops inside the People view's logic. The People markup is complete; the tail of its behaviour is not (the reopen card's copy, the withdraw / stop-link / removal confirmation copy, the end of the plan line). Those rows are marked not readable below, never inferred. An older complete pull (2026-09-10) is byte-identical on every overlapping People line, so it is a strong hint, not evidence. The fix is on the design side: split People and access into its own file.

2 · The intended journeys (from the design) ​

2a · A reader opens a shared biodata (phone browser, no app needed) ​

  1. The forwardable link (qrsetu.com/<slug>/biodata) opens at Basic; a person's own link (…/biodata/<id>) opens at whatever the family released to that person.
  2. Photographs (Parichay): Basic shows the cover alone, cropped 3:2. Released shows every shown photograph (up to 4) at 4:5 in a carousel that auto-advances every 4.2 s, pauses when touched and resumes 6 s after a swipe, with dots below the card (44 px targets, the active one 18 px wide). A tap opens a full-screen viewer: count, Close, previous and next, swipe; the crop at Basic, the whole photograph (contain) at Released. No zoom is drawn in round 57. Chitra and Patrika draw no viewer.
  3. Asking for more: any Basic reader, on either kind of link, sees "Ask the family for the fuller profile" (Parichay) or a "Held back for now" card listing what is held back (Chitra, Patrika). The reader gives their name, who they are, and a WhatsApp number; on Parichay a 6-digit code sent on WhatsApp confirms the number; the page then says the family was told on WhatsApp.
  4. The page closes with the growth card and "Report this profile".
  5. Sharing the page onwards: the page's own share card (WhatsApp, Copy link, More apps) sends a message ("Sharing <first>'s profile. Please have a look and let us know.") with the link. The chat shows a card with the first name, one neutral line and a branded image with no face.

2b · The owner shares and answers (the app) ​

  1. People and access: the first share opens a sheet; "Send on WhatsApp" sends that person's own link. Each holder is a row with state, opens, expiry chips and actions (Extend, Withdraw, Message, Release more, Give access again, Copy link, Stop this link).
  2. A request arrives: a "Waiting for your answer" card with who asked, what they asked for, the number "confirmed by one code", how they used the link, what they see today and the honest line. The owner picks 30, 90 or 180 days and taps Release, "Ask them something first" or "Not now". The reader's page then fills in on its own.
  3. Removal by the subject is honoured within 72 hours and cannot be refused. Conclude withdraws every release and turns every link into a thank-you page; reopen starts with zero releases.

2c · The owner previews and changes the design (the app) ​

  1. Preview (BiodataView, round 56): the real page framed at ?embed=1 for every design, with only the app's chrome: a header (back, title, address), a three-way reader switch (Anyone with the link, A family you released, Owner preview), and at Owner preview an owner band (Edit, People with the waiting count, and a design line "Your readers open it as <design>" with Change and open in a browser). The frame is also handed the app's light/dark mode (appearance) and any closed state. Inside the frame the page drops its own site header.
  2. Change the design: the editor's "How it reads" card switches in place and offers "Preview as …" and "Compare all three", which opens the picker (both modes, live full-screen preview, a switched state).

3 · Photographs: design against build ​

AreaDesign (round 57)Build (measured)Gap
Basic herocover only, 3:2 cropcover only, 3:2✅
Released heroup to 4, 4:5, carouselup to 4, 4:5, scroll-snap strip✅ (mechanism differs, behaviour matches)
Auto-advanceevery 4.2 s; pause on touch, resume 6 s after a swipeevery 3 s (≈3.4 s measured); pause on touch, pointer, focus and while the viewer is open; resume ≈3 s after⚠ interval is an owner call (QRS-1371)
Dotsbelow the card, 44 px, active 18 pxbelow the card, 44 px, active 18 px✅ (added 2026-09-27)
Tap to openfull-screen vieweropens off-screen: blank to the reader (live devv and local, whenever motion is allowed)❌ QRS-1373
Viewer contentcount, Close, prev/next, swipe; crop at Basic, contain at Releasedsame, plus zoom 1x/1.8x/2.5x at Released and a note⚠ zoom is not in round 57 (QRS-1395)
Viewer closeClose button onlyClose, Escape, arrows; focus returns to body✅ plus extra keyboard support
Chitra, Patrika viewernone drawnnone✅ matches; a design gap for the owner's parity request (QRS-1370)
Photo addressnot specifiedpresigned for 120 s, no cache headers; a re-fetch after that returns 403⚠ risk (QRS-1397)

Why the viewer is blank (QRS-1373), measured. PhotoViewer is position: fixed. It renders inside [data-parity="biodata-page-profile"], which carries the class pc-rise (BiodataPage.tsx:288-289). That class runs pcRise .5s … both (sections/parichayCss.ts:29), and both keeps transform: translateY(0) after the animation ends. A transformed ancestor becomes the containing block for fixed children. Result on devv (414×896): the viewer box starts at y=102 and is 2,485 px tall, the photograph is centred at y≈1,215, and the page is locked from scrolling, so the reader sees the dark overlay, the count and Close, and nothing else. It works under reduced motion, where .pc-rise{animation:none} removes the transform, which is the run-web driver's default and is exactly why my check on 2026-09-27 missed it. The presigned URL is not the cause: the image is reused from the page (one R2 request, 0 at open), and it still decodes after 130 s.

4 · Request more details: design against build ​

StepDesign (round 57)Build (measured)Gap
Who can askany Basic reader, on the forwardable link or a person linkthe form shows on both; the route refuses the forwardable link (no_token, HTTP 400, before the backend)❌ QRS-1376
Nav chip"Ask for more" scrolls to the block#ask anchor, scrolls✅ (this is the "CTA that acts as a link")
Fieldsname, who you are, WhatsApp (+91, 10 digits)same three; any E.164 number accepted (+44… passes)⚠ minor
Validation copythree per-field messagesroute-level messages; fields cleared on any error⚠ QRS-1396
WhatsApp code step6-digit code, "Change the number", "Send the code again"none; phone_verified has no writer anywhere❌ QRS-1377
Pending"You have asked the family for more", "They were told on WhatsApp"cookie-scoped pending card; the family is not told (no WhatsApp, no push)❌ QRS-1378
Owner sees itwaiting card in People and accesswaiting card renders (from get_my_biodata_shares)✅ where reachable
Owner releasesthe reader's page "fills in on its own"a new share is minted with no address (profile_id not sent) and its token is discarded by the hook: nobody can open it❌ QRS-1379
"Ask them something first"opens Chatsgeneric chat list; an anonymous asker has no conversation⚠ blocked by the conversation model (QRS-1177)
Chitra, Patrikaa "Held back for now" card built from what is held backno ask UI at all; the public read carries no requestable (QRS-1263)❌ QRS-1381
Inside the Previewnot addressed by the designsubmitting the form blanks the frame❌ QRS-1382
Languageper-language copyevery askError.*, pendingTitle and pendingNote is English in hi and mr; the redirect drops ?lang❌ QRS-1396

Where it breaks, in order: (1) the reader on the forwardable link cannot ask at all; (2) a reader on a person link can, but nobody verifies the number and nobody tells the family; (3) the family's release goes nowhere. The reader waits for a page that never fills in.

5 · People and access: design against build ​

#Design (round 57)Build (measured)Gap
1Share with the first person sends that person's linkmint works; Send and Copy share the forwardable address❌ QRS-1374
2share another personthe sheet keeps the first URL and offers no mint button until the screen remounts❌ QRS-1375
3Stop this link switches the forwardable link offcalls withdraw; the page ignores the withdrawn row and keeps serving❌ QRS-1385
4Give access againalways refused on a withdrawn row (the SQL requires withdrawn_at is null); works on a lapsed one❌ QRS-1386
5a row answered from a request is namedthe row has no name and empty initials (the SQL insert never sets label)❌ QRS-1387
6Conclude withdraws every releasethe SQL withdraws no share; released rows stay actionable❌ QRS-1388
7Reopen starts with zero releaseslands on a draft the screen describes as published❌ QRS-1389
8back to My QR setuno back control; tabs push onto the stack; a mint refusal's reason is dropped; server and client count the link cap differently; a retry mints a new idempotency key; conclude can send version 0❌ QRS-1390
9the waiting card, facts, honest line, release box, lapse chipsrender as designed; the "confirmed by one code" label shows although nothing confirms⚠ QRS-1377 / QRS-1046
10removal card, 72 h clockrenders; rows stay actionable during removal (the design does not say otherwise)✅ as drawn
11reopen / withdraw / removal confirmation copybuiltnot readable in the round-57 pull (QRS-1399)

6 · The in-app Preview and the design picker ​

AreaDesign (round 56/57)Build (measured)Gap
Framethe real page at ?embed=1, every designyes, WebView / iframe, projected draft✅
Page chrome insidesite header hiddensite header shown (wordmark, language pills)❌ QRS-1383
Reader switchthree options, incl. Owner previewtwo (Anyone / Released)❌ QRS-1384
Owner bandonly at Owner preview; Edit, People (n waiting), design line with Change and open-in-browseralways shown; Edit and People; no design line❌ QRS-1384
Theme to the page`appearance=lightdark; closed state`neither: a dark phone frames a light page; closed states only as a native card
Scan another family's biodatatheir pageyour own profile (the scan's slug and share are ignored)❌ QRS-1380
Hub eyeopens the Previewpushes ?viewer=[object Object] (the press event); falls back to Released⚠ QRS-1391
Productionthe page on qrsetu.comthe embed returns 404 on qrsetu.com (the web is not promoted)⚠ release item QRS-1392
Picker ("How it reads", "Compare all three")designed in fullnot built; nothing writes template_key though the backend accepts it❌ QRS-1367

7 · WhatsApp sharing ​

DesignBuild (measured)Gap
Preview tagsfirst name title, one neutral line, branded image, no faceall present to WhatsApp's own crawler: og:title "Vedaa, marriage profile", description, 1200×628 PNG (200, 33 KB), twitter:card✅
Preview imagewash, QR setu pill in the brand face, "Marriage profile" on a dark bandwash, QR setu pill (not the brand face), "Marriage profile" in grey, no band⚠ minor
What the app sharesa message and the link ("Sharing <first>'s profile. Please have a look…")the bare URL❌ QRS-1394
The page's own share cardWhatsApp · Copy link · More appsnot built❌ QRS-1120
Why a plain link—not reproducible from here. The page answers in 1.5-2.7 s with no cache, and WhatsApp builds the card on the sender's phone while composing; a link shared before 11 Sep (when the tags shipped) may also be cached by WhatsApp as plain⚠ unverified: needs a device test (below)

⚠ The design deliberately puts no photograph and no profile detail in the chat preview: "the preview carries a first name and nothing more. No photograph, no surname, no age and no city", because a chat caches it and a revocation can never reach that copy. A preview with the person's photo would reverse a disclosure rule, which is the owner's call, not a fix.

8 · The Biodata ID ​

QuestionAnswerEvidence
Is the format documented?Yes, decision D6 (2026-08-31): QRS 482 011 735, nine digits grouped 3-3-3, stored as a number with QRS added at display, a keyed Feistel permutation over a hidden sequence (never sequential, no collisions), gender as a chip beside it and never encoded, a space never a hyphen, and lookup by number authenticated, rate-limited, Basic only and an owner switchconsumer/decisions.md D6
Is it generated in the backend?Yes: manage-biodata mints it at first publish with the BIODATA_REFERENCE_KEY secretmanage-biodata/index.ts:184-193, reference.ts
Is it stored per record?Yes: biodata.profiles.reference, unique; every published or retired Dev profile has one, drafts noneDev: 3G5FH0X, 3QF0KT8, 3N9EAVM, …
Does it match D6?No: a 32-bit permutation encoded as 7 base-32 characters (3G5FH0X), stored as text, not D6's 9-digit number. No recorded decision changed it; it has been this way since the first commit (2026-09-05)reference.ts
Exposed through APIs?owner reads only (get_my_biodata); not in the public readget_public_biodata projection
Shown in the UI?nowhere, in the app or on the pageno reader of .reference
Where the design shows itnowhere: no placement in any of the 7 biodata / My QR setu filesfresh pulls today

So the scope is: (1) the owner decides the format (keep the 7-character code or move to D6; moving changes the codes on existing Dev profiles, harmless before launch); (2) a design request for where it appears and how it sits beside the gender chip; (3) exposure in the public read; (4) the lookup by number, if wanted for launch (QRS-1393).

9 · Consolidated fix list, by dependency and impact ​

Wave 1 · the three broken journeys (no design question, no backend decision).

idfixlayer
QRS-1373render the photo viewer outside the transformed section (a portal to body), and stop the entrance animation leaving a transformweb
QRS-1374Send and Copy share the minted person linkapp
QRS-1375clear the minted link when the share sheet closesapp
QRS-1380a scanned biodata opens that family's page, not the owner'sapp
QRS-1382the ask form inside the Preview never submits the frame awayweb
QRS-1383hide the site header in embed mode, as the design doesweb
QRS-1387name the row created from an answered requestbackend (SQL)
QRS-1386"Give access again" on a withdrawn row mints a new link rather than calling a release the SQL refusesapp + backend
QRS-1391the hub eye passes a tier, not the press eventapp

Wave 2 · the request flow (needs one owner decision, then backend).

idfixdepends on
QRS-1376let a Basic reader on the forwardable link ask (the design's rule): a request tied to the profile and the asker's number rather than to a share tokenowner decision on the model, backend report first
QRS-1377the WhatsApp code step for the asker, and phone_verified written; until then the "one code" copy is removedreuses the WhatsApp OTP sender; backend
QRS-1378tell the family a request arrived (in-app notification now, WhatsApp template later)backend; WhatsApp template approval
QRS-1379a release reaches the asker: send profile_id, keep the minted link, deliver it to the asker's verified numberQRS-1377
QRS-1381Chitra and Patrika ask cards, fed by requestable in the public readQRS-1263 backend
QRS-1385, QRS-1388, QRS-1389stop-link, conclude and reopen do what their copy saysbackend
QRS-1390, QRS-1396the People screen's smaller defects; the ask copy translated, ?lang kept, fields kept on errorapp, web, i18n

Wave 3 · Preview, picker, sharing, ID.

idfixdepends on
QRS-1384the round-56 Preview chrome: three-way switch, band at Owner preview, design line, appearance, closed states—
QRS-1367the "How it reads" card and the pickerfresh pull, screen by screen
QRS-1394, QRS-1120share a message with the link; the page's own share card; the preview image details—
QRS-1393the Biodata IDowner decision + design request
QRS-1392promote the web so the embed exists on qrsetu.com before the app shipsrelease

Owner calls, not fixes: QRS-1371 (3 s or the design's 4.2 s), QRS-1395 (keep zoom, which round 57 removed), QRS-1393 (the ID format), whether the chat preview should ever carry more than a first name.

Design requests (Claude Design): QRS-1399 (split the hub artboard below 256 KiB), QRS-1370 (a viewer for Chitra and Patrika, if the owner wants one), QRS-1400 (the design-side ambiguities the four design passes recorded: e.g. Chitra and Patrika have no code step though their copy says one code confirms; Parichay's ask chips are hard-coded; "With the family since Sunday" is a literal; the celebration offers a PDF the core module says does not exist; the editor's "Anyone with the link" row resolves to the released tier; toast styling is not in the mirror).

A correction I owe (QRS-1401): on 2026-09-27 I recorded that BiodataView.dc.html "still draws its own reading" and marked 31 contract rows blocked on it. That was wrong: the round-56 artboard already frames the page, and it was on disk when I wrote it. Those rows must be re-assessed against round 56, and QRS-1368's open half closed. The design also absorbed QRS-1361 in round 57 (the growth card's Marathi and Hindi words), so that allowance will go stale on the next gate run.

10 · Re-validation checklist ​

Each line is answered by a command whose output is pasted, or by a named manual check on a device. "Motion allowed" means the check is run without reduced motion; the driver's default hides QRS-1373.

#CheckHowPass
1Photo viewer, Parichay, both tiers, motion allowedrun-web driver motion no-preference, open the viewer, read the <img> rectthe photograph is inside the viewport
2Same on live devv, phone browserowner, Android Chrome and iPhone Safarithe photograph shows on tap
3Auto-advance and dotsdriver, 10 s scroll sample, dot tapadvances at the agreed interval; a dot moves it and never opens the viewer
4Ask on the forwardable linkdriver POST to /<slug>/biodataaccepted once QRS-1376 lands; until then the form is not shown there
5Code stepDev, a real numbera code arrives on WhatsApp; a wrong code is refused with copy
6Family toldDevthe owner gets the in-app notification (and WhatsApp once approved)
7Release reaches the askerDev, end to endthe asker's page opens at Released from the link they receive
8People: first and second sharejest render + deviceSend and Copy carry each person's own link
9Stop this link, Give access again, conclude, reopenpgTAP + Deveach does what its copy says
10Preview chromejest render against the round-56 specthree options, band at Owner preview, design line, appearance, closed states
11Scan another family's biodatadevicetheir page opens
12Ask inside the Previewdriver embedthe frame never goes blank
13WhatsApp sharedevice: share from the app to a WhatsApp chat, wait for the card before sendingthe message text and the card (first name, line, image)
14Biodata IDper the owner's decisionthe format, the public read and the placement match
15Presentation gatenpm run check:biodata-presentation0 DIVERGED, stale allowances removed
16Parity contractsnpm run check:design-paritythe People, Preview and page contracts re-assessed against round 57
17Suitesnpm test, npm run test:db, npm run test:efgreen, counts pasted

Method, so the next audit can repeat it ​

  • The design was pulled fresh and read by four agents that saw only the design; four more measured only the implementation, from the served page in a real browser, jest renders of the real screens, and Dev's logs and rows (read-only). Neither side could colour the other.
  • The owner's four reports were each reproduced before being explained.
  • Three things this audit could not do: a native device run (the WebView, the phone's reduced-motion default, WhatsApp itself), WebKit and Firefox, and the unreadable tail of the hub artboard.