Skip to content

ADR-0033 · The biodata reading has one renderer, and the app embeds it ​

Status: 🟢 Accepted · Decided: 2026-09-27 (product owner) · Supersedes, for the biodata reading only: ADR-0011 item 3 and ADR-0019's rejection of react-native-webview · Unchanged: ADR-0019 for the Setu Card (its fidelity preview stays expo-web-browser) · Evidence:consumer/biodata-preview-public-parity-rca.md

The decision, in one sentence ​

A marriage biodata is drawn once, by the web page, and every place that shows it, including the app, shows that page.

What the owner decided ​

#QuestionAnswer
D1Which presentation is canonicalThe web page as it renders in a phone browser. The in-app Preview matches it. A family who scans the QR with QR setu installed opens the app and sees that same page.
D2Public-only contentRemoved: the app strip ("QR setu, in your browser or on your phone" + Download), the grow block ("For someone you love" / "Make one for someone in your family", "Start one, free", "Free to create", "Takes about ten minutes…") and both footnotes ("Kept current by the family…", "Exact date of birth… never shared…"). Kept: QR setu's "Ask on WhatsApp" support card.
D3How the app shows the pageEmbedded inside the app screen (no browser bar): react-native-webview on Android and iOS, an <iframe> on the Web PWA, which has no WebView.
D4"Report this profile"Kept, so a misused or fake profile can be acted on. It becomes an approved part of the design, not an implementation extra.
D6How the embedded page gets the owner's draft (2026-09-27)The app hands it the draft. A draft that is not published, and the released tier, cannot be read by the page from the database without knowing the owner; the owner chose the road that extends nothing: the app, which holds the record legitimately, cuts it to the previewed tier (projectBiodataEmbedRecord, SQL's clauses) and passes it in. Declined: an owner session or a signed owner link reaching the Worker (QRS-1359, superseded).
D5Opening the app from a scanAndroid App Links and iOS Universal Links for the biodata address. ⏸ Deferred by the owner the same day until the Apple Team ID and a release signing key exist (QRS-1360). Until then a scan opens the web page, and anything that needs the app keeps sending people to the store listing or the app's home.

Why ​

Two approved artboards and two renderers produced two experiences, and every check passed, because each surface was validated only against its own artboard (the RCA). Parity maintained by hand between a React Native renderer and a DOM renderer drifts. One renderer makes the Preview, the shared link and the QR scan the same page by construction, and then parity needs checking once, between the page and its artboard.

What it costs, stated so the choice stays informed ​

The two ADRs this supersedes rejected react-native-webview for these reasons, and each one still applies. It is now paid for deliberately:

CostConsequenceHow it is contained
A new native moduleinstalled builds cannot receive it over the air; new Android and iOS builds are neededshipped in the same native build as the App Links (D5)
No Web PWA implementationthe PWA needs its own <iframe> path, so there are two embed mechanismsa .web.tsx platform file; the page is identical, only the host differs
A new parity seam at the frame boundarysafe-area insets, keyboard, back navigation, scrolling and dark mode must cross the framea device pass on Android, iOS and the PWA is part of done (DoD item 5 in the RCA)
App sizeone more native librarymeasured before and after, and reported (the app-size budget)
Framing a page that takes input (the ask form)clickjacking if any origin can frame itin embed mode the page sends frame-ancestors for the app's own origins only

Consequences ​

  1. The native reading blocks are retired (BiodataViewScreen's ReaderBlocks, AboutBlocks, FamilyDrawing, GrowthCta). The owner band and the tier switch stay native, drawn around the embedded page. A lint rule refuses a native reading block, so a second renderer cannot return quietly.
  2. The web page gains an embed mode (?embed=1): no site header, and the page leaves the chrome to its host. Content, order and presentation are unchanged, because that is the point.
  3. Parity is checked once, page against artboard, by the presentation gate (QRS-1357). The app side is checked as "shows the same URL, plus only the approved chrome".
  4. Open-in-app (D5) needs /.well-known/assetlinks.json and /.well-known/apple-app-site-association served by the web Worker, intentFilters and associatedDomains in app.json, and an app route for /<slug>/biodata that opens the embedded reader.

How the embed works (built 2026-09-27, QRS-1368) ​

PieceWhereWhat it does
The contract@qrsetu/domain biodata/embed.tsthe payload shape, its parser, projectBiodataEmbedRecord, who may frame the page, the embed address
The pageapps/web embed/BiodataEmbed.tsx, route ?embed=1reads nothing; waits for the payload; draws it with the same resolver and design page a browser gets; hands every outbound link to the host; the language pills stay inside
Android and iOSBiodataViewScreen/BiodataPageFrame.tsxreact-native-webview: the payload injected before load, pushed again on every change and on the page's ready; any navigation away opens with the system
Web PWABiodataPageFrame.web.tsxan iframe; the payload posted to the page's own origin only

Why a page that draws what it is handed is not a forgery surface: it accepts a payload only from the app's own WebView (a global no other site can set) or from a parent frame whose origin is on biodataEmbedParentOrigins (the page's own origin, where the PWA lives, plus the two local previews on a non-production host); the response's frame-ancestors names the same origins, and every other response of the route sends frame-ancestors 'none'; the parser refuses a malformed payload and an unprojected draft. What it does not try to stop is the owner's device rendering the owner's data.

Why a PROJECTED draft and not the whole one: the public read has no "held back" markers, because SQL removes what the tier has not earned. The whole draft would have drawn markers in the Preview that no recipient sees, which is the discrepancy this ADR exists to remove. A test asserts that the projected record reads exactly as the whole draft does, less the markers, at both tiers.

Open, and not decided by this ADR ​

  • iOS Universal Links need the Apple Developer Team ID, which is recorded nowhere in the repo. Android App Links need the signing certificate's SHA-256; release builds currently sign with the debug keystore (decisions.md §5).
  • The design still draws the retired native reading (BiodataView.dc.html), so the Preview's contract rows for the reading are blocked until the design re-issues the Preview as owner band plus page (QRS-1368).
  • Device pass. react-native-webview is a native module: a new Android and iOS build, then the frame boundary (insets, back, scrolling, links) on real devices and the PWA.