diff --git a/specs/projects/sdk/INDEX.md b/specs/projects/sdk/INDEX.md index f133e4ac9..65dc29ea8 100644 --- a/specs/projects/sdk/INDEX.md +++ b/specs/projects/sdk/INDEX.md @@ -6,15 +6,15 @@ Status: Active (WebView-first + native keychain/crypto) ## Start Here 1. [SDK Overview](./OVERVIEW.md) — current scope, active architecture, paused-work policy -2. Open the relevant active workstream `SPEC.md` — read `Backlog` and `Active Plans` first +2. Open the relevant active workstream `INDEX.md` or `SPEC.md` 3. Open the linked `plans/-.md` file — execute from that file 4. If you are reviving native/KMP/RN work, open [Paused Work](./paused/INDEX.md) before touching any native spec ## Active Workstreams -| Workstream | Spec | Focus | +| Workstream | Entry | Focus | | -------------------- | ------------------------------------------------------------------- | --------------------------------------------- | -| WebView UI | [WebView Spec](./workstreams/webview/SPEC.md) | Sumsub Web SDK integration, KYC result flow | +| WebView UI | [WebView Index](./workstreams/webview/INDEX.md) | Sumsub Web SDK integration, KYC result flow | | SDK Core | [SDK Core Spec](./workstreams/sdk-core/SPEC.md) | Browser-portable engine | | Native Shells (Lite) | [Native Shells Lite Spec](./workstreams/native-shells-lite/SPEC.md) | Plain Kotlin + Swift for keychain/crypto only | | Build Pipeline | [Build Pipeline Spec](./workstreams/build-pipeline/SPEC.md) | Bundle webview-app into native shells | diff --git a/specs/projects/sdk/workstreams/webview/INDEX.md b/specs/projects/sdk/workstreams/webview/INDEX.md new file mode 100644 index 000000000..f5abba149 --- /dev/null +++ b/specs/projects/sdk/workstreams/webview/INDEX.md @@ -0,0 +1,7 @@ +# WebView Workstream + +- [Workstream Spec](./SPEC.md) +- [Screen Inventory](./SCREEN-INVENTORY.md) +- [Ticket Plan](./TICKET-PLAN.md) + +Use the inventory as the source of truth for scope and counts. Use the ticket plan to stage spec work, candidate tickets, and PR slices before creating Linear issues. diff --git a/specs/projects/sdk/workstreams/webview/SCREEN-INVENTORY.md b/specs/projects/sdk/workstreams/webview/SCREEN-INVENTORY.md new file mode 100644 index 000000000..07782a906 --- /dev/null +++ b/specs/projects/sdk/workstreams/webview/SCREEN-INVENTORY.md @@ -0,0 +1,267 @@ +# Webview Migration — Screen Inventory + +Master list of 3.0 production-scope screens grouped by flow, for migrating euclid to the webview app (`selfxyz/self/packages/webview-app`). + +Scope notes: + +- Summary totals count **Euclid source screens** from `@selfxyz/euclid`, not webview wrapper files. +- Webview-only production screens are tracked separately and are **not** included in the Euclid totals. +- Tunnel/demo screens are listed for context but are **not** counted in 3.0 production totals. + +Last updated: 2026-03-24 + +--- + +## Current Webview App State + +### Already Implemented (PR #1858 + prior work) + +These screens already exist in `selfxyz/self/packages/webview-app/src/screens/`. + +**Euclid screen migrations** (10 webview wrappers covering 11 euclid source screens): + +| Webview Screen | Euclid Source | Folder | +| ------------------------------- | ------------------------------------------- | ------------- | +| `HomeScreen` | `HomeScreen` | `home/` | +| `SettingsScreen` | `SettingsViewScreen` | `account/` | +| `SecurityScreen` | `SecurityScreen` | `account/` | +| `NotificationPreferencesScreen` | `NotificationPreferencesScreen` | `account/` | +| `DevModeScreen` | `DevModeScreen` | `account/` | +| `CountryPickerScreen` | `CountryPickerScreen` | `onboarding/` | +| `IDSelectionScreen` | `IDTypeScreen` | `onboarding/` | +| `ProvingScreen` | `ProofProgressScreen` | `proving/` | +| `VerificationResultScreen` | `ProofSuccessScreen` / `ProofFailureScreen` | `proving/` | +| `ComingSoonScreen` | `ComingSoonScreen` | (root) | + +**Webview-only production screens** (3 screens — no euclid equivalent, created for the webview app): + +| Webview Screen | Purpose | Folder | +| ----------------------------- | -------------------------------------------------------------------------- | ------------- | +| `ConfirmIdentificationScreen` | Confirm ID details before provider handoff | `onboarding/` | +| `ProviderLaunchScreen` | Provider placeholder (replaces native camera/NFC screens removed in WV-03) | `onboarding/` | +| `ProviderResultScreen` | Provider result handoff and routing after Sumsub / KYC completion | `onboarding/` | + +**Tunnel flow screens** (PoC / demo flow from PR #1858 — not 3.0 production scope): + +- `TourScreen`, `TunnelCountryPickerScreen`, `TunnelIDTypeScreen`, `KycMockScreen`, `TunnelProvingScreen`, `TunnelProofReceiptScreen`, `TunnelResultScreen` + +The tunnel route wrapper `TourScreen` renders the real Euclid `LaunchTour1Screen`–`LaunchTour4Screen` sequence for demo wiring. It does not mean the full registration flow is migrated end-to-end. + +### Completed Specs (WV-01 through WV-04) + +| ID | Title | Status | +| ----- | ------------------------------ | ------ | +| WV-01 | Dynamic proof request items | Done | +| WV-02 | KYC provider contract | Done | +| WV-03 | Remove native scan assumptions | Done | +| WV-04 | Host callback contract | Done | + +### Open Linear Issues (from PR #1858 audit) + +| Issue | Title | Priority | +| --------- | ------------------------------------------------------ | -------- | +| SELF-2357 | Euclid migration: validate exports + API compatibility | Urgent | +| SELF-2358 | Sumsub / WV-05 contract compliance | High | +| SELF-2359 | Tunnel + proving flow data propagation fixes | High | +| SELF-2360 | Settings persistence, test coverage, doc cleanup | Medium | + +Note: SELF-2358 references WV-05 contract compliance. WV-05 is now documented in `plans/WV-05-sumsub-web-sdk.md`; keep issue execution aligned with that spec. + +--- + +## Remaining Work — Grouped by Flow + +### Priority 1: Registration Flow + +#### Tour (4 screens) — NOT YET MIGRATED + +| Screen | Key Components | Purpose | Status | +| ------------------- | ---------------------------------------------- | --------------------------------------- | ------ | +| `LaunchTour1Screen` | ProgressBar, LottieAnimation, SelfLogo, Button | Step 1/4 — centered dialogue with logo | Todo | +| `LaunchTour2Screen` | ProgressBar, LottieAnimation, Button | Step 2/4 — bottom-aligned with gradient | Todo | +| `LaunchTour3Screen` | ProgressBar, LottieAnimation, Button | Step 3/4 — bottom-aligned | Todo | +| `LaunchTour4Screen` | ProgressBar, LottieAnimation, Button | Step 4/4 — CTA + Terms/Privacy | Todo | + +Note: `@selfxyz/euclid` currently exports both the 4-step `LaunchTour1Screen`–`LaunchTour4Screen` sequence and a separate legacy `TourScreen`. This inventory treats the 4-step launch tour as the 3.0 production scope. The legacy `TourScreen` is not counted in the totals below. + +#### Country & ID Type Selection — ALREADY MIGRATED + +`CountryPickerScreen`, `IDTypeScreen` (as `IDSelectionScreen`), `ComingSoonScreen` are done. + +#### EU ID Screens (6 screens) — DEFERRED FROM ACTIVE WEBVIEW MIGRATION + +| Screen | Key Components | Purpose | Status | +| ---------------------------- | -------------------------------------- | --------------------------------------- | ------ | +| `EuIdInstructionsScreen` | InstructionFlowScaffold | Step 3/7 — front side scan instructions | Todo | +| `EuIdBackInstructionsScreen` | InstructionFlowScaffold | Step 4/7 — back side scan instructions | Todo | +| `EuIdViewfinderScreen` | InstructionFlowScaffold + camera frame | Step 5/7 — camera viewfinder | Todo | +| `EuIdCanInstructionsScreen` | InstructionFlowScaffold | CAN entry instructions | Todo | +| `EuIdNfcInstructionsScreen` | InstructionFlowScaffold | Step 6/7 — NFC chip scan instructions | Todo | +| `EuIdNfcSuccessScreen` | Success UI | Step 7/7 — NFC read confirmation | Todo | + +Note: Camera/NFC native screens were removed in WV-03. These six screens are not part of the provider-backed registration flow. Providers own their own onboarding/capture guidance. Keep these screens inventoried only; they are deferred unless a separate Self-owned EU ID flow is approved in a future spec. + +#### Registration Outcome (3 screens) — NOT YET MIGRATED + +| Screen | Key Components | Purpose | Status | +| --------------------------- | ---------------- | --------------------------- | ------ | +| `ScanSuccessScreen` | Success UI | Document scan succeeded | Todo | +| `RegistrationFailureScreen` | Error UI, Button | Registration failed — retry | Todo | +| `SumsubFailureScreen` | Error UI | Sumsub verification failed | Todo | + +Note: `SumsubFailureScreen` migration may overlap with SELF-2358 (Sumsub / WV-05 contract compliance). Coordinate with that issue to avoid duplicate work. + +#### Social Sign-On / Onboarding (4 screens) — NOT YET MIGRATED + +| Screen | Key Components | Purpose | Status | +| -------------------------------- | ----------------------- | ---------------------------------------- | ------ | +| `SocialSignOnMethodPickerScreen` | LottieAnimation, Button | Choose backup method (Apple/Google/Seed) | Todo | +| `SocialSignOnPickerScreen` | Button | Simplified social sign-on picker | Todo | +| `ConflictDetectedScreen` | Button, hero image | Account conflict resolution | Todo | +| `PushNotificationPromptScreen` | LottieAnimation, Button | Enable push notifications | Todo | + +**Registration remaining: 17 screens** + +--- + +### Priority 2: Disclose Flow (Proof Generation) + +#### Core Proof Flow — PARTIALLY MIGRATED + +`ProvingScreen` (covers `ProofProgressScreen`) and `VerificationResultScreen` (covers success/failure) already exist. + +| Screen | Key Components | Purpose | Status | +| --------------------------- | --------------------------------------------- | ---------------------------------------------- | ------ | +| `QRViewfinderScreen` | Camera frame, scanner overlay | Scan QR code to start proof | Todo | +| `ProofRequestScreen` | TopNavigation, ProofRequest, IDPicker, Button | Review request, select doc, long-press confirm | Todo | +| `ProofGenerationScreen` | IDCard, LottieAnimation, ProofGeneration | ID card + generation animation | Todo | +| `ProofResultScreen` | Proof result display | Final proof details | Todo | +| `ProofRequestReceiptScreen` | Receipt UI | Accepted request receipt | Todo | +| `ProofHistoryScreen` | TopNavigation, DetailedTableView | Chronological list of proofs | Todo | + +#### Proof Dialogues (5 screens) — NOT YET MIGRATED + +| Screen | Key Components | Purpose | Status | +| ------------------------------- | ------------------------------- | ---------------------------------- | ------ | +| `SimpleDialogueScreen` | Dialogue, TopNavigationDialogue | Text-only message overlay | Todo | +| `DialogueWithCtaScreen` | Dialogue, Button | Message with action buttons | Todo | +| `ProofGenerationDialogueScreen` | Dialogue, ProofGeneration | Progress overlay during generation | Todo | +| `ProofGenerationSuccessScreen` | Success animation | Generation success confirmation | Todo | +| `ProofSuccessBackupScreen` | Success UI, Button | Post-proof backup prompt | Todo | + +#### Sumsub Verification (2 screens) — NOT YET MIGRATED + +| Screen | Key Components | Purpose | Status | +| --------------------------------- | -------------- | ------------------------------- | ------ | +| `SumsubPendingScreen` | Loading UI | Waiting for Sumsub verification | Todo | +| `SumsubVerificationSuccessScreen` | Success UI | Sumsub verification passed | Todo | + +Related: SELF-2358 covers Sumsub contract compliance. + +#### Other (1 screen) + +| Screen | Key Components | Purpose | Status | +| ------------------ | -------------- | ------------------- | ------ | +| `NovaSplashScreen` | Splash UI | Nova feature splash | Todo | + +**Disclose remaining: 14 screens** + +--- + +### Priority 3: Home + ID Data — PARTIALLY MIGRATED + +`HomeScreen` is done. + +| Screen | Key Components | Purpose | Status | +| -------------- | ------------------------------------------------------- | ------------------------- | ------ | +| `IDDataScreen` | TopNavigationDialogue, ExposedIDCard, DetailedTableView | View extracted ID details | Todo | + +--- + +### Priority 4: Recovery & Backup Flow (5 screens) — NOT YET MIGRATED + +| Screen | Key Components | Purpose | Status | +| -------------------------- | --------------------------------------------------------- | -------------------------------- | ------ | +| `LaunchRecoveryScreen` | SocialSignOnButton, Button | Recovery landing — choose method | Todo | +| `SecretPhraseInputScreen` | TopNavigationDialogue, SecretPhraseInput, Button | Enter 24-word phrase | Todo | +| `RecoverySuccessScreen` | Success UI | Recovery succeeded | Todo | +| `BackupMethodPickerScreen` | TopNavigationDialogue | Choose backup method | Todo | +| `RecoveryPhraseScreen` | TopNavigationDialogue, RecoveryPhrase, SocialSignOnButton | View/reveal recovery phrase | Todo | + +--- + +### Priority 5: Settings — MOSTLY MIGRATED + +`SettingsScreen`, `SecurityScreen`, `NotificationPreferencesScreen`, `DevModeScreen` are done. + +| Screen | Key Components | Purpose | Status | +| ----------------------- | ---------------------------------------- | --------------------- | ------ | +| `ManageDocumentsScreen` | DetailedTableView, DetailedTableViewCell | Manage registered IDs | Todo | + +--- + +## Deprioritized (3.1 / Skip) + +| Flow | Screens | Reason | +| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | +| **Passport Registration** (6 screens) | `PassportInstructionsScreen`, `PassportCodeScanInstructionsScreen`, `PassportCodeScanViewfinderScreen`, `PassportNfcInstructionsScreen`, `PassportNfcErrorScreen`, `PassportNfcSuccessScreen` | Deferred to 3.1 (SELF-2145) | +| **Aadhaar Registration** (3 screens) | `AadhaarAppInstructionsScreen`, `AadhaarUploadSuccessScreen`, `AadhaarUploadErrorScreen` | Deferred to 3.1 (SELF-2235) | +| **Points** (2 screens) | `PointsScreen`, `InviteScreen` | Deferred to 3.1 (SELF-2249) | + +--- + +## Summary + +| Category | Total Screens | Already Done | Remaining | +| ---------------------------- | ------------- | ------------ | --------- | +| Registration: Tour | 4 | 0 | 4 | +| Registration: Country/ID | 3 | 3 | 0 | +| Registration: EU ID Scan | 6 | 0 | 6 | +| Registration: Outcome | 3 | 0 | 3 | +| Registration: Social Sign-On | 4 | 0 | 4 | +| Disclose: Core Proof | 9 | 3\* | 6 | +| Disclose: Proof Dialogues | 5 | 0 | 5 | +| Disclose: Sumsub | 2 | 0 | 2 | +| Disclose: Other | 1 | 0 | 1 | +| Home + ID Data | 2 | 1 | 1 | +| Recovery & Backup | 5 | 0 | 5 | +| Settings | 5 | 4 | 1 | +| **Total (3.0 scope)** | **49** | **11** | **38** | + +"Already Done" counts Euclid source-screen coverage, not wrapper file count. The webview app currently has 10 Euclid-backed wrapper screens plus 3 webview-only production screens (`ConfirmIdentificationScreen`, `ProviderLaunchScreen`, `ProviderResultScreen`), which are not included in the totals above. + +\*`ProvingScreen` covers `ProofProgressScreen`. `VerificationResultScreen` covers both `ProofSuccessScreen` and `ProofFailureScreen`, so this row has 3 Euclid source screens already covered. + +Counting basis for the 49-screen total: + +- `@selfxyz/euclid` v1.2.0 currently exports 61 screen components. +- 11 screens are explicitly deprioritized for 3.1 (`Passport*`, `Aadhaar*`, `Points*`). +- 1 legacy marketing screen (`TourScreen`) is excluded from 3.0 tracking because the current scope uses `LaunchTour1Screen`–`LaunchTour4Screen` instead. + +--- + +## Coverage Status + +All current `@selfxyz/euclid` screen components are accounted for in one of these buckets: + +- **Done in webview app**: 11 Euclid source screens are already covered by current webview wrappers +- **Remaining in 3.0 scope**: 38 Euclid source screens remain planned for migration +- **Deferred to 3.1**: 11 Euclid source screens are intentionally deprioritized +- **Excluded from 3.0 count**: 1 legacy Euclid screen (`TourScreen`) is tracked as out of active 3.0 scope because the launch-tour sequence uses `LaunchTour1Screen`–`LaunchTour4Screen` + +That means every current Euclid screen is accounted for in the inventory. + +Flow-level status is slightly different: + +- All screen families are accounted for +- A small number of flow decisions still need explicit product/spec confirmation before ticket creation + +Resolved flow decisions: + +- EU ID helper screens are deferred from the active webview migration (WV-10); not part of provider-backed registration +- Main `/proving` route is the core disclose path (WV-11); tunnel stays as the reference/demo flow from WV-08 + +Open flow decisions: + +- whether `ConfirmIdentificationScreen` remains a separate registration step +- whether `SumsubPendingScreen` and `SumsubVerificationSuccessScreen` are end-user product screens or support-state screens diff --git a/specs/projects/sdk/workstreams/webview/SPEC.md b/specs/projects/sdk/workstreams/webview/SPEC.md index f9bcec806..bd8444ded 100644 --- a/specs/projects/sdk/workstreams/webview/SPEC.md +++ b/specs/projects/sdk/workstreams/webview/SPEC.md @@ -1,6 +1,6 @@ # WebView-Only Verification Experience — Implementation Spec -> Last updated: 2026-03-21 +> Last updated: 2026-03-25 > Owner: WebView / Product Platform > Project: [SDK Overview](../../OVERVIEW.md) > Status: Active @@ -44,16 +44,23 @@ On **March 11, 2026**, the active SDK scope changed to **WebView only, with no c ## Backlog -| ID | Title | Status | Priority | Depends On | Plan | Notes | -| ----- | ----------------------------------------------------------------------------------------------- | ----------- | -------- | ---------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | -| WV-01 | Dynamic proof request items sourced from request context | Done | High | - | [plans/WV-01-dynamic-proof-request-items.md](./plans/WV-01-dynamic-proof-request-items.md) | Existing active follow-up | -| WV-02 | Define the KYC-provider contract for document capture, MRZ/liveness handoff, and result mapping | Done | High | - | [plans/WV-02-kyc-provider-contract.md](./plans/WV-02-kyc-provider-contract.md) | Provider-backed path replaces Self-owned native scan flow; active contract is now documented | -| WV-03 | Remove native NFC and native-scan assumptions from active WebView screens, copy, and docs | Done | High | WV-02 | [plans/WV-03-remove-native-scan-assumptions.md](./plans/WV-03-remove-native-scan-assumptions.md) | Active UX/docs now route to a provider placeholder instead of Self-managed scan screens | -| WV-04 | Define the host callback contract for launch, dismiss, and final result without native modules | Done | Medium | WV-02 | [plans/WV-04-host-callback-contract.md](./plans/WV-04-host-callback-contract.md) | Browser host fallback now uses `postMessage` for iframe/popup embedding while native transports keep their current behavior | -| WV-05 | Integrate KYC provider Web SDK into ProviderLaunchScreen (Sumsub as default) | In Progress | High | WV-02 | [plans/WV-05-sumsub-web-sdk.md](./plans/WV-05-sumsub-web-sdk.md) | Code complete on `feat/webview-sdk`, needs testing | -| WV-06 | Wire KYC result through verification pipeline to host lifecycle callback | Ready | High | WV-05 | [plans/WV-06-kyc-result-flow.md](./plans/WV-06-kyc-result-flow.md) | Sumsub result → kycResultStore → ConfirmIdentificationScreen → lifecycle.setResult() | -| WV-07 | SelfClient assembly and proving machine export for WebView | Done | High | SC-03 | [plans/WV-07-selfclient-proving-assembly.md](./plans/WV-07-selfclient-proving-assembly.md) | Export useProvingStore, map bridge→SDK adapters, keychain-backed documents, create real SelfClient | -| WV-08 | Wire tunnel flow with real proving machine (register → disclose) | Ready | High | WV-07 | [plans/WV-08-tunnel-proving-flow.md](./plans/WV-08-tunnel-proving-flow.md) | Replace mock tunnel proving with real provingMachine: Sumsub → store doc → prove → disclose → result | +| ID | Title | Status | Priority | Depends On | Plan | Notes | +| ----- | ----------------------------------------------------------------------------------------------- | ----------- | -------- | ------------------- | ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- | +| WV-01 | Dynamic proof request items sourced from request context | Done | High | - | [plans/WV-01-dynamic-proof-request-items.md](./plans/WV-01-dynamic-proof-request-items.md) | Existing active follow-up | +| WV-02 | Define the KYC-provider contract for document capture, MRZ/liveness handoff, and result mapping | Done | High | - | [plans/WV-02-kyc-provider-contract.md](./plans/WV-02-kyc-provider-contract.md) | Provider-backed path replaces Self-owned native scan flow; active contract is now documented | +| WV-03 | Remove native NFC and native-scan assumptions from active WebView screens, copy, and docs | Done | High | WV-02 | [plans/WV-03-remove-native-scan-assumptions.md](./plans/WV-03-remove-native-scan-assumptions.md) | Active UX/docs now route to a provider placeholder instead of Self-managed scan screens | +| WV-04 | Define the host callback contract for launch, dismiss, and final result without native modules | Done | Medium | WV-02 | [plans/WV-04-host-callback-contract.md](./plans/WV-04-host-callback-contract.md) | Browser host fallback now uses `postMessage` for iframe/popup embedding while native transports keep their current behavior | +| WV-05 | Integrate KYC provider Web SDK into ProviderLaunchScreen (Sumsub as default) | In Progress | High | WV-02 | [plans/WV-05-sumsub-web-sdk.md](./plans/WV-05-sumsub-web-sdk.md) | Code complete on `feat/webview-sdk`, needs testing | +| WV-06 | Wire KYC result through verification pipeline to host lifecycle callback | Ready | High | WV-05 | [plans/WV-06-kyc-result-flow.md](./plans/WV-06-kyc-result-flow.md) | Sumsub result → kycResultStore → ConfirmIdentificationScreen → lifecycle.setResult() | +| WV-07 | SelfClient assembly and proving machine export for WebView | Done | High | SC-03 | [plans/WV-07-selfclient-proving-assembly.md](./plans/WV-07-selfclient-proving-assembly.md) | Export useProvingStore, map bridge→SDK adapters, keychain-backed documents, create real SelfClient | +| WV-08 | Wire tunnel flow with real proving machine (register → disclose) | Ready | High | WV-05, WV-06, WV-07 | [plans/WV-08-tunnel-proving-flow.md](./plans/WV-08-tunnel-proving-flow.md) | Replace mock tunnel proving with real provingMachine: Sumsub → store doc → prove → disclose → result | +| WV-09 | Registration core (tour, outcomes, social sign-on, prompts) | Ready | High | WV-05, WV-06 | [plans/WV-09-registration-core.md](./plans/WV-09-registration-core.md) | 11 Euclid wrapper screens, 4 PRs: tour → outcomes → social/prompts → integration | +| WV-10 | EU ID separation decision | Ready | Medium | WV-09 | [plans/WV-10-eu-id-helper-flow.md](./plans/WV-10-eu-id-helper-flow.md) | Record that EU ID is outside provider-backed registration and defer the 6 EU ID screens from the active webview route spine | +| WV-11 | Disclose core | Ready | High | WV-07, WV-08 | [plans/WV-11-disclose-core.md](./plans/WV-11-disclose-core.md) | Request-context entry → proof request → generation → result | +| WV-12 | Proof overlays, history, and post-proof support | Blocked | Medium | WV-11 | — | Spec needed; receipt, history, dialogues, success/backup prompts, Sumsub pending/success, Nova splash | +| WV-13 | Home, document management, and ID data | Blocked | Medium | WV-11 | — | Spec needed; IDDataScreen, ManageDocumentsScreen, HomeScreen follow-through | +| WV-14 | Recovery and backup surfaces | Blocked | Low | WV-13 | — | Spec needed; recovery method picker, phrase display/input, recovery success | +| WV-15 | Settings follow-through and support routes | Blocked | Low | WV-13 | — | Spec needed; settings persistence, security/notification actions, dev-mode completion | Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` @@ -69,6 +76,9 @@ Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` | [plans/WV-06-kyc-result-flow.md](./plans/WV-06-kyc-result-flow.md) | WV-06 | Ready | | [plans/WV-07-selfclient-proving-assembly.md](./plans/WV-07-selfclient-proving-assembly.md) | WV-07 | Done | | [plans/WV-08-tunnel-proving-flow.md](./plans/WV-08-tunnel-proving-flow.md) | WV-08 | Ready | +| [plans/WV-09-registration-core.md](./plans/WV-09-registration-core.md) | WV-09 | Ready | +| [plans/WV-10-eu-id-helper-flow.md](./plans/WV-10-eu-id-helper-flow.md) | WV-10 | Ready | +| [plans/WV-11-disclose-core.md](./plans/WV-11-disclose-core.md) | WV-11 | Ready | ## Completion Checklist @@ -224,7 +234,8 @@ Host-facing mapping rules: - Provider results are inputs to the Self flow, not direct host results. - Only the full Self verification lifecycle emits `VERIFICATION_COMPLETE` or calls `lifecycle.setResult`. -- If provider `success` unlocks the KYC proof path and the later Self proof flow completes, Self emits `VERIFICATION_COMPLETE { success: true, verificationId, userId }`. +- For the registration path, the terminal lifecycle event occurs when Self has stored the attested KYC document and the user confirms ownership. That registration session may emit `VERIFICATION_COMPLETE { success: true, verificationId, userId }` without running a ZK proof. +- For a disclose / proving path, provider-backed or previously stored identity data may unlock the later Self proof flow, and that separate session emits `VERIFICATION_COMPLETE { success: true, verificationId, userId }` when proving completes. - If the verification session terminates after provider `partial`, `cancel`, or `error`, Self emits `VERIFICATION_COMPLETE { success: false, verificationId, userId, error }` using the normalized Self error code rather than the raw provider payload. ## Host Callback Contract diff --git a/specs/projects/sdk/workstreams/webview/TICKET-PLAN.md b/specs/projects/sdk/workstreams/webview/TICKET-PLAN.md new file mode 100644 index 000000000..9a5ba1269 --- /dev/null +++ b/specs/projects/sdk/workstreams/webview/TICKET-PLAN.md @@ -0,0 +1,526 @@ +# Webview Migration — Ticket Planning + +Plan for creating specs, candidate tickets, and PR slices for the remaining webview screen migration work. This document is for planning only. It does not create Linear tickets. + +Last updated: 2026-03-24 + +Related docs: + +- [Screen Inventory](./SCREEN-INVENTORY.md) +- [WebView Spec](./SPEC.md) + +--- + +## Purpose + +The next planning step is not "create one ticket per screen." That would produce the wrong execution order and too much coordination overhead. + +Instead, we should: + +- group work by **flow boundary** +- align groups to **spec-sized contracts** +- split implementation into **PR-sized slices** +- order the work so each merged PR unlocks the next user-visible section of the flow + +The ticket candidates below are intentionally larger than a single screen but smaller than a whole product area. + +--- + +## Planning Checklist + +Use this checklist to track planning readiness before creating Linear issues. + +### Foundation specs + +- [x] `WV-05` spec exists +- [x] `WV-05` issue created (SELF-2414) +- [ ] `WV-05` status wording updated to reflect contract/testing gap +- [x] `WV-06` spec exists in repo +- [x] `WV-06` issue created (SELF-2415) +- [x] `WV-07` spec exists +- [x] `WV-07` issue created (SELF-2416) +- [x] `WV-08` spec exists +- [x] `WV-08` issue created (SELF-2417) + +### Proposed migration specs + +- [x] `WV-09` spec created +- [x] `WV-09` issue group created (SELF-2418) +- [x] `WV-10` spec created +- [x] `WV-10` issue group created (SELF-2419) +- [x] `WV-11` spec created +- [x] `WV-11` issue group created (SELF-2420) +- [ ] `WV-12` spec created +- [x] `WV-12` issue group created (SELF-2421) +- [ ] `WV-13` spec created +- [x] `WV-13` issue group created (SELF-2422) +- [ ] `WV-14` spec created +- [x] `WV-14` issue group created (SELF-2423) +- [ ] `WV-15` spec created +- [x] `WV-15` issue group created (SELF-2424) + +### Pre-ticket decisions + +- [x] EU ID helper-flow role decided +- [ ] `ConfirmIdentificationScreen` step ownership decided +- [x] primary proving surface decided (main `/proving` route per WV-11; tunnel stays as reference/demo) +- [ ] `SumsubPendingScreen` / `SumsubVerificationSuccessScreen` product status decided + +--- + +## Planning Principles + +### 1. Sequence by user journey, not by component family + +The real product path is: + +1. launch and intro +2. registration entry +3. provider handoff and provider result normalization +4. registration success or failure +5. stored-document home state +6. disclose request and proof generation +7. proof result and history +8. recovery, settings, and lower-priority support surfaces + +If we implement screens outside that order, we will create UI islands that are hard to validate end to end. + +### 2. Separate contract work from screen migration work + +WV-05, WV-06, WV-07, and WV-08 are not just visual migrations. They define the runtime boundary: + +- provider launch and normalization +- KYC result persistence +- SelfClient / proving machine assembly +- register-to-disclose tunnel integration + +Ticket planning should treat those as foundation specs, not "screen tickets." + +### 3. Prefer PRs that close a usable branch of the flow + +A good PR should leave the app in a more complete and testable state. The ideal unit is: + +- one coherent route chain +- one backing contract +- one or two validation paths + +Avoid PRs that only add scattered screens with no route completion. + +### 4. Keep ticket shape close to ownership boundaries + +The natural boundaries are: + +- onboarding / registration +- provider contract and KYC normalization +- proving / disclose +- home / settings / recovery + +These boundaries match the current route layout in `packages/webview-app/src/App.tsx` and the active webview specs. + +--- + +## Recommended Order + +### Priority tiers + +The intended planning priority is: + +1. **Tier 0: Foundation contracts** + `WV-05`, `WV-06`, `WV-07`, `WV-08` +2. **Tier 1: Registration** + Tour, provider handoff, provider result, registration outcomes, and registration-adjacent social sign-on / prompt screens +3. **Tier 2: Disclose** + QR entry, proof request, generation, result, receipt/history, dialogues, and disclose support states +4. **Tier 3: Support surfaces** + Home follow-through, document management, settings completion, and recovery / backup + +This means ticket creation should prioritize **registration first, disclose second, related support screens third**. + +There are **4 total tiers** in the planning model: + +- `Tier 0` foundation +- `Tier 1` registration +- `Tier 2` disclose +- `Tier 3` support surfaces + +### Phase 0: Foundation corrections before new migration tickets + +Do not open the main migration ticket set until the existing foundation work is explicitly restated and de-risked. + +Candidate planning items: + +- restate WV-05 as "implementation present, contract validation incomplete" +- define WV-06 explicitly in a repo plan file before ticket creation +- confirm WV-07 and WV-08 remain the proving integration path +- keep screen-migration tickets dependent on those foundation specs where applicable + +Why first: + +- registration outcome screens depend on normalized provider terminal states +- proof flow screens depend on the proving machine and stored-document pipeline +- otherwise ticket sequencing will drift into mock-only UI + +### Phase 1: Registration spine + +This is the first user-visible product narrative and should be planned as a single ordered chain: + +1. launch tour +2. country picker +3. ID type +4. provider launch +5. provider result +6. confirm identification +7. outcome screens +8. social sign-on conflict and backup prompts + +The country and ID screens already exist, and provider launch/result already exist as webview-only screens. That means the first new migration effort should focus on the missing parts around them rather than reopening the whole onboarding surface. + +Recommended planning order inside registration: + +- Tour first, because it is the earliest missing user-facing entry point +- Registration outcomes second, because they define terminal states for real provider flows +- EU ID is explicitly out of the provider-backed registration spine and should be tracked as a separate decision/defer item, not a provider helper layer +- Social sign-on and push prompt last within WV-09, because they are registration-adjacent but not on the critical proof path + +### Phase 2: Disclose spine + +After registration spine planning, move to the proof request and result experience in this order: + +1. QR entry +2. proof request review +3. proof generation +4. proof result +5. proof receipt and history +6. proof dialogues +7. sumsub pending / success and Nova splash where still needed + +Why this order: + +- QR and request review are the start of the disclose flow +- generation and result are the critical happy path +- receipt/history are post-completion support surfaces +- dialogue screens are mostly overlays and can be attached after the main route chain is stable + +### Phase 3: Post-core support surfaces + +These are valuable but not the shortest path to a usable 3.0 verification flow: + +- ID data +- manage documents +- recovery and backup + +These should be ticketed after the registration and disclose spines are planned. + +--- + +## Screen Ordering Notes + +### Registration flow ordering + +The registration screens should not be planned as four unrelated buckets. The correct narrative is: + +1. `LaunchTour1Screen` +2. `LaunchTour2Screen` +3. `LaunchTour3Screen` +4. `LaunchTour4Screen` +5. `CountryPickerScreen` +6. `IDSelectionScreen` +7. `ConfirmIdentificationScreen` +8. `ProviderLaunchScreen` +9. `ProviderResultScreen` +10. `ScanSuccessScreen` or `RegistrationFailureScreen` or `SumsubFailureScreen` +11. `SocialSignOnMethodPickerScreen` or `SocialSignOnPickerScreen` +12. `ConflictDetectedScreen` when the account path is ambiguous +13. `PushNotificationPromptScreen` +14. `HomeScreen` + +Special note on EU ID screens: + +- `EuIdInstructionsScreen` +- `EuIdBackInstructionsScreen` +- `EuIdViewfinderScreen` +- `EuIdCanInstructionsScreen` +- `EuIdNfcInstructionsScreen` +- `EuIdNfcSuccessScreen` + +These sit inside the legacy registration journey, but the active webview scope no longer assumes Self-owned camera or NFC capture. Per WV-10, EU ID is not part of provider-backed registration and the six screens are deferred from the active webview migration path unless a separate Self-owned flow is approved in a future spec. + +### Disclose flow ordering + +The proof flow should be planned as: + +1. `QRViewfinderScreen` +2. `ProofRequestScreen` +3. `ProofGenerationScreen` +4. `ProofGenerationDialogueScreen` +5. `ProofGenerationSuccessScreen` +6. `ProofResultScreen` +7. `ProofRequestReceiptScreen` +8. `ProofHistoryScreen` +9. `ProofSuccessBackupScreen` + +Supporting overlays: + +- `SimpleDialogueScreen` +- `DialogueWithCtaScreen` + +Supporting status screens: + +- `SumsubPendingScreen` +- `SumsubVerificationSuccessScreen` +- `NovaSplashScreen` + +This ordering keeps the main proof path stable before adding support surfaces. + +### Recovery and settings ordering + +These should be planned after document persistence and home state are stable: + +1. `ManageDocumentsScreen` +2. `IDDataScreen` +3. `LaunchRecoveryScreen` +4. `BackupMethodPickerScreen` +5. `RecoveryPhraseScreen` +6. `SecretPhraseInputScreen` +7. `RecoverySuccessScreen` + +Reason: + +- document management and ID data depend on stored document shape +- recovery UX should follow the same storage and account state assumptions + +--- + +## Proposed Specs + +The existing WV specs already cover foundational platform work. The remaining ticket planning should be grouped into a small set of migration specs rather than one spec per screen. + +### Existing foundation specs + +| Spec | Purpose | Status | +| ------- | ---------------------------------------------- | ----------- | +| `WV-05` | Sumsub Web SDK launch integration | In progress | +| `WV-06` | KYC result flow and terminal mapping | Ready | +| `WV-07` | SelfClient assembly and proving machine export | Ready | +| `WV-08` | Tunnel proving integration | Ready | + +### Proposed new migration specs + +| Proposed Spec | Scope | Depends On | +| ------------- | ----------------------------------------------------------------------- | ------------ | +| `WV-09` | Registration core — tour, outcomes, social sign-on, and prompt surfaces | WV-05, WV-06 | +| `WV-10` | EU ID separation decision and defer record | WV-09 | +| `WV-11` | Disclose spine migration | WV-07, WV-08 | +| `WV-12` | Proof overlays, history, and post-proof support | WV-11 | +| `WV-13` | Home, document management, and ID data | WV-11 | +| `WV-14` | Recovery and backup surfaces | WV-13 | +| `WV-15` | Settings follow-through and support routes | WV-13 | + +Notes: + +- `WV-09` should cover the route spine from tour through provider outcome and the remaining registration-adjacent social sign-on / prompt screens, not just the four tour screens. +- `WV-10` is intentionally separate because EU ID is not part of provider-backed registration; the six screens are deferred unless a separate Self-owned flow is approved. +- `WV-11` should establish the core disclose route chain before ticketing dialogue variants. + +--- + +## Candidate Tickets By Spec + +These are draft ticket groups only. Do not create them yet. + +### WV-09: Registration core + +Candidate tickets: + +- `Registration intro and launch tour` +- `Registration terminal states and outcome screens` +- `Registration social sign-on, conflict, and prompt surfaces` +- `Registration route integration from tour to provider result` + +Screens primarily affected: + +- `LaunchTour1Screen` +- `LaunchTour2Screen` +- `LaunchTour3Screen` +- `LaunchTour4Screen` +- `ScanSuccessScreen` +- `RegistrationFailureScreen` +- `SumsubFailureScreen` +- `SocialSignOnMethodPickerScreen` +- `SocialSignOnPickerScreen` +- `ConflictDetectedScreen` +- `PushNotificationPromptScreen` +- integration around `CountryPickerScreen`, `IDSelectionScreen`, `ConfirmIdentificationScreen`, `ProviderLaunchScreen`, `ProviderResultScreen` + +Suggested PR slices: + +- PR1: Launch tour route and navigation +- PR2: Registration outcome states and route wiring +- PR3: Social sign-on, conflict, and prompt surfaces +- PR4: End-to-end registration spine integration and tests + +### WV-10: EU ID separation decision + +Candidate tickets: + +- `Record EU ID separation from provider-backed registration` +- `Clean up backlog wording for deferred EU ID flow` + +Screens primarily affected: + +- `EuIdInstructionsScreen` +- `EuIdBackInstructionsScreen` +- `EuIdViewfinderScreen` +- `EuIdCanInstructionsScreen` +- `EuIdNfcInstructionsScreen` +- `EuIdNfcSuccessScreen` + +Suggested PR slices: + +- PR1: Product decision and spec +- PR2: Planning/backlog cleanup only if needed + +### WV-11: Disclose spine migration + +Candidate tickets: + +- `QR entry and proof request review` +- `Proof generation and result route chain` +- `Disclose route integration with real proving pipeline` + +Screens primarily affected: + +- `QRViewfinderScreen` +- `ProofRequestScreen` +- `ProofGenerationScreen` +- `ProofResultScreen` + +Suggested PR slices: + +- PR1: QR and request review +- PR2: Generation and result +- PR3: Wire to proving pipeline and route guards + +### WV-12: Proof overlays and post-proof surfaces + +Candidate tickets: + +- `Proof receipt and history` +- `Proof dialogue and CTA overlays` +- `Post-proof success and support surfaces` + +Screens primarily affected: + +- `ProofRequestReceiptScreen` +- `ProofHistoryScreen` +- `SimpleDialogueScreen` +- `DialogueWithCtaScreen` +- `ProofGenerationDialogueScreen` +- `ProofGenerationSuccessScreen` +- `ProofSuccessBackupScreen` +- `SumsubPendingScreen` +- `SumsubVerificationSuccessScreen` +- `NovaSplashScreen` + +Suggested PR slices: + +- PR1: Receipt and history +- PR2: Overlay dialogues +- PR3: Pending / success support states + +### WV-13: Home and document surfaces + +Candidate tickets: + +- `ID data view` +- `Manage documents` +- `Home follow-through for registered document state` + +Screens primarily affected: + +- `IDDataScreen` +- `ManageDocumentsScreen` +- `HomeScreen` integration follow-up + +Suggested PR slices: + +- PR1: Manage documents +- PR2: ID data and home integration + +### WV-14: Recovery and backup + +Candidate tickets: + +- `Recovery method selection and phrase display` +- `Phrase input and recovery success` + +Screens primarily affected: + +- `LaunchRecoveryScreen` +- `BackupMethodPickerScreen` +- `RecoveryPhraseScreen` +- `SecretPhraseInputScreen` +- `RecoverySuccessScreen` + +Suggested PR slices: + +- PR1: Method picker and phrase view +- PR2: Phrase input and success + +### WV-15: Settings support routes + +Candidate tickets: + +- `Settings support and placeholder route completion` +- `Persisted settings follow-through` +- `Security, notifications, and dev-mode action completion` + +Screens primarily affected: + +- `SettingsScreen` +- `SecurityScreen` +- `NotificationPreferencesScreen` +- `DevModeScreen` + +Suggested PR slices: + +- PR1: Support and placeholder route completion +- PR2: Persistence and non-placeholder actions + +--- + +## Proposed Ticket Creation Order + +When we are ready to create Linear tickets, the recommended order is: + +1. Foundation follow-up for `WV-05` and `WV-06` +2. `WV-09` registration spine +3. `WV-10` EU ID separation decision +4. `WV-07` and `WV-08` implementation tickets if still not created +5. `WV-11` disclose spine +6. `WV-12` proof overlays and post-proof surfaces +7. `WV-13` home and document surfaces +8. `WV-15` settings support routes +9. `WV-14` recovery and backup + +This ordering keeps the shortest path to an end-to-end verification journey ahead of secondary account-management work. + +Short version: + +- Registration before disclose +- Disclose before support surfaces +- Support surfaces before long-tail polish or optional flows + +--- + +## Open Questions Before Ticket Creation + +These questions should be answered in docs before generating tickets: + +1. Resolved: EU ID is not part of the provider-backed registration flow. Providers own their own onboarding guides. +2. Should `ConfirmIdentificationScreen` remain a distinct step before provider launch, or should it be folded into provider result / review? +3. Resolved: The main `/proving` route is the core disclose path (WV-11). Tunnel stays as the reference/demo flow from WV-08. +4. Are `SumsubPendingScreen` and `SumsubVerificationSuccessScreen` part of the end-user 3.0 flow, or are they implementation support states? +5. Should recovery and backup stay in the first migration wave, or explicitly move behind document-management completion? + +Until those are resolved, we should plan tickets but avoid opening them. diff --git a/specs/projects/sdk/workstreams/webview/plans/WV-06-kyc-result-flow.md b/specs/projects/sdk/workstreams/webview/plans/WV-06-kyc-result-flow.md new file mode 100644 index 000000000..cc6b79865 --- /dev/null +++ b/specs/projects/sdk/workstreams/webview/plans/WV-06-kyc-result-flow.md @@ -0,0 +1,377 @@ +# WV-06: Wire KYC Result Through Verification Pipeline + +> Last updated: 2026-03-25 +> Status: Ready +> Priority: High +> Depends on: WV-05 (In Progress — Sumsub Web SDK integration) + +- Workstream: webview +- Backlog ID: WV-06 +- Linear: SELF-2415 +- Owner: TBD +- Branch: TBD +- PR: TBD + +## Why + +The KYC result pipeline is broken. `ProviderLaunchScreen` hands a +`KycProviderResult` to `ProviderResultScreen` via route state, but +`ProviderResultScreen` navigates directly to `/proving` without: + +1. Extracting the attestation from the provider result +2. Constructing a `KycData` document from `serializedApplicantInfo` +3. Persisting the document via the documents adapter (keychain-backed) +4. Routing through `ConfirmIdentificationScreen` for ownership confirmation + +This means the proving flow has no document to prove against. +`ConfirmIdentificationScreen` exists but is bypassed — the route from +provider result skips it entirely. + +The RN app solves this in `app/src/hooks/useSumsubWebSocket.ts:93-137`: +receive attestation → `deserializeApplicantInfo()` → construct `KycData` → +`storeDocumentWithDeduplication()` → navigate. This spec implements the +equivalent pipeline for the webview app. + +## What You Will Do + +### 1. Create a KYC result store + +**Create:** `packages/webview-app/src/stores/kycResultStore.ts` + +The webview-app uses React Context for most state and already has `zustand` +as a dependency. Use a simple module-scoped store anyway — the KYC result +only needs to survive one navigation hop from `ProviderResultScreen` to +`ConfirmIdentificationScreen`, and a Zustand store would be overweight for +a single transient value that is cleared after use. + +```typescript +import type { KycProviderResult } from '../types/kycProvider'; + +let _result: KycProviderResult | null = null; + +export function setKycResult(result: KycProviderResult): void { + _result = result; +} + +export function getKycResult(): KycProviderResult | null { + return _result; +} + +export function clearKycResult(): void { + _result = null; +} +``` + +### 2. Create a document construction utility + +**Create:** `packages/webview-app/src/utils/buildKycDocument.ts` + +Extract attestation fields from `KycProviderResult` and construct a `KycData` +document matching the shape the proving machine expects. Follow the same +pattern as `app/src/hooks/useSumsubWebSocket.ts:104-118`. + +The return type separates the `KycData` payload (what gets stored via +`saveDocument()`) from the `DocumentMetadata` (what goes in the catalog). +This matches how `storeDocumentWithDeduplication()` works in +`packages/mobile-sdk-alpha/src/documents/utils.ts:211-256`. + +The storage ID is a content hash for deduplication, following the same +pattern the SDK uses. Do not invent a local hash helper. Reuse the canonical +`calculateContentHash()` utility so KYC documents behave like other stored +documents. + +```typescript +import { calculateContentHash } from '@selfxyz/common'; +import { deserializeApplicantInfo } from '@selfxyz/common/utils/kyc/api'; +import type { KycData, DocumentMetadata } from '@selfxyz/common/utils/types'; +import type { KycProviderResult } from '../types/kycProvider'; + +export interface KycDocumentBundle { + /** Content hash used as storage key and catalog ID */ + id: string; + /** KycData payload — passed to documents.saveDocument(id, data) */ + data: KycData; + /** Catalog entry — pushed to catalog.documents[] */ + metadata: DocumentMetadata; +} + +export function buildKycDocument(result: KycProviderResult): KycDocumentBundle { + if (!result.attestation) { + throw new Error('Cannot build KYC document: attestation missing'); + } + + const { serializedApplicantInfo, signature, pubkey } = result.attestation; + const applicantInfo = deserializeApplicantInfo(serializedApplicantInfo); + + const documentType = applicantInfo.idType; + + const data: KycData = { + documentType, + documentCategory: 'kyc', + mock: applicantInfo.idNumber?.startsWith('Mock') ?? false, + serializedApplicantInfo, + signature, + pubkey: [...pubkey], + }; + + // Content hash for deduplication — same utility used by the SDK's + // storeDocumentWithDeduplication(). + const contentHash = calculateContentHash(data); + + const metadata: DocumentMetadata = { + id: contentHash, + documentType, + documentCategory: 'kyc', + data: serializedApplicantInfo, + mock: data.mock, + isRegistered: false, + idType: applicantInfo.idType, + }; + + return { id: contentHash, data, metadata }; +} +``` + +### 3. Update ProviderResultScreen to store result and route to confirm + +**File:** `packages/webview-app/src/screens/onboarding/ProviderResultScreen.tsx` + +Change the success/partial navigation path: + +- **Before:** `success`/`partial` → navigate to `/proving` +- **After:** `success` with attestation → `setKycResult(result)` → navigate + to `/onboarding/confirm` + +Keep the existing error/cancel handling unchanged. + +Add a guard: if status is `success` but `attestation` is missing, treat it +as an error with code `provider_missing_attestation`. This enforces the WV-02 +contract requirement that success requires a complete attestation payload. + +```typescript +import { setKycResult } from '../../stores/kycResultStore'; + +// In the button press handler, replace the success/partial case: +if (providerResult.status === 'success' && providerResult.attestation) { + setKycResult(providerResult); + navigate('/onboarding/confirm'); +} else if (providerResult.status === 'success' && !providerResult.attestation) { + // Contract violation: success without attestation + analytics.trackEvent('provider_result_missing_attestation'); + // Show error state — do not advance +} else if (providerResult.status === 'partial') { + // Partial results cannot advance into proving per WV-02 contract + // Show "verification in progress" state with dismiss action +} +``` + +### 4. Update ConfirmIdentificationScreen to persist document + +**File:** `packages/webview-app/src/screens/onboarding/ConfirmIdentificationScreen.tsx` + +The current screen calls `lifecycle.setResult()` with a generic +`documentOwnershipConfirmed` claim and navigates home. Update it to: + +1. Read the KYC result from the store on mount +2. Build the KYC document from the attestation +3. On confirm: persist the document via the documents adapter, then call + `lifecycle.setResult()`, then navigate home + +```typescript +import { getKycResult, clearKycResult } from '../../stores/kycResultStore'; +import { buildKycDocument } from '../../utils/buildKycDocument'; + +export const ConfirmIdentificationScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic, lifecycle, documents } = useSelfClient(); + const { request, verificationId } = useVerificationRequest(); + + const kycResult = getKycResult(); + + useEffect(() => { + if (!kycResult?.attestation) { + // Guard: no result in store, return to a stable registration entry point + // instead of re-entering provider launch without the required state. + navigate('/onboarding/id-type', { replace: true }); + return; + } + haptic.trigger('success'); + }, [haptic, kycResult, navigate]); + + const onConfirm = useCallback(async () => { + if (!kycResult?.attestation) return; + + haptic.trigger('selection'); + analytics.trackEvent('ownership_confirmed'); + + try { + // 1. Build document bundle (KycData + DocumentMetadata) + const { id, data, metadata } = buildKycDocument(kycResult); + + // 2. Persist document payload + await documents.saveDocument(id, data); + + // 3. Update catalog with correct DocumentMetadata schema + // Uses selectedDocumentId (not selectedId) per common/src/utils/types.ts:36 + const catalog = await documents.loadDocumentCatalog(); + const existing = catalog.documents.find(d => d.id === id); + if (!existing) { + catalog.documents.push(metadata); + } + catalog.selectedDocumentId = id; + await documents.saveDocumentCatalog(catalog); + + // 4. Report terminal result to host + // This is NOT an intermediate provider success — it is the terminal + // Self verification result for the registration path. The workstream + // spec (SPEC.md:225) says "only the full verification lifecycle emits + // lifecycle.setResult()." Document ownership confirmation IS that + // terminal lifecycle event: the user has completed KYC, Self has + // stored the document, and the registration verification is done. + // Proving (if needed) is a separate verification session. + await lifecycle.setResult({ + success: true, + userId: request.userId, + verificationId, + claims: { + resultType: 'documentOwnershipConfirmed', + documentId: id, + }, + }); + + analytics.trackEvent('kyc_document_stored', { documentId: id }); + } catch (err) { + const message = err instanceof Error ? err.message : 'Unknown error'; + analytics.trackEvent('kyc_document_store_error', { error: message }); + } + + clearKycResult(); + navigate('/'); + }, [analytics, documents, haptic, kycResult, lifecycle, navigate, request.userId, verificationId]); + + if (!kycResult?.attestation) return null; + + return ( + } + /> + ); +}; +``` + +### 5. Expose documents adapter from SelfClientProvider + +**File:** `packages/webview-app/src/providers/SelfClientProvider.tsx` + +The current `SelfClientAdapters` type includes `documents` from the bridge +adapters. Verify it is exposed via `useSelfClient()`. If not, add it to the +context value. The `documents` adapter provides `saveDocument()`, +`loadDocumentCatalog()`, and `saveDocumentCatalog()` — all needed by step 4. + +This is a verification step, not necessarily a code change. If `documents` is +already in the context value, no modification needed. + +## Files You Will Create + +| File | What | Risk | +| ---------------------------------------------------- | -------------------------------------- | ------- | +| `packages/webview-app/src/stores/kycResultStore.ts` | Module-scoped KYC result holder | **Low** | +| `packages/webview-app/src/utils/buildKycDocument.ts` | Attestation → KycData document builder | **Low** | + +## Files You Will Modify + +| File | Change | Risk | +| ----------------------------------------------------------------------------- | -------------------------------------------------- | ---------- | +| `packages/webview-app/src/screens/onboarding/ProviderResultScreen.tsx` | Store result, route to confirm instead of proving | **Medium** | +| `packages/webview-app/src/screens/onboarding/ConfirmIdentificationScreen.tsx` | Persist document on confirm, read from store | **Medium** | +| `packages/webview-app/src/providers/SelfClientProvider.tsx` | Verify documents adapter is exposed (may be no-op) | **Low** | +| `specs/projects/sdk/workstreams/webview/SPEC.md` | Update WV-06 status to In Progress | **None** | + +## Files You Will NOT Modify + +| File | Why | +| ---------------------------------------------------------------------- | -------------------------------------------------------------------- | +| `packages/webview-app/src/screens/onboarding/ProviderLaunchScreen.tsx` | Upstream of this spec — already produces KycProviderResult correctly | +| `packages/webview-app/src/screens/proving/ProvingScreen.tsx` | Downstream — WV-08 wires proving to stored documents | +| `packages/webview-app/src/utils/sumsubProvider.ts` | Provider-specific code unchanged — WV-05 scope | +| `packages/webview-bridge/**` | Bridge layer unchanged | +| `packages/mobile-sdk-alpha/**` | SDK unchanged | + +## Constraints + +- **Module-scoped store, not Zustand.** The webview-app has `zustand` as a + dependency, but a module-scoped store is the right choice here: the KYC + result is a single transient value that survives one navigation hop and is + cleared after use. A Zustand store would be overweight for this. +- **Attestation is required for success.** `success` without `attestation` is + a contract violation per WV-02. Do not advance past ProviderResultScreen + without a complete attestation payload. +- **Partial does not advance.** Per the WV-02 contract, `partial` means the + provider returned an insufficient outcome. Show a "verification in progress" + state, not a confirmation screen. +- **Document storage uses the existing adapter.** Use whatever `documents` + adapter is available in `SelfClientProvider`. Today that is IndexedDB + (browser adapter); after WV-07 it will be keychain-backed. This spec does + not depend on which adapter backs it. +- **`lifecycle.setResult()` is called once, and it is the terminal result.** + The confirmation screen is the single point where `setResult()` fires for + the KYC registration path. ProviderResultScreen does NOT call it (it only + handles dismiss on error/cancel). This is consistent with the workstream + spec (SPEC.md:225-228) which says "only the full Self verification + lifecycle emits `VERIFICATION_COMPLETE` or calls `lifecycle.setResult`." + Document ownership confirmation IS that terminal lifecycle event — the + user completed provider KYC, Self stored the attested document, and + the registration verification session is done. If the host later needs + a ZK proof against this document, that is a separate disclose session + with its own `lifecycle.setResult()` call. +- **Guard failures return to a stable route.** If `ConfirmIdentificationScreen` + is entered without a stored KYC result, redirect to `/onboarding/id-type` + rather than back into provider launch. Provider launch depends on upstream + route state that may no longer be available on refresh or direct navigation. + +## Terminal State Mapping + +| Provider status | Attestation present? | ProviderResultScreen action | Next screen | +| ----------------------- | -------------------- | --------------------------------------- | ------------------------ | +| `success` | Yes | Store result | `/onboarding/confirm` | +| `success` | No | Show error (contract violation) | Stay | +| `partial` | — | Show "verification in progress" | Stay (dismiss available) | +| `cancel` | — | `lifecycle.dismiss({ reason: 'back' })` | `/` | +| `error` (retryable) | — | Show error with retry | Back one step | +| `error` (non-retryable) | — | `lifecycle.dismiss()` | `/` | + +## Validation + +```bash +# webview-app builds +cd packages/webview-app && yarn build + +# Manual validation: +# 1. Complete Sumsub flow → ProviderResultScreen shows success +# 2. Button navigates to ConfirmIdentificationScreen (not /proving) +# 3. Confirm button persists document and calls lifecycle.setResult() +# 4. Cancel/error at ProviderResultScreen calls lifecycle.dismiss() +# 5. Direct navigation to /onboarding/confirm without result redirects back +``` + +## Definition of Done + +- [ ] `kycResultStore` holds result between ProviderResultScreen and ConfirmIdentificationScreen +- [ ] `buildKycDocument()` constructs KycData from attestation using `deserializeApplicantInfo()` +- [ ] ProviderResultScreen routes success+attestation to `/onboarding/confirm` +- [ ] ProviderResultScreen rejects success without attestation (contract enforcement) +- [ ] ProviderResultScreen does not advance `partial` results +- [ ] ConfirmIdentificationScreen persists document via documents adapter +- [ ] ConfirmIdentificationScreen calls `lifecycle.setResult()` with document info +- [ ] Guard redirects if ConfirmIdentificationScreen is entered without a stored result +- [ ] `yarn workspace @selfxyz/webview-app build` passes +- [ ] Backlog row updated in SPEC.md + +## Status Log + +- 2026-03-25: Plan created. diff --git a/specs/projects/sdk/workstreams/webview/plans/WV-08-tunnel-proving-flow.md b/specs/projects/sdk/workstreams/webview/plans/WV-08-tunnel-proving-flow.md index 996cfeccb..dc82c515b 100644 --- a/specs/projects/sdk/workstreams/webview/plans/WV-08-tunnel-proving-flow.md +++ b/specs/projects/sdk/workstreams/webview/plans/WV-08-tunnel-proving-flow.md @@ -3,7 +3,7 @@ > Last updated: 2026-03-24 > Status: Ready > Priority: High -> Depends on: WV-07 (Ready — SelfClient assembly + proving machine export) +> Depends on: WV-05 (In Progress), WV-06 (Ready), WV-07 (Ready) - Workstream: webview - Backlog ID: WV-08 diff --git a/specs/projects/sdk/workstreams/webview/plans/WV-09-registration-core.md b/specs/projects/sdk/workstreams/webview/plans/WV-09-registration-core.md new file mode 100644 index 000000000..69817543c --- /dev/null +++ b/specs/projects/sdk/workstreams/webview/plans/WV-09-registration-core.md @@ -0,0 +1,951 @@ +# WV-09: Registration Core + +> Last updated: 2026-03-25 +> Status: Ready +> Priority: High +> Depends on: WV-05 (In Progress), WV-06 (Ready) + +- Workstream: webview +- Backlog ID: WV-09 +- Linear: SELF-2418 +- Owner: TBD +- Branch: TBD +- PR: TBD + +## Why + +The webview app has the middle of the registration flow (country picker, ID +selection, provider launch/result, confirm identification) but is missing +the bookends: the intro tour at the start and the outcome/prompt screens at +the end. A user currently lands on HomeScreen with no onboarding entry point +and sees no registration outcome feedback after provider completion. + +The RN app uses its own non-Euclid onboarding screens (Disclaimer, +SaveRecoveryPhrase). The webview app uses Euclid components. The 11 screens +in this spec are all Euclid wrappers — there is no RN reference +implementation to port from. The wrapper pattern is established by existing +screens (HomeScreen, CountryPickerScreen, SettingsScreen). + +## Prerequisites + +- **WV-05 done** — Sumsub Web SDK integrated in ProviderLaunchScreen +- **WV-06 done** — KYC result persisted via ConfirmIdentificationScreen + +WV-07/WV-08 (proving machine) are NOT prerequisites. This spec covers the +registration flow up to and including document ownership confirmation. +Proving is a separate verification session. + +## Scope + +**11 new Euclid wrapper screens** organized into three groups: + +| Group | Screens | Count | +| ------------------------ | -------------------------------------------------------------------------------------------------------------- | ----- | +| Tour | LaunchTour1–4 | 4 | +| Registration outcomes | ScanSuccessScreen, RegistrationFailureScreen, SumsubFailureScreen | 3 | +| Social sign-on & prompts | SocialSignOnMethodPickerScreen, SocialSignOnPickerScreen, ConflictDetectedScreen, PushNotificationPromptScreen | 4 | + +Plus route wiring in `App.tsx` and end-to-end integration of the full +registration route chain from tour through provider result. + +## What You Will Do + +### PR 1: Launch tour route and navigation + +#### 1a. Create the production tour screen + +**Create:** `packages/webview-app/src/screens/onboarding/TourScreen.tsx` + +The tunnel flow already has a `TourScreen` at +`packages/webview-app/src/screens/tunnel/TourScreen.tsx` that demonstrates +the pattern: parameterized route, switch on step number, render +LaunchTour1–4. The production tour screen follows the same structure but +routes into the production onboarding flow instead of the tunnel flow. + +```typescript +import React, { useCallback } from 'react'; +import { useNavigate, useParams, Navigate } from 'react-router-dom'; +import { + LaunchTour1Screen, + LaunchTour2Screen, + LaunchTour3Screen, + LaunchTour4Screen, +} from '@selfxyz/euclid'; +import { useSelfClient } from '../../providers/SelfClientProvider'; + +const insets = { top: 0, bottom: 0 }; + +export const TourScreen: React.FC = () => { + const navigate = useNavigate(); + const { step } = useParams<{ step: string }>(); + const { analytics, haptic } = useSelfClient(); + const stepNum = parseInt(step ?? '1', 10); + + const onNext = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('tour_next', { step: stepNum }); + if (stepNum < 4) { + navigate(`/onboarding/tour/${stepNum + 1}`); + } else { + navigate('/onboarding/country'); + } + }, [navigate, stepNum, haptic, analytics]); + + const onRestore = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('tour_restore_pressed'); + navigate('/recovery'); + }, [navigate, haptic, analytics]); + + const onSkip = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('tour_skipped'); + navigate('/onboarding/country'); + }, [navigate, haptic, analytics]); + + switch (step) { + case '1': + return ; + case '2': + return ; + case '3': + return ; + case '4': + return ( + window.open('https://self.xyz/terms', '_blank')} + onPrivacyPress={() => window.open('https://self.xyz/privacy', '_blank')} + /> + ); + default: + return ; + } +}; +``` + +#### 1b. Add tour routes to App.tsx + +**File:** `packages/webview-app/src/App.tsx` + +Add the production tour route: + +```typescript +} /> +``` + +Import as `OnboardingTourScreen` to avoid collision with the tunnel +`TourScreen`. Use the alias: + +```typescript +import { TourScreen as OnboardingTourScreen } from './screens/onboarding/TourScreen'; +``` + +#### 1c. Wire HomeScreen to tour entry + +The HomeScreen already exists. When the user has no registered document, the +primary CTA should navigate to `/onboarding/tour/1`. Check +`packages/webview-app/src/screens/home/HomeScreen.tsx` — if the "Get +Started" or equivalent CTA navigates elsewhere (e.g., directly to +`/onboarding/country`), update it to go through the tour first. + +Do NOT change HomeScreen behavior when the user already has a registered +document. + +#### 1d. Validation + +```bash +cd packages/webview-app && yarn build +``` + +**Definition of Done for PR 1:** + +- [ ] Production `TourScreen` renders LaunchTour1–4 at `/onboarding/tour/:step` +- [ ] Tour step 4 `onNext` navigates to `/onboarding/country` +- [ ] Tour `onRestore` navigates to `/recovery` (falls through to `/` until WV-14 adds the route) +- [ ] Tour `onSkip` navigates to `/onboarding/country` +- [ ] HomeScreen CTA routes to `/onboarding/tour/1` when no document exists +- [ ] `yarn build` passes + +--- + +### PR 2: Registration outcome screens and onboarding state store + +These three screens are terminal states that the registration flow can reach +after provider completion. They are rendered based on the provider result +status and the downstream document persistence outcome. + +#### 2-prereq. Create an onboarding state store + +**Create:** `packages/webview-app/src/stores/onboardingStore.ts` + +The existing onboarding screens pass `countryCode` and `documentType` via +route state (`navigate('/path', { state: { countryCode, documentType } })`). +This breaks when a failure screen needs to retry — the retry target +(`/onboarding/provider`) expects state that was set two screens ago and is +lost after the error redirect. + +Create a module-scoped store (same pattern as WV-06's `kycResultStore`) +that persists onboarding context across the registration flow: + +```typescript +interface OnboardingState { + countryCode: string | null; + documentType: string | null; +} + +let _state: OnboardingState = { countryCode: null, documentType: null }; + +export function setOnboardingState(s: Partial): void { + _state = { ..._state, ...s }; +} +export function getOnboardingState(): OnboardingState { + return _state; +} +export function clearOnboardingState(): void { + _state = { countryCode: null, documentType: null }; +} +``` + +Update `CountryPickerScreen` to call `setOnboardingState({ countryCode })` +when the user selects a country. + +Update `IDSelectionScreen` to call +`setOnboardingState({ documentType })` when the user selects a doc type. +Both screens continue passing state via route state as they do today — +the store is a parallel persistence layer, not a replacement. + +Update `ProviderLaunchScreen` to read from `getOnboardingState()` as +fallback when route state is missing (direct navigation or retry from +failure screen). + +Update `IDSelectionScreen` to read `countryCode` from +`getOnboardingState()` as fallback when `location.state` is missing, and +derive `documentTypes` from `country-document-types.json` using that +`countryCode`. The JSON lookup is already imported in +`CountryPickerScreen.tsx:9` — use the same import. If `countryCode` is +present but yields no document types from the JSON, redirect to +`/onboarding/country` (same as the guard for missing country). + +Call `clearOnboardingState()` from HomeScreen on mount, so stale context +does not leak across sessions. + +#### 2a. Create ScanSuccessScreen wrapper + +**Create:** `packages/webview-app/src/screens/onboarding/ScanSuccessScreen.tsx` + +This screen is shown after ConfirmIdentificationScreen successfully persists +the KYC document (WV-06). It confirms that the document was stored and +registration is complete. + +Euclid `ScanSuccessScreen` props: + +```typescript +interface ScanSuccessScreenProps extends SafeArea { + navLabel: string; + totalSteps: number; + currentStep: number; + title: string; + description: string; + buttonLabel: string; + onClose: () => void; + onHelp?: () => void; + onFinish: () => void; +} +``` + +Wrapper implementation: + +- `navLabel`: `"Registration"` +- `totalSteps`: `5` (tour → country → id-type → provider → confirm) +- `currentStep`: `5` +- `title`: `"Identity verified"` +- `description`: `"Your document has been securely registered with Self."` +- `buttonLabel`: `"Continue"` +- `onClose`: same as `onFinish` — advances to `/onboarding/backup` +- `onFinish`: navigate to `/onboarding/backup` + +Both `onClose` and `onFinish` advance to the backup prompt. The Euclid +screen exposes a close affordance (X button in the nav bar), and allowing +it to skip the prompt chain would let users bypass backup and notification +setup. If product later decides close should exit directly, change `onClose` +to navigate to `/` — but the default is to advance. + +```typescript +import { ScanSuccessScreen as EuclidScanSuccessScreen } from '@selfxyz/euclid'; + +export const ScanSuccessScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic } = useSelfClient(); + + const onFinish = useCallback(() => { + haptic.trigger('success'); + analytics.trackEvent('registration_success_continue'); + navigate('/onboarding/backup'); + }, [navigate, haptic, analytics]); + + const onClose = useCallback(() => { + analytics.trackEvent('registration_success_close'); + navigate('/onboarding/backup'); + }, [navigate, analytics]); + + return ( + + ); +}; +``` + +#### 2b. Create RegistrationFailureScreen wrapper + +**Create:** `packages/webview-app/src/screens/onboarding/RegistrationFailureScreen.tsx` + +Shown when document persistence fails in ConfirmIdentificationScreen or +when the provider returns a non-retryable error. + +Euclid `RegistrationFailureScreen` props: + +```typescript +interface RegistrationFailureScreenProps extends SafeArea { + failureTitle: string; + failureDescription: string; + onDismiss: () => void; + onTryDifferentMethod: () => void; +} +``` + +Wrapper: + +- Read `title` and `description` from route state (passed by the screen + that detected the failure). Generic copy is used when state is missing + (direct navigation or browser refresh) — this is valid, not an error. +- `onDismiss`: call `lifecycle.dismiss()` then navigate to `/` +- `onTryDifferentMethod`: navigate to `/onboarding/country` + +```typescript +import { RegistrationFailureScreen as EuclidRegistrationFailureScreen } from '@selfxyz/euclid'; + +export const RegistrationFailureScreen: React.FC = () => { + const navigate = useNavigate(); + const { state } = useLocation(); + const { analytics, haptic, lifecycle } = useSelfClient(); + + const title = state?.title ?? 'Registration failed'; + const description = state?.description ?? 'Something went wrong. Please try again.'; + + const onDismiss = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('registration_failure_dismiss'); + lifecycle.dismiss({ reason: 'user_cancel' }); + navigate('/'); + }, [navigate, haptic, analytics, lifecycle]); + + const onTryDifferentMethod = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('registration_failure_retry'); + navigate('/onboarding/country'); + }, [navigate, haptic, analytics]); + + return ( + + ); +}; +``` + +#### 2c. Create SumsubFailureScreen wrapper + +**Create:** `packages/webview-app/src/screens/onboarding/SumsubFailureScreen.tsx` + +Shown when the KYC provider returns a retryable error or a rejection. + +Euclid `SumsubFailureScreen` props: + +```typescript +interface SumsubFailureScreenProps extends SafeArea { + title: string; + description: string; + onDismiss: () => void; + onTryAgain: () => void; + backgroundSrc?: string; +} +``` + +Wrapper: + +- Read `title` and `description` from route state. Generic copy is used + when state is missing — this is valid, not an error. +- `onDismiss`: call `lifecycle.dismiss()` then navigate to `/` +- `onTryAgain`: navigate back to `/onboarding/provider` to re-launch Sumsub + +```typescript +import { SumsubFailureScreen as EuclidSumsubFailureScreen } from '@selfxyz/euclid'; + +export const SumsubFailureScreen: React.FC = () => { + const navigate = useNavigate(); + const { state } = useLocation(); + const { analytics, haptic, lifecycle } = useSelfClient(); + + const title = state?.title ?? 'Verification failed'; + const description = state?.description ?? 'The identity verification could not be completed.'; + + const onDismiss = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('sumsub_failure_dismiss'); + lifecycle.dismiss({ reason: 'user_cancel' }); + navigate('/'); + }, [navigate, haptic, analytics, lifecycle]); + + const onTryAgain = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('sumsub_failure_retry'); + // Onboarding state store preserves countryCode/documentType across + // the failure redirect, so ProviderLaunchScreen can read it on retry. + navigate('/onboarding/provider'); + }, [navigate, haptic, analytics]); + + return ( + + ); +}; +``` + +#### 2d. Add outcome routes to App.tsx + +**File:** `packages/webview-app/src/App.tsx` + +```typescript +} /> +} /> +} /> +``` + +#### 2e. Wire ProviderResultScreen error paths to outcome screens + +**File:** `packages/webview-app/src/screens/onboarding/ProviderResultScreen.tsx` + +After WV-06 lands, `ProviderResultScreen` handles success by routing to +`/onboarding/confirm`. The error/cancel paths need to route to the new +outcome screens: + +- `error` with `retryable: true` → navigate to `/onboarding/provider-failure` + with state `{ title, description, retryable: true }` +- `error` with `retryable: false` → navigate to `/onboarding/failure` with + state `{ title, description }` +- `cancel` → navigate to `/onboarding/failure` with state + `{ title: 'Verification cancelled', description: '...' }` + +Do NOT change the success path (that is WV-06's responsibility). + +#### 2f. Wire ConfirmIdentificationScreen success/failure to outcome screens + +**File:** `packages/webview-app/src/screens/onboarding/ConfirmIdentificationScreen.tsx` + +After WV-06 lands, this screen persists the document and calls +`lifecycle.setResult()`. Update the navigation after successful persistence: + +- Success: navigate to `/onboarding/success` instead of `/` +- Catch block (persistence failure): navigate to `/onboarding/failure` with + state `{ title: 'Registration failed', description: 'Could not save your document. Please try again.' }` + +#### 2g. Validation + +```bash +cd packages/webview-app && yarn build +``` + +**Definition of Done for PR 2:** + +- [ ] ScanSuccessScreen renders at `/onboarding/success` +- [ ] RegistrationFailureScreen renders at `/onboarding/failure` with route state +- [ ] SumsubFailureScreen renders at `/onboarding/provider-failure` with retry action +- [ ] ProviderResultScreen error paths route to the correct outcome screen +- [ ] ConfirmIdentificationScreen routes to `/onboarding/success` after persistence +- [ ] `yarn build` passes + +--- + +### PR 3: Social sign-on, conflict, and prompt surfaces + +These screens are registration-adjacent: they appear after successful +registration to handle account backup, conflict resolution, and +notification opt-in. They are not on the critical verification path but +complete the registration user journey. + +#### 3a. Create SocialSignOnMethodPickerScreen wrapper + +**Create:** `packages/webview-app/src/screens/onboarding/SocialSignOnMethodPickerScreen.tsx` + +Euclid props: + +```typescript +interface SocialSignOnMethodPickerScreenProps extends SafeArea { + onApple: () => void; + onGoogle: () => void; + onSeedPhrase: () => void; + onDismiss: () => void; +} +``` + +Wrapper: + +- `onApple`: analytics → navigate to `/coming-soon` +- `onGoogle`: analytics → navigate to `/coming-soon` +- `onSeedPhrase`: analytics → navigate to `/coming-soon` +- `onDismiss`: analytics → navigate to `/onboarding/notifications` + +All three sign-on/backup actions (Apple, Google, seed phrase) route to +`/coming-soon`. Apple/Google require native platform integration, and seed +phrase recovery is WV-14 scope. Do not use different placeholder targets — +they all land in the same deferred state. + +```typescript +import { SocialSignOnMethodPickerScreen as EuclidSocialSignOnMethodPicker } from '@selfxyz/euclid'; + +export const SocialSignOnMethodPickerScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic } = useSelfClient(); + + const onApple = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('social_signon_apple'); + navigate('/coming-soon'); + }, [navigate, haptic, analytics]); + + const onGoogle = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('social_signon_google'); + navigate('/coming-soon'); + }, [navigate, haptic, analytics]); + + const onSeedPhrase = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('social_signon_seed_phrase'); + navigate('/coming-soon'); + }, [navigate, haptic, analytics]); + + const onDismiss = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('social_signon_dismiss'); + navigate('/onboarding/notifications'); + }, [navigate, haptic, analytics]); + + return ( + + ); +}; +``` + +#### 3b. Create SocialSignOnPickerScreen wrapper + +**Create:** `packages/webview-app/src/screens/onboarding/SocialSignOnPickerScreen.tsx` + +Euclid props: + +```typescript +interface SocialSignOnPickerScreenProps extends SafeArea { + onApple: () => void; + onGoogle: () => void; + onICloud: () => void; + onGoogleCloud: () => void; + onSeedPhrase: () => void; + onDismiss: () => void; + defaultExpanded?: boolean; +} +``` + +Same pattern as method picker — all sign-on actions route to `/coming-soon` +until native integration lands. `onDismiss` navigates to +`/onboarding/notifications`. + +#### 3c. Create ConflictDetectedScreen wrapper + +**Create:** `packages/webview-app/src/screens/onboarding/ConflictDetectedScreen.tsx` + +Euclid props: + +```typescript +interface ConflictDetectedScreenProps extends SafeArea { + title: string; + description: string; + primaryActionLabel: string; + secondaryActionLabel: string; + onPrimaryAction: () => void; + onSecondaryAction: () => void; + onClose: () => void; +} +``` + +Wrapper: + +- Read `title`, `description`, `primaryActionLabel`, `secondaryActionLabel` + from route state. Provide sensible defaults: + - `title`: `"Account conflict detected"` + - `description`: `"A different identity is already registered with this account."` + - `primaryActionLabel`: `"Continue with new"` + - `secondaryActionLabel`: `"Keep existing"` +- `onPrimaryAction`: analytics → continue registration (navigate forward) +- `onSecondaryAction`: analytics → navigate to `/` +- `onClose`: navigate to `/` + +The conflict resolution logic (which account wins, how to merge) depends on +backend infrastructure not yet available in the webview flow. Wire the UI +callbacks and analytics. The actual resolution behavior will be implemented +when the account system supports it. + +#### 3d. Create PushNotificationPromptScreen wrapper + +**Create:** `packages/webview-app/src/screens/onboarding/PushNotificationPromptScreen.tsx` + +Euclid props: + +```typescript +interface PushNotificationPromptScreenProps extends SafeArea { + onEnableNotifications: () => void; + onDismiss: () => void; + onClose?: () => void; +} +``` + +Wrapper: + +- `onEnableNotifications`: analytics → request notification permission via + browser `Notification.requestPermission()` API → navigate to `/` +- `onDismiss`: analytics → navigate to `/` +- `onClose`: navigate to `/` + +Push notifications in a WebView context use the browser Notifications API, +not native push. If `Notification` is not available (e.g., in an iframe +without permission policy), skip the request and navigate home. + +```typescript +const onEnableNotifications = useCallback(async () => { + haptic.trigger('selection'); + analytics.trackEvent('push_notification_enable'); + if ('Notification' in window) { + await Notification.requestPermission(); + } + navigate('/'); +}, [navigate, haptic, analytics]); +``` + +#### 3e. Add routes to App.tsx + +**File:** `packages/webview-app/src/App.tsx` + +```typescript +} /> +} /> +} /> +} /> +``` + +#### 3f. Validation + +```bash +cd packages/webview-app && yarn build +``` + +**Definition of Done for PR 3:** + +- [ ] SocialSignOnMethodPickerScreen renders at `/onboarding/backup` +- [ ] SocialSignOnPickerScreen renders at `/onboarding/signin` +- [ ] ConflictDetectedScreen renders at `/onboarding/conflict` with route state +- [ ] PushNotificationPromptScreen renders at `/onboarding/notifications` +- [ ] Social sign-on actions route to `/coming-soon` (deferred) +- [ ] Push notification uses browser Notification API with graceful fallback +- [ ] `yarn build` passes + +--- + +### PR 4: End-to-end registration integration + +This PR wires the full registration route chain and validates the +end-to-end flow. No new screens — only route sequencing and guard logic. + +#### 4a. Wire the post-registration prompt chain + +After `ScanSuccessScreen` (registration success), the user should flow +through the prompt screens before landing on HomeScreen: + +``` +/onboarding/success → /onboarding/backup → /onboarding/notifications → / +``` + +Update `ScanSuccessScreen.onFinish` to navigate to `/onboarding/backup` +instead of `/`. + +Update `SocialSignOnMethodPickerScreen.onDismiss` to navigate to +`/onboarding/notifications`. + +Update `PushNotificationPromptScreen.onDismiss` and +`onEnableNotifications` to navigate to `/`. + +#### 4b. Add route guards for screens that require upstream state + +Screens that read from route state or module-scoped stores need guards or +fallbacks to prevent direct-navigation crashes or empty/broken UI on direct +navigation. + +**Existing screens (guard missing today):** + +- `IDSelectionScreen`: expects `countryCode` and `documentTypes` from + `location.state` (`IDSelectionScreen.tsx:45`). Add fallback: read + `countryCode` from `getOnboardingState()`, derive `documentTypes` from + `country-document-types.json`. Guard: if no `countryCode` from either + source, or if the JSON yields no document types for that country, + redirect to `/onboarding/country`. +- `ProviderLaunchScreen`: expects `countryCode` and `documentType` from + `location.state` (`ProviderLaunchScreen.tsx:27`). Add fallback: read + both from `getOnboardingState()`. Guard: if `countryCode` or + `documentType` is missing from both sources, redirect to + `/onboarding/id-type`. + +**New screens — render with generic copy, do not redirect:** +`RegistrationFailureScreen`, `SumsubFailureScreen`, and +`ConflictDetectedScreen` all have hardcoded fallback copy (e.g., +`state?.title ?? 'Registration failed'`). These screens render correctly +without route state — the generic copy is the intended fallback, not a +broken state. Do NOT add redirect guards for missing `state` on these +screens. The fallback copy exists precisely so direct navigation or +browser refresh does not crash. + +Guard pattern for existing screens (fallback to onboarding store): + +```typescript +import countryDocumentTypes from '../../data/country-document-types.json'; +import { getOnboardingState } from '../../stores/onboardingStore'; + +const { state } = useLocation(); +const onboardingState = getOnboardingState(); +const countryCode = state?.countryCode ?? onboardingState.countryCode; +const documentTypes = state?.documentTypes + ?? (countryCode ? countryDocumentTypes[countryCode] : null); +if (!countryCode || !documentTypes?.length) { + return ; +} +``` + +#### 4c. Document the full registration route chain + +The complete production registration flow after this spec: + +``` +/onboarding/tour/1 + → /onboarding/tour/2 + → /onboarding/tour/3 + → /onboarding/tour/4 + → /onboarding/country (existing) + → /onboarding/id-type (existing) + → /onboarding/provider (existing — WV-05) + → /onboarding/provider-result (existing — WV-06 wires result) + → /onboarding/confirm (existing — WV-06 persists document) + → /onboarding/success (this spec) + → /onboarding/backup (this spec — deferred actions) + → /onboarding/notifications (this spec) + → / (HomeScreen) + +Error paths: + /onboarding/provider-result → /onboarding/provider-failure (retryable) + /onboarding/provider-result → /onboarding/failure (non-retryable / cancel) + /onboarding/confirm → /onboarding/failure (persistence error) + +Conflict path (when backend supports it): + /onboarding/confirm → /onboarding/conflict → continue or dismiss +``` + +#### 4d. Validation + +```bash +cd packages/webview-app && yarn build + +# Manual end-to-end validation: +# 1. Launch app → HomeScreen CTA goes to /onboarding/tour/1 +# 2. Step through tour 1–4 → arrives at /onboarding/country +# 3. Select country → select ID type → provider launches +# 4. Provider success → confirm screen → success screen +# 5. Success → backup prompt → notification prompt → home +# 6. Provider error → failure screen with retry/dismiss +# 7. Direct navigation to /onboarding/success → renders (no guard crash) +# 8. Direct navigation to /onboarding/failure without state → renders generic fallback copy +``` + +**Definition of Done for PR 4:** + +- [ ] Full route chain from tour through notifications works end-to-end +- [ ] Post-registration prompt chain flows: success → backup → notifications → home +- [ ] Route guards prevent crashes on direct navigation +- [ ] Error paths route correctly from ProviderResultScreen +- [ ] `yarn build` passes + +## Files You Will Create + +| File | What | PR | +| -------------------------------------------------------------------------------- | ------------------------------------------------------------ | ---- | +| `packages/webview-app/src/stores/onboardingStore.ts` | Module-scoped onboarding context (countryCode, documentType) | PR 2 | +| `packages/webview-app/src/screens/onboarding/TourScreen.tsx` | Production tour wrapper (LaunchTour1–4) | PR 1 | +| `packages/webview-app/src/screens/onboarding/ScanSuccessScreen.tsx` | Registration success screen | PR 2 | +| `packages/webview-app/src/screens/onboarding/RegistrationFailureScreen.tsx` | Registration failure screen | PR 2 | +| `packages/webview-app/src/screens/onboarding/SumsubFailureScreen.tsx` | Provider failure screen | PR 2 | +| `packages/webview-app/src/screens/onboarding/SocialSignOnMethodPickerScreen.tsx` | Backup method picker | PR 3 | +| `packages/webview-app/src/screens/onboarding/SocialSignOnPickerScreen.tsx` | Sign-on picker | PR 3 | +| `packages/webview-app/src/screens/onboarding/ConflictDetectedScreen.tsx` | Account conflict screen | PR 3 | +| `packages/webview-app/src/screens/onboarding/PushNotificationPromptScreen.tsx` | Push notification prompt | PR 3 | + +## Files You Will Modify + +| File | Change | PR | Risk | +| ----------------------------------------------------------------------------- | ---------------------------------------------------- | ------ | ---------- | +| `packages/webview-app/src/App.tsx` | Add 9 new routes | PR 1–3 | **Low** | +| `packages/webview-app/src/screens/home/HomeScreen.tsx` | Wire CTA to tour entry | PR 1 | **Low** | +| `packages/webview-app/src/screens/onboarding/CountryPickerScreen.tsx` | Write to onboarding store on selection | PR 2 | **Low** | +| `packages/webview-app/src/screens/onboarding/IDSelectionScreen.tsx` | Write to onboarding store, add fallback read + guard | PR 2/4 | **Low** | +| `packages/webview-app/src/screens/onboarding/ProviderLaunchScreen.tsx` | Add fallback read from onboarding store + guard | PR 4 | **Low** | +| `packages/webview-app/src/screens/onboarding/ProviderResultScreen.tsx` | Wire error paths to outcome screens | PR 2 | **Medium** | +| `packages/webview-app/src/screens/onboarding/ConfirmIdentificationScreen.tsx` | Route to success/failure instead of `/` | PR 2 | **Medium** | +| `packages/webview-app/src/screens/onboarding/ScanSuccessScreen.tsx` | Wire `onFinish` to backup prompt | PR 4 | **Low** | +| `specs/projects/sdk/workstreams/webview/SPEC.md` | Add WV-09 backlog row | PR 1 | **None** | + +## Files You Will NOT Modify + +| File | Why | +| ----------------------------------------------------------- | ------------------------------------- | +| `packages/webview-app/src/screens/tunnel/*` | Tunnel flow is separate — WV-08 scope | +| `packages/webview-app/src/providers/SelfClientProvider.tsx` | No new adapter requirements | +| `packages/webview-bridge/**` | No bridge changes needed | +| `packages/mobile-sdk-alpha/**` | No SDK changes needed | +| `app/**` | RN app unchanged | + +## Constraints + +- **All screens are Euclid wrappers.** No custom UI. Every screen imports a + Euclid component and passes props. The wrapper adds navigation, analytics, + haptics, and route state — nothing else. +- **Follow the established wrapper pattern.** Use `insets={{ top: 0, bottom: 0 }}`, + `useSelfClient()`, `useNavigate()`, `useCallback()` for all handlers. + See existing screens (CountryPickerScreen, SettingsScreen) as reference. +- **Social sign-on actions are deferred.** Apple/Google sign-in require native + platform integration that is out of scope for the webview-only delivery. + Route to `/coming-soon`. Do not stub auth flows. +- **Conflict resolution is deferred.** The ConflictDetectedScreen wires UI + callbacks only. Actual account conflict logic depends on backend + infrastructure not yet available. +- **Push notifications use browser API.** Use `Notification.requestPermission()` + with a guard for environments where it is unavailable. Do not import any + native notification module. +- **No modifications to ProviderResultScreen success path.** That is WV-06's + responsibility. Only wire the error/cancel paths. +- **Route state is the data transport for outcome screens.** Pass `title`, + `description`, and flags via `navigate(path, { state: {...} })`. Do not + use URL params for error details. +- **Two guard strategies depending on screen type.** Existing onboarding + screens (`IDSelectionScreen`, `ProviderLaunchScreen`) that need upstream + data use fallback-to-store then redirect if data is still missing. New + outcome screens (`RegistrationFailureScreen`, `SumsubFailureScreen`, + `ConflictDetectedScreen`) render with generic fallback copy — they do + not redirect, because a generic error/conflict screen is useful even + without specific route state. + +## Resolved Questions + +1. **Why not reuse the tunnel TourScreen?** The tunnel tour routes into + `/tunnel/kyc` after step 4. The production tour routes into + `/onboarding/country`. Different exit targets mean different wrappers. + The Euclid components are shared; only the wrapper differs. + +2. **Where does the backup prompt fit?** After registration success, before + the user lands on HomeScreen. The chain is: + success → backup → notifications → home. Dismissing any prompt skips + to the next step. + +3. **What about the recovery flow?** `onRestore` in the tour screens navigates + to `/recovery`. That route does not exist yet — it is WV-14 scope. + For now it will hit the catch-all `` route. + +4. **What copy goes in the outcome screens?** ScanSuccessScreen and + failure screens use hardcoded copy in the wrapper (title, description, + buttonLabel). The copy values are listed in the screen sections above. + If product wants different copy, update the wrapper — no Euclid changes + needed. + +5. **ScanSuccessScreen step counter** — The `totalSteps` and `currentStep` + props drive the RegistrationNav progress bar. Using 5 total steps + (tour → country → id-type → provider → confirm) reflects the + user-visible registration journey. The tour is collapsed to one step + because it is a single entry point from the user's perspective. + +6. **How does retry from provider failure work?** The onboarding state store + (`onboardingStore.ts`) persists `countryCode` and `documentType` across + the registration flow. When `SumsubFailureScreen.onTryAgain` navigates + to `/onboarding/provider`, `ProviderLaunchScreen` reads from the store + as fallback when route state is missing. This also fixes direct + navigation to `/onboarding/id-type` or `/onboarding/provider`. + +7. **Can ScanSuccessScreen.onClose skip the prompt chain?** No. Both + `onClose` and `onFinish` advance to `/onboarding/backup`. The Euclid + screen exposes a close affordance (X button), and allowing it to bypass + backup/notification setup would create an inconsistent experience. If + product decides close should exit directly, change `onClose` to `/`. + +8. **WV-06 dependency risk** — This spec assumes WV-06 lands as specified: + ProviderResultScreen routes success to `/onboarding/confirm`, and + ConfirmIdentificationScreen persists the document and calls + `lifecycle.setResult()`. The current app still routes success to + `/proving` and confirm to `/`. If WV-06 lands differently, PR 2 + (outcome screen wiring) will need adjustment. The new screens + themselves are not affected — only the routing from existing screens. + +## Validation + +```bash +# webview-app builds +cd packages/webview-app && yarn build +``` + +## Definition of Done + +- [ ] 11 new Euclid wrapper screens created in `packages/webview-app/src/screens/onboarding/` +- [ ] 9 new routes added to App.tsx +- [ ] HomeScreen CTA routes to tour when no document exists +- [ ] Tour steps 1–4 navigate correctly, step 4 exits to country picker +- [ ] ProviderResultScreen error/cancel paths route to outcome screens +- [ ] ConfirmIdentificationScreen routes to success screen on persist +- [ ] Post-registration prompt chain works: success → backup → notifications → home +- [ ] Route guards prevent crashes on direct navigation to state-dependent screens +- [ ] Social sign-on actions route to `/coming-soon` +- [ ] Push notification uses browser Notification API +- [ ] `yarn workspace @selfxyz/webview-app build` passes +- [ ] Backlog row added in SPEC.md + +## Status Log + +- 2026-03-25: Plan created. diff --git a/specs/projects/sdk/workstreams/webview/plans/WV-10-eu-id-helper-flow.md b/specs/projects/sdk/workstreams/webview/plans/WV-10-eu-id-helper-flow.md new file mode 100644 index 000000000..ddb9b75dc --- /dev/null +++ b/specs/projects/sdk/workstreams/webview/plans/WV-10-eu-id-helper-flow.md @@ -0,0 +1,210 @@ +# WV-10: EU ID Helper-Flow Decision + +> Last updated: 2026-03-25 +> Status: Ready +> Priority: High +> Depends on: WV-09 (Ready) + +- Workstream: webview +- Backlog ID: WV-10 +- Linear: SELF-2419 +- Owner: TBD +- Branch: TBD +- PR: TBD + +## Why + +The remaining unmigrated EU ID screens were originally grouped as a possible +"helper flow" around registration: + +- `EuIdInstructionsScreen` +- `EuIdBackInstructionsScreen` +- `EuIdViewfinderScreen` +- `EuIdCanInstructionsScreen` +- `EuIdNfcInstructionsScreen` +- `EuIdNfcSuccessScreen` + +That framing is no longer correct for the active webview migration. + +The current registration spine is provider-first. `WV-05` and `WV-06` define a +KYC provider launch/result contract where the provider owns document capture, +scan guidance, and verification onboarding. Sumsub, Didit, and similar +providers already supply their own guided UX for camera capture and document +verification. + +EU ID is **not** part of that provider-backed path. If Self supports EU ID in +the future, it will be a **separate Self-owned flow**, not a provider helper +layer inside Sumsub/Didit registration. + +This spec records that decision so the webview backlog does not accidentally +re-introduce native-scan assumptions into the provider flow. + +## Decision + +### 1. EU ID is not part of provider-backed registration + +Do **not** insert the EU ID screens into the active registration chain: + +```text +/onboarding/tour/:step + → /onboarding/country + → /onboarding/id-type + → /onboarding/provider + → /onboarding/provider-result + → /onboarding/confirm + → registration outcomes / prompts +``` + +Provider-owned onboarding stays inside the provider SDK or hosted provider +experience. Self does not wrap or duplicate that onboarding with Euclid +instruction screens. + +### 2. EU ID remains a separate product decision + +The six EU ID screens are **not approved for route integration** in the active +webview app. + +If product later decides to support EU ID in webview, that work must be scoped +as a separate Self-owned flow with its own: + +- route spine +- platform capability requirements +- bridge/native dependency review +- success/failure contracts +- document persistence contract + +That future work is outside `WV-10`. + +### 3. Current migration behavior + +For the active migration: + +- Do not create routes for the six EU ID screens in `packages/webview-app` +- Do not route any provider-backed registration action into EU ID screens +- Do not adapt the EU ID screens into "provider prep" or "provider helper" UI +- Do not treat the mobile app's native scan flow as a contract for webview + +The current webview registration path continues without EU ID. + +## Scope + +This is a **decision spec**. It does not migrate or integrate the six screens. + +### In scope + +- Record that EU ID is outside provider-backed registration +- Remove ambiguity from the webview backlog and planning docs +- Define the correct ownership boundary for future EU ID work +- Mark the six EU ID screens as deferred from the active webview route spine + +### Out of scope + +- Creating any of the six Euclid wrapper screens +- Adding EU ID routes to `packages/webview-app/src/App.tsx` +- Building a separate Self-owned EU ID flow +- Reintroducing camera or NFC capture into the provider-backed registration path +- Changing `WV-05`, `WV-06`, or `WV-09` route behavior + +## What You Will Do + +### 1. Update planning language to reflect the decision + +**Files:** + +- `specs/projects/sdk/workstreams/webview/SPEC.md` +- `specs/projects/sdk/workstreams/webview/TICKET-PLAN.md` +- `specs/projects/sdk/workstreams/webview/SCREEN-INVENTORY.md` + +Update wording so `WV-10` is described as a **decision/defer item**, not an +implementation bucket inside the provider flow. + +Required changes: + +- `SPEC.md`: describe `WV-10` as the EU ID separation/defer decision +- `TICKET-PLAN.md`: remove wording that implies the EU ID screens are the next + registration sub-step after provider-backed registration +- `SCREEN-INVENTORY.md`: keep the six screens inventoried, but note they are + deferred from the active webview migration path pending a separate Self-owned + flow decision + +### 2. Do not add implementation tickets under this spec + +`WV-10` should not create implementation PR slices for these screens right now. + +If follow-up tickets are needed, they should be limited to documentation and +backlog cleanup, for example: + +- `Record EU ID separation from provider registration` +- `Clean up backlog wording for deferred EU ID flow` + +Do not open screen-migration tickets under `WV-10` unless product later +approves a separate EU ID flow. + +### 3. Leave route and screen code unchanged + +No webview app code changes are required by this spec. + +## Files You Will Modify + +| File | Change | Risk | +| ------------------------------------------------------------ | ---------------------------------------------------- | -------- | +| `specs/projects/sdk/workstreams/webview/SPEC.md` | Clarify `WV-10` as decision/defer work | **None** | +| `specs/projects/sdk/workstreams/webview/TICKET-PLAN.md` | Remove provider-helper ambiguity | **None** | +| `specs/projects/sdk/workstreams/webview/SCREEN-INVENTORY.md` | Mark EU ID screens as deferred from active migration | **None** | + +## Files You Will NOT Modify + +| File | Why | +| ------------------------------------------------ | ------------------------------------------------------------------ | +| `packages/webview-app/src/App.tsx` | No route integration happens in this spec | +| `packages/webview-app/src/screens/onboarding/**` | No screen migration or wrapper creation happens in this spec | +| `packages/webview-app/src/screens/tunnel/**` | Tunnel/proving flow unaffected | +| `packages/mobile-sdk-alpha/**` | Mobile/native flows are not the webview contract | +| `app/src/screens/documents/scanning/**` | RN native scan flow is reference context only, not migration scope | + +## Files You May Create + +None. + +## Constraints + +- **Provider-owned onboarding stays provider-owned.** Do not duplicate Sumsub, + Didit, or similar onboarding guidance in Self-owned Euclid wrappers. +- **No native scan assumptions.** `WV-03` removed camera/NFC-native ownership + from the active webview migration path. `WV-10` must not reverse that. +- **Inventory is not commitment.** Keeping the six EU ID screens in + `SCREEN-INVENTORY.md` does not mean they belong in the current route spine. +- **Future EU ID work requires a new spec.** If product approves a separate + Self-owned EU ID flow later, write a new implementation spec for that flow + rather than expanding this decision doc. + +## Resolved Questions + +1. **Can we just follow the mobile app?** No. The RN app still contains + native camera/NFC scanning and fallback behavior, which is not the contract + for the provider-first webview flow. + +2. **Should EU ID be inserted before or after provider launch?** No. EU ID is + not part of provider-backed registration. + +3. **Should the six screens become provider helper/prep screens?** No. + Providers own their own onboarding guides and capture UX. + +4. **Do the six screens need to be deleted from planning docs?** No. Keep them + inventoried, but mark them as deferred from the active migration path. + +## Validation + +This spec is complete when: + +- `WV-10` is described consistently as a decision/defer item across planning docs +- no planning doc implies the six EU ID screens belong in the provider-backed route chain +- the active implementation order continues from `WV-09` to `WV-11` without EU ID integration work + +## Definition of Done + +- [ ] `WV-10` plan file exists in `plans/` +- [ ] `SPEC.md` wording no longer implies EU ID is part of provider-backed registration +- [ ] `TICKET-PLAN.md` wording no longer implies EU ID is the next registration helper flow +- [ ] `SCREEN-INVENTORY.md` marks the six screens as deferred from the active webview migration path +- [ ] No webview route or screen implementation work is created under `WV-10` diff --git a/specs/projects/sdk/workstreams/webview/plans/WV-11-disclose-core.md b/specs/projects/sdk/workstreams/webview/plans/WV-11-disclose-core.md new file mode 100644 index 000000000..3a540e095 --- /dev/null +++ b/specs/projects/sdk/workstreams/webview/plans/WV-11-disclose-core.md @@ -0,0 +1,390 @@ +# WV-11: Disclose Core + +> Last updated: 2026-03-25 +> Status: Ready +> Priority: High +> Depends on: WV-07 (Done), WV-08 (Ready) + +- Workstream: webview +- Backlog ID: WV-11 +- Linear: SELF-2420 +- Owner: TBD +- Branch: TBD +- PR: TBD + +## Why + +The webview app already has a partial disclose surface: + +- `VerificationRequestProvider` parses verification request context from + `window.location.search` +- `ProvingScreen` renders Euclid's `ProofRequestScreen` +- `VerificationResultScreen` renders a terminal success/failure state + +But the route is not a real disclose flow yet. Today: + +- `/proving` immediately calls `lifecycle.setResult()` from the request review + screen +- there is no dedicated proof-generation route +- `useProvingStore` is not wired into the main disclose path +- the tunnel flow contains the only proving-machine integration reference, and + even that flow is still partially mocked + +`WV-11` turns the main disclose path into a real route spine: + +```text +request context + → proof request review + → proof generation + → proof result + → lifecycle.setResult() / lifecycle.dismiss() +``` + +This is the first non-registration disclose contract for the active webview +app. It uses the already-registered document path and the proving machine +assembled in `WV-07`, while reusing the proving integration patterns proven in +`WV-08`. + +## Prerequisites + +- **WV-07 done** — `SelfClient` and `useProvingStore` are available in + `packages/webview-app` +- **WV-08 ready** — tunnel flow defines the reference integration pattern for + real proving-machine wiring +- **WV-01 done** — proof request labels already come from verification request + context instead of hardcoded UI + +## Scope + +This spec covers the **main disclose route chain** in `packages/webview-app`. + +### In scope + +- turn `/proving` into the proof request review step +- add a dedicated `/proving/generating` route for real proving progress +- keep `/proving/result` as the terminal success/failure route +- wire `useProvingStore` into the main disclose path with circuit type + `disclose` +- move `lifecycle.setResult()` to the terminal result screen +- add request-context guards so direct navigation does not show broken proof UI + +### Out of scope + +- browser camera QR scanning +- proof receipt/history screens +- proof dialogue overlays +- post-proof backup prompts +- Sumsub pending/success support screens +- Nova splash or unrelated support routes + +`QRViewfinderScreen` remains inventoried, but **QR capture is not the active +entry contract** for `WV-11`. The canonical disclose entrypoint is the host- +supplied verification request context in the launch URL/query string. + +## What You Will Do + +### PR 1: Proof request review and route guards + +#### 1a. Keep request context as the canonical disclose entrypoint + +**Files:** + +- `packages/webview-app/src/providers/VerificationRequestProvider.tsx` +- `packages/webview-app/src/utils/verificationRequest.ts` +- `packages/webview-app/src/screens/proving/ProvingScreen.tsx` + +The main disclose flow starts from request context already parsed from +`window.location.search`. Do not introduce a second request source for `WV-11`. + +The review screen should require enough request context to render meaningful +proof items: + +- `request.disclosures` present, or +- `displayLabels` present + +If both are empty, the screen should treat the route as invalid and redirect to +`/`. + +#### 1b. Convert `/proving` into review-only behavior + +**File:** `packages/webview-app/src/screens/proving/ProvingScreen.tsx` + +Keep this screen as the Euclid `ProofRequestScreen` wrapper, but change its +behavior: + +- `onConfirm` should **not** call `lifecycle.setResult()` +- `onConfirm` should navigate to `/proving/generating` +- `onClose` should call `lifecycle.dismiss({ reason: 'user_cancel' })` then + navigate to `/` + +The user confirmation on this screen is the approval gate for disclose proving. + +#### 1c. Add the new generation route + +**File:** `packages/webview-app/src/App.tsx` + +Add: + +```typescript +} /> +``` + +Use a new wrapper component name that does not collide with Euclid exports. + +#### 1d. Validation + +```bash +cd packages/webview-app && yarn build +``` + +**Definition of Done for PR 1:** + +- [ ] `/proving` renders proof request review only +- [ ] missing disclose request context redirects to `/` +- [ ] confirm on `/proving` navigates to `/proving/generating` +- [ ] cancel on `/proving` dismisses the lifecycle session and returns home +- [ ] `yarn build` passes + +--- + +### PR 2: Real proof generation screen + +#### 2a. Create the generation wrapper + +**Create:** `packages/webview-app/src/screens/proving/ProofGenerationScreen.tsx` + +This wrapper owns the real proving-machine integration for the main disclose +flow. + +Use: + +```typescript +import { useEffect, useRef } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ProofGenerationScreen as EuclidProofGenerationScreen } from '@selfxyz/euclid'; +import { useProvingStore } from '@selfxyz/mobile-sdk-alpha/browser'; +import { useSelfClient } from '../../providers/SelfClientProvider'; +``` + +Behavior: + +1. On mount, initialize the proving machine with circuit type `disclose` +2. Drive Euclid progress UI from `useProvingStore(state => state.currentState)` +3. When the machine reaches `ready_to_prove`, automatically call + `setUserConfirmed(client)` once, because the user already approved the proof + request on the previous screen +4. On `completed`, navigate to `/proving/result` with success state +5. On `error`, `failure`, `passport_not_supported`, or + `passport_data_not_found`, navigate to `/proving/result` with failure state + +Use a one-shot ref so `setUserConfirmed(client)` is not fired repeatedly on +re-renders. + +#### 2b. State → UI mapping + +Use the proving-machine state to drive Euclid step copy: + +| provingMachine state | Euclid step / meaning | +| --------------------- | -------------------------- | +| `idle` | loading | +| `parsing_id_document` | preparing document | +| `fetching_data` | fetching verification data | +| `validating_document` | validating document | +| `init_tee_connexion` | connecting to prover | +| `ready_to_prove` | ready / auto-starting | +| `proving` | generating proof | +| `post_proving` | finalizing | +| terminal error states | navigate to result | +| `completed` | navigate to result | + +The exact Euclid prop names may differ, but the wrapper should preserve this +semantic mapping. + +#### 2c. Route guard for direct navigation + +If the generation route is opened without valid disclose request context, or if +there is no stored document available for disclose, navigate to `/` instead of +rendering a stuck loading state. + +Treat the following as invalid entry: + +- no disclose request items from query params +- proving store cannot start because no document is available + +#### 2d. Validation + +```bash +cd packages/webview-app && yarn build +``` + +**Definition of Done for PR 2:** + +- [ ] `/proving/generating` uses `useProvingStore` with circuit type `disclose` +- [ ] proving progress reflects real proving-machine state +- [ ] user confirmation is auto-forwarded once at `ready_to_prove` +- [ ] success and failure navigate to `/proving/result` +- [ ] invalid direct navigation redirects instead of hanging +- [ ] `yarn build` passes + +--- + +### PR 3: Terminal result contract and lifecycle callback + +#### 3a. Make the result screen the terminal callback owner + +**File:** `packages/webview-app/src/screens/proving/VerificationResultScreen.tsx` + +This screen becomes the single terminal point for the disclose session. + +Required behavior: + +- success path builds a `VerificationResult` with: + - `success: true` + - `userId: request.userId` + - `verificationId` + - `claims.resultType: 'proofRequested'` +- failure path builds a `VerificationResult` with: + - `success: false` + - `userId: request.userId` + - `verificationId` + - normalized `error` + +On terminal CTA: + +- if result has not been sent yet, call `lifecycle.setResult(result)` +- on success, after terminal result handling, call `lifecycle.dismiss()` +- on failure or retry, do not call `lifecycle.dismiss()` before the retry path runs +- do not navigate home first and leave the host session hanging + +This fixes the current bug where `/proving` sends the result before proof +generation happens. + +#### 3b. Success/failure copy + +Use disclose-specific copy rather than registration copy: + +- success: proof generated / identity shared successfully +- failure: proof generation failed / request could not be completed + +Do not reuse "ID Verified" registration language here. + +#### 3c. Add retry behavior + +On failure, the primary action should retry the disclose flow by navigating +back to `/proving` after ensuring `lifecycle.setResult(result)` has been called +if needed. Keep the host session open for that retry path. + +On success, the terminal action should close the session after result delivery +by calling `lifecycle.dismiss()`. + +If the Euclid component only supports a single button, prefer: + +- success button: `Done` +- failure button: `Try Again` + +#### 3d. Validation + +```bash +cd packages/webview-app && yarn build +``` + +**Definition of Done for PR 3:** + +- [ ] `VerificationResultScreen` owns the disclose terminal callback +- [ ] `lifecycle.setResult()` no longer fires from the review screen +- [ ] success result uses `claims.resultType: 'proofRequested'` +- [ ] failure result sends a normalized error payload +- [ ] success closes the host session after result delivery +- [ ] failure can re-enter the review flow +- [ ] `yarn build` passes + +## Files You Will Modify + +| File | Change | Risk | +| ----------------------------------------------------------------------- | --------------------------------- | ---------- | +| `packages/webview-app/src/App.tsx` | Add `/proving/generating` route | **Low** | +| `packages/webview-app/src/screens/proving/ProvingScreen.tsx` | Review-only behavior + guards | **Medium** | +| `packages/webview-app/src/screens/proving/VerificationResultScreen.tsx` | Terminal disclose result handling | **Medium** | +| `specs/projects/sdk/workstreams/webview/SPEC.md` | Link `WV-11` to this plan | **None** | +| `specs/projects/sdk/workstreams/webview/TICKET-PLAN.md` | Mark spec created | **None** | + +## Files You Will NOT Modify + +| File | Why | +| --------------------------------------------------------- | --------------------------------------------------------- | +| `packages/webview-app/src/screens/onboarding/**` | Registration flow is already covered by `WV-09` | +| `packages/webview-app/src/screens/tunnel/**` | Tunnel proving flow stays the reference path from `WV-08` | +| `packages/mobile-sdk-alpha/src/proving/provingMachine.ts` | Engine behavior is consumed as-is | +| `packages/webview-bridge/**` | Host callback contract already defined in `WV-04` | +| `packages/native-shell-android/**` | No native work required | +| `packages/native-shell-ios/**` | No native work required | + +## Files You May Create + +| File | What | +| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | +| `packages/webview-app/src/screens/proving/ProofGenerationScreen.tsx` | Main disclose proving wrapper | +| `packages/webview-app/src/stores/discloseResultStore.ts` | Optional module-scoped terminal result state if navigation state becomes too fragile | + +## Constraints + +- **Query params are the canonical disclose request source.** Do not invent a + second request contract for `WV-11`. +- **No QR camera dependency in this spec.** `QRViewfinderScreen` remains + inventory only. Browser QR scanning can be scoped later if product decides it + is needed. +- **No lifecycle callback before proving completes.** The review screen is not + a terminal state. +- **Use circuit type `disclose`.** The main disclose flow is not a register → + disclose chain like the tunnel flow. +- **Result screens own teardown.** Follow the `WV-04` contract: terminal + screens deliver the result, then dismiss the host session. + +## Resolved Questions + +1. **Is QR scanning part of `WV-11`?** No. The active disclose entry contract + is the host-supplied verification request context in the launch URL/query + string. QR capture remains deferred. + +2. **Should `WV-11` reuse the tunnel flow files?** No. Tunnel remains the + proving integration reference and separate route family. `WV-11` upgrades + the main `/proving` path. + +3. **Where should `lifecycle.setResult()` fire?** Only from the terminal + result screen after proving succeeds or fails, never from the request review + screen. + +4. **What result type should disclose emit?** `claims.resultType: +'proofRequested'`. + +5. **Does `WV-11` include receipt/history/dialogue surfaces?** No. Those stay + in later support specs. + +## Validation + +```bash +cd packages/webview-app && yarn build +``` + +Manual validation checklist: + +1. Launch with valid disclose query params and confirm `/proving` shows the + requested proof items. +2. Confirm the CTA routes to `/proving/generating` instead of immediately + sending a lifecycle result. +3. Verify proving progress advances through real proving-machine states. +4. Verify success lands on `/proving/result`, sends `self:result`, then + dismisses the host session. +5. Verify cancel from the review screen dismisses the session. +6. Verify invalid direct navigation to `/proving` or `/proving/generating` + redirects to `/`. + +## Definition of Done + +- [ ] Main disclose flow uses the route chain `/proving` → `/proving/generating` → `/proving/result` +- [ ] `/proving` is review-only and no longer sends terminal results +- [ ] `/proving/generating` is backed by `useProvingStore` with `disclose` +- [ ] `/proving/result` sends the final lifecycle result and dismisses the session +- [ ] invalid direct navigation is guarded +- [ ] `WV-11` backlog row links to this plan +- [ ] `yarn build` passes