Appearance
Onboarding and authentication: end-to-end validation
⚠ A LOG. VALIDATED 2026-08-31 AGAINST DESIGN ROUND 13, FETCHED LIVE.
Do not edit it forward to match a later round. Run a new one.
What was measured: every file in the journey read in full from the Claude Design project — BrandSplash.dc.html, WelcomeStory.dc.html, Onboarding.dc.html, PinGate.dc.html — and the navigation between them traced by reading the actual href and location.href values, not by assuming the round-13 instructions were followed.
✅ SUPERSEDED BY DESIGN ROUND 14, RE-VERIFIED 2026-08-31. THE FINDINGS BELOW ARE THE RECORD, NOT THE CURRENT STATE.
Both blockers and all eight gaps are closed, each verified by reading the files rather than by reading a changelog. QRS-929 (no returning-user path) · QRS-930 (PinGate could not tell a new device from a returning one) · QRS-931 (language) carry the closure evidence.
The journey now walks end to end for a new account AND a returning one, in both audiences, with the language chosen once and carried from the choose screen through to ConsumerHome. Round 14 also resolved G4 by removing the dead dim machinery rather than adding tiles, which was one of the two options offered: the consequence is that the hub has no "more is coming" curiosity device, and that is an accepted trade rather than an oversight.
One residual, cosmetic: gnumSkip is still computed in renderVals while the template no longer renders it. Harmless dead value, not a control.
The verdict in one line
Round 13 was implemented correctly and thoroughly. The journey is intact for a NEW account and broken for a RETURNING one, because the design assumes every verified number is a first-time signup.
| Class | Count |
|---|---|
| 🔴 Implementation Blocker | 2 |
| 🟠 Important Gap | 8 |
| 🔵 Enhancement | 3 |
The journey, traced
BrandSplash.dc.html timed brand moment, first run only
⚠ no link out — see G8
WelcomeStory.dc.html
phase 'choose' → "For my business" | "For myself" pick(variant)
phase 'story' → 5 scenes, locked to the chosen variant backOut() returns to choose
final CTA → href: Onboarding.dc.html?type=business|individual&ref=
Onboarding.dc.html
consumer ['language','phone', otp|gnumber ,'name']
business ['language','phone', otp|gnumber ,'name','brand','industry','location','slug',
'permissions','highlights','celebrate','dashboard']
consumer name step → location.href = PinGate.dc.html?lang=<uiLang>
PinGate.dc.html
offer → set → confirm → biometric → ../consumer/ConsumerHome.dc.html
(also: enter, forgot — reachable only by the startStage review prop, see B2)✅ Verified correct — do not re-ask for any of this
Every round-13 instruction landed, and two of them landed better than specified:
- The choice comes first.
phase: 'choose'is the initial state.?variant=is a review-only override that jumps to the story, with a comment saying so: "A first run has no variant yet and always sees the choose screen." backOut()returns to the choice and resets the step. Back navigation works.- Scenes 1 and 3 are now variant-aware. The consumer opening reads "Your own digital identity, yours to keep and yours to share." rather than the business line.
- The Aadhaar analogy is business-only, with the reasoning kept in a code comment.
- The vision scene is scene 3, using
hubArtConsumer(), and its nodes come fromPERSONAL_KINDSvia a dynamic import ofconsumer-data.js— exactly the data-driven wiring asked for, so a new personal card type updates the screen for free. finalwas moved off the per-branch reading onto the last scene, with a comment explaining that marking it per branch "let one story dead end without a closing CTA". That was not in the brief. They found a real defect in their own previous round.- Google is a permanent second option, under an "Or" divider (
किंवा/या), 48px target, and the code comment reads "A permanent second option, quieter than the primary but not historical". - The Google path swaps the verification step rather than skipping it:
const verify = this.state.viaGoogle ? 'gnumber' : 'otp'. - The degraded reading is built.
gnumUnverified: st.deliveryFailedrenders a warning block and a "confirmed later" note, so a number accepted without verification is labelled as such. - Email: zero occurrences in the file. Not hidden, not toggled. Gone.
?lang=is passed to PinGate, and PinGate reads it before its own prop.- Per-country digit length, not a minimum, with a comment: an eleventh digit cannot be typed and a partial number cannot enable the CTA.
- Merchant copy is a separate table (
PHONE_COPY_BIZ/OTP_COPY_BIZ), so both audiences get a true reading of the purpose line rather than one borrowed from the other.
🔴 Implementation Blockers
B1 · There is no returning-user sign-in path. Every verified number is treated as a new signup.
Measured, not inferred. In Onboarding.dc.html: existing 0 · returning 0 · accountExists 0 · hasAccount 0 · isNewAccount 0 · skipSetup 0. The single "Welcome back" string is the resume-an-unfinished-setup banner mid-wizard, not a sign-in.
buildFlow always appends name after verification, and the business flow always continues into brand, industry, location and slug. So a person who reinstalls the app, or signs in on a second phone, is walked through the entire setup again — and a returning merchant is asked to re-create a brand, an industry, a location and a slug that is immutable once distributed.
⚠ The brief asked specifically to confirm "Sign Up, Sign In". Sign-in is not represented at all.
🔎 Half of this already has an answer on the repo side, which is why the classification needs care.resolveEntryRoute and the AuthStep fix (QRS-918) already route a verified, set-up account to /consumer or /dashboard instead of into the wizard. So the routing is solved. What is missing is the design's account of the moment: what a returning person sees between "code verified" and landing, and — for a merchant — that setup must be skipped rather than repeated. A developer building from these screens alone would build the wrong thing, because the screens state an assumption (every signup is new) that is false from day two.
B2 · The PIN gate cannot tell a new device from a returning one, and the distinction is the whole point
PinGate.dc.html has all five stages, and startStage is a review prop — there is no runtime branch. Because a PIN is per device (the reason it was made a device gate rather than an onboarding step), the two cases are opposite:
| Situation | Correct stage |
|---|---|
| returning on the same device | enter |
| returning on a new device, reinstall, second phone | set — the old PIN does not exist here |
| brand new account | offer → set |
Nothing decides between them, and the middle row is the one that will be got wrong: a returning user on a new phone must not be shown "Enter your PIN" for a PIN that was never created on that device. Coupled to B1 and should be resolved with it.
🟠 Important Gaps
| # | Gap | Evidence | Recommendation |
|---|---|---|---|
| G1 | The story's language is auto-detected from navigator.language, and the only control is a 28px review pill | navLang.startsWith('mr') ? 'mr' : (navLang.startsWith('hi') ? 'hi' : 'en') | Indian Android overwhelmingly reports en-IN / en-GB, so most Marathi families see the English story — the first impression, in the launch market, in the wrong language. CLAUDE.md's own standard is an in-flow language select, never a forced device locale. Put a real language control on the choose screen, which is already the first interactive screen. |
| G2 | The chosen language is dropped at the handoff | the CTA is Onboarding.dc.html?type=…&ref=… — no lang | Onboarding's step 0 is language, so the question is asked twice. One query parameter fixes it, and combined with G1 it removes a whole onboarding step. |
| G3 | The Google number step is silently skippable | skipGoogleNumber: () => this.goKey('name') | The entire reason WhatsApp is primary is capturing the number (D8). A skip with no consequence and no later prompt defeats it. Owner decision. Recommend required, except when deliveryFailed is what pushed them to Google in the first place. |
| G4 | The hub's dimmed "more coming" tiles never render | dim: !n[0] — every node has an icon, so dim is always false | The opacity and dashed-line machinery is present and fires for nothing. Round 13 asked for one or two deliberately unlabelled dimmed tiles as the curiosity device; that is the part not delivered. A mechanism that can never trigger is the QRS-013 green-no-op shape. |
| G5 | The language pill is 28px tall | height: '28px' on the choose screen | Below the 44px touch target, on what is now the first interactive screen in the product. e2e/layout-invariants would flag it once built. |
| G6 | resume: On is business-only and silently overrides audience | it hardcodes type: 'business' plus a Vedaa Lahade fixture | A consumer who abandons mid-flow has no represented resume. Lower severity (the consumer flow is four steps) but the silent override of another prop is the kind of inconsistency that costs a review pass. |
| G7 | PinGate drops the language on the final hop | landingUrl() { return '../consumer/ConsumerHome.dc.html'; } | Same class as G2. The language chosen at step 0 does not reach the app. |
| G8 | BrandSplash.dc.html has no navigation out | zero location.href, zero links | Its notes correctly say it is a timed screen and app config, so this is not an implementation blocker — but the clickable journey has no first link, and every review of "the whole flow" has to start one screen in. |
🔵 Enhancements
- The merchant has no PIN gate at all.
PinGateis reached only from the consumer name step. The merchant console holds payments and banking, so a device lock there is arguably worth more. Needs a decision, not a screen, and not before launch. - No "change number" affordance on the
gnumberstep. The OTP step haseditPhoneNum; this one does not. - No celebrate moment on the consumer path.
celebrateanddashboardare business-only. Probably correct — "you made an account" is thin — but it is a deliberate asymmetry rather than an oversight, so it is recorded.
What this validation cannot tell you
- Whether the screens look right. Nothing here is a visual judgement; that stays a screenshot and a human.
- Whether the copy is good in Marathi or Hindi. The tables are complete and structurally correct. Whether they read naturally to a native speaker is a translator's call, and round 9 already flagged the editor's guidance prose as wanting one.
- Anything about the repo beyond the four seams already measured. The client build is separate work; this validates the design's completeness and its internal wiring.