Appearance
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.
| Source | What it is | How it was read |
|---|---|---|
| In-app artboard | prototype/consumer/BiodataView.dc.html, pulled fresh today | rendered on the local mirror, ordered text captured at basic and released |
| Public artboard | prototype/setu-card/BiodataPage.dc.html, pulled fresh today, byte-identical to the build's 09-26 pull | same, both tiers |
| Deployed public page | https://devv.qrsetu.com/vedaalahade/biodata | fetched today; carries no data-design attribute, so it predates commit d69cdab |
| Current public page | the HEAD build (759757c) served by wrangler against real Dev data | rendered for vedaalahade, basic tier (a stranger's link) |
| Current in-app Preview | the owner's screenshot (owner view, released-level content) | not re-rendered: it needs the owner's session |
| Contracts | design-system/parity-contracts/biodata-view.json, biodata-page.json | row 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
| Layer | State |
|---|---|
| Approved design | Two 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 rule | BiodataView.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. |
| Code | Two 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. |
| Validation | biodata-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. |
| Deployment | devv.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. |
2 · In-app Preview versus shared link, as experienced
| In-app Preview (owner's left screenshot) | Shared link (owner's right screenshot, devv) | |
|---|---|---|
| Source artboard | BiodataView.dc.html | BiodataPage.dc.html (an older revision of it, since devv is stale) |
| Renderer | React Native, in the app | DOM, a Cloudflare Worker |
| Tier shown | owner view, released-level | a stranger's link, basic |
| Build | the 8080 export of today | a 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.
| # | Element | A · in-app artboard | B · public artboard | C · app | D · public HEAD | Verdict |
|---|---|---|---|---|---|---|
| 1 | Page header | back arrow, "Marriage profile", the address | QR setu wordmark, Share, three language pills, the "not listed anywhere" line | as A | as B, no Share (QRS-1120) | design conflict |
| 2 | Opening blessing | above the hero, inside the reader | its own card above the hero | present | present | presentation differs |
| 3 | Hero | photograph carousel with a count and dots; the name sits in a card below | one photograph; the name is overlaid on it, with a "Shared by the family" chip | as A | as B | design conflict |
| 4 | Cover photograph | the first photograph in the family's order | "the cover" | QR logo image (photo 1 of 4) | the scanned paper biodata | data gap: the two surfaces pick different covers (QRS-1352) |
| 5 | Identity summary | one meta line ("21 years · 5 ft 11 in · Pune") plus a status pill ("Open") | fact tiles (Profile, Age, Status, Lives in, Height, Diet) | as A | tiles | design conflict |
| 6 | Age | "21 years" | "29 years" | "21 years" | "21", unit missing | defect (QRS-1351): a false pass on facts_strip |
| 7 | Quick chips | marital, work, education, diet, blood group, languages | languages only, as their own row | as A | languages absent (not in the data) | design conflict |
| 8 | Keep this profile · Share | two buttons under the name | Share in the header plus a share card near the end | present | both absent (QRS-1120) | design conflict + known gap |
| 9 | Section nav | none | Overview · Work · Looking for · Family · Who to talk to | none | present | design conflict |
| 10 | Education and work | a card with an icon, a line count ("5 lines") and label/value rows | a timeline: kicker, title, sub-line, rail | as A | timeline | design conflict |
| 11 | Education detail | "Education: B.E. / B.Tech", "Earlier degree: B.E. / B.Tech" | institution and year as sub-lines | as A | a stray "Education" line under the title | defect (QRS-1353), cause not yet measured |
| 12 | Looking for | a card, "N lines", short labels ("Age", "Height") | long labels ("Age they are looking for"), the quote leads | as A | as B | design conflict |
| 13 | Community, family, kundli | cards with line counts; a relationship list | labelled rows; a family drawing; a map | as A | as B | design conflict |
| 14 | Ask for more (basic) | "Ask to see more" in the tier band at the top | a card low on the page with a five-item checklist and a phone field | not on screen (owner view) | as B | design conflict |
| 15 | Who to talk to (released) | a card, Call + Message | a card, Call + Message, plus two explanatory sentences | as A | not shown at basic (correct) | presentation differs |
| 16 | The page this profile lives on | address, Show QR, Copy link | the "Share this profile" card instead | as A | absent (QRS-1120) | design conflict |
| 17 | App strip | none | "QR setu, in your browser or on your phone" + Download | none | present | public-only content |
| 18 | Footnotes | none | three: "Written in English by the family…", "Kept current by the family…", "Date of birth, income and home address are never shared…" | none | two of three | public-only content |
| 19 | Grow 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 A | as B | presentation differs, extra line |
| 20 | Ask on WhatsApp (support) | inside the grow card | its own block after it | as A | as B | presentation differs |
| 21 | Report this profile | none | none | none | present | added by implementation (QRS-1299, a safety path the artboard omits) |
| 22 | "For someone you love… three benefits… Start one, free" | none | none (an older revision had it) | none | none | stale deploy only: gone at HEAD |
| 23 | "Where Vedaa works is shared once the family releases it" | "One more line here is released…" | present | not applicable | removed 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):
| Content | Why it reads as unnecessary | Conflict with a repo rule |
|---|---|---|
| "A personal profile, shared by the family. Not listed anywhere and not searchable." | explains the product instead of presenting the person | yes: 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." | same | yes (QRS-549) |
| "Kept current by the family. There is no version two…" | product explanation | no |
| "Written in English by the family. QR setu never translates…" | product explanation | borderline |
| "One code confirms the number, and that is all we ask. Reading this page never needed an account…" | reassurance under the ask button | yes (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…" | reassurance | yes (QRS-549) |
| App strip: "QR setu, in your browser or on your phone" + Download | promotion inside a family's document | no |
| "Takes about ten minutes. Nothing is public until you send the link." | promotion copy | partly |
⚠ 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
| Area | Root cause | Structural? |
|---|---|---|
| Design process | The 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 management | We 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 architecture | Two 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 mapping | Each 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 implementation | Faithful 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/configuration | Public-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 rendering | Not a cause here. Both are phone layouts. | no |
| QA process | The 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 validation | Every check validates one surface against its own artboard, or content only. None validates one experience across surfaces. See section 6. | yes, primary |
| Definition of Done | Nothing 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/interpretation | I 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
| Check | What it measures | Why it could not see this |
|---|---|---|
check:design-parity | that every contract row has a verdict, evidence and a tracker id | presence, never truth; and no contract spans two surfaces |
biodata-page.json (web vs BiodataPage) | the public page against the public artboard | the 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 artboard | same, from the other side |
check:biodata-parity | for every design, at both tiers: which fields appear (coverage), what leaks (disclosure), and whether values are faithful | content 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 place | says nothing about how it is shown |
| Independent parity signoff | re-measures a contract's claims | inherits the contract's scope: one surface, one artboard |
The owner's review page (review.html, today) | the artboard next to our build, per surface | it 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
- 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.
- 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. - Formatting in the view model, once. Age with its unit, the cover choice and every derived line are resolved in
@qrsetu/domainand rendered verbatim, so no surface can format differently. - 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.
- 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.
- 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:
- One canonical artboard exists for its reading, and the in-app artboard is chrome only.
- It has one renderer. The in-app Preview and the recipient's in-app reading show that renderer's page.
- The presentation gate (section 10) is green for every tier × language × state, with every exception approved and listed.
check:biodata-parityis green (content and disclosure).- The deployed URL, the in-app Preview and a QR scan were validated on a real phone, against the stated build.
- The owner reviewed the side-by-side at a pinned tier and signed off.
- 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:
- Render the canonical artboard with its own seed record.
- Render the built page with the same record through the run-web mock, so the words match.
- Extract the ordered block inventory from both: block ids (
data-sloton the build, the artboard's section markers on the design), the treatment of each block, and its text. - 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.
11 · Recommended plan (only after the owner's decisions)
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:
- 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.
- Gate first. Build
check:biodata-presentationand show it failing on today's state. That proves it can see this gap. - Web. Rebuild the public page to the canonical artboard until the gate is green.
- 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=1framing follows when the signed owner parameter exists (backend, report first). - Data. Fix QRS-1351, 1352 and 1353 in the view model, once.
- 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.
- 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 |
|---|---|
| D1 | The 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. |
| D2 | Remove the app strip, the grow block and both footnotes. Keep the "Ask on WhatsApp" support card. |
| D3 | Embed the page inside the app screen (react-native-webview; an <iframe> on the PWA). |
| D4 | Keep "Report this profile". |
| D5 | Build 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.