Skip to content

Biodata Preview versus public page: root-cause analysis ​

Measured 2026-09-27. Raised by the owner with two screenshots of the same profile (vedaalahade): the in-app Preview on the left, the shared link on the right. The owner asked for a process, architecture and QA failure analysis before any fix. This page is that analysis. No code has been changed for it.

THE SHORT ANSWER

There is no single approved design for this experience. Claude Design holds two approved artboards for the same reading: consumer/BiodataView.dc.html (the in-app reader) and setu-card/BiodataPage.dc.html (the public Parichay page). They differ in hierarchy, section treatment and content. We then built two renderers, one native, one web, each faithful to its own artboard, and validated each against its own artboard only. Every check we ran could pass while the two experiences stayed different. The owner's one-renderer decision (2026-09-21, reaffirmed 2026-09-23) would have made them identical by construction, and it was not implemented for the default design.

0 · How this was measured ​

Every row below can be rechecked with these sources.

SourceWhat it isHow it was read
In-app artboardprototype/consumer/BiodataView.dc.html, pulled fresh todayrendered on the local mirror, ordered text captured at basic and released
Public artboardprototype/setu-card/BiodataPage.dc.html, pulled fresh today, byte-identical to the build's 09-26 pullsame, both tiers
Deployed public pagehttps://devv.qrsetu.com/vedaalahade/biodatafetched today; carries no data-design attribute, so it predates commit d69cdab
Current public pagethe HEAD build (759757c) served by wrangler against real Dev datarendered for vedaalahade, basic tier (a stranger's link)
Current in-app Previewthe owner's screenshot (owner view, released-level content)not re-rendered: it needs the owner's session
Contractsdesign-system/parity-contracts/biodata-view.json, biodata-page.jsonrow verdicts and designPaths read directly

⚠ Two of the four readings are at different tiers. The owner's Preview shows released-level content (employer, full name, height, contact); the shared link is basic. The disclosure differences (employer withheld, no contact, first name only) are correct by design and are not counted as parity gaps below. The presentation differences are, because they persist at a matched tier: the in-app artboard at basic still draws a different page from the public artboard at basic.

1 · Current-state gap analysis ​

LayerState
Approved designTwo artboards for one reading. The design's own round-54 audit (my-qrsetu/BiodataParity.dc.html) claims the Preview "differs from the public reading in exactly two respects, both of them chrome", and supports it by showing both artboards consume one view model. That proves content parity. The two artboards still draw that content differently.
Design framing ruleBiodataView.dc.html frames the real page (?embed=1) only for a non-default design (isFramed = !TPLS.isDefault(tpl), and DEFAULT_TPL = 'parichay'). For Parichay, the default, the in-app reader keeps its own rendering. That exemption is how a second rendering survived in the design.
CodeTwo renderers. The app's BiodataViewScreen (React Native: ReaderBlocks, AboutBlocks, FamilyDrawing, OwnerBand, GrowthCta) and the web's designs/Parichay + sections/* (DOM). They share resolveBiodataView (what is shown) and BIODATA_READING_ORDER. They share no presentation.
Validationbiodata-view.json compares the app to BiodataView.dc.html (63 rows: 40 pass, 15 gap, 8 blocked). biodata-page.json compares the web to BiodataPage.dc.html (128 rows: 68 pass, 25 gap, 35 blocked). No contract compares Preview with the public page.
Deploymentdevv.qrsetu.com serves a build from before the three-design rebuild: 10 commits are unpushed, and no web deploy has run since the Actions billing block (QRS-790). The Preview's own link points there. The owner reviews the deployed page; we validated a local Worker.
In-app Preview (owner's left screenshot)Shared link (owner's right screenshot, devv)
Source artboardBiodataView.dc.htmlBiodataPage.dc.html (an older revision of it, since devv is stale)
RendererReact Native, in the appDOM, a Cloudflare Worker
Tier shownowner view, released-levela stranger's link, basic
Buildthe 8080 export of todaya build older than d69cdab

3 · Element by element ​

Columns: A the in-app artboard · B the public artboard · C the app build · D the public build at HEAD. A row is a parity gap when C and D differ at a matched tier. It is a design conflict when A and B differ.

#ElementA · in-app artboardB · public artboardC · appD · public HEADVerdict
1Page headerback arrow, "Marriage profile", the addressQR setu wordmark, Share, three language pills, the "not listed anywhere" lineas Aas B, no Share (QRS-1120)design conflict
2Opening blessingabove the hero, inside the readerits own card above the heropresentpresentpresentation differs
3Herophotograph carousel with a count and dots; the name sits in a card belowone photograph; the name is overlaid on it, with a "Shared by the family" chipas Aas Bdesign conflict
4Cover photographthe first photograph in the family's order"the cover"QR logo image (photo 1 of 4)the scanned paper biodatadata gap: the two surfaces pick different covers (QRS-1352)
5Identity summaryone meta line ("21 years · 5 ft 11 in · Pune") plus a status pill ("Open")fact tiles (Profile, Age, Status, Lives in, Height, Diet)as Atilesdesign conflict
6Age"21 years""29 years""21 years""21", unit missingdefect (QRS-1351): a false pass on facts_strip
7Quick chipsmarital, work, education, diet, blood group, languageslanguages only, as their own rowas Alanguages absent (not in the data)design conflict
8Keep this profile · Sharetwo buttons under the nameShare in the header plus a share card near the endpresentboth absent (QRS-1120)design conflict + known gap
9Section navnoneOverview · Work · Looking for · Family · Who to talk tononepresentdesign conflict
10Education and worka card with an icon, a line count ("5 lines") and label/value rowsa timeline: kicker, title, sub-line, railas Atimelinedesign conflict
11Education detail"Education: B.E. / B.Tech", "Earlier degree: B.E. / B.Tech"institution and year as sub-linesas Aa stray "Education" line under the titledefect (QRS-1353), cause not yet measured
12Looking fora card, "N lines", short labels ("Age", "Height")long labels ("Age they are looking for"), the quote leadsas Aas Bdesign conflict
13Community, family, kundlicards with line counts; a relationship listlabelled rows; a family drawing; a mapas Aas Bdesign conflict
14Ask for more (basic)"Ask to see more" in the tier band at the topa card low on the page with a five-item checklist and a phone fieldnot on screen (owner view)as Bdesign conflict
15Who to talk to (released)a card, Call + Messagea card, Call + Message, plus two explanatory sentencesas Anot shown at basic (correct)presentation differs
16The page this profile lives onaddress, Show QR, Copy linkthe "Share this profile" card insteadas Aabsent (QRS-1120)design conflict
17App stripnone"QR setu, in your browser or on your phone" + Downloadnonepresentpublic-only content
18Footnotesnonethree: "Written in English by the family…", "Kept current by the family…", "Date of birth, income and home address are never shared…"nonetwo of threepublic-only content
19Grow CTA"FREE ON QR SETU · Make one for someone in your family""FREE TO CREATE · Make one…" + "Takes about ten minutes. Nothing is public until you send the link."as Aas Bpresentation differs, extra line
20Ask on WhatsApp (support)inside the grow cardits own block after itas Aas Bpresentation differs
21Report this profilenonenonenonepresentadded by implementation (QRS-1299, a safety path the artboard omits)
22"For someone you love… three benefits… Start one, free"nonenone (an older revision had it)nonenonestale deploy only: gone at HEAD
23"Where Vedaa works is shared once the family releases it""One more line here is released…"presentnot applicableremoved at HEAD (QRS-1296)devv shows the old line

Tally. Of the 23 elements, 13 are design conflicts (the two approved artboards disagree), 3 are defects in our code (rows 4, 6, 11), 3 are public-only content from the public artboard, 1 is an implementation addition (row 21), and 2 exist only because devv is stale.

4 · Unnecessary and extra content ​

The goal is the approved information architecture, not "closer". So each item needs a decision to keep or remove, not a restyle.

Essential to a biodata reading (both artboards agree):

  • the opening blessing
  • who this is: name, photographs, the key facts
  • the family's own words
  • education and work
  • what they are looking for
  • family, and at released tier community and kundli
  • the family's own added fields
  • how to reach them: the ask at basic, the contact at released
  • share
  • a way to report the page (safety, QRS-1299)

In the approved public artboard, not in the in-app one (candidates for removal, owner's call):

ContentWhy it reads as unnecessaryConflict with a repo rule
"A personal profile, shared by the family. Not listed anywhere and not searchable."explains the product instead of presenting the personyes: a privacy reassurance at the point of use (QRS-549)
"Exact date of birth, contact number, income and home address are never shared on QR setu, to anyone."sameyes (QRS-549)
"Kept current by the family. There is no version two…"product explanationno
"Written in English by the family. QR setu never translates…"product explanationborderline
"One code confirms the number, and that is all we ask. Reading this page never needed an account…"reassurance under the ask buttonyes (QRS-549)
"Names and relations only. QR setu never shares their numbers…" (family) and "Published by the family. QR setu never puts a profile on a map…"reassuranceyes (QRS-549)
App strip: "QR setu, in your browser or on your phone" + Downloadpromotion inside a family's documentno
"Takes about ten minutes. Nothing is public until you send the link."promotion copypartly

⚠ This is where implementation interpretation failed as well. CLAUDE.md forbids volunteering a privacy reassurance at the point of use (QRS-549), and the public artboard carries at least five. We implemented them verbatim because the artboard was treated as the source of truth, and never raised the conflict. A conflict between an approved design and a standing rule is the owner's decision, and it was not put to them.

Added by implementation: "Report this profile" (QRS-1299). It is deliberate: a public page about a person with no report path is a safety gap. It was recorded as a design defect, but it was never shown to the owner as an addition in a review.

The in-app reader also has content the public page lacks: Keep this profile, the status pill, line counts, the chip row, the address + QR + Copy link block. Keep this profile needs an account, so it is in-app only by nature; the rest is presentation that one of the two artboards has to give up.

5 · Root causes ​

AreaRoot causeStructural?
Design processThe design produced two artboards for one reading, and its own parity audit certified them as equal because they share a view model. It also exempts the default design from framing, so the default design keeps two renderings.yes
Source-of-truth managementWe treated each artboard as the source of truth for its own surface. Nobody named the one canonical presentation, so faithfully implementing "the design" produced two experiences. I never raised that the two approved artboards contradict the owner's parity requirement. That was the most important thing to raise, and it is on me.yes, primary
Component architectureTwo renderers: React Native for the in-app reader, DOM for the public page. They share only the resolver. React Native and DOM cannot share presentation components, so two renderers mean parity has to be maintained by hand, and it drifts. The owner's decision "one renderer: the Preview shows the real page" (2026-09-21) and "the page carries it behind ?embed=1" (2026-09-23) was implemented for neither the default design nor the others. QRS-1302 was parked behind a backend dependency (the signed owner parameter), and the native reader stayed.yes, primary
Data/schema mappingEach surface formats some values itself, so they drift: the age unit is added per surface (Chitra and Patrika do, Parichay does not, QRS-1351). Cover selection differs between surfaces (QRS-1352). The education sub-line differs (QRS-1353).yes: formatting belongs in the view model, once
Public rendering implementationFaithful to the public artboard (68 of 128 rows measured passing), including content the owner does not want. Three defects (rows 4, 6, 11).no, it followed the wrong source
Content/configurationPublic-only explanatory and promotional content came from the artboard and was accepted without a content review against the repo rule (QRS-549) or against the in-app artboard.yes
Responsive/mobile renderingNot a cause here. Both are phone layouts.no
QA processThe owner reviews the deployed link; we validated a local Worker. devv is a pre-redesign build, and nothing on the page says which build it is. Comparisons were not pinned to one tier. No device pass has been run.yes
Parity validationEvery check validates one surface against its own artboard, or content only. None validates one experience across surfaces. See section 6.yes, primary
Definition of DoneNothing in the DoD requires two surfaces that present the same experience to be the same. "Done" could be reported per surface, with partial tallies.yes
Communication/interpretationI read "Approved Design → In-App Preview → Actual rendering stay aligned" as "each rendering matches its approved artboard" rather than "one experience everywhere". I reported wave 2 as done with per-surface tallies. The owner reasonably read that as parity progress.yes, in how I report

6 · Why the existing checks passed ​

CheckWhat it measuresWhy it could not see this
check:design-paritythat every contract row has a verdict, evidence and a tracker idpresence, never truth; and no contract spans two surfaces
biodata-page.json (web vs BiodataPage)the public page against the public artboardthe public artboard is the other presentation; matching it cannot produce the in-app look
biodata-view.json (app vs BiodataView)the app against the in-app artboardsame, from the other side
check:biodata-parityfor every design, at both tiers: which fields appear (coverage), what leaks (disclosure), and whether values are faithfulcontent and disclosure only; blind to layout, hierarchy and extra content by design
resolveBiodataView shared by both surfaces (QRS-1281)that both surfaces decide what is shown in one placesays nothing about how it is shown
Independent parity signoffre-measures a contract's claimsinherits the contract's scope: one surface, one artboard
The owner's review page (review.html, today)the artboard next to our build, per surfaceit too paired each surface with its own artboard; it did not put the two surfaces side by side

And one false pass inside the scope that was checked: facts_strip is marked pass while the age tile prints "21" instead of "21 years" (QRS-1351).

7 · Required process and architecture changes ​

  1. One canonical presentation per experience. For each biodata design there is exactly one artboard for the reading. The in-app artboard shrinks to the in-app chrome around it: the owner band, the tier switch, and the owner's edit and share actions. A Claude Design correction prompt makes the design say this. The owner decides which of today's two presentations becomes canonical.
  2. One renderer. Extend ADR-0019's principle (one renderer for a public surface) to the biodata reading, with an ADR. The in-app Preview and a recipient's in-app reading show the web page. Today that means expo-web-browser, as the owner chose on 2026-09-23. Later it can be framed with ?embed=1, once the signed owner parameter exists (backend, report first). The native reading blocks are retired; the native owner band stays. Parity then holds by construction, not by review.
  3. Formatting in the view model, once. Age with its unit, the cover choice and every derived line are resolved in @qrsetu/domain and rendered verbatim, so no surface can format differently.
  4. A cross-surface presentation gate (section 10). It fails when the rendered page and the canonical artboard differ in blocks, order or content, at every tier and language.
  5. Build identity on every page, and review only against a build that states its commit. The deployed URL is the validation target, never only a local Worker.
  6. A content review step. Every rendered block traces to an artboard element id. Anything else is an addition with a QRS id and an explicit owner approval. Any design copy that conflicts with a repo rule (QRS-549) goes to the owner before it is built.

8 · Strict design-to-rendering parity checklist ​

For each design × tier (basic, released, owner) × language (mr, hi, en) × state (live, no photograph, pending, expired, withdrawn, concluded, removed, 404):

  • [ ] The canonical artboard is freshly pulled, and its revision is recorded.
  • [ ] The rendered page is the deployed build, and its build id matches the commit under review.
  • [ ] Same fixture on both sides, so words can be compared as well as layout.
  • [ ] Blocks: every artboard block is present, and no rendered block is absent from the artboard unless it is a listed, approved addition.
  • [ ] Order: the block sequence is identical.
  • [ ] Content: labels, values, units and helper copy are identical, word for word.
  • [ ] Presentation: each block uses the same treatment: card, tile, timeline, row, chip, overlay.
  • [ ] Disclosure: each tier shows exactly its fields and nothing more (check:biodata-parity).
  • [ ] In-app: the Preview shows the same page, plus only the approved chrome.
  • [ ] QR: scanning the printed QR opens the same URL and the same page.
  • [ ] Phone: checked at 360, 390 and 414, and once on a real device (Android and iOS).
  • [ ] Every difference is either fixed or listed as an approved exception with a QRS id. There is nothing unexplained.

9 · Revised Definition of Done for these screens ​

A biodata design is done only when all of these are true:

  1. One canonical artboard exists for its reading, and the in-app artboard is chrome only.
  2. It has one renderer. The in-app Preview and the recipient's in-app reading show that renderer's page.
  3. The presentation gate (section 10) is green for every tier × language × state, with every exception approved and listed.
  4. check:biodata-parity is green (content and disclosure).
  5. The deployed URL, the in-app Preview and a QR scan were validated on a real phone, against the stated build.
  6. The owner reviewed the side-by-side at a pinned tier and signed off.
  7. The contract counts are complete: no row blank, and a pass measured, not claimed.

Partial tallies are reported as partial, never as a completed wave.

10 · Prevention mechanism ​

The gate: check:biodata-presentation. For each design × tier × language × state:

  1. Render the canonical artboard with its own seed record.
  2. Render the built page with the same record through the run-web mock, so the words match.
  3. Extract the ordered block inventory from both: block ids (data-slot on the build, the artboard's section markers on the design), the treatment of each block, and its text.
  4. Diff them. Fail on any missing, extra or reordered block, and on any text difference. An allowlist carries the approved exceptions, each with a QRS id.

It must be mutation-tested both ways: remove a block and it fails; add the Download strip back and it fails.

What it cannot see: spacing, colour and type metrics. A screenshot diff at fixed viewports, with a tolerance, covers those as a second stage.

Structural guard. Once the in-app Preview shows the web page, a lint rule refuses a native reading block in BiodataViewScreen, so a second renderer cannot come back quietly.

Freshness guard. Every page emits its build id. The review page shows it. A check warns when devv is behind develop.

Owner decisions first (nothing proceeds without them):

  • D1. Which presentation is canonical: the in-app reader's (cards, line counts, meta line and chips; the owner's stated preference), or the public Parichay page's (tiles, timeline)?
  • D2. Which public-only content is removed (section 4).
  • D3. Confirm one renderer: the in-app Preview shows the web page, and the native reading is retired.
  • D4. Whether "Report this profile" stays, as an approved addition.

Then, in order:

  1. Design. One Claude Design correction prompt: one reading artboard per design, the in-app artboard reduced to chrome, D2's content removed, and the pending artboard defects (QRS-1317, 1336, 1345) folded in.
  2. Gate first. Build check:biodata-presentation and show it failing on today's state. That proves it can see this gap.
  3. Web. Rebuild the public page to the canonical artboard until the gate is green.
  4. App. The Preview opens the real page through expo-web-browser, with the draft state carried by the existing view-state grammar (viewState.ts). The owner band stays native. ?embed=1 framing follows when the signed owner parameter exists (backend, report first).
  5. Data. Fix QRS-1351, 1352 and 1353 in the view model, once.
  6. Deploy and validate. With the owner's go-ahead (Actions quota): deploy devv, then run the section 8 checklist on the deployed URL, the Preview and a QR scan on a phone. Then an independent signoff.
  7. Chitra and Patrika go through the same steps. They already have one renderer on the web, so for them the app step is what is left.

12 · The owner's decisions (2026-09-27) ​

Recorded in ADR-0033 and decisions.md D9.

#Decision
D1The web page as it renders in a phone browser is canonical; the in-app Preview and a scan into the installed app show the same page.
D2Remove the app strip, the grow block and both footnotes. Keep the "Ask on WhatsApp" support card.
D3Embed the page inside the app screen (react-native-webview; an <iframe> on the PWA).
D4Keep "Report this profile".
D5Build open-in-app (App Links, Universal Links) in this same round.

The plan in section 11 stands with one change: the app step embeds the page instead of opening a browser panel, and open-in-app is added before the deploy.