diff --git a/.claude/skills/gaps-to-issues/SKILL.md b/.claude/skills/gaps-to-issues/SKILL.md new file mode 100644 index 000000000..9d45b9c30 --- /dev/null +++ b/.claude/skills/gaps-to-issues/SKILL.md @@ -0,0 +1,93 @@ +--- +name: gaps-to-issues +description: Create Linear issues from a PR audit doc — one issue per PR bucket with acceptance criteria and linked audit findings. +disable-model-invocation: false +user-invocable: true +argument-hint: '[path-to-audit-doc]' +--- + +# Gaps to Issues + +You take a PR audit document (produced by `/pr-audit`) and create Linear issues from its PR buckets. + +This skill **stops at issue creation**. Use `/spec-from-audit` to generate specs for the created issues. + +## Input + +`$ARGUMENTS` — Path to the audit document (e.g., `docs/reviews/2026-03-23-branch-audit.md`). If not provided, look for the most recently modified file in `docs/reviews/`. + +## Workflow + +### Step 1: Read the Audit Document + +Read the audit doc. Verify it has: + +- A "PR Buckets" section with at least one bucket +- Each bucket has: name, estimated LOC, findings, files, acceptance criteria + +If the audit doc is missing buckets or acceptance criteria, tell the user and stop. + +### Step 2: Ask for Linear Context + +Ask the user: + +1. Which **Linear team** to create issues under (suggest the team from recent issues if detectable) +2. Which **Linear project** to add issues to (optional) +3. Whether to set **priority** per bucket or use a default +4. Whether to **consolidate** any buckets before creating issues + +Wait for confirmation before proceeding. + +### Step 3: Create Issues + +For each bucket, create a Linear issue using `mcp__linear-server__save_issue`: + +- **Title:** Bucket name (concise, under 70 characters) +- **Team:** From Step 2 +- **Project:** From Step 2 (if provided) +- **Priority:** Based on the highest severity finding in the bucket: + - Contains Critical findings → Urgent (1) + - Contains High findings → High (2) + - Contains Medium findings → Medium (3) + - Contains only Low findings → Low (4) +- **Description:** Keep it lightweight — the spec (created later by `/spec-from-audit`) is the source of truth. The issue body should contain: + - One-sentence goal + - List of finding IDs from the audit (e.g., "Covers findings #1, #3, #7") + - Dependencies on other issues if any + - A note: "See attached spec document for full implementation plan." + - Do NOT duplicate scope, file lists, or acceptance criteria — that belongs in the spec +- **Links:** Add the PR URL if one exists + +If the user asked to consolidate buckets, merge the relevant findings. + +### Step 4: Update the Audit Document + +Add a "Follow-up Issues" section to the top of the audit doc (below the header metadata) with a table of all created issues: + +```markdown +## Follow-up Issues + +| Issue | Priority | Title | +|-------|----------|-------| +| [SELF-NNNN](url) | Urgent | Bucket name | +| [SELF-NNNN](url) | High | Bucket name | +``` + +Update the audit doc status to: `Audit complete — issues created` + +### Step 5: Present Results + +Show the user: + +1. Table of created issues with IDs, priorities, and URLs +2. Any buckets that were skipped or consolidated +3. Remind them: "Use `/spec-from-audit` to generate specs for these issues." + +**Stop here.** Do not create specs. + +## Important Notes + +- Never run `git commit` or `git push`. +- Ask before creating — never create issues without user confirmation on team/project/priority. +- If a bucket seems too large (>2k LOC estimate), suggest splitting before creating. +- If buckets have dependencies between them, note this in the issue descriptions and use Linear's `blockedBy` field. diff --git a/.claude/skills/pr-audit/SKILL.md b/.claude/skills/pr-audit/SKILL.md new file mode 100644 index 000000000..7fe687d45 --- /dev/null +++ b/.claude/skills/pr-audit/SKILL.md @@ -0,0 +1,210 @@ +--- +name: pr-audit +description: Multi-agent PR review — component, integration, and routing analysis merged into a structured audit doc with severity and PR buckets. +disable-model-invocation: false +user-invocable: true +argument-hint: '[base-branch (default: dev)]' +--- + +# PR Audit + +You run a multi-pass review of the current branch, pull in external PR feedback, and produce a structured audit document with findings grouped into PR-sized fix buckets. + +This skill **stops at the audit doc**. It does not create issues or specs — use `/gaps-to-issues` and `/spec-from-audit` for those steps. + +## Input + +`$ARGUMENTS` — Optional base branch name (default: `dev`). + +## Workflow + +### Step 1: Gather the Diff + +Use `$ARGUMENTS` if provided, otherwise default to `dev`. + +Verify the base branch exists: + +```bash +git rev-parse --verify +``` + +If it does not exist, ask the user which branch to diff against. + +Gather the full diff and context: + +```bash +git log ..HEAD --oneline --no-decorate +git diff ...HEAD --stat +git diff ...HEAD +git diff ...HEAD --name-only +git status -s +``` + +Read the full diff carefully. You will reference it throughout the review passes. + +### Step 2: Multi-Agent Review (3 Parallel Passes) + +Run all three review passes **in parallel** using the Agent tool. Each agent receives the diff context and reviews through a specific lens. + +#### Pass A: Component-Level Review + +For every new or changed screen/component file, check: + +- Missing or incorrect imports (especially cross-package imports that don't resolve) +- Wrong or missing props vs the component's actual interface — read the component definition, don't guess +- Missing Lottie/animation assets or props that the component supports but aren't passed +- Placeholder logic left in production code (hardcoded values, no-op callbacks, `TODO`/`FIXME`) +- Missing error/loading/empty states +- Raw hex colors instead of design tokens +- Files exceeding 800 LOC + +#### Pass B: Integration-Level Review + +For the full set of changed files, check: + +- Provider/contract flow violations — does data flow match what specs define? +- State propagation between screens — is state passed correctly through navigation, or lost on refresh/direct-entry? +- Persistence gaps — state that should survive reload but only lives in `useState` +- Event ordering issues — race conditions, guards that swallow later events +- Breaking changes to shared interfaces consumed by other packages +- Build-time vs runtime config that should be dynamic + +#### Pass C: Route Wiring Review + +For all navigation-related changes, check: + +- Every `navigate()` / `push()` / `replace()` target has a corresponding route definition +- Every new route definition is reachable from at least one navigation call +- Orphaned routes — defined but unreachable +- Screens that receive `location.state` but have no guard for direct-entry (missing state) +- Deep link configuration consistency (if applicable) + +### Step 3: Pull in PR Feedback + +Check if a PR exists for this branch: + +```bash +gh pr view --json number,url 2>/dev/null +``` + +If a PR exists, fetch external review comments: + +```bash +gh api repos/{owner}/{repo}/pulls/{number}/comments --paginate +gh api repos/{owner}/{repo}/pulls/{number}/reviews --paginate +gh api repos/{owner}/{repo}/issues/{number}/comments --paginate +``` + +Extract actionable items from CodeRabbit, Codex, and human reviewers. Ignore resolved conversations and pure acknowledgment comments. + +If no PR exists, skip this step. + +### Step 4: Merge and Deduplicate + +Combine findings from all three passes and PR feedback. Deduplicate — if Pass A and a CodeRabbit comment flag the same issue, keep one entry and note both sources. + +Assign severity: + +| Severity | Criteria | +|----------|----------| +| **Critical** | Build-breaking, crashes, data loss, security holes, blocked user flows | +| **High** | Incorrect behavior visible to users, contract violations, missing error handling on critical paths | +| **Medium** | Missing states, prop mismatches, design token violations, placeholder logic | +| **Low** | Style, minor code quality, non-blocking TODOs | + +### Step 5: Group into PR-Sized Buckets + +Group findings into fix buckets. Each bucket: + +- Targets ≤2,000 LOC changed +- Groups related findings touching the same files or logical area +- Is independently mergeable (note dependencies if unavoidable) +- Has a clear name, estimated LOC, list of files, and acceptance criteria + +### Step 6: Write the Audit Document + +Determine the branch slug from the current branch name (strip `feat/`, `fix/`, etc., replace `/` with `-`). + +Create the audit document at: + +``` +docs/reviews/YYYY-MM-DD--audit.md +``` + +Create `docs/reviews/` if it does not exist. + +Structure: + +```markdown +# PR Audit — + +> Date: YYYY-MM-DD +> Branch: `` +> Base: `` +> PR: #NNN (if exists) +> Reviewers: Claude Code (component, integration, routing passes), [external reviewers] +> Status: Audit complete — ready for review + +## Summary + +[2-3 sentences describing what the branch does and the overall health assessment.] + +## Findings + +### Critical + +- [ ] [finding] — `path/to/file.ts:NN` + - **Source:** [Pass A | Pass B | Pass C | CodeRabbit | reviewer] + - **Fix:** [concrete action] + +### High + +- [ ] ... + +### Medium + +- [ ] ... + +### Low + +- [ ] ... + +## PR Buckets + +### Bucket 1: [name] +- **Estimated LOC:** ~NNN +- **Findings:** #1, #3, #7 +- **Files:** [list] +- **Dependencies:** [none | Bucket N must land first] +- **Acceptance criteria:** + - [ ] [criterion] + +### Bucket 2: [name] +... + +## What Works Well + +[List things the branch does correctly — not just problems.] +``` + +### Step 7: Present to User + +Show the user: + +1. Total finding count by severity +2. Each bucket with name, estimated LOC, and findings covered +3. Any findings that didn't fit cleanly into a bucket + +Tell the user: + +> "Audit complete. Review the findings and buckets above. When ready, use `/gaps-to-issues` to create Linear issues from these buckets, then `/spec-from-audit` to generate specs." + +**Stop here.** Do not create issues or specs. + +## Important Notes + +- Never run `git commit` or `git push`. Stage the audit doc but stop there. +- Read the full diff — do not summarize from file names alone. +- Findings must be specific and actionable with file paths and line numbers. +- When estimating LOC for buckets, be conservative. +- If the diff is very large (>10k LOC), warn the user and suggest focusing on critical/high only. diff --git a/.claude/skills/spec-from-audit/SKILL.md b/.claude/skills/spec-from-audit/SKILL.md new file mode 100644 index 000000000..ab0a600f0 --- /dev/null +++ b/.claude/skills/spec-from-audit/SKILL.md @@ -0,0 +1,136 @@ +--- +name: spec-from-audit +description: Generate one Linear spec document per issue — agent-executable implementation plans with file paths, validation commands, and acceptance criteria. +disable-model-invocation: false +user-invocable: true +argument-hint: '[issue IDs or path-to-audit-doc]' +--- + +# Spec from Audit + +You take Linear issues (created by `/gaps-to-issues`) and generate one spec per issue as a Linear document. Each spec is an agent-executable implementation plan — a new Claude Code session with no prior context should be able to pick up the spec and produce a correct PR. + +## Input + +`$ARGUMENTS` — Either: +- A list of Linear issue IDs (e.g., `SELF-2357 SELF-2358 SELF-2359`) +- A path to an audit doc that has a "Follow-up Issues" section + +If not provided, look for the most recently modified audit doc in `docs/reviews/` and extract issue IDs from it. + +## Workflow + +### Step 1: Gather Issue Context + +For each issue ID, fetch the issue details: + +- Use `mcp__linear-server__get_issue` to read the title, description, and acceptance criteria +- If an audit doc path is available, read the full audit doc for additional context on findings + +### Step 2: Read the Codebase + +For each issue, read the files referenced in its description/scope. You need to understand the **current state** of the code to write accurate specs with line numbers. + +Do not guess file contents — read them. Specs with wrong line numbers or stale code references are worse than no spec. + +### Step 3: Generate Specs + +For each issue, create a spec as a Linear document using `mcp__linear-server__create_document`. + +Link the document to the **issue** (not the project). + +**Title format:** `SPEC: ` + +**Spec structure:** + +```markdown +## Overview + +You are [doing what] in [which area]. [1-2 sentences on why this matters.] + +## Preconditions + +- [What must be true before starting — dependencies on other PRs, packages published, etc.] + +## [Problem 1 / Area 1]: [descriptive name] + +**File:** `path/to/file.ts` + +[Description of the current problem with exact line numbers.] + +### Fix + +[Step-by-step instructions. Be explicit about what to change and why.] + +## [Problem 2 / Area 2]: [descriptive name] + +... + +## Files to modify + +- `path/to/file.ts` — [what changes] +- `path/to/other.ts` — [what changes] + +## Files NOT to modify + +- `path/to/untouched/` — [why] + +## Dependencies + +- [Other issues/PRs that must land first, if any] + +## Validation + +```bash +[relevant validation commands from the repo] +``` + +## Definition of Done + +- [ ] [acceptance criterion from the issue] +- [ ] [acceptance criterion] +- [ ] All validation commands pass +``` + +### Spec-Writing Rules + +The cardinal rule: **if two reasonable engineers could implement different solutions from the same spec, the spec is too open.** Specs must contain decisions, not options. + +Follow these strictly: + +1. **Decisions, not options** — "Use local wrappers" not "Consider adding to Euclid or using local wrappers." Every ambiguous implementation choice must be resolved in the spec. If you genuinely can't decide, flag it as a blocker and ask the user — don't embed it as an option. +2. **Second person** — "You are fixing...", "You will modify..." +3. **Exact file paths with line numbers** — `src/utils/sumsubProvider.ts:118`, not "the provider file" +4. **Current code, not stale references** — you read the files in Step 2, use what you actually saw +5. **Explicit constraints** — "You will NOT modify..." sections prevent scope creep +6. **Required vs optional** — mark every item. Don't let agents infer priority. +7. **Validation command** — agents will run it. If it's not there, they'll skip validation. +8. **One spec = one PR ≤2k LOC** — if a spec feels like it would produce >2k LOC, flag it and suggest splitting +9. **Self-contained** — the spec must include enough context to execute without reading other specs or the full audit doc + +Every spec should follow this structure: +1. State the goal in one sentence +2. List decisions already made +3. Define scope and out-of-scope +4. Name the files to modify +5. Define done criteria in behavior terms + +### Step 4: Present Results + +Show the user: + +1. Table of created specs with issue IDs and document URLs +2. Any issues that were too complex and need manual spec-writing +3. Any issues where codebase reading revealed the problem is already fixed or different than described + +**Stop here.** + +## Important Notes + +- Never run `git commit` or `git push`. +- Always read the actual files before writing specs — stale line numbers are actively harmful. +- If an issue references files that don't exist, flag it rather than guessing. +- If you discover the issue description is wrong (e.g., the code was already fixed), note this and ask the user whether to still create the spec or update the issue. +- Specs are for agents, not humans. Write them as precise instructions, not explanatory documents. +- The spec is the source of truth, not the issue body. Issue bodies are lightweight pointers — the spec must be fully self-contained. Do not assume the agent has read the issue description. +- Link specs to issues (not projects). Use `mcp__linear-server__create_document` with the `issue` parameter. diff --git a/CLAUDE.md b/CLAUDE.md index f63605c44..6145b4e00 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -46,57 +46,54 @@ nvm use && corepack enable && yarn install ## Specs & Planning -**Every feature — even minor ones — uses the spec system.** Before implementing, read the relevant specs, write a plan to disk, then execute. No exceptions. A plan that only exists in session memory is a plan that will be lost. +**Every feature — even minor ones — needs a spec.** Specs live in **Linear** as documents attached to issues. Do not create spec files in the `specs/` folder — that folder is deprecated and being migrated to Linear. -### Spec System (`specs/`) +### Where Specs Live -| File | Purpose | When to Read | -| ------------------------------------------------ | ------------------------------------------- | ------------------------ | -| [Specs README](./specs/README.md) | Table of contents, reading order | First. Always. | -| [Templates](./specs/framework/TEMPLATES.md) | Copy-paste templates for all three tiers | When creating a new spec | -| [SDK Overview](./specs/projects/sdk/OVERVIEW.md) | Architecture, bridge protocol, module table | For system-level context | - -Workstream specs live in `specs/projects/sdk/workstreams/*/` with `SPEC.md` (living implementation details). - -### Spec-Reading Protocol (for chunk execution) - -To execute a chunk: - -1. Read `specs/projects/sdk/INDEX.md` — find your workstream -2. Read the workstream `SPEC.md` — find your chunk -3. If you need architecture context, read the project `OVERVIEW.md` - -That's it. Do not read framework docs unless you are writing a new spec. +- **Specs → Linear documents**, attached to the relevant Linear issue (not the project) +- **Architecture context → `specs/projects/sdk/OVERVIEW.md`** (read-only reference, still in repo) +- **Audit docs → `docs/reviews/`** (PR audit findings, kept in repo for git history) +- **`specs/` folder** — deprecated. Existing specs are being migrated to Linear. Do not create new files here. ### Planning Protocol -1. **Read** the relevant workstream specs and this file's Key Rules — understand the current state and constraints -2. **Write a plan to disk** — use the appropriate tier from `specs/framework/TEMPLATES.md`: - - **Large features / new workstreams:** Create a full implementation spec (`specs/projects/sdk/workstreams//SPEC.md`) - - **Medium features / multi-chunk work:** Create a plan file in `workstreams//plans/` named `-.md` and link it from the backlog in the relevant `SPEC.md` - - **Small features / single-chunk fixes:** Create a minimal plan file in `workstreams//plans/` named `-.md` or add the chunk to an existing active plan -3. **Include in every plan:** scope of work, files modified, I/O examples, validation command, definition of done -4. **Then implement** — update chunk status as you complete work -5. **After completion:** Mark chunks done in SPEC.md status tables. Review status checklists at session start — if something is marked "Done" that isn't, or "Pending" that's in progress, fix it first. +1. **Read** this file's Key Rules and any relevant Linear issue/spec — understand the current state and constraints +2. **Create a Linear issue** if one doesn't exist — include scope, files modified, acceptance criteria +3. **Create a spec as a Linear document** attached to the issue — this is the execution plan +4. **Then implement** — one spec = one PR ≤2k LOC +5. **After completion:** Update the Linear issue status. Close when done. ### Spec-Writing Guidelines -When writing specs, follow these principles so they work as AI agent prompts: +Specs are agent-executable prompts. A new Claude Code session with no prior context must be able to pick up the spec and produce a correct PR. -- **Use second person.** "You are making X portable" not "X should be made portable." +- **Make decisions, not options.** "Use local wrappers" not "Consider adding to Euclid or using local wrappers." Agents can't choose between approaches — tell them which one. +- **Use second person.** "You are fixing X" not "X should be fixed." - **Be explicit about constraints.** "You will NOT modify..." not just "Focus on..." -- **Provide exact file paths with line numbers.** `src/proving/provingMachine.ts:543` not "the proving machine file." +- **Provide exact file paths with line numbers.** `src/utils/sumsubProvider.ts:118` not "the provider file." - **State the validation command.** Agents will run it. If it's not there, they'll skip validation. -- **One chunk = one self-contained prompt.** The chunk must include enough context to execute without reading the full spec. -- **One PR = one plan file.** A plan file is the execution handoff. It must be self-contained enough that a new agent can pick it up after session loss. -- **Use `--remote` for M and L chunks.** Medium and large chunks benefit from `claude --remote` so work continues in the background. +- **One spec = one PR ≤2k LOC.** If a spec would produce >2k LOC, split it. +- **Mark items as required vs optional.** Don't let agents infer priority. +- **Include out-of-scope sections.** These are as important as in-scope sections for preventing drift. +- **Use `--remote` for medium+ work.** Medium and large specs benefit from `claude --remote` so work continues in the background. -### Why Even Minor Features +### Audit Pipeline Skills + +Three Claude Code skills automate the review-to-implementation pipeline: + +1. **`/pr-audit`** — Multi-agent review (component + integration + routing), produces audit doc in `docs/reviews/` +2. **`/gaps-to-issues`** — Creates Linear issues from audit PR buckets +3. **`/spec-from-audit`** — Generates agent-executable specs as Linear documents, one per issue + +Run them in sequence with review pauses between each step. + +### Why Specs - Prevents scope creep — writing "files NOT modified" forces focus -- Survives session loss — API errors, context overflow, `/clear` won't destroy the plan -- Enables parallel work — multiple agents can pick up chunks from the same plan +- Survives session loss — specs live in Linear, not session memory +- Enables parallel work — multiple agents can pick up specs from the same project - Creates audit trail — what was planned vs what was built +- Enables cross-tool review — anyone with Linear access can review specs, not just GitHub users ## Validation Commands diff --git a/app/AGENTS.md b/app/AGENTS.md index 77ff59089..059fad1a4 100644 --- a/app/AGENTS.md +++ b/app/AGENTS.md @@ -290,5 +290,5 @@ See `.cursor/rules/test-memory-optimization.mdc` for comprehensive guidelines, e The Self Wallet app serves as a **test environment** for the SDK refactor. For SDK architecture context: -- **[SDK Overview](../specs/projects/sdk/OVERVIEW.md)** — System architecture, bridge protocol, decision matrix -- **[SDK Project Index](../specs/projects/sdk/INDEX.md)** — Workstream links and entry point +- **[SDK Overview](../specs/projects/sdk/OVERVIEW.md)** — System architecture, bridge protocol, decision matrix (read-only reference) +- **Implementation specs** — Live in Linear as documents attached to issues. Check the relevant Linear issue for the current spec. Do not create spec files in `specs/` — that folder is deprecated. diff --git a/docs/reviews/2026-03-23-euclid-settings-tunnel-audit.md b/docs/reviews/2026-03-23-euclid-settings-tunnel-audit.md new file mode 100644 index 000000000..29d61992c --- /dev/null +++ b/docs/reviews/2026-03-23-euclid-settings-tunnel-audit.md @@ -0,0 +1,160 @@ +# PR Audit — feat/euclid-settings-screens-rd2 + +> Date: 2026-03-23 +> Branch: `feat/euclid-settings-screens-rd2` +> Base: `dev` +> PR: #1858 +> Reviewers: Claude Code, Codex +> Status: Ship as-is with follow-up issues for known gaps + +## Follow-up Issues + +| Issue | Priority | Title | +| ------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------ | +| [SELF-2357](https://linear.app/selfprotocol/issue/SELF-2357) | Urgent | Euclid migration — complete euclid-web → euclid and validate API compatibility | +| [SELF-2358](https://linear.app/selfprotocol/issue/SELF-2358) | High | Sumsub / WV-05 contract compliance | +| [SELF-2359](https://linear.app/selfprotocol/issue/SELF-2359) | High | Tunnel + proving flow data propagation and UI contract fixes | +| [SELF-2360](https://linear.app/selfprotocol/issue/SELF-2360) | Medium | Settings persistence, test coverage, and doc/spec cleanup | + +Each issue has a spec attached as a Linear document. + +## Summary + +This PR adds Euclid 3.0 settings sub-screens (Security, Notifications, Dev Mode), a PoC tunnel flow, the `euclid-web` → `euclid` migration, and a Sumsub Web SDK integration in the provider launch flow. It ships with known gaps that are tracked as follow-up issues. + +## Merged Findings + +Findings from both Claude Code and Codex reviews, deduplicated and grouped by follow-up issue. + +### Issue 1: Euclid migration — complete `euclid-web` → `euclid` and validate exports + +**Severity:** Critical (build-breaking) + +- 3 settings screen components (`SecurityScreen`, `NotificationPreferencesScreen`, `DevModeScreen`) are imported from `@selfxyz/euclid` but not yet exported from the package +- `LaunchTour1Screen`–`LaunchTour4Screen` don't exist — Euclid only exports a single `TourScreen` +- `ProofGenerationScreen` doesn't exist — only a `ProofGeneration` component (not a screen wrapper) +- ~~`ProviderResultScreen` still imports from `@selfxyz/euclid-web`~~ — **fixed** (migrated to `@selfxyz/euclid`) +- ~~`ProviderLaunchScreen` still imports from `@selfxyz/euclid-web`~~ — **fixed** (migrated to `@selfxyz/euclid`, removed unused `BodyText`) +- Multiple screens migrated from `euclid-web` to `euclid` — blast radius is package-wide, not just settings +- `euclid-web` is being retired; all imports should use `@selfxyz/euclid` +- Settings handover doc (`docs/superpowers/plans/2026-03-22-settings-handover.md (legacy location)`) says the app still imports `euclid-web` — stale + +**Acceptance criteria:** + +- All imports use `@selfxyz/euclid`, zero references to `euclid-web` +- All imported screen components exist in Euclid's exports +- `yarn workspace @selfxyz/webview-app build` passes + +### Issue 2: Sumsub / WV-05 contract compliance + +**Severity:** High (incorrect behavior) + +- `normalizeSumsubStatus()` marks `reviewAnswer === 'GREEN'` as `status: 'success'` without requiring `attestation` — breaks the KYC contract +- `onApplicantSubmitted` emits `status: 'partial'` through the `onComplete` callback (not `onError`), so `ProviderResultScreen` treats it as success and routes to `/proving` +- `emitOnce` guard means if `onApplicantSubmitted` fires before `applicantReviewComplete`, the actual terminal status is silently dropped +- `ProvingScreen` fabricates a successful `VerificationResult` without consuming any provider attestation payload +- `teeUrl` not parsed from launch URL/query params — `fetchSumsubAccessToken()` uses `VITE_SUMSUB_TEE_URL` or hardcoded default +- `fetchSumsubAccessToken` signature doesn't accept `teeUrl` as a parameter — even after parsing, plumbing is missing +- Token refresh callback in `launchSumsubWebSdk` (line 167) also calls `fetchSumsubAccessToken()` with no args — refresh hits hardcoded URL too +- `sumsub-websdk.d.ts` custom type declaration added — unclear if it matches actual SDK API or is a stub +- WV-05 plan says "code complete, needs testing" but `teeUrl` isn't parsed, service file structure wasn't created, and normalization doesn't match the contract +- Invalid step value: `TunnelProvingScreen` passes `step="generatingProof"` — valid values are `"registeringId" | "generatingProof1" | "awaitingVerification" | "finishingUp"` + +**Acceptance criteria:** + +- `normalizeSumsubStatus` requires attestation for `success` +- `partial` status blocked from entering proving flow +- `teeUrl` parsed from query params, threaded into token fetch and refresh +- WV-05 plan status downgraded to "partial implementation" + +### Issue 3: Tunnel flow correctness and state propagation + +**Severity:** High (data loss / broken UX) + +- Selected ID type discarded in `TunnelIDTypeScreen.onIDTypeSelect` — ignores the param, always navigates to `/tunnel/proof/receipt` +- State passed via `location.state` is fragile — lost on refresh or direct navigation, no fallback or route guard +- `TunnelProofReceiptScreen` passes invalid `documentType="passport"` prop to `ProofRequestScreen` (not a valid prop) +- `TunnelProofReceiptScreen` mock items lack icons and `onInfoPress` callbacks +- `KycMockScreen` is raw HTML divs with inline styles instead of Euclid components +- `TunnelResultScreen` missing `animationSource` (Lottie) — uses icon only +- `Date.now()` in `TunnelProofReceiptScreen` creates new timestamp on every render +- Main proving flow (`ProvingScreen.tsx`) hardcodes `documentType='passport'` — affects primary flow, not just tunnel + +**Acceptance criteria:** + +- ID type selection persisted and forwarded through the tunnel chain +- Screens handle missing state gracefully (redirect or error) +- Correct Euclid component props used throughout + +### Issue 4: Settings persistence and bridge-backed actions + +**Severity:** Medium (non-functional features) + +- `SecurityScreen` backup state is hardcoded `false` — should query actual state from bridge/storage +- `SecurityScreen` handlers for backup, recovery phrase, restore all navigate to `/coming-soon` +- `NotificationPreferencesScreen` toggles are local `useState` — lost on reload, never persisted +- `DevModeScreen` mock config is local state — lost on reload +- `DevModeScreen` "Generate mock document" fires analytics but doesn't create or store anything +- `SettingsScreen` "Manage Documents", "Get support", "Share Self" all navigate to `/coming-soon` + +**Acceptance criteria:** + +- Settings state backed by bridge/storage where applicable +- Placeholder routes documented as intentional + +### Issue 5: Test coverage + +**Severity:** Medium + +- No tests for Sumsub normalization/result mapping (`normalizeSumsubStatus`, `buildProviderResult`) +- No route tests for tunnel flow progression +- No smoke tests for settings wrapper navigation and action wiring +- No tests for provider launch/result flow + +**Acceptance criteria:** + +- Unit tests for Sumsub normalization logic +- Route/navigation tests for tunnel flow +- Smoke tests for settings screens + +### Issue 6: Doc/spec drift + +**Severity:** Low + +- Settings handover doc (`docs/superpowers/plans/2026-03-22-settings-handover.md (legacy location)`) is stale — references `euclid-web`, understates scope +- Settings integration plan (`docs/superpowers/plans/2026-03-22-settings-screen-integration.md (legacy location)`) scoped to settings only but branch includes tunnel/provider/migration +- WV-05 plan status overstated +- Orphaned route: `/onboarding/confirm` defined in App.tsx but never navigated to + +**Acceptance criteria:** + +- Docs reflect actual branch state +- WV-05 status corrected +- Orphaned route removed or documented + +## Actionable PR feedback (CodeRabbit + Codex connector) + +Extracted from PR #1858 inline comments. Items already covered above are not repeated. + +### Build-breaking (addressed in this branch) + +- **`@selfxyz/euclid-web` removed from `package.json` but still imported** — `ProviderLaunchScreen` and `ProviderResultScreen` still referenced it. Both migrated to `@selfxyz/euclid` in this branch. (CodeRabbit P1, Codex connector P1) + +### Actionable (not yet addressed) + +- **`TunnelIDTypeScreen`: pass selected `idType` to next screen** — `onIDTypeSelect` ignores the param. Suggested fix: `navigate('/tunnel/proof/receipt', { state: { documentType: idType.id } })`. (CodeRabbit, mapped to Issue 3) +- **`TunnelProofReceiptScreen`: read document type from route state** — Currently hardcodes `documentType="passport"`. Should read from `location.state` with fallback. (CodeRabbit, mapped to Issue 3) +- **`NotificationPreferencesScreen`: persist toggles** — Use `storage` adapter from `useSelfClient()` to save/load preferences. (CodeRabbit, mapped to Issue 4) +- **`DevModeScreen`: mock document generation is a no-op** — Should either persist the mock or show a toast indicating the feature isn't functional yet. (CodeRabbit, mapped to Issue 4) + +### Nitpicks (low priority) + +- **`NotificationPreferencesScreen`: memoize toggle handlers** — `toggles` array recreated every render. `useMemo` with `toggleValues` as dependency would be cleaner. + +## What works well + +- Route wiring is clean — every route has a file, every `navigate()` targets a defined route +- Settings menu restructure (App settings / Support & feedback / Developer tools) is well-organized +- Sumsub SDK lifecycle management (mount/destroy/abort) is properly handled +- Analytics events are consistently tracked across all screens +- `emitOnce` pattern prevents duplicate callbacks (though it has the silenced-review-status side effect) diff --git a/docs/superpowers/plans/2026-03-22-settings-handover.md b/docs/superpowers/plans/2026-03-22-settings-handover.md new file mode 100644 index 000000000..5a445eeea --- /dev/null +++ b/docs/superpowers/plans/2026-03-22-settings-handover.md @@ -0,0 +1,102 @@ +# Euclid 3.0 Settings Integration — Handover + +**Branch:** `feat/euclid-settings-screens` (based off `origin/main`) +**Commit:** `feat(webview-app): add Euclid 3.0 settings sub-screens` + +## What Was Done + +Three new wrapper screens were added to `packages/webview-app/src/screens/account/`: + +| File | Euclid Component | Route | +| ----------------------------------- | ------------------------------- | ------------------------- | +| `SecurityScreen.tsx` | `SecurityScreen` | `/settings/security` | +| `NotificationPreferencesScreen.tsx` | `NotificationPreferencesScreen` | `/settings/notifications` | +| `DevModeScreen.tsx` | `DevModeScreen` | `/settings/dev-mode` | + +Routes added to `App.tsx`. `SettingsScreen.tsx` updated with three sections (App settings, Support & feedback, Developer tools) — "Security", "Notifications", and "Dev mode" now navigate to real screens instead of `/coming-soon`. + +Each wrapper follows the existing pattern (see `CountryPickerScreen.tsx`): import Euclid component, wire with `useNavigate()` + `useSelfClient()` bridge adapters, manage local UI state. + +## Blocker: Euclid Package Publish + +The installed `@selfxyz/euclid-web@1.0.2` does **not** export `SecurityScreen`, `NotificationPreferencesScreen`, or `DevModeScreen`. These screens exist on `origin/main` of the **euclid repo** (`/Users/evinova-self/Documents/euclid`) but haven't been published to npm yet. + +**To unblock, you need to publish a new version of `@selfxyz/euclid`** (note: package was renamed from `euclid-web` to `euclid` on euclid's `origin/main`). + +Steps: + +1. In the euclid repo, check out `origin/main` and verify the screens export: `git show origin/main:packages/euclid/src/screens/index.ts` +2. Bump the version and publish (the euclid repo has automated publishing via PR merge of version bump PRs) +3. In the self repo, update `packages/webview-app/package.json` to use the new version (and rename dependency from `@selfxyz/euclid-web` to `@selfxyz/euclid` if the package name changed) +4. Run `yarn install` to pull the new package +5. Run `yarn workspace @selfxyz/webview-app exec tsc --noEmit` to verify type-check passes + +### Package Rename Note + +The euclid repo renamed the web package from `@selfxyz/euclid-web` to `@selfxyz/euclid`. The webview-app still imports from `@selfxyz/euclid-web`. When updating the dependency, you'll also need to update all import paths across the webview-app screens: + +``` +- import { ... } from '@selfxyz/euclid-web'; ++ import { ... } from '@selfxyz/euclid'; +``` + +## Pre-Existing Type Errors + +These errors exist on `origin/main` independent of our changes: + +- `BridgeProvider.tsx` — `browserHost` not in `WebViewBridgeOptions` +- `SelfClientProvider.tsx` — `lifecycle.dismiss()` argument mismatch +- `ConfirmIdentificationScreen.tsx`, `ProvingScreen.tsx`, `VerificationResultScreen.tsx` — `VerificationResult` type mismatch +- `ProviderLaunchScreen.tsx` — `lifecycle.dismiss()` argument mismatch + +## What Still Uses `/coming-soon` + +These items in SettingsScreen still navigate to `/coming-soon` (no Euclid screen exists yet): + +- **"Manage Documents"** — needs a document management screen +- **"Get support"** — needs external link / bridge intent +- **"Share Self"** — needs native share sheet via bridge + +Within the sub-screens, these actions are also stubbed: + +- **SecurityScreen**: "Backup your account", "Reveal recovery phrase", "Restore an account" → all go to `/coming-soon` (need native bridge integration for biometric auth, iCloud backup, wallet restore) +- **NotificationPreferencesScreen**: Toggle state is local-only (needs bridge storage persistence) +- **DevModeScreen**: "Generate mock document" tracks analytics and navigates home but doesn't actually create a mock document yet + +## Navigation Flow + +``` +/settings (SettingsViewScreen) + ├── Manage Documents → /coming-soon + ├── Security → /settings/security ✅ NEW + │ ├── Backup your account → /coming-soon + │ ├── Reveal recovery phrase → /coming-soon + │ ├── Restore an account → /coming-soon + │ └── Disable backups → local dialogue toggle + ├── Notifications → /settings/notifications ✅ NEW + │ └── Toggles → local state only + ├── Get support → /coming-soon + ├── Share Self → /coming-soon + ├── Dev mode → /settings/dev-mode ✅ NEW + │ ├── Steppers/toggles → local state + │ ├── Generate mock document → analytics + navigate home + │ └── Reset all values → reset state + └── Close Self → lifecycle.dismiss() +``` + +## Validation + +After unblocking the euclid dependency: + +```bash +yarn workspace @selfxyz/webview-app exec tsc --noEmit # type-check +yarn workspace @selfxyz/webview-app build # Vite production build +yarn lint # lint +``` + +## Related Resources + +- **Implementation plan:** `docs/superpowers/plans/2026-03-22-settings-screen-integration.md` +- **Euclid screen source:** `euclid repo origin/main:packages/euclid/src/screens/settings/` +- **Euclid storybook stories:** `euclid repo origin/main:packages/storybook/stories/*Screen.stories.tsx` +- **Linear tickets:** SELF-2223 (Settings), SELF-2311 (Update core app) diff --git a/docs/superpowers/plans/2026-03-22-settings-screen-integration.md b/docs/superpowers/plans/2026-03-22-settings-screen-integration.md new file mode 100644 index 000000000..a3167225b --- /dev/null +++ b/docs/superpowers/plans/2026-03-22-settings-screen-integration.md @@ -0,0 +1,667 @@ +# Settings Screen Integration — Import Euclid 3.0 Screens into WebView App + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the five `/coming-soon` navigations in the webview-app's SettingsScreen with real Euclid 3.0 sub-screens (Security, Notifications, Dev Mode), plus wire the remaining menu items to appropriate bridge actions. + +**Architecture:** Each settings sub-screen gets a thin wrapper in `packages/webview-app/src/screens/account/` that imports the Euclid component from `@selfxyz/euclid-web`, wires it with `useSelfClient()` bridge adapters and `useNavigate()` from React Router, and manages local UI state (e.g. dialogue visibility, toggle values). Routes are added to `App.tsx`. The existing wrapper pattern (see `CountryPickerScreen.tsx`, `ComingSoonScreen.tsx`) is followed exactly. + +**Tech Stack:** React, React Router, `@selfxyz/euclid-web` (Euclid 3.0 component library), `@selfxyz/webview-bridge` (bridge adapters) + +**Existing pattern to follow:** + +``` +// webview-app wrapper screen pattern: +import { EuclidScreen } from '@selfxyz/euclid-web'; +import { useSelfClient } from '../../providers/SelfClientProvider'; +import { useNavigate } from 'react-router-dom'; + +export const WrapperScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic, lifecycle } = useSelfClient(); + // Wire Euclid props to bridge adapters + React Router + return ; +}; +``` + +--- + +## File Map + +| Action | File | Responsibility | +| ------ | ---------------------------------------------------------------------------- | -------------------------------------------------------------------- | +| Modify | `packages/webview-app/src/App.tsx` | Add 3 new routes | +| Modify | `packages/webview-app/src/screens/account/SettingsScreen.tsx` | Replace `/coming-soon` navigations with real routes + bridge actions | +| Create | `packages/webview-app/src/screens/account/SecurityScreen.tsx` | Wrapper for Euclid `SecurityScreen` | +| Create | `packages/webview-app/src/screens/account/NotificationPreferencesScreen.tsx` | Wrapper for Euclid `NotificationPreferencesScreen` | +| Create | `packages/webview-app/src/screens/account/DevModeScreen.tsx` | Wrapper for Euclid `DevModeScreen` | + +--- + +## Task 1: SecurityScreen wrapper + +The most important sub-screen. Wires backup state, recovery phrase, and restore actions through the bridge. + +**Files:** + +- Create: `packages/webview-app/src/screens/account/SecurityScreen.tsx` + +- [ ] **Step 1: Create SecurityScreen wrapper** + +```tsx +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + SecurityScreen as EuclidSecurityScreen, + LeftArrowIcon, + CloudKeyIcon, + LockIcon, + ZapShieldIcon, +} from '@selfxyz/euclid-web'; + +import { useSelfClient } from '../../providers/SelfClientProvider'; + +export const SecurityScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic, storage } = useSelfClient(); + const [isBackupEnabled, setIsBackupEnabled] = useState(false); + const [showDisableDialogue, setShowDisableDialogue] = useState(false); + + // TODO: Read actual backup state from bridge storage on mount + // useEffect(() => { storage.get('backup_enabled').then(...) }, []); + + const onBack = useCallback(() => { + haptic.trigger('selection'); + navigate('/settings'); + }, [navigate, haptic]); + + const onBackupAccount = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('security_backup_account_pressed'); + navigate('/coming-soon'); + }, [navigate, haptic, analytics]); + + const onRevealRecoveryPhrase = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('security_reveal_phrase_pressed'); + navigate('/coming-soon'); + }, [navigate, haptic, analytics]); + + const onRestoreAccount = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('security_restore_account_pressed'); + navigate('/coming-soon'); + }, [navigate, haptic, analytics]); + + const onDisableBackups = useCallback(() => { + haptic.trigger('warning'); + setShowDisableDialogue(true); + }, [haptic]); + + const onDisableICloudBackups = useCallback(() => { + haptic.trigger('warning'); + analytics.trackEvent('security_backups_disabled'); + setIsBackupEnabled(false); + setShowDisableDialogue(false); + // TODO: Persist via bridge storage + }, [haptic, analytics]); + + const onDismissDialogue = useCallback(() => { + haptic.trigger('selection'); + setShowDisableDialogue(false); + }, [haptic]); + + return ( + ( + + )} + cloudKeyIcon={CloudKeyIcon} + lockIcon={LockIcon} + zapShieldIcon={ZapShieldIcon} + isBackupEnabled={isBackupEnabled} + onBack={onBack} + onBackupAccount={onBackupAccount} + onRevealRecoveryPhrase={onRevealRecoveryPhrase} + onRestoreAccount={onRestoreAccount} + onDisableBackups={onDisableBackups} + showDisableDialogue={showDisableDialogue} + onDisableICloudBackups={onDisableICloudBackups} + onDismissDialogue={onDismissDialogue} + /> + ); +}; +``` + +- [ ] **Step 2: Verify type-check passes** + +Run: `cd /Users/evinova-self/Documents/self && yarn workspace @selfxyz/webview-app exec tsc --noEmit` +Expected: No errors related to SecurityScreen + +- [ ] **Step 3: Commit** + +```bash +git add packages/webview-app/src/screens/account/SecurityScreen.tsx +git commit -m "feat(webview-app): add SecurityScreen wrapper for Euclid 3.0" +``` + +--- + +## Task 2: NotificationPreferencesScreen wrapper + +Manages toggle state locally (persisted via bridge storage in future). + +**Files:** + +- Create: `packages/webview-app/src/screens/account/NotificationPreferencesScreen.tsx` + +- [ ] **Step 1: Create NotificationPreferencesScreen wrapper** + +```tsx +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + NotificationPreferencesScreen as EuclidNotificationPreferencesScreen, + LeftArrowIcon, +} from '@selfxyz/euclid-web'; + +import { useSelfClient } from '../../providers/SelfClientProvider'; + +const defaultToggles = [ + { + key: 'self', + label: 'Allow Self notifications', + description: 'App updates and more', + }, + { + key: 'nova', + label: 'Allow Nova notifications', + description: 'Never miss a mission', + }, + { + key: 'points', + label: 'Allow Self Points notifications', + description: 'Points and rewards', + }, + { + key: 'id_status', + label: 'Allow ID status notifications', + description: 'Document verification updates', + }, +]; + +export const NotificationPreferencesScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic } = useSelfClient(); + const [toggleValues, setToggleValues] = useState>({ + self: true, + nova: true, + points: true, + id_status: false, + }); + + const onBack = useCallback(() => { + haptic.trigger('selection'); + navigate('/settings'); + }, [navigate, haptic]); + + const toggles = defaultToggles.map(t => ({ + label: t.label, + description: t.description, + value: toggleValues[t.key] ?? false, + onToggleChange: (value: boolean) => { + haptic.trigger('selection'); + analytics.trackEvent('notification_toggle_changed', { + key: t.key, + value, + }); + setToggleValues(prev => ({ ...prev, [t.key]: value })); + }, + })); + + return ( + ( + + )} + onBack={onBack} + toggles={toggles} + /> + ); +}; +``` + +- [ ] **Step 2: Verify type-check passes** + +Run: `cd /Users/evinova-self/Documents/self && yarn workspace @selfxyz/webview-app exec tsc --noEmit` +Expected: No errors related to NotificationPreferencesScreen + +- [ ] **Step 3: Commit** + +```bash +git add packages/webview-app/src/screens/account/NotificationPreferencesScreen.tsx +git commit -m "feat(webview-app): add NotificationPreferencesScreen wrapper for Euclid 3.0" +``` + +--- + +## Task 3: DevModeScreen wrapper + +Manages mock document generation state. More complex — has steppers, dropdowns, toggle, and IDCard display. + +**Files:** + +- Create: `packages/webview-app/src/screens/account/DevModeScreen.tsx` + +- [ ] **Step 1: Create DevModeScreen wrapper** + +```tsx +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + DevModeScreen as EuclidDevModeScreen, + LeftArrowIcon, +} from '@selfxyz/euclid-web'; +import type { IDCardProps } from '@selfxyz/euclid-web'; + +import { useSelfClient } from '../../providers/SelfClientProvider'; + +const ageOptions = ['18 or older', '21 or older', '25 or older', '30 or older']; +const expiryOptions = ['1 year', '2 years', '5 years', '10 years']; + +export const DevModeScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic } = useSelfClient(); + + const [documentType, setDocumentType] = useState('passport'); + const [nationality, setNationality] = useState('united states of america'); + const [ageIndex, setAgeIndex] = useState(1); + const [expiryIndex, setExpiryIndex] = useState(2); + const [ofacCheck, setOfacCheck] = useState(true); + + const idCard: IDCardProps = { + variant: 'dev-passport', + title: 'Developer Passport', + subtitle: 'Digital credential for developers', + }; + + const onBack = useCallback(() => { + haptic.trigger('selection'); + navigate('/settings'); + }, [navigate, haptic]); + + const onResetAllValues = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('dev_mode_reset'); + setDocumentType('passport'); + setNationality('united states of america'); + setAgeIndex(1); + setExpiryIndex(2); + setOfacCheck(true); + }, [haptic, analytics]); + + const onGenerateMockDocument = useCallback(() => { + haptic.trigger('success'); + analytics.trackEvent('dev_mode_generate_mock', { + documentType, + nationality, + age: ageOptions[ageIndex], + expiresIn: expiryOptions[expiryIndex], + ofacCheck, + }); + navigate('/'); + }, [ + navigate, + haptic, + analytics, + documentType, + nationality, + ageIndex, + expiryIndex, + ofacCheck, + ]); + + return ( + ( + + )} + onBack={onBack} + idCard={idCard} + documentType={documentType} + onDocumentTypePress={() => { + setDocumentType(prev => (prev === 'passport' ? 'id_card' : 'passport')); + }} + nationality={nationality} + onNationalityPress={() => { + setNationality(prev => + prev === 'united states of america' + ? 'germany' + : 'united states of america', + ); + }} + age={ageOptions[ageIndex]} + onAgeIncrement={() => + setAgeIndex(prev => Math.min(prev + 1, ageOptions.length - 1)) + } + onAgeDecrement={() => setAgeIndex(prev => Math.max(prev - 1, 0))} + documentExpiresIn={expiryOptions[expiryIndex]} + onDocumentExpiresIncrement={() => + setExpiryIndex(prev => Math.min(prev + 1, expiryOptions.length - 1)) + } + onDocumentExpiresDecrement={() => + setExpiryIndex(prev => Math.max(prev - 1, 0)) + } + ofacCheck={ofacCheck} + onOfacCheckChange={value => { + haptic.trigger('selection'); + setOfacCheck(value); + }} + onResetAllValues={onResetAllValues} + onGenerateMockDocument={onGenerateMockDocument} + /> + ); +}; +``` + +- [ ] **Step 2: Verify type-check passes** + +Run: `cd /Users/evinova-self/Documents/self && yarn workspace @selfxyz/webview-app exec tsc --noEmit` +Expected: No errors related to DevModeScreen + +- [ ] **Step 3: Commit** + +```bash +git add packages/webview-app/src/screens/account/DevModeScreen.tsx +git commit -m "feat(webview-app): add DevModeScreen wrapper for Euclid 3.0" +``` + +--- + +## Task 4: Wire routes in App.tsx + +Add the three new routes under `/settings/*`. + +**Files:** + +- Modify: `packages/webview-app/src/App.tsx` + +- [ ] **Step 1: Add imports and routes** + +Add these imports after the existing `SettingsScreen` import (line 16): + +```tsx +import { SecurityScreen } from './screens/account/SecurityScreen'; +import { NotificationPreferencesScreen } from './screens/account/NotificationPreferencesScreen'; +import { DevModeScreen } from './screens/account/DevModeScreen'; +``` + +Add these routes after the `/settings` route (after line 34): + +```tsx +} /> +} /> +} /> +``` + +- [ ] **Step 2: Verify type-check passes** + +Run: `cd /Users/evinova-self/Documents/self && yarn workspace @selfxyz/webview-app exec tsc --noEmit` +Expected: PASS + +- [ ] **Step 3: Commit** + +```bash +git add packages/webview-app/src/App.tsx +git commit -m "feat(webview-app): add settings sub-screen routes" +``` + +--- + +## Task 5: Wire SettingsScreen menu items to real routes + +Replace the five `/coming-soon` navigations with real routes and bridge actions. + +**Files:** + +- Modify: `packages/webview-app/src/screens/account/SettingsScreen.tsx` + +- [ ] **Step 1: Update SettingsScreen menu items** + +Replace the full component with updated navigation wiring. Key changes: + +- "View document info" → `navigate('/coming-soon')` (no Euclid screen for this yet — keep as-is) +- "Recovery phrase" → `navigate('/settings/security')` (Security screen handles this) +- "Cloud backup" → `navigate('/settings/security')` (Security screen handles this) +- "Get support" → call `lifecycle.dismiss()` with support intent (or keep `/coming-soon`) +- "Share Self" → call `lifecycle.dismiss()` with share intent (or keep `/coming-soon`) + +Additionally, add the Settings sub-sections that match the Euclid Storybook stories: + +- **App settings section**: Manage Documents, Security, Notifications +- **Support section**: Support, Send feedback +- **Developer tools section** (conditional): Dev mode + +```tsx +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + SettingsViewScreen, + LeftArrowIcon, + QuestionCircleStrokeIcon, + DocumentDetailsIcon, + LockIcon, + NotificationIcon, + ChatStrokeIcon, + ShareIcon, + CodeIcon, +} from '@selfxyz/euclid-web'; + +import { useSelfClient } from '../../providers/SelfClientProvider'; + +export const SettingsScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic, lifecycle } = useSelfClient(); + + const onBack = useCallback(() => { + haptic.trigger('selection'); + navigate('/'); + }, [navigate, haptic]); + + const onDismiss = useCallback(async () => { + haptic.trigger('selection'); + analytics.trackEvent('settings_dismiss_pressed'); + lifecycle.dismiss(); + }, [haptic, analytics, lifecycle]); + + return ( + ( + + )} + infoIcon={({ size, color }) => ( + + )} + onClose={onBack} + showBackupInfoBox={false} + isBackupEnabled={false} + CTAs={[]} + sections={[ + { + title: 'App settings', + items: [ + { + icon: DocumentDetailsIcon, + label: 'Manage Documents', + description: 'Recovery phrase, passport data', + onPress: () => { + haptic.trigger('selection'); + analytics.trackEvent('settings_manage_documents_pressed'); + navigate('/coming-soon'); + }, + }, + { + icon: LockIcon, + label: 'Security', + description: 'Recovery phrase, passport data', + onPress: () => { + haptic.trigger('selection'); + analytics.trackEvent('settings_security_pressed'); + navigate('/settings/security'); + }, + }, + { + icon: NotificationIcon, + label: 'Notifications', + description: 'Preferences, notification types', + onPress: () => { + haptic.trigger('selection'); + analytics.trackEvent('settings_notifications_pressed'); + navigate('/settings/notifications'); + }, + }, + ], + }, + { + title: 'Support & feedback', + items: [ + { + icon: ChatStrokeIcon, + label: 'Get support', + description: 'Help center & support', + onPress: () => { + haptic.trigger('selection'); + analytics.trackEvent('settings_support_pressed'); + navigate('/coming-soon'); + }, + }, + { + icon: ShareIcon, + label: 'Share Self', + description: 'Share Self with friends', + onPress: () => { + haptic.trigger('selection'); + analytics.trackEvent('settings_share_pressed'); + navigate('/coming-soon'); + }, + }, + ], + }, + { + title: 'Developer tools', + items: [ + { + icon: CodeIcon, + label: 'Dev mode', + description: 'Manage mock IDs, simulate proofs', + onPress: () => { + haptic.trigger('selection'); + analytics.trackEvent('settings_dev_mode_pressed'); + navigate('/settings/dev-mode'); + }, + }, + ], + }, + ]} + connectHeading="" + connectSubheading="" + connectButtons={[]} + bottomSectionItems={[ + { + label: 'Close Self', + onPress: onDismiss, + }, + ]} + /> + ); +}; +``` + +- [ ] **Step 2: Verify type-check passes** + +Run: `cd /Users/evinova-self/Documents/self && yarn workspace @selfxyz/webview-app exec tsc --noEmit` +Expected: PASS + +- [ ] **Step 3: Verify Vite build succeeds** + +Run: `cd /Users/evinova-self/Documents/self && yarn workspace @selfxyz/webview-app build` +Expected: Build succeeds, bundle output in `dist/` + +- [ ] **Step 4: Commit** + +```bash +git add packages/webview-app/src/screens/account/SettingsScreen.tsx +git commit -m "feat(webview-app): wire settings menu to Security, Notifications, and DevMode screens" +``` + +--- + +## Task 6: Final validation + +- [ ] **Step 1: Full type-check** + +Run: `cd /Users/evinova-self/Documents/self && yarn workspace @selfxyz/webview-app exec tsc --noEmit` +Expected: PASS with zero errors + +- [ ] **Step 2: Vite production build** + +Run: `cd /Users/evinova-self/Documents/self && yarn workspace @selfxyz/webview-app build` +Expected: Build succeeds. Bundle size should be similar to before (~294 KB, may increase slightly with 3 new screens) + +- [ ] **Step 3: Lint check** + +Run: `cd /Users/evinova-self/Documents/self && yarn lint` +Expected: No new lint errors in changed files + +--- + +## Navigation Flow After Implementation + +``` +/settings (SettingsViewScreen) + ├─ "Manage Documents" → /coming-soon (no screen yet) + ├─ "Security" → /settings/security (SecurityScreen) + │ ├─ "Backup your account" → /coming-soon (future: backup flow) + │ ├─ "Reveal recovery phrase" → /coming-soon (future: bridge to native) + │ ├─ "Restore an account" → /coming-soon (future: restore flow) + │ └─ "Disable backups" → shows dialogue → local state toggle + ├─ "Notifications" → /settings/notifications (NotificationPreferencesScreen) + │ └─ Toggle changes → local state (future: bridge to native prefs) + ├─ "Get support" → /coming-soon (future: external link) + ├─ "Share Self" → /coming-soon (future: share sheet via bridge) + ├─ "Dev mode" → /settings/dev-mode (DevModeScreen) + │ ├─ Steppers/toggles → local state + │ ├─ "Generate mock document" → analytics event + navigate home + │ └─ "Reset all values" → reset local state + └─ "Close Self" → lifecycle.dismiss() +``` + +## Out of Scope + +- Persisting toggle/backup state via bridge storage (marked with `// TODO` comments) +- Recovery phrase reveal flow (requires biometric auth via bridge) +- Cloud backup flow (requires native iCloud/Google backup APIs) +- Restore account flow (requires native wallet restore) +- Support link / Share sheet (requires native intents via bridge) +- Manage Documents screen (no Euclid screen exists yet) +- `@selfxyz/euclid-web` → `@selfxyz/euclid` package rename (separate dependency update task) diff --git a/packages/mobile-sdk-alpha/AGENTS.md b/packages/mobile-sdk-alpha/AGENTS.md index 51035d3c4..0305f1fc0 100644 --- a/packages/mobile-sdk-alpha/AGENTS.md +++ b/packages/mobile-sdk-alpha/AGENTS.md @@ -142,14 +142,14 @@ yarn types # Verify type checking yarn build # Confirm build still works ``` -## SDK Architecture Specs +## SDK Architecture -For architecture context, implementation details, and workstream coordination: +For architecture context: -- **[SDK Overview](../../specs/projects/sdk/OVERVIEW.md)** — System architecture, bridge protocol, decision matrix -- **[SDK Core Spec](../../specs/projects/sdk/workstreams/sdk-core/SPEC.md)** — Implementation chunks for this package (mobile-sdk-alpha) +- **[SDK Overview](../../specs/projects/sdk/OVERVIEW.md)** — System architecture, bridge protocol, decision matrix (read-only reference) +- **Implementation specs** — Live in Linear as documents attached to issues. Check the relevant Linear issue for the current spec. Do not create spec files in `specs/` — that folder is deprecated. -Before implementing SDK work, read `CLAUDE.md` Key Rules and `specs/projects/sdk/workstreams/sdk-core/SPEC.md` for constraints and validation commands. +Before implementing SDK work, read `CLAUDE.md` Key Rules for constraints and validation commands. ## Notes diff --git a/packages/webview-app/package.json b/packages/webview-app/package.json index e8b333d1e..1b744ae7f 100644 --- a/packages/webview-app/package.json +++ b/packages/webview-app/package.json @@ -10,8 +10,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@selfxyz/euclid-core": "^1.0.1", - "@selfxyz/euclid-web": "^1.0.2", + "@selfxyz/euclid": "^1.2.0", + "@selfxyz/euclid-core": "^1.2.0", "@selfxyz/mobile-sdk-alpha": "workspace:^", "@selfxyz/webview-bridge": "workspace:^", "@sumsub/websdk": "^2.0.0", diff --git a/packages/webview-app/src/App.tsx b/packages/webview-app/src/App.tsx index 908007578..fcd4eec4c 100644 --- a/packages/webview-app/src/App.tsx +++ b/packages/webview-app/src/App.tsx @@ -15,7 +15,17 @@ import { HomeScreen } from './screens/home/HomeScreen'; import { ProvingScreen } from './screens/proving/ProvingScreen'; import { VerificationResultScreen } from './screens/proving/VerificationResultScreen'; import { SettingsScreen } from './screens/account/SettingsScreen'; +import { SecurityScreen } from './screens/account/SecurityScreen'; +import { NotificationPreferencesScreen } from './screens/account/NotificationPreferencesScreen'; +import { DevModeScreen } from './screens/account/DevModeScreen'; import { ComingSoonScreen } from './screens/ComingSoonScreen'; +import { TourScreen } from './screens/tunnel/TourScreen'; +import { KycMockScreen } from './screens/tunnel/KycMockScreen'; +import { TunnelCountryPickerScreen } from './screens/tunnel/TunnelCountryPickerScreen'; +import { TunnelIDTypeScreen } from './screens/tunnel/TunnelIDTypeScreen'; +import { TunnelProofReceiptScreen } from './screens/tunnel/TunnelProofReceiptScreen'; +import { TunnelProvingScreen } from './screens/tunnel/TunnelProvingScreen'; +import { TunnelResultScreen } from './screens/tunnel/TunnelResultScreen'; export const App: React.FC = () => ( @@ -40,11 +50,21 @@ export const App: React.FC = () => ( } /> } /> } /> + } /> + } /> + } /> } /> } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> } /> diff --git a/packages/webview-app/src/screens/ComingSoonScreen.tsx b/packages/webview-app/src/screens/ComingSoonScreen.tsx index 90e62fb51..c48d39293 100644 --- a/packages/webview-app/src/screens/ComingSoonScreen.tsx +++ b/packages/webview-app/src/screens/ComingSoonScreen.tsx @@ -4,7 +4,7 @@ import React, { useCallback } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { ComingSoonScreen as EuclidComingSoonScreen } from '@selfxyz/euclid-web'; +import { ComingSoonScreen as EuclidComingSoonScreen } from '@selfxyz/euclid'; import { useSelfClient } from '../providers/SelfClientProvider'; import { getCountryName, renderFlag } from '../utils/countryFlags'; @@ -50,7 +50,7 @@ export const ComingSoonScreen: React.FC = () => { : "We're working to roll out support for this feature." } description="If you'd like to be notified when this becomes available, let us know." - onSignUpPress={onNotifyMe} + onNotifyPress={onNotifyMe} onBack={onDismiss} renderFlag={renderFlag} /> diff --git a/packages/webview-app/src/screens/account/DevModeScreen.tsx b/packages/webview-app/src/screens/account/DevModeScreen.tsx new file mode 100644 index 000000000..2c187424b --- /dev/null +++ b/packages/webview-app/src/screens/account/DevModeScreen.tsx @@ -0,0 +1,96 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + DevModeScreen as EuclidDevModeScreen, + LeftArrowIcon, +} from '@selfxyz/euclid'; +import type { IDCardProps } from '@selfxyz/euclid'; + +import { useSelfClient } from '../../providers/SelfClientProvider'; + +const ageOptions = ['18 or older', '21 or older', '25 or older', '30 or older']; +const expiryOptions = ['1 year', '2 years', '5 years', '10 years']; + +export const DevModeScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic } = useSelfClient(); + + const [documentType, setDocumentType] = useState('passport'); + const [nationality, setNationality] = useState('united states of america'); + const [ageIndex, setAgeIndex] = useState(1); + const [expiryIndex, setExpiryIndex] = useState(2); + const [ofacCheck, setOfacCheck] = useState(true); + + const idCard: IDCardProps = { + variant: 'dev-passport', + title: 'Developer Passport', + subtitle: 'Digital credential for developers', + }; + + const onBack = useCallback(() => { + haptic.trigger('selection'); + navigate('/settings'); + }, [navigate, haptic]); + + const onResetAllValues = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('dev_mode_reset'); + setDocumentType('passport'); + setNationality('united states of america'); + setAgeIndex(1); + setExpiryIndex(2); + setOfacCheck(true); + }, [haptic, analytics]); + + const onGenerateMockDocument = useCallback(() => { + haptic.trigger('success'); + analytics.trackEvent('dev_mode_generate_mock', { + documentType, + nationality, + age: ageOptions[ageIndex], + expiresIn: expiryOptions[expiryIndex], + ofacCheck, + }); + navigate('/'); + }, [navigate, haptic, analytics, documentType, nationality, ageIndex, expiryIndex, ofacCheck]); + + return ( + ( + + )} + onBack={onBack} + idCard={idCard} + documentType={documentType} + onDocumentTypePress={() => { + setDocumentType(prev => (prev === 'passport' ? 'id_card' : 'passport')); + }} + nationality={nationality} + onNationalityPress={() => { + setNationality(prev => + prev === 'united states of america' ? 'germany' : 'united states of america', + ); + }} + age={ageOptions[ageIndex]} + onAgeIncrement={() => setAgeIndex(prev => Math.min(prev + 1, ageOptions.length - 1))} + onAgeDecrement={() => setAgeIndex(prev => Math.max(prev - 1, 0))} + documentExpiresIn={expiryOptions[expiryIndex]} + onDocumentExpiresIncrement={() => + setExpiryIndex(prev => Math.min(prev + 1, expiryOptions.length - 1)) + } + onDocumentExpiresDecrement={() => setExpiryIndex(prev => Math.max(prev - 1, 0))} + ofacCheck={ofacCheck} + onOfacCheckChange={value => { + haptic.trigger('selection'); + setOfacCheck(value); + }} + onResetAllValues={onResetAllValues} + onGenerateMockDocument={onGenerateMockDocument} + /> + ); +}; diff --git a/packages/webview-app/src/screens/account/NotificationPreferencesScreen.tsx b/packages/webview-app/src/screens/account/NotificationPreferencesScreen.tsx new file mode 100644 index 000000000..2a8adfa2a --- /dev/null +++ b/packages/webview-app/src/screens/account/NotificationPreferencesScreen.tsx @@ -0,0 +1,57 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + NotificationPreferencesScreen as EuclidNotificationPreferencesScreen, + LeftArrowIcon, +} from '@selfxyz/euclid'; + +import { useSelfClient } from '../../providers/SelfClientProvider'; + +const defaultToggles = [ + { key: 'self', label: 'Allow Self notifications', description: 'App updates and more' }, + { key: 'nova', label: 'Allow Nova notifications', description: 'Never miss a mission' }, + { key: 'points', label: 'Allow Self Points notifications', description: 'Points and rewards' }, + { key: 'id_status', label: 'Allow ID status notifications', description: 'Document verification updates' }, +]; + +export const NotificationPreferencesScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic } = useSelfClient(); + const [toggleValues, setToggleValues] = useState>({ + self: true, + nova: true, + points: true, + id_status: false, + }); + + const onBack = useCallback(() => { + haptic.trigger('selection'); + navigate('/settings'); + }, [navigate, haptic]); + + const toggles = defaultToggles.map(t => ({ + label: t.label, + description: t.description, + value: toggleValues[t.key] ?? false, + onToggleChange: (value: boolean) => { + haptic.trigger('selection'); + analytics.trackEvent('notification_toggle_changed', { key: t.key, value }); + setToggleValues(prev => ({ ...prev, [t.key]: value })); + }, + })); + + return ( + ( + + )} + onBack={onBack} + toggles={toggles} + /> + ); +}; diff --git a/packages/webview-app/src/screens/account/SecurityScreen.tsx b/packages/webview-app/src/screens/account/SecurityScreen.tsx new file mode 100644 index 000000000..673da5165 --- /dev/null +++ b/packages/webview-app/src/screens/account/SecurityScreen.tsx @@ -0,0 +1,83 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + SecurityScreen as EuclidSecurityScreen, + LeftArrowIcon, + CloudKeyIcon, + LockIcon, + ZapShieldIcon, +} from '@selfxyz/euclid'; + +import { useSelfClient } from '../../providers/SelfClientProvider'; + +export const SecurityScreen: React.FC = () => { + const navigate = useNavigate(); + const { analytics, haptic } = useSelfClient(); + const [isBackupEnabled, setIsBackupEnabled] = useState(false); + const [showDisableDialogue, setShowDisableDialogue] = useState(false); + + const onBack = useCallback(() => { + haptic.trigger('selection'); + navigate('/settings'); + }, [navigate, haptic]); + + const onBackupAccount = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('security_backup_account_pressed'); + navigate('/coming-soon'); + }, [navigate, haptic, analytics]); + + const onRevealRecoveryPhrase = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('security_reveal_phrase_pressed'); + navigate('/coming-soon'); + }, [navigate, haptic, analytics]); + + const onRestoreAccount = useCallback(() => { + haptic.trigger('selection'); + analytics.trackEvent('security_restore_account_pressed'); + navigate('/coming-soon'); + }, [navigate, haptic, analytics]); + + const onDisableBackups = useCallback(() => { + haptic.trigger('warning'); + setShowDisableDialogue(true); + }, [haptic]); + + const onDisableICloudBackups = useCallback(() => { + haptic.trigger('warning'); + analytics.trackEvent('security_backups_disabled'); + setIsBackupEnabled(false); + setShowDisableDialogue(false); + }, [haptic, analytics]); + + const onDismissDialogue = useCallback(() => { + haptic.trigger('selection'); + setShowDisableDialogue(false); + }, [haptic]); + + return ( + ( + + )} + cloudKeyIcon={CloudKeyIcon} + lockIcon={LockIcon} + zapShieldIcon={ZapShieldIcon} + isBackupEnabled={isBackupEnabled} + onBack={onBack} + onBackupAccount={onBackupAccount} + onRevealRecoveryPhrase={onRevealRecoveryPhrase} + onRestoreAccount={onRestoreAccount} + onDisableBackups={onDisableBackups} + showDisableDialogue={showDisableDialogue} + onDisableICloudBackups={onDisableICloudBackups} + onDismissDialogue={onDismissDialogue} + /> + ); +}; diff --git a/packages/webview-app/src/screens/account/SettingsScreen.tsx b/packages/webview-app/src/screens/account/SettingsScreen.tsx index dbc5effbb..add662d6e 100644 --- a/packages/webview-app/src/screens/account/SettingsScreen.tsx +++ b/packages/webview-app/src/screens/account/SettingsScreen.tsx @@ -10,10 +10,11 @@ import { QuestionCircleStrokeIcon, DocumentDetailsIcon, LockIcon, - CloudKeyIcon, + NotificationIcon, ChatStrokeIcon, ShareIcon, -} from '@selfxyz/euclid-web'; + CodeIcon, +} from '@selfxyz/euclid'; import { useSelfClient } from '../../providers/SelfClientProvider'; @@ -47,35 +48,35 @@ export const SettingsScreen: React.FC = () => { CTAs={[]} sections={[ { - title: 'Account', + title: 'App settings', items: [ { icon: DocumentDetailsIcon, - label: 'View document info', - description: 'View your stored document details', + label: 'Manage Documents', + description: 'Recovery phrase, passport data', onPress: () => navigate('/coming-soon'), }, { icon: LockIcon, - label: 'Recovery phrase', - description: 'View your recovery phrase', - onPress: () => navigate('/coming-soon'), + label: 'Security', + description: 'Recovery phrase, passport data', + onPress: () => navigate('/settings/security'), }, { - icon: CloudKeyIcon, - label: 'Cloud backup', - description: 'Manage your cloud backup', - onPress: () => navigate('/coming-soon'), + icon: NotificationIcon, + label: 'Notifications', + description: 'Preferences, notification types', + onPress: () => navigate('/settings/notifications'), }, ], }, { - title: 'Support', + title: 'Support & feedback', items: [ { icon: ChatStrokeIcon, label: 'Get support', - description: 'Contact us for help', + description: 'Help center & support', onPress: () => navigate('/coming-soon'), }, { @@ -86,6 +87,23 @@ export const SettingsScreen: React.FC = () => { }, ], }, + { + title: 'Developer tools', + items: [ + { + icon: CodeIcon, + label: 'Dev mode', + description: 'Manage mock IDs, simulate proofs', + onPress: () => navigate('/settings/dev-mode'), + }, + { + icon: CodeIcon, + label: 'Tunnel flow', + description: 'Demo: register + disclose in one flow', + onPress: () => navigate('/tunnel/tour/1'), + }, + ], + }, ]} connectHeading="" connectSubheading="" diff --git a/packages/webview-app/src/screens/home/HomeScreen.tsx b/packages/webview-app/src/screens/home/HomeScreen.tsx index 119e54af2..ac1418a02 100644 --- a/packages/webview-app/src/screens/home/HomeScreen.tsx +++ b/packages/webview-app/src/screens/home/HomeScreen.tsx @@ -7,8 +7,8 @@ import { useNavigate } from 'react-router-dom'; import { HomeScreen as EuclidHomeScreen, GearIcon, -} from '@selfxyz/euclid-web'; -import type { IDCardVariant } from '@selfxyz/euclid-web'; +} from '@selfxyz/euclid'; +import type { IDCardVariant } from '@selfxyz/euclid'; import { useSelfClient } from '../../providers/SelfClientProvider'; diff --git a/packages/webview-app/src/screens/onboarding/ConfirmIdentificationScreen.tsx b/packages/webview-app/src/screens/onboarding/ConfirmIdentificationScreen.tsx index 7de570edb..61764922d 100644 --- a/packages/webview-app/src/screens/onboarding/ConfirmIdentificationScreen.tsx +++ b/packages/webview-app/src/screens/onboarding/ConfirmIdentificationScreen.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { StatusState, CheckCircleIcon, colors } from '@selfxyz/euclid-web'; +import { StatusState, CheckCircleIcon, colors } from '@selfxyz/euclid'; import type { VerificationResult } from '@selfxyz/webview-bridge'; import { useSelfClient } from '../../providers/SelfClientProvider'; diff --git a/packages/webview-app/src/screens/onboarding/CountryPickerScreen.tsx b/packages/webview-app/src/screens/onboarding/CountryPickerScreen.tsx index d34f88f80..ed67f2170 100644 --- a/packages/webview-app/src/screens/onboarding/CountryPickerScreen.tsx +++ b/packages/webview-app/src/screens/onboarding/CountryPickerScreen.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { CountryPickerScreen as EuclidCountryPickerScreen } from '@selfxyz/euclid-web'; +import { CountryPickerScreen as EuclidCountryPickerScreen } from '@selfxyz/euclid'; import countryDocumentTypes from '../../data/country-document-types.json'; import { useSelfClient } from '../../providers/SelfClientProvider'; diff --git a/packages/webview-app/src/screens/onboarding/IDSelectionScreen.tsx b/packages/webview-app/src/screens/onboarding/IDSelectionScreen.tsx index 0d7b9bd04..fed873834 100644 --- a/packages/webview-app/src/screens/onboarding/IDSelectionScreen.tsx +++ b/packages/webview-app/src/screens/onboarding/IDSelectionScreen.tsx @@ -4,8 +4,8 @@ import React, { useCallback } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { IDTypeScreen } from '@selfxyz/euclid-web'; -import type { IDType } from '@selfxyz/euclid-web'; +import { IDTypeScreen } from '@selfxyz/euclid'; +import type { IDType } from '@selfxyz/euclid'; import { useSelfClient } from '../../providers/SelfClientProvider'; import { getCountryName, renderFlag } from '../../utils/countryFlags'; diff --git a/packages/webview-app/src/screens/onboarding/ProviderLaunchScreen.tsx b/packages/webview-app/src/screens/onboarding/ProviderLaunchScreen.tsx index f8c0d8e2a..eef360a87 100644 --- a/packages/webview-app/src/screens/onboarding/ProviderLaunchScreen.tsx +++ b/packages/webview-app/src/screens/onboarding/ProviderLaunchScreen.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; -import { Button, Title, Description, colors, spacing } from '@selfxyz/euclid-web'; +import { Button, Title, Description, colors, spacing } from '@selfxyz/euclid'; import { useSelfClient } from '../../providers/SelfClientProvider'; import { useVerificationRequest } from '../../providers/VerificationRequestProvider'; diff --git a/packages/webview-app/src/screens/onboarding/ProviderResultScreen.tsx b/packages/webview-app/src/screens/onboarding/ProviderResultScreen.tsx index 5ee5be3ec..c05011b58 100644 --- a/packages/webview-app/src/screens/onboarding/ProviderResultScreen.tsx +++ b/packages/webview-app/src/screens/onboarding/ProviderResultScreen.tsx @@ -9,7 +9,7 @@ import { CheckCircleIcon, WarningOctagonIcon, colors, -} from '@selfxyz/euclid-web'; +} from '@selfxyz/euclid'; import { useSelfClient } from '../../providers/SelfClientProvider'; import type { KycProviderResult } from '../../types/kycProvider'; diff --git a/packages/webview-app/src/screens/proving/ProvingScreen.tsx b/packages/webview-app/src/screens/proving/ProvingScreen.tsx index d374df3a0..16473ad90 100644 --- a/packages/webview-app/src/screens/proving/ProvingScreen.tsx +++ b/packages/webview-app/src/screens/proving/ProvingScreen.tsx @@ -4,7 +4,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { ProofRequestScreen, SelfLogo } from '@selfxyz/euclid-web'; +import { ProofRequestScreen, SelfLogo } from '@selfxyz/euclid'; import type { VerificationResult } from '@selfxyz/webview-bridge'; import { useSelfClient } from '../../providers/SelfClientProvider'; @@ -98,6 +98,8 @@ export const ProvingScreen: React.FC = () => { appEndpoint={appEndpoint} timestamp={timestamp} items={proofItems} + // TODO: hardcoding for now, fetch real value + documentType='passport' /> ); }; diff --git a/packages/webview-app/src/screens/proving/VerificationResultScreen.tsx b/packages/webview-app/src/screens/proving/VerificationResultScreen.tsx index bdb856cfc..9ba92b7ab 100644 --- a/packages/webview-app/src/screens/proving/VerificationResultScreen.tsx +++ b/packages/webview-app/src/screens/proving/VerificationResultScreen.tsx @@ -9,7 +9,7 @@ import { CheckCircleIcon, WarningOctagonIcon, colors, -} from '@selfxyz/euclid-web'; +} from '@selfxyz/euclid'; import type { VerificationResult } from '@selfxyz/webview-bridge'; import { useSelfClient } from '../../providers/SelfClientProvider'; diff --git a/packages/webview-app/src/screens/tunnel/KycMockScreen.tsx b/packages/webview-app/src/screens/tunnel/KycMockScreen.tsx new file mode 100644 index 000000000..757ec92f2 --- /dev/null +++ b/packages/webview-app/src/screens/tunnel/KycMockScreen.tsx @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { Button } from '@selfxyz/euclid'; + +export const KycMockScreen: React.FC = () => { + const navigate = useNavigate(); + + const onContinue = useCallback(() => { + navigate('/tunnel/registration/country'); + }, [navigate]); + + return ( +
+

KYC mock

+
+ ); +}; diff --git a/packages/webview-app/src/screens/tunnel/TourScreen.tsx b/packages/webview-app/src/screens/tunnel/TourScreen.tsx new file mode 100644 index 000000000..4d5c8fad2 --- /dev/null +++ b/packages/webview-app/src/screens/tunnel/TourScreen.tsx @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { useNavigate, useParams, Navigate } from 'react-router-dom'; +import { + LaunchTour1Screen, + LaunchTour2Screen, + LaunchTour3Screen, + LaunchTour4Screen, +} from '@selfxyz/euclid'; + +const insets = { top: 0, bottom: 0 }; + +export const TourScreen: React.FC = () => { + const navigate = useNavigate(); + const { step } = useParams<{ step: string }>(); + const stepNum = parseInt(step ?? '1', 10); + + const onNext = useCallback(() => { + navigate(stepNum < 4 ? `/tunnel/tour/${stepNum + 1}` : '/tunnel/kyc'); + }, [navigate, stepNum]); + + switch (step) { + case '1': + return ; + case '2': + return ; + case '3': + return ; + case '4': + return ; + default: + return ; + } +}; diff --git a/packages/webview-app/src/screens/tunnel/TunnelCountryPickerScreen.tsx b/packages/webview-app/src/screens/tunnel/TunnelCountryPickerScreen.tsx new file mode 100644 index 000000000..3f9c77b80 --- /dev/null +++ b/packages/webview-app/src/screens/tunnel/TunnelCountryPickerScreen.tsx @@ -0,0 +1,53 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback, useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { CountryPickerScreen as EuclidCountryPickerScreen } from '@selfxyz/euclid'; + +import { getCountryName, renderFlag } from '../../utils/countryFlags'; + +const MOCK_COUNTRIES = [ + { countryCode: 'US' }, + { countryCode: 'GB' }, + { countryCode: 'DE' }, + { countryCode: 'PL' }, + { countryCode: 'FR' }, +]; + +const MOCK_DOCUMENT_TYPES: Record = { + US: ['p', 'i'], + GB: ['p'], + DE: ['p', 'i'], + PL: ['p', 'i'], + FR: ['p', 'i'], +}; + +export const TunnelCountryPickerScreen: React.FC = () => { + const navigate = useNavigate(); + const [search, setSearch] = useState(''); + + const onCountrySelect = useCallback( + (countryCode: string) => { + navigate('/tunnel/registration/id-type', { + state: { countryCode, documentTypes: MOCK_DOCUMENT_TYPES[countryCode] ?? ['p'] }, + }); + }, + [navigate], + ); + + return ( + navigate('/tunnel/kyc')} + renderFlag={renderFlag} + getCountryName={getCountryName} + searchValue={search} + onSearchChange={setSearch} + /> + ); +}; diff --git a/packages/webview-app/src/screens/tunnel/TunnelIDTypeScreen.tsx b/packages/webview-app/src/screens/tunnel/TunnelIDTypeScreen.tsx new file mode 100644 index 000000000..93fe20ef5 --- /dev/null +++ b/packages/webview-app/src/screens/tunnel/TunnelIDTypeScreen.tsx @@ -0,0 +1,56 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { useLocation, useNavigate } from 'react-router-dom'; +import { IDTypeScreen } from '@selfxyz/euclid'; +import type { IDType } from '@selfxyz/euclid'; + +import { getCountryName, renderFlag } from '../../utils/countryFlags'; + +const docTypeToIDType = (docType: string): IDType => { + switch (docType) { + case 'p': + return { id: 'p', title: 'Passport', subtitle: 'Verified Biometric Passport' }; + case 'i': + return { id: 'i', title: 'ID Card', subtitle: 'Verified Biometric ID card' }; + default: + return { id: docType, title: 'Unknown Document', subtitle: '' }; + } +}; + +const renderIDTypeIcon = (idType: IDType): React.ReactNode => { + const emoji = idType.id === 'p' ? '🛂' : '🪪'; + return {emoji}; +}; + +export const TunnelIDTypeScreen: React.FC = () => { + const navigate = useNavigate(); + const location = useLocation(); + + const { countryCode = 'US', documentTypes = ['p'] } = + (location.state as { countryCode?: string; documentTypes?: string[] }) || {}; + + const idTypes = documentTypes.map(docTypeToIDType); + + const onIDTypeSelect = useCallback( + (_idType: IDType) => { + navigate('/tunnel/proof/receipt'); + }, + [navigate], + ); + + return ( + navigate(-1)} + renderFlag={renderFlag} + renderIDTypeIcon={renderIDTypeIcon} + /> + ); +}; diff --git a/packages/webview-app/src/screens/tunnel/TunnelProofReceiptScreen.tsx b/packages/webview-app/src/screens/tunnel/TunnelProofReceiptScreen.tsx new file mode 100644 index 000000000..15f58573f --- /dev/null +++ b/packages/webview-app/src/screens/tunnel/TunnelProofReceiptScreen.tsx @@ -0,0 +1,41 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ProofRequestScreen, SelfLogo } from '@selfxyz/euclid'; + +const MOCK_ITEMS = [ + { label: 'Full Name' }, + { label: 'Date of Birth' }, + { label: 'Nationality' }, + { label: 'Age above 18' }, +]; + +export const TunnelProofReceiptScreen: React.FC = () => { + const navigate = useNavigate(); + + const onConfirm = useCallback(() => { + navigate('/tunnel/proof/generating'); + }, [navigate]); + + const onClose = useCallback(() => { + navigate(-1); + }, [navigate]); + + return ( + } + appName="KYC" + appEndpoint="example.com" + documentType="passport" + timestamp={Date.now()} + items={MOCK_ITEMS} + /> + ); +}; diff --git a/packages/webview-app/src/screens/tunnel/TunnelProvingScreen.tsx b/packages/webview-app/src/screens/tunnel/TunnelProvingScreen.tsx new file mode 100644 index 000000000..93f52a879 --- /dev/null +++ b/packages/webview-app/src/screens/tunnel/TunnelProvingScreen.tsx @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { ProofGenerationScreen } from '@selfxyz/euclid'; + +const MOCK_ID_CARD = { + variant: 'passport' as const, + title: 'Passport', + subtitle: 'Mock Passport', +}; + +export const TunnelProvingScreen: React.FC = () => { + const navigate = useNavigate(); + + useEffect(() => { + const timer = setTimeout(() => { + navigate('/tunnel/proof/result'); + }, 3000); + return () => clearTimeout(timer); + }, [navigate]); + + return ( + + ); +}; diff --git a/packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx b/packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx new file mode 100644 index 000000000..f49c2fb09 --- /dev/null +++ b/packages/webview-app/src/screens/tunnel/TunnelResultScreen.tsx @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2025-2026 Social Connect Labs, Inc. +// SPDX-License-Identifier: BUSL-1.1 +// NOTE: Converts to Apache-2.0 on 2029-06-11 per LICENSE. + +import React, { useCallback } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { StatusState, CheckCircleIcon, colors } from '@selfxyz/euclid'; + +export const TunnelResultScreen: React.FC = () => { + const navigate = useNavigate(); + + const onContinue = useCallback(() => { + navigate('/'); + }, [navigate]); + + return ( + } + /> + ); +}; diff --git a/specs/projects/sdk/INDEX.md b/specs/projects/sdk/INDEX.md index 21c794660..f133e4ac9 100644 --- a/specs/projects/sdk/INDEX.md +++ b/specs/projects/sdk/INDEX.md @@ -12,12 +12,12 @@ Status: Active (WebView-first + native keychain/crypto) ## Active Workstreams -| Workstream | Spec | Focus | -| ---------- | ---- | ----- | -| WebView UI | [WebView Spec](./workstreams/webview/SPEC.md) | Sumsub Web SDK integration, KYC result flow | -| SDK Core | [SDK Core Spec](./workstreams/sdk-core/SPEC.md) | Browser-portable engine | +| Workstream | Spec | Focus | +| -------------------- | ------------------------------------------------------------------- | --------------------------------------------- | +| WebView UI | [WebView Spec](./workstreams/webview/SPEC.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 | +| Build Pipeline | [Build Pipeline Spec](./workstreams/build-pipeline/SPEC.md) | Bundle webview-app into native shells | ## Paused Workstreams diff --git a/specs/projects/sdk/OVERVIEW.md b/specs/projects/sdk/OVERVIEW.md index 0c894ddd2..f5962210e 100644 --- a/specs/projects/sdk/OVERVIEW.md +++ b/specs/projects/sdk/OVERVIEW.md @@ -110,19 +110,19 @@ On **March 20, 2026**, the active SDK delivery target was refined: ## Module Table -| Module | Location | Status | Current Role | Action Needed | -| -------------------- | ----------------------------------------------------------------- | --------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | -| WebView UI | `packages/webview-app/` | Active | Primary product surface, KYC provider handoff, verification UX | Integrate KYC provider Web SDK (WV-05), wire result flow (WV-06) | -| SDK Core | `packages/mobile-sdk-alpha/` | Active | Shared engine for WebView/browser delivery | Keep browser entry clean and request-driven | -| WebView Bridge | `packages/webview-bridge/` | Active | Bridge protocol v1 between WebView and native shells | Stable — no changes needed | -| Android Shell | `packages/native-shell-android/` | New | Thin Kotlin shell: keychain/crypto + WebView host | Build (NSL-01) | -| iOS Shell | `packages/native-shell-ios/` | New | Thin Swift shell: keychain/crypto + WebView host | Build (NSL-02) | -| Test App | `packages/sdk-test-app/` | New | Minimal native apps for end-to-end testing | Adapt from kmp-sdk-test-app (NSL-03) | -| KMP Native Shell | `packages/kmp-sdk/` | Deprecated | Reference for native shell porting, replaced by native-shell-android/ios | Do not advance; use as port reference only | -| Swift Providers | `packages/self-sdk-swift/` | Deprecated | Reference for iOS keychain/crypto porting, replaced by native-shell-ios | Do not advance; use as port reference only | -| RN SDK | `packages/rn-sdk/` | Paused | Retained React Native shell work | Do not advance unless scope reopens | -| Native Consolidation | `app/ios/`, `packages/mobile-sdk-alpha/ios/`, related native code | Paused | Historical native cleanup and parity track | Keep as reference only for now | -| MiniPay Sample | `packages/kmp-minipay-sample/` | Paused | Historical KMP integration example | Resume only if KMP path returns | +| Module | Location | Status | Current Role | Action Needed | +| -------------------- | ----------------------------------------------------------------- | ---------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------- | +| WebView UI | `packages/webview-app/` | Active | Primary product surface, KYC provider handoff, verification UX | Integrate KYC provider Web SDK (WV-05), wire result flow (WV-06) | +| SDK Core | `packages/mobile-sdk-alpha/` | Active | Shared engine for WebView/browser delivery | Keep browser entry clean and request-driven | +| WebView Bridge | `packages/webview-bridge/` | Active | Bridge protocol v1 between WebView and native shells | Stable — no changes needed | +| Android Shell | `packages/native-shell-android/` | New | Thin Kotlin shell: keychain/crypto + WebView host | Build (NSL-01) | +| iOS Shell | `packages/native-shell-ios/` | New | Thin Swift shell: keychain/crypto + WebView host | Build (NSL-02) | +| Test App | `packages/sdk-test-app/` | New | Minimal native apps for end-to-end testing | Adapt from kmp-sdk-test-app (NSL-03) | +| KMP Native Shell | `packages/kmp-sdk/` | Deprecated | Reference for native shell porting, replaced by native-shell-android/ios | Do not advance; use as port reference only | +| Swift Providers | `packages/self-sdk-swift/` | Deprecated | Reference for iOS keychain/crypto porting, replaced by native-shell-ios | Do not advance; use as port reference only | +| RN SDK | `packages/rn-sdk/` | Paused | Retained React Native shell work | Do not advance unless scope reopens | +| Native Consolidation | `app/ios/`, `packages/mobile-sdk-alpha/ios/`, related native code | Paused | Historical native cleanup and parity track | Keep as reference only for now | +| MiniPay Sample | `packages/kmp-minipay-sample/` | Paused | Historical KMP integration example | Resume only if KMP path returns | ## Scope Rules diff --git a/specs/projects/sdk/workstreams/build-pipeline/SPEC.md b/specs/projects/sdk/workstreams/build-pipeline/SPEC.md index 3e96ad502..358f79ce1 100644 --- a/specs/projects/sdk/workstreams/build-pipeline/SPEC.md +++ b/specs/projects/sdk/workstreams/build-pipeline/SPEC.md @@ -32,18 +32,18 @@ ## Dependencies -| Depends On | Type | Status | Notes | -|------------|------|--------|-------| -| `packages/webview-app/` | Upstream | Active | Source of the WebView bundle | -| `packages/native-shell-android/` | Downstream | Ready | Receives assets at `src/main/assets/self-wallet/` | -| `packages/native-shell-ios/` | Downstream | Ready | Receives assets at `Resources/self-sdk-web/` | +| Depends On | Type | Status | Notes | +| -------------------------------- | ---------- | ------ | ------------------------------------------------- | +| `packages/webview-app/` | Upstream | Active | Source of the WebView bundle | +| `packages/native-shell-android/` | Downstream | Ready | Receives assets at `src/main/assets/self-wallet/` | +| `packages/native-shell-ios/` | Downstream | Ready | Receives assets at `Resources/self-sdk-web/` | ## Backlog -| ID | Title | Status | Priority | Depends On | Plan | PR | -|----|-------|--------|----------|------------|------|----| -| BP-01 | WebView bundle build + copy script | Done | Medium | NSL-01, NSL-02 | [plans/BP-01-build-script.md](./plans/BP-01-build-script.md) | Complete on `feat/webview-sdk` | -| BP-02 | Runtime bundle integrity for CDN loading | Deferred | High | — | — | — | +| ID | Title | Status | Priority | Depends On | Plan | PR | +| ----- | ---------------------------------------- | -------- | -------- | -------------- | ------------------------------------------------------------ | ------------------------------ | +| BP-01 | WebView bundle build + copy script | Done | Medium | NSL-01, NSL-02 | [plans/BP-01-build-script.md](./plans/BP-01-build-script.md) | Complete on `feat/webview-sdk` | +| BP-02 | Runtime bundle integrity for CDN loading | Deferred | High | — | — | — | Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` @@ -61,9 +61,9 @@ Trigger: when remote/CDN bundle loading is implemented. ## Active Plans -| Plan | IDs | Status | -|------|-----|--------| -| [plans/BP-01-build-script.md](./plans/BP-01-build-script.md) | BP-01 | Done | +| Plan | IDs | Status | +| ------------------------------------------------------------ | ----- | ------ | +| [plans/BP-01-build-script.md](./plans/BP-01-build-script.md) | BP-01 | Done | ## Completion Checklist @@ -73,8 +73,8 @@ Trigger: when remote/CDN bundle loading is implemented. ## Related Specs -| Spec | Relationship | -|------|-------------| -| [SDK Overview](../../OVERVIEW.md) | Parent architecture | -| [WebView Spec](../webview/SPEC.md) | Upstream — produces the bundle | +| Spec | Relationship | +| --------------------------------------------------- | -------------------------------- | +| [SDK Overview](../../OVERVIEW.md) | Parent architecture | +| [WebView Spec](../webview/SPEC.md) | Upstream — produces the bundle | | [Native Shells Lite](../native-shells-lite/SPEC.md) | Downstream — consumes the bundle | diff --git a/specs/projects/sdk/workstreams/native-shells-lite/SPEC.md b/specs/projects/sdk/workstreams/native-shells-lite/SPEC.md index 607cb6174..640a04388 100644 --- a/specs/projects/sdk/workstreams/native-shells-lite/SPEC.md +++ b/specs/projects/sdk/workstreams/native-shells-lite/SPEC.md @@ -37,39 +37,39 @@ ## Dependencies -| Depends On | Type | Status | Notes | -|------------|------|--------|-------| -| `packages/webview-bridge/` | Upstream (bridge protocol) | Done | Defines message shapes and transport names | -| `packages/webview-app/` | Upstream (WebView bundle) | Active | Native shells load this bundle | -| Build pipeline | Downstream | Ready | Copies webview-app dist into native assets | +| Depends On | Type | Status | Notes | +| -------------------------- | -------------------------- | ------ | ------------------------------------------ | +| `packages/webview-bridge/` | Upstream (bridge protocol) | Done | Defines message shapes and transport names | +| `packages/webview-app/` | Upstream (WebView bundle) | Active | Native shells load this bundle | +| Build pipeline | Downstream | Ready | Copies webview-app dist into native assets | ## Ownership Boundaries -| Area | Owner | Notes | -|------|-------|-------| -| `packages/native-shell-android/` | Native Shells (Lite) | New package | -| `packages/native-shell-ios/` | Native Shells (Lite) | New package | -| `packages/sdk-test-app/` | Native Shells (Lite) | Adapted from kmp-sdk-test-app | -| `packages/kmp-sdk/` | Paused | Reference only, do not modify | -| `packages/self-sdk-swift/` | Paused | Reference only, do not modify | +| Area | Owner | Notes | +| -------------------------------- | -------------------- | ----------------------------- | +| `packages/native-shell-android/` | Native Shells (Lite) | New package | +| `packages/native-shell-ios/` | Native Shells (Lite) | New package | +| `packages/sdk-test-app/` | Native Shells (Lite) | Adapted from kmp-sdk-test-app | +| `packages/kmp-sdk/` | Paused | Reference only, do not modify | +| `packages/self-sdk-swift/` | Paused | Reference only, do not modify | ## Backlog -| ID | Title | Status | Priority | Depends On | Plan | PR | -|----|-------|--------|----------|------------|------|----| -| NSL-01 | Android native shell (plain Kotlin) | In Progress | High | - | [plans/NSL-01-android-shell.md](./plans/NSL-01-android-shell.md) | Code complete on `feat/webview-sdk`, needs testing | -| NSL-02 | iOS native shell (plain Swift) | In Progress | High | - | [plans/NSL-02-ios-shell.md](./plans/NSL-02-ios-shell.md) | Code complete on `feat/webview-sdk`, needs testing | -| NSL-03 | Test apps (adapt from kmp-sdk-test-app) | In Progress | Medium | NSL-01, NSL-02 | [plans/NSL-03-test-apps.md](./plans/NSL-03-test-apps.md) | Code complete on `feat/webview-sdk`, needs build verification | +| ID | Title | Status | Priority | Depends On | Plan | PR | +| ------ | --------------------------------------- | ----------- | -------- | -------------- | ---------------------------------------------------------------- | ------------------------------------------------------------- | +| NSL-01 | Android native shell (plain Kotlin) | In Progress | High | - | [plans/NSL-01-android-shell.md](./plans/NSL-01-android-shell.md) | Code complete on `feat/webview-sdk`, needs testing | +| NSL-02 | iOS native shell (plain Swift) | In Progress | High | - | [plans/NSL-02-ios-shell.md](./plans/NSL-02-ios-shell.md) | Code complete on `feat/webview-sdk`, needs testing | +| NSL-03 | Test apps (adapt from kmp-sdk-test-app) | In Progress | Medium | NSL-01, NSL-02 | [plans/NSL-03-test-apps.md](./plans/NSL-03-test-apps.md) | Code complete on `feat/webview-sdk`, needs build verification | Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` ## Active Plans -| Plan | IDs | Status | -|------|-----|--------| -| [plans/NSL-01-android-shell.md](./plans/NSL-01-android-shell.md) | NSL-01 | In Progress (code complete, needs testing) | -| [plans/NSL-02-ios-shell.md](./plans/NSL-02-ios-shell.md) | NSL-02 | In Progress (code complete, needs testing) | -| [plans/NSL-03-test-apps.md](./plans/NSL-03-test-apps.md) | NSL-03 | In Progress (code complete, needs build verification) | +| Plan | IDs | Status | +| ---------------------------------------------------------------- | ------ | ----------------------------------------------------- | +| [plans/NSL-01-android-shell.md](./plans/NSL-01-android-shell.md) | NSL-01 | In Progress (code complete, needs testing) | +| [plans/NSL-02-ios-shell.md](./plans/NSL-02-ios-shell.md) | NSL-02 | In Progress (code complete, needs testing) | +| [plans/NSL-03-test-apps.md](./plans/NSL-03-test-apps.md) | NSL-03 | In Progress (code complete, needs build verification) | ## Completion Checklist @@ -82,19 +82,19 @@ Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` These existing files define the contract and patterns to port: -| What | Source | Notes | -|------|--------|-------| -| Bridge protocol (message shapes) | `packages/webview-bridge/src/types.ts` | Canonical — native must match | -| Bridge transport detection | `packages/webview-bridge/src/bridge.ts` | Android: `SelfNativeAndroid`, iOS: `SelfNativeIOS` | -| Crypto adapter expectations | `packages/webview-bridge/src/adapters/crypto.ts` | Defines request params and response shapes | -| Storage adapter expectations | `packages/webview-bridge/src/adapters/storage.ts` | Defines request params and response shapes | -| KMP MessageRouter | `packages/kmp-sdk/.../commonMain/.../MessageRouter.kt` | Port routing logic | -| Android SecureStorage | `packages/kmp-sdk/.../androidMain/.../SecureStorageBridgeHandler.kt` | Port EncryptedSharedPreferences pattern | -| Android WebView host | `packages/kmp-sdk/.../androidMain/.../AndroidWebViewHost.kt` | Port WebViewAssetLoader + JS interface | -| Android Activity | `packages/kmp-sdk/.../androidMain/.../SelfVerificationActivity.kt` | Port entry point, simplify permissions | -| iOS CryptoProvider | `packages/self-sdk-swift/.../CryptoProviderImpl.swift` | Port EC P-256 signing | -| iOS SecureStorage | `packages/self-sdk-swift/.../SecureStorageProviderImpl.swift` | Port Keychain Services | -| iOS WebView host | `packages/self-sdk-swift/.../WebViewProviderImpl.swift` | Port WKWebView + script message handler | +| What | Source | Notes | +| -------------------------------- | -------------------------------------------------------------------- | -------------------------------------------------- | +| Bridge protocol (message shapes) | `packages/webview-bridge/src/types.ts` | Canonical — native must match | +| Bridge transport detection | `packages/webview-bridge/src/bridge.ts` | Android: `SelfNativeAndroid`, iOS: `SelfNativeIOS` | +| Crypto adapter expectations | `packages/webview-bridge/src/adapters/crypto.ts` | Defines request params and response shapes | +| Storage adapter expectations | `packages/webview-bridge/src/adapters/storage.ts` | Defines request params and response shapes | +| KMP MessageRouter | `packages/kmp-sdk/.../commonMain/.../MessageRouter.kt` | Port routing logic | +| Android SecureStorage | `packages/kmp-sdk/.../androidMain/.../SecureStorageBridgeHandler.kt` | Port EncryptedSharedPreferences pattern | +| Android WebView host | `packages/kmp-sdk/.../androidMain/.../AndroidWebViewHost.kt` | Port WebViewAssetLoader + JS interface | +| Android Activity | `packages/kmp-sdk/.../androidMain/.../SelfVerificationActivity.kt` | Port entry point, simplify permissions | +| iOS CryptoProvider | `packages/self-sdk-swift/.../CryptoProviderImpl.swift` | Port EC P-256 signing | +| iOS SecureStorage | `packages/self-sdk-swift/.../SecureStorageProviderImpl.swift` | Port Keychain Services | +| iOS WebView host | `packages/self-sdk-swift/.../WebViewProviderImpl.swift` | Port WKWebView + script message handler | ## Bridge Domain Contract @@ -102,27 +102,27 @@ Only 3 domains are implemented by the native shells: ### `secureStorage` -| Method | Params | Response | -|--------|--------|----------| -| `get` | `{ key: string }` | `{ value: string \| null }` | -| `set` | `{ key: string, value: string }` | `null` | -| `remove` | `{ key: string }` | `null` | +| Method | Params | Response | +| -------- | -------------------------------- | --------------------------- | +| `get` | `{ key: string }` | `{ value: string \| null }` | +| `set` | `{ key: string, value: string }` | `null` | +| `remove` | `{ key: string }` | `null` | ### `crypto` -| Method | Params | Response | -|--------|--------|----------| -| `generateKey` | `{ keyRef: string }` | `{ keyRef: string, success: true }` | -| `getPublicKey` | `{ keyRef: string }` | `{ publicKey: string }` (base64) | -| `sign` | `{ data: string, keyRef: string }` (data is base64) | `{ signature: string }` (base64) | +| Method | Params | Response | +| -------------- | --------------------------------------------------- | ----------------------------------- | +| `generateKey` | `{ keyRef: string }` | `{ keyRef: string, success: true }` | +| `getPublicKey` | `{ keyRef: string }` | `{ publicKey: string }` (base64) | +| `sign` | `{ data: string, keyRef: string }` (data is base64) | `{ signature: string }` (base64) | ### `lifecycle` -| Method | Params | Response | -|--------|--------|----------| -| `ready` | `{}` | `null` (no-op) | -| `dismiss` | `{ reason?: string }` | `null` (finishes Activity / dismisses VC) | -| `setResult` | `{ success: bool, userId?, verificationId?, error? }` | `null` (forwards to host, then finishes) | +| Method | Params | Response | +| ----------- | ----------------------------------------------------- | ----------------------------------------- | +| `ready` | `{}` | `null` (no-op) | +| `dismiss` | `{ reason?: string }` | `null` (finishes Activity / dismisses VC) | +| `setResult` | `{ success: bool, userId?, verificationId?, error? }` | `null` (forwards to host, then finishes) | Any other domain request returns a `DOMAIN_NOT_FOUND` error response. @@ -144,6 +144,7 @@ When the WebView calls `lifecycle.setResult()`, the native shell forwards the re ### Android → Host `LifecycleHandler` calls `Activity.setResult()` with: + - `RESULT_OK` when `success: true` - `RESULT_CANCELED` when user dismisses - `RESULT_FIRST_USER` on error @@ -153,6 +154,7 @@ Result JSON is included as Intent extra (`xyz.self.sdk.RESULT_DATA`). Host reads ### iOS → Host `LifecycleHandler` invokes the `SelfSdkCallback` protocol: + - `onSuccess(result:)` when `success: true` - `onCancelled()` when user dismisses - `onFailure(error:)` on error @@ -165,10 +167,10 @@ The host never sees raw KYC provider output. The WebView normalizes provider res ## Related Specs -| Spec | Relationship | -|------|-------------| -| [SDK Overview](../../OVERVIEW.md) | Parent architecture | -| [WebView Spec](../webview/SPEC.md) | Sibling — owns Sumsub integration and WebView UX | -| [SDK Core Spec](../sdk-core/SPEC.md) | Sibling — owns mobile-sdk-alpha engine | -| [Build Pipeline Spec](../build-pipeline/SPEC.md) | Downstream — bundles webview-app into native shells | -| [Paused Native Shells](../../paused/native-shells/SPEC.md) | Predecessor — KMP-based, now deprecated | +| Spec | Relationship | +| ---------------------------------------------------------- | --------------------------------------------------- | +| [SDK Overview](../../OVERVIEW.md) | Parent architecture | +| [WebView Spec](../webview/SPEC.md) | Sibling — owns Sumsub integration and WebView UX | +| [SDK Core Spec](../sdk-core/SPEC.md) | Sibling — owns mobile-sdk-alpha engine | +| [Build Pipeline Spec](../build-pipeline/SPEC.md) | Downstream — bundles webview-app into native shells | +| [Paused Native Shells](../../paused/native-shells/SPEC.md) | Predecessor — KMP-based, now deprecated | diff --git a/specs/projects/sdk/workstreams/webview/SPEC.md b/specs/projects/sdk/workstreams/webview/SPEC.md index 06bd41220..213f82a8a 100644 --- a/specs/projects/sdk/workstreams/webview/SPEC.md +++ b/specs/projects/sdk/workstreams/webview/SPEC.md @@ -44,27 +44,27 @@ 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() | +| 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() | Allowed statuses: `Ready`, `In Progress`, `Blocked`, `Deferred`, `Done` ## Active Plans -| Plan | IDs | Status | -| ------------------------------------------------------------------------------------------------ | ----- | ------ | -| [plans/WV-01-dynamic-proof-request-items.md](./plans/WV-01-dynamic-proof-request-items.md) | WV-01 | Done | -| [plans/WV-02-kyc-provider-contract.md](./plans/WV-02-kyc-provider-contract.md) | WV-02 | Done | -| [plans/WV-03-remove-native-scan-assumptions.md](./plans/WV-03-remove-native-scan-assumptions.md) | WV-03 | Done | -| [plans/WV-04-host-callback-contract.md](./plans/WV-04-host-callback-contract.md) | WV-04 | Done | +| Plan | IDs | Status | +| ------------------------------------------------------------------------------------------------ | ----- | ------------------------------------------ | +| [plans/WV-01-dynamic-proof-request-items.md](./plans/WV-01-dynamic-proof-request-items.md) | WV-01 | Done | +| [plans/WV-02-kyc-provider-contract.md](./plans/WV-02-kyc-provider-contract.md) | WV-02 | Done | +| [plans/WV-03-remove-native-scan-assumptions.md](./plans/WV-03-remove-native-scan-assumptions.md) | WV-03 | Done | +| [plans/WV-04-host-callback-contract.md](./plans/WV-04-host-callback-contract.md) | WV-04 | Done | | [plans/WV-05-sumsub-web-sdk.md](./plans/WV-05-sumsub-web-sdk.md) | WV-05 | In Progress (code complete, needs testing) | -| [plans/WV-06-kyc-result-flow.md](./plans/WV-06-kyc-result-flow.md) | WV-06 | Ready | +| [plans/WV-06-kyc-result-flow.md](./plans/WV-06-kyc-result-flow.md) | WV-06 | Ready | ## Completion Checklist diff --git a/yarn.lock b/yarn.lock index 1af651674..36d392ad9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7033,6 +7033,24 @@ __metadata: languageName: node linkType: hard +"@lottiefiles/dotlottie-react@npm:^0.18.4": + version: 0.18.7 + resolution: "@lottiefiles/dotlottie-react@npm:0.18.7" + dependencies: + "@lottiefiles/dotlottie-web": "npm:0.67.0" + peerDependencies: + react: ^17 || ^18 || ^19 + checksum: 10c0/9c940db91112ef9de681c6cf8e7238d852f7f2f772b9fb2eacc214b561b22d77db965ce8dd1d3823f8a0cf69d36be9459a4b0863301ba8725aed7cdc59fd2104 + languageName: node + linkType: hard + +"@lottiefiles/dotlottie-web@npm:0.67.0": + version: 0.67.0 + resolution: "@lottiefiles/dotlottie-web@npm:0.67.0" + checksum: 10c0/462a882b3cba7c68e575c2e1c3479afaae1cd39b16a74111b886d02a86053c1738e845d7379e31d8cc75bb66dd63c01a58b4160caa287aa5fc9a4a54dd6b72e1 + languageName: node + linkType: hard + "@mapbox/node-pre-gyp@npm:^1.0": version: 1.0.11 resolution: "@mapbox/node-pre-gyp@npm:1.0.11" @@ -10638,22 +10656,12 @@ __metadata: languageName: unknown linkType: soft -"@selfxyz/euclid-core@npm:^1.0.0, @selfxyz/euclid-core@npm:^1.0.1": - version: 1.0.1 - resolution: "@selfxyz/euclid-core@npm:1.0.1" - checksum: 10c0/13b704c3fe1c8be99cd39a22a8ab22925e26c97b43f41a04e59ff313068c6806ead84f3417b6945253acaa85e2134815b8008320e9fcc4dcb15e640878a3b40e - languageName: node - linkType: hard - -"@selfxyz/euclid-web@npm:^1.0.2": - version: 1.0.2 - resolution: "@selfxyz/euclid-web@npm:1.0.2" - dependencies: - "@selfxyz/euclid-core": "npm:^1.0.0" +"@selfxyz/euclid-core@npm:^1.2.0": + version: 1.2.0 + resolution: "@selfxyz/euclid-core@npm:1.2.0" peerDependencies: react: ">=18.2.0" - react-dom: ">=18.2.0" - checksum: 10c0/3fb4afe120e69bba9545ad358be1b5405c65030df35fe0c53a75adc96fe2515e79570aefe57b38e328ee653e55c20a8c09094071038e06f071ef7ee5387cfad4 + checksum: 10c0/198bcbd21e674a67ac801eb2b5f6a56cb08ae9fd09904deda78f07999540d3a7ccbfffa56599ab4ced2d8204732d2d7576d63eb14983a076223b546cef31e4b0 languageName: node linkType: hard @@ -10670,6 +10678,20 @@ __metadata: languageName: node linkType: hard +"@selfxyz/euclid@npm:^1.2.0": + version: 1.2.0 + resolution: "@selfxyz/euclid@npm:1.2.0" + dependencies: + "@lottiefiles/dotlottie-react": "npm:^0.18.4" + "@selfxyz/euclid-core": "npm:^1.2.0" + lottie-react: "npm:^2.4.1" + peerDependencies: + react: ">=18.2.0" + react-dom: ">=18.2.0" + checksum: 10c0/73e50bcf8540c0f2d91e3b232016c90f1019587acf24a79d9fd0e1d07cfb977aae5336972769bc824eade8ea02e02b3e583906cffa11853b389a7e2b20a34e58 + languageName: node + linkType: hard + "@selfxyz/kmp-sdk-test-app@workspace:packages/kmp-sdk-test-app": version: 0.0.0-use.local resolution: "@selfxyz/kmp-sdk-test-app@workspace:packages/kmp-sdk-test-app" @@ -11110,8 +11132,8 @@ __metadata: version: 0.0.0-use.local resolution: "@selfxyz/webview-app@workspace:packages/webview-app" dependencies: - "@selfxyz/euclid-core": "npm:^1.0.1" - "@selfxyz/euclid-web": "npm:^1.0.2" + "@selfxyz/euclid": "npm:^1.2.0" + "@selfxyz/euclid-core": "npm:^1.2.0" "@selfxyz/mobile-sdk-alpha": "workspace:^" "@selfxyz/webview-bridge": "workspace:^" "@sumsub/websdk": "npm:^2.0.0"