mirror of
https://github.com/selfxyz/self.git
synced 2026-04-27 03:01:15 -04:00
Add webview app specs (#1866)
* save wip specs * update * updates * address feedback
This commit is contained in:
@@ -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/<BACKLOG-ID>-<slug>.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 |
|
||||
|
||||
7
specs/projects/sdk/workstreams/webview/INDEX.md
Normal file
7
specs/projects/sdk/workstreams/webview/INDEX.md
Normal file
@@ -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.
|
||||
267
specs/projects/sdk/workstreams/webview/SCREEN-INVENTORY.md
Normal file
267
specs/projects/sdk/workstreams/webview/SCREEN-INVENTORY.md
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
526
specs/projects/sdk/workstreams/webview/TICKET-PLAN.md
Normal file
526
specs/projects/sdk/workstreams/webview/TICKET-PLAN.md
Normal file
@@ -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.
|
||||
@@ -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 (
|
||||
<StatusState
|
||||
variant="success"
|
||||
title="Confirm your identity"
|
||||
description="By continuing, you certify that this passport, biometric ID or Aadhaar card belongs to you and is not stolen or forged. Once registered with Self, this document will be permanently linked to your identity and can't be linked to another one."
|
||||
buttonText="Confirm"
|
||||
onButtonPress={onConfirm}
|
||||
icon={<CheckCircleIcon size={64} color={colors.green500} />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### 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.
|
||||
@@ -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
|
||||
|
||||
@@ -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 <LaunchTour1Screen insets={insets} onNext={onNext} onRestore={onRestore} />;
|
||||
case '2':
|
||||
return <LaunchTour2Screen insets={insets} onNext={onNext} onRestore={onRestore} />;
|
||||
case '3':
|
||||
return <LaunchTour3Screen insets={insets} onNext={onNext} onRestore={onRestore} />;
|
||||
case '4':
|
||||
return (
|
||||
<LaunchTour4Screen
|
||||
insets={insets}
|
||||
onNext={onNext}
|
||||
onSkip={onSkip}
|
||||
onRestore={onRestore}
|
||||
onTermsPress={() => window.open('https://self.xyz/terms', '_blank')}
|
||||
onPrivacyPress={() => window.open('https://self.xyz/privacy', '_blank')}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <Navigate to="/onboarding/tour/1" replace />;
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
#### 1b. Add tour routes to App.tsx
|
||||
|
||||
**File:** `packages/webview-app/src/App.tsx`
|
||||
|
||||
Add the production tour route:
|
||||
|
||||
```typescript
|
||||
<Route path="/onboarding/tour/:step" element={<OnboardingTourScreen />} />
|
||||
```
|
||||
|
||||
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<OnboardingState>): 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 (
|
||||
<EuclidScanSuccessScreen
|
||||
insets={{ top: 0, bottom: 0 }}
|
||||
navLabel="Registration"
|
||||
totalSteps={5}
|
||||
currentStep={5}
|
||||
title="Identity verified"
|
||||
description="Your document has been securely registered with Self."
|
||||
buttonLabel="Continue"
|
||||
onClose={onClose}
|
||||
onFinish={onFinish}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### 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 (
|
||||
<EuclidRegistrationFailureScreen
|
||||
insets={{ top: 0, bottom: 0 }}
|
||||
failureTitle={title}
|
||||
failureDescription={description}
|
||||
onDismiss={onDismiss}
|
||||
onTryDifferentMethod={onTryDifferentMethod}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### 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 (
|
||||
<EuclidSumsubFailureScreen
|
||||
insets={{ top: 0, bottom: 0 }}
|
||||
title={title}
|
||||
description={description}
|
||||
onDismiss={onDismiss}
|
||||
onTryAgain={onTryAgain}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### 2d. Add outcome routes to App.tsx
|
||||
|
||||
**File:** `packages/webview-app/src/App.tsx`
|
||||
|
||||
```typescript
|
||||
<Route path="/onboarding/success" element={<ScanSuccessScreen />} />
|
||||
<Route path="/onboarding/failure" element={<RegistrationFailureScreen />} />
|
||||
<Route path="/onboarding/provider-failure" element={<SumsubFailureScreen />} />
|
||||
```
|
||||
|
||||
#### 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 (
|
||||
<EuclidSocialSignOnMethodPicker
|
||||
insets={{ top: 0, bottom: 0 }}
|
||||
onApple={onApple}
|
||||
onGoogle={onGoogle}
|
||||
onSeedPhrase={onSeedPhrase}
|
||||
onDismiss={onDismiss}
|
||||
/>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
#### 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
|
||||
<Route path="/onboarding/backup" element={<SocialSignOnMethodPickerScreen />} />
|
||||
<Route path="/onboarding/signin" element={<SocialSignOnPickerScreen />} />
|
||||
<Route path="/onboarding/conflict" element={<ConflictDetectedScreen />} />
|
||||
<Route path="/onboarding/notifications" element={<PushNotificationPromptScreen />} />
|
||||
```
|
||||
|
||||
#### 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 <Navigate to="/onboarding/country" replace />;
|
||||
}
|
||||
```
|
||||
|
||||
#### 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 `<Navigate to="/" replace />` 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.
|
||||
@@ -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`
|
||||
@@ -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
|
||||
<Route path="/proving/generating" element={<ProofGenerationRouteScreen />} />
|
||||
```
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user