Skip to content

fix: ui/ux imrovements#573

Closed
coodos wants to merge 21 commits intomainfrom
fix/eid-wallet-ui-ux-improvements
Closed

fix: ui/ux imrovements#573
coodos wants to merge 21 commits intomainfrom
fix/eid-wallet-ui-ux-improvements

Conversation

@coodos
Copy link
Contributor

@coodos coodos commented Dec 9, 2025

Description of change

Issue Number

Type of change

  • Fix (a change which fixes an issue)

How the change has been tested

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Toast notification system (success/error/info)
    • New verification pages and stores for passport/selfie flows
    • Hidden multi-tap version action in Settings
  • Bug Fixes

    • Improved navigation for scan/decline/cancel flows
    • Better browser back-button handling and auth-based redirects
    • Added error handling to profile creation
  • UI/UX

    • Identity card: replaced share flow with a copy-to-clipboard button for eName
    • Drawer now hides on close for reuse; removed QR-sharing UI
  • Chores

    • App version bumped to 0.5.0

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 9, 2025

Warning

Rate limit exceeded

@coodos has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 22 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 3c9482a and 09aa717.

📒 Files selected for processing (1)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte (3 hunks)

Walkthrough

Adds a toast notification subsystem; replaces IdentityCard eName share flow with a copy-to-clipboard button; updates Drawer lifecycle/styling (hide-on-close, pane CSS); removes QR-sharing UI from main; modifies navigation/back behaviors across several routes; adds verify pages, stores, and enhanced verify/selfie/passport flows; bumps package/tauri versions and onboarding name validation.

Changes

Cohort / File(s) Summary
Toast subsystem
infrastructure/eid-wallet/src/lib/ui/Toast/Toast.svelte, infrastructure/eid-wallet/src/lib/ui/Toast/toast.ts
New toast store (toastStore) and helper (toast) plus Toast.svelte component. Toasts support success/error/info types, auto-dismiss (default 3000ms), add/remove/clear API, and UI rendering with animations and ARIA roles.
IdentityCard copy action
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
Replaced eName shareBtn flow with a Copy01Icon button that copies userId to clipboard and triggers toast success/error. Removed shareBtn from IIdentityCard and adjusted rendering/async copy handling.
Drawer lifecycle & styling
infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte
Removed fitHeight usage, changed close behavior to hide pane instead of destroy, guarded operations on element presence, and added global pane CSS to position/size/overflow the pane. Pane instance preserved for reuse.
Main page: QR sharing removed
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Removed Drawer-based QR sharing UI, QrCode rendering, related imports and share/view props on IdentityCard. Added try/catch around retryProfileCreation.
Scan-QR navigation simplification
infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte
Simplified decline handlers to always close corresponding drawers and navigate to /main; converted handleRevealDrawerCancel to async and use goto("/main").
Onboarding name validation
infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte
Trim/validate demo name input (isValidName), show inline error, use trimmed value on final submit; input wiring and button enablement updated.
Settings hidden/version and navigation
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Reworked imports and setup, added handleVersionTap tap-sequence logic, made cancelDelete navigate to /main, and adjusted lifecycle hooks and header title effect.
Verify flow: context, stores, pages, and steps
infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte, .../verify/steps/document-type.svelte, .../verify/steps/passport.svelte, .../verify/steps/selfie.svelte, .../verify/store.ts, .../verify/passport/+page.svelte, .../verify/selfie/+page.svelte
Exposes showVeriffModal and verifyData via setContext; adds three verification stores (verificationPerson, verificationDocument, verificationWebsocketData); introduces new verify pages and navigation-backed UI (back buttons, modal-aware goBack), and augments selfie step with registry/provisioning branches, SSE-driven states, camera lifecycle, and global state interactions.
App layout: auth-guard & toast mount
infrastructure/eid-wallet/src/routes/+layout.svelte
Mounts Toast component; makes navigation handler async and introduces auth-guard redirects (prevent navigation to login/register/onboarding when vault exists); adds popstate listener to intercept back navigation to protected auth routes.
Root/bootstrap changes
infrastructure/eid-wallet/src/routes/+page.svelte
Removed immediate clearPin invocation during mount; defined clearPin with error handling but not auto-invoked; onboarding status retrieval wrapped in try/catch and reorganized.
Version bumps & config
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src-tauri/tauri.conf.json
Bumped package and tauri top-level version from 0.4.0 → 0.5.0 and updated Android bundle versionCode; minor formatting changes in tauri config.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Component
    participant ToastStore as toastStore
    participant ToastComp as Toast.svelte
    participant Timer as Auto-remove Timer

    UI->>ToastStore: toast.success("message", 3000)
    ToastStore->>ToastStore: add toast {id, type, message, duration}
    ToastStore->>ToastComp: notify subscribers (new toasts list)
    activate ToastComp
    ToastComp->>ToastComp: render toast (animate-in)
    ToastComp->>Timer: schedule remove(id) after duration
    Note over Timer: countdown
    Timer->>ToastStore: remove(id)
    ToastStore->>ToastComp: notify subscribers (updated list)
    ToastComp->>ToastComp: fade out / remove toast
    deactivate ToastComp
Loading
sequenceDiagram
    participant Browser as Browser
    participant Layout as +layout.svelte
    participant Guard as AuthGuardLogic
    participant Router as Router/goto

    Browser->>Layout: popstate / navigate (back)
    Layout->>Guard: isVaultPresent? / isTargetAuthRoute?
    alt vault exists AND target is auth route
        Guard->>Router: goto("/main") (replace history)
        Router->>Browser: update history / prevent back to auth
    else
        Guard->>Browser: allow navigation/back
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus review on: toast UUID generation and auto-remove timers (race conditions), Drawer pane lifecycle (present/hide/destroy timing), verify/selfie provisioning branches (registry calls, SSE handling, globalState updates), and navigation/popstate edge cases.
  • Also search for residual uses of removed shareBtn in IIdentityCard.

Possibly related PRs

Suggested reviewers

  • sosweetham
  • grv-saini-20

Poem

🐰
I nibble code and tap the key,
Toasts that pop for all to see,
Drawers that hide and panes that stay,
A copied name to save the day—
Hop, patch, release with glee!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning Description is incomplete; most required sections (Issue Number, tested scenarios, and all checklist items) are empty or unchecked, providing insufficient context for review. Fill in the Issue Number, describe specific testing performed, and update checklist items to reflect actual completion status and document the key changes made.
Title check ❓ Inconclusive Title contains a typo ('imrovements' should be 'improvements') and is vague about specific changes; doesn't clearly convey what UI/UX improvements were made. Fix the typo and be more specific about the primary UI/UX changes, e.g., 'Add toast notifications and refactor verification flow' or 'Implement clipboard copy for eID and improve drawer behavior'.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (10)
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte (1)

48-52: Verify that navigating to "/main" on cancel is intentional.

The async/await pattern for goto is good and aligns with the PR's navigation improvements. However, navigating to "/main" when the user cancels the delete operation seems unexpected—users typically expect to remain on the settings page when canceling an action.

Consider whether this navigation is intentional for back-navigation flow, or if the drawers should simply close and leave the user on the settings page.

Optionally, consider adding error handling around the navigation:

 async function cancelDelete() {
     isDeleteConfirmationOpen = false;
     isFinalConfirmationOpen = false;
-    await goto("/main");
+    try {
+        await goto("/main");
+    } catch (error) {
+        console.error("Navigation failed:", error);
+    }
 }
infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte (4)

236-244: Simplify validation logic.

The validation contains redundant checks:

  • If trimmed.length === 0, the regex test on line 242 will never execute (the function returns false on line 240)
  • The regex [\s\n\r] is redundant since \s already includes newlines and carriage returns
  • An empty string won't match /^[\s\n\r]+$/ anyway

Consider simplifying:

-const isValidName = (name: string): boolean => {
-    if (!name) return false;
-    const trimmed = name.trim();
-    if (trimmed.length === 0) return false;
-    // Check if name is not just whitespace, newlines, or enter characters
-    if (/^[\s\n\r]+$/.test(trimmed)) return false;
-    return true;
-};
+const isValidName = (name: string): boolean => {
+    return name?.trim().length > 0;
+};

263-263: Unnecessary fallback after validation.

Since line 250 validates that trimmedName is not empty (via isValidName), the fallback to capitalize(...) will never execute. The validation ensures trimmedName is always valid when reaching this line.

Consider simplifying:

-name: trimmedName || capitalize(`${falso.randFirstName()} ${falso.randLastName()}`),
+name: trimmedName,

371-373: Remove redundant input handler.

The oninput handler is unnecessary because bind:value={demoName} on line 370 already handles two-way data binding automatically. The explicit handler duplicates this functionality.

Remove the redundant oninput handler:

 <input
     type="text"
     bind:value={demoName}
-    oninput={(e) => {
-        demoName = (e.target as HTMLInputElement).value;
-    }}
     class="border-1 border-gray-200 w-full rounded-md font-medium my-2 p-2"
     placeholder="Enter your demo name for ePassport"
 />

382-383: Reuse validation function for consistency.

The inline validation logic duplicates the checks in isValidName. This violates the DRY principle and makes the code harder to maintain—if validation requirements change, both locations need updates.

Use the existing validation function:

 <ButtonAction
-    variant={!demoName || !demoName.trim() || /^[\s\n\r]+$/.test(demoName.trim()) ? "soft" : "solid"}
-    disabled={!demoName || !demoName.trim() || /^[\s\n\r]+$/.test(demoName.trim())}
+    variant={!isValidName(demoName) ? "soft" : "solid"}
+    disabled={!isValidName(demoName)}
     class="w-full"
     callback={handleFinalSubmit}>Continue</ButtonAction
 >
infrastructure/eid-wallet/src/routes/+page.svelte (1)

2-40: clearPin is now only defined, never invoked — confirm this behavior change

The new onMount logic initializes clearPin (with good error handling) but never calls it, so any prior behavior that cleared the PIN automatically on landing is effectively removed. If the intent is to stop auto-clearing and only clear the PIN from some other UI/action, this is fine; otherwise you likely want to either invoke clearPin in the appropriate branch or drop it here to avoid confusion.

infrastructure/eid-wallet/src/routes/+layout.svelte (2)

488-521: popstate guard works, but preventDefault + pushState may yield odd history

The popstate handler correctly checks for an existing vault before forcing users away from /login//register//onboarding, but a couple of details are worth tightening:

  • event.preventDefault() has no effect for popstate events; control comes from your subsequent goto("/main") and history.pushState.
  • Calling goto("/main") (which pushes a new entry) and then history.pushState again can create duplicated /main entries, making the back button feel “sticky”.

You might want to instead do something like await goto("/main", { replaceState: true }) or use history.replaceState so the restricted entry is replaced rather than duplicated, and you can drop preventDefault() entirely.


3-4: Toast integration is fine; consider simplifying the render syntax

Importing Toast and rendering it at the layout root is good for global notifications. Since Toast is a concrete component (not dynamic), you don’t need <svelte:component> here; <Toast /> would be simpler and avoids relying on the now-deprecated <svelte:component> form in Svelte 5.(reddit.com)

Also applies to: 540-541

infrastructure/eid-wallet/src/lib/ui/Toast/Toast.svelte (2)

7-14: Prefer idiomatic Svelte 5 store subscription.

In Svelte 5, you can subscribe to stores more concisely using the $ prefix directly. The manual $effect subscription is verbose and unnecessary.

Apply this diff to use the idiomatic pattern:

-let toasts = $state<Array<{ id: string; message: string; type?: "success" | "error" | "info"; duration?: number }>>([]);
-
-$effect(() => {
-    const unsubscribe = toastStore.subscribe((value) => {
-        toasts = value;
-    });
-    return unsubscribe;
-});
+let toasts = $derived($toastStore);

Note: You'll also need to update the removeToast function to directly reference $toastStore instead of the local toasts variable, or keep the current approach if you need the local reactive variable for other reasons.


36-58: Consider adding a maximum toast limit.

The toast list can grow unbounded if many toasts are triggered rapidly, potentially overwhelming the UI. Consider limiting the number of visible toasts (e.g., max 3-5) and queuing or auto-dismissing older ones.

This can be implemented in the store's add method in toast.ts by checking the array length before adding a new toast.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 49a865f and 9e403dc.

📒 Files selected for processing (14)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte (2 hunks)
  • infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte (3 hunks)
  • infrastructure/eid-wallet/src/lib/ui/Toast/Toast.svelte (1 hunks)
  • infrastructure/eid-wallet/src/lib/ui/Toast/toast.ts (1 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (0 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte (3 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte (1 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte (2 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (3 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/document-type.svelte (1 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (3 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte (2 hunks)
  • infrastructure/eid-wallet/src/routes/+layout.svelte (4 hunks)
  • infrastructure/eid-wallet/src/routes/+page.svelte (1 hunks)
💤 Files with no reviewable changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-11-14T17:54:47.719Z
Learnt from: coodos
Repo: MetaState-Prototype-Project/prototype PR: 437
File: infrastructure/eid-wallet/src/lib/global/controllers/security.ts:138-157
Timestamp: 2025-11-14T17:54:47.719Z
Learning: The `setOnboardingPin` method in `infrastructure/eid-wallet/src/lib/global/controllers/security.ts` is intentionally designed to allow overwriting existing PINs without checking if a PIN already exists. This is a workaround for Android keeping stale files around during app reinstallation or onboarding, which causes errors on some devices. Do not suggest adding guards to prevent PIN overwrites in this method.

Applied to files:

  • infrastructure/eid-wallet/src/routes/+page.svelte
🔇 Additional comments (9)
infrastructure/eid-wallet/src/routes/(auth)/onboarding/+page.svelte (2)

247-256: Good validation implementation.

The validation flow correctly trims the input, validates it, provides user feedback, and prevents submission of invalid names.


377-379: Good user feedback implementation.

The conditional error display provides clear inline feedback for name validation errors while keeping other error messages in their appropriate locations.

infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte (1)

101-105: Unified cancel/decline handlers now always redirect to /main

The three handlers now just close their respective drawers and navigate to the main dashboard, regardless of prior error state or request type. This is technically sound, but it’s a behavior change from the previous conditional flows, so please confirm this redirect-on-cancel UX is intended for auth, signing, and reveal flows.

Also applies to: 122-126, 139-142

infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (1)

15-16: Context wiring and fullscreen Drawer behavior look consistent

Providing showVeriffModal via setContext and binding it into <Drawer bind:isPaneOpen={showVeriffModal} fullScreen={$verifStep === 1 || $verifStep === 2}> aligns with how document-type.svelte consumes the context and with the intended fullscreen experience for steps 1 and 2. The timing also works out since the modal is only opened after onMount has run, so children will see the context when they mount.

Also applies to: 252-265, 404-405

infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte (1)

4-5: Selfie step back-navigation is wired correctly

The new header with Button.Icon and onclick={() => verifStep.set(1)} cleanly returns the user to the document-capture step without touching the capture logic; combined with Passport’s cleanup on step change, this should behave correctly.

Also applies to: 76-90

infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (1)

4-5: Passport step back-navigation aligns with the verify flow

The back icon now drives verifStep.set(0), returning to the document-type selection while letting the existing onMount cleanup stop the camera on unmount. This brings the passport step in line with the other verify steps’ back UX.

Also applies to: 231-256

infrastructure/eid-wallet/src/routes/(auth)/verify/steps/document-type.svelte (1)

3-22: Back behavior via showVeriffModal context is robust

Using getContext("showVeriffModal") with a { value: boolean } contract and falling back to window.history.back() if the context is missing gives a clean “close drawer or go back” behavior. The updated header with Button.Icon hooked to goBack looks good.

Also applies to: 26-37

infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte (1)

67-82: Verify clipboard API availability in your deployment environment.

The navigator.clipboard.writeText API requires a secure context (HTTPS or localhost). If this application is deployed over plain HTTP, the clipboard operation will fail.

Please confirm that the application is served over HTTPS in production. Additionally, consider adding a fallback mechanism for browsers that don't support the Clipboard API:

onclick={async () => {
    if (userId) {
        try {
            if (navigator.clipboard && navigator.clipboard.writeText) {
                await navigator.clipboard.writeText(userId);
                toast.success("eName copied to clipboard");
            } else {
                // Fallback for older browsers or insecure contexts
                const textArea = document.createElement("textarea");
                textArea.value = userId;
                textArea.style.position = "fixed";
                textArea.style.opacity = "0";
                document.body.appendChild(textArea);
                textArea.select();
                const success = document.execCommand('copy');
                document.body.removeChild(textArea);
                if (success) {
                    toast.success("eName copied to clipboard");
                } else {
                    toast.error("Failed to copy eName");
                }
            }
        } catch (error) {
            console.error("Failed to copy:", error);
            toast.error("Failed to copy eName");
        }
    }
}}
infrastructure/eid-wallet/src/lib/ui/Toast/toast.ts (1)

15-32: [rewritten comment]
[classification tag]

Comment on lines 61 to 85
<style>
@keyframes slide-in-from-top-2 {
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}

@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

.animate-in {
animation: slide-in-from-top-2 0.3s ease-out, fade-in 0.3s ease-out;
}
</style>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add exit animations for smoother UX.

The component defines entrance animations (slide-in-from-top-2, fade-in) but no exit animations. When a toast is removed, it disappears abruptly, which creates a jarring user experience.

Consider using Svelte's transition directives for smoother enter/exit animations:

+import { fly, fade } from 'svelte/transition';
+
 {#each toasts as toast (toast.id)}
     <div
+        transition:fly={{ y: -20, duration: 300 }}
         class={cn(
-            "pointer-events-auto flex items-center justify-between gap-3 rounded-lg border p-4 shadow-lg animate-in slide-in-from-top-2 fade-in",
+            "pointer-events-auto flex items-center justify-between gap-3 rounded-lg border p-4 shadow-lg",
             getToastClasses(toast.type),
         )}
         role="alert"
     >

Then you can remove the custom CSS animations and use Svelte's built-in transition system.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/lib/ui/Toast/Toast.svelte around lines 61-85,
the component only defines entrance CSS keyframe animations and lacks exit
animations, causing abrupt removals; replace or augment this with Svelte
transitions by importing a built-in transition (e.g., fly or slide/fade) and
apply a transition directive to the toast element (e.g., use:transitionName or
transition:fade with configured params) so the component animates both in and
out, and remove the custom CSS animations if the Svelte transitions cover both
directions to keep styles consistent.

Comment on lines +396 to +421
onNavigate(async (navigation) => {
if (!document.startViewTransition) return;

const from = navigation.from?.url.pathname;
const to = navigation.to?.url.pathname;

if (!from || !to || from === to) return;

// Prevent navigation to login/register/onboarding when logged in
if (to === "/login" || to === "/register" || to === "/onboarding") {
try {
const gs = getContext<() => GlobalState>("globalState");
if (gs) {
const state = gs();
const vault = await state.vaultController.vault;
if (vault) {
// User is logged in, prevent navigation and redirect to main
await goto("/main");
return;
}
}
} catch (error) {
// If we can't check, allow navigation
console.error("Error checking auth state:", error);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Guard in onNavigate should handle globalState being undefined

In the auth guard you do:

const gs = getContext<() => GlobalState>("globalState");
if (gs) {
    const state = gs();
    const vault = await state.vaultController.vault;
    ...
}

Because the context function exists from startup even before GlobalState.create() resolves, gs() can return undefined, leading to state.vaultController throwing if a navigation to /login//register//onboarding happens very early. Consider defensively handling that:

const gs = getContext<() => GlobalState | undefined>("globalState");
const state = gs?.();
if (state) {
    const vault = await state.vaultController.vault;
    if (vault) {
        await goto("/main");
        return;
    }
}

This keeps the guard robust while preserving the intended redirect behavior.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/+layout.svelte around lines 396-421, the
auth guard assumes gs() returns a GlobalState but gs() can be undefined early
causing state.vaultController to throw; update the guard to allow gs to be a
function returning GlobalState | undefined, call gs?.() and assign to a local
state variable, then only access state.vaultController.vault if state is truthy
(preserving the existing try/catch and redirect to /main when a vault exists) so
navigation checks are defensive against an unresolved global state.

Comment on lines 449 to +483
$effect(() => {
if (mainWrapper) {
swipedetect(mainWrapper, (dir: string) => {
if (dir === "right") window.history.back();
if (dir === "right") {
// Prevent going back to login/register/onboarding screens when logged in
const currentPath = window.location.pathname;
if (currentPath === "/login" || currentPath === "/register" || currentPath === "/onboarding") {
// If we're on login/register/onboarding, allow back navigation
window.history.back();
} else {
// Check if we're logged in by checking if we can access globalState
try {
const gs = getContext<() => GlobalState>("globalState");
if (gs) {
// User is logged in, prevent going back to login/register/onboarding
const historyLength = window.history.length;
// Only allow back if we're not going to login/register/onboarding
const previousPath = document.referrer;
if (previousPath &&
!previousPath.includes("/login") &&
!previousPath.includes("/register") &&
!previousPath.includes("/onboarding")) {
window.history.back();
} else {
// Redirect to main instead
goto("/main");
}
} else {
window.history.back();
}
} catch {
window.history.back();
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n infrastructure/eid-wallet/src/routes/+layout.svelte | sed -n '420,520p'

Repository: MetaState-Prototype-Project/prototype

Length of output: 4850


🏁 Script executed:

rg "vault" infrastructure/eid-wallet/src/routes/+layout.svelte -B 2 -A 2

Repository: MetaState-Prototype-Project/prototype

Length of output: 3079


🏁 Script executed:

rg "getContext" infrastructure/eid-wallet/src/routes/+layout.svelte -B 3 -A 3

Repository: MetaState-Prototype-Project/prototype

Length of output: 2898


🏁 Script executed:

wc -l infrastructure/eid-wallet/src/routes/+layout.svelte && cat infrastructure/eid-wallet/src/routes/+layout.svelte | sed -n '1,50p'

Repository: MetaState-Prototype-Project/prototype

Length of output: 1709


🏁 Script executed:

cat infrastructure/eid-wallet/src/routes/+layout.svelte | sed -n '1,100p'

Repository: MetaState-Prototype-Project/prototype

Length of output: 3641


🏁 Script executed:

rg "onNavigate" infrastructure/eid-wallet/src/routes/+layout.svelte -B 5 -A 20

Repository: MetaState-Prototype-Project/prototype

Length of output: 1905


Mirror the vault check from onNavigate/popstate in the swipe handler

The swipe handler at lines 462-478 treats context existence as authentication, but the onNavigate and popstate handlers both check const vault = await state.vaultController.vault; before confirming the user is logged in. Apply the same vault verification in the swipe handler to prevent logged-out users from being treated as authenticated.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/+layout.svelte around lines 449-483, the
swipe handler currently assumes presence of the globalState context implies the
user is logged in; instead mirror the onNavigate/popstate logic by retrieving
the state/vault and awaiting state.vaultController.vault to confirm an unlocked
vault before treating the user as authenticated. Modify the try block to get the
state/context (e.g., const state = getContext("globalState") or similar), await
const vault = await state.vaultController.vault, and only use the "prevent back
/ redirect to /main" logic when vault is truthy; otherwise fall back to
window.history.back(); ensure errors are caught and handled the same way as
other handlers.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte (2)

84-97: Extract duplicated class-toggling logic into a helper function.

The same logic for adding/removing drawer-fullscreen/drawer-normal classes appears three times. Extract it to reduce duplication and improve maintainability:

+function updatePaneClass(isFullScreen: boolean) {
+    const paneElement = document.querySelector(".pane") as HTMLElement;
+    if (paneElement) {
+        paneElement.classList.toggle("drawer-fullscreen", isFullScreen);
+        paneElement.classList.toggle("drawer-normal", !isFullScreen);
+    }
+}
+
 // Initialize pane only once when element is available
 $effect(() => {
     if (!drawerElem) return;
     // ... pane initialization ...
 
-    // Add class to pane element based on fullScreen prop
-    // Use setTimeout to ensure pane element is created
-    setTimeout(() => {
-        const paneElement = document.querySelector(".pane") as HTMLElement;
-        if (paneElement) {
-            if (fullScreen) {
-                paneElement.classList.add("drawer-fullscreen");
-                paneElement.classList.remove("drawer-normal");
-            } else {
-                paneElement.classList.add("drawer-normal");
-                paneElement.classList.remove("drawer-fullscreen");
-            }
-        }
-    }, 0);
+    setTimeout(() => updatePaneClass(fullScreen), 0);
 
     return () => pane?.destroy();
 });

Apply the same pattern in the other two locations (lines 106-116 and 120-132).

Also applies to: 106-116, 120-132


56-57: Consider handling window resize for fullScreenHeight.

The height is calculated once on initialization. If the device rotates or window resizes, the fullscreen height won't update. This may cause layout issues on devices that support orientation changes.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e403dc and 0d5313a.

📒 Files selected for processing (7)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte (2 hunks)
  • infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte (6 hunks)
  • infrastructure/eid-wallet/src/lib/ui/Toast/Toast.svelte (1 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (0 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (3 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (3 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte (2 hunks)
💤 Files with no reviewable changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (1)
  • infrastructure/eid-wallet/src/lib/ui/Toast/Toast.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (5)
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte (1)

4-4: LGTM! Imports align with the new copy functionality.

The toast and Copy01Icon imports are correctly added to support the new copy-to-clipboard feature.

Also applies to: 7-7

infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte (1)

76-91: LGTM! Back navigation implementation is clean.

The back button integration follows the consistent pattern used across verification steps, and the layout restructuring maintains readability while adding the navigation control.

infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (1)

404-405: Good use of conditional fullScreen for camera-based verification steps.

Enabling fullscreen mode for passport (step 1) and selfie (step 2) capture provides better UX for camera views.

infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (1)

228-256: LGTM! Consistent back navigation implementation.

The back button correctly navigates to step 0 (document type selection), and the layout restructuring aligns with the pattern in selfie.svelte.

infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte (1)

150-183: Good approach using static CSS with class-based toggling.

The styling correctly addresses the previous feedback about dynamic expressions in style blocks. The fallback selector :not(.drawer-fullscreen) provides reasonable safety for the default case.

Comment on lines 70 to 85
<Button.Icon
icon={Copy01Icon}
iconColor={"white"}
strokeWidth={2}
onclick={async () => {
if (userId) {
try {
await navigator.clipboard.writeText(userId);
toast.success("eName copied to clipboard");
} catch (error) {
console.error("Failed to copy:", error);
toast.error("Failed to copy eName");
}
}
}}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add an accessible label or tooltip for the copy button.

The copy-to-clipboard functionality works correctly with proper error handling and toast notifications. However, the button lacks an accessible label or tooltip, which may leave users uncertain about its purpose.

Consider adding an aria-label or title attribute to improve accessibility:

 <Button.Icon
     icon={Copy01Icon}
     iconColor={"white"}
     strokeWidth={2}
+    title="Copy eName to clipboard"
     onclick={async () => {

Optional: Consider adding brief visual feedback on successful copy.

For enhanced UX, you could temporarily change the icon or add a visual indicator when the copy succeeds, in addition to the toast notification.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button.Icon
icon={Copy01Icon}
iconColor={"white"}
strokeWidth={2}
onclick={async () => {
if (userId) {
try {
await navigator.clipboard.writeText(userId);
toast.success("eName copied to clipboard");
} catch (error) {
console.error("Failed to copy:", error);
toast.error("Failed to copy eName");
}
}
}}
/>
<Button.Icon
icon={Copy01Icon}
iconColor={"white"}
strokeWidth={2}
title="Copy eName to clipboard"
onclick={async () => {
if (userId) {
try {
await navigator.clipboard.writeText(userId);
toast.success("eName copied to clipboard");
} catch (error) {
console.error("Failed to copy:", error);
toast.error("Failed to copy eName");
}
}
}}
/>
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
around lines 70 to 85, the Copy button is missing an accessible label/tooltip;
add an aria-label (e.g., aria-label="Copy eName to clipboard") or a title prop
on the Button.Icon component so screen readers and hover users know its purpose,
and optionally add a short local state flash (e.g., set a transient "copied"
boolean for 1–2s to swap the icon or add a visual indicator) triggered on
successful copy while keeping the existing toast and error handling.

Comment on lines 87 to 88
const paneElement = document.querySelector(".pane") as HTMLElement;
if (paneElement) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

CupertinoPane cssClass option element reference API documentation

💡 Result:

The cssClass option (type: string) adds one or more class names to the pane wrapper so you can style the pane instance. It is documented as "Additional classes to apply for wrapper" (i.e., the .cupertino-pane wrapper element). Use it in the settings passed to the constructor:

Example:
new CupertinoPane('.cupertino-pane', { cssClass: 'my-pane-theme another-class' });

Sources: panejs Settings documentation [1], changelog / notes about cssClass applying to .cupertino-pane-wrapper [2].

References
[1] panejs Settings — cssClass option.
[2] Release notes / changelog mentioning cssClass applied to .cupertino-pane-wrapper.


🏁 Script executed:

cd infrastructure/eid-wallet && cat -n src/lib/ui/Drawer/Drawer.svelte | head -150

Repository: MetaState-Prototype-Project/prototype

Length of output: 5500


🌐 Web query:

CupertinoPane API element reference pane.el pane.element how to get pane container

💡 Result:

Use the instance's paneEl property. The constructor accepts a selector or HTMLElement, and the created CupertinoPane exposes the DOM container as myPane.paneEl.

Example:

  • Create: const pane = new CupertinoPane('.cupertino-pane');
  • Get container element: const container = pane.paneEl;

Sources: Cupertino Pane docs (constructor accepts selector/element). [1] Internal source shows event handlers attach to instance.paneEl. [2]

References
[1] Getting Started — Cupertino Panes documentation.
[2] cupertino-pane source (events.ts) on UNPKG (uses instance.paneEl).


🏁 Script executed:

# Search for any usage of the pane object to see how it's used in the codebase
cd infrastructure/eid-wallet && rg "pane\." src/lib/ui/Drawer/Drawer.svelte -A 2 -B 2

Repository: MetaState-Prototype-Project/prototype

Length of output: 532


Replace global DOM queries with CupertinoPane's element reference.

Using document.querySelector(".pane") selects the first .pane in the document, breaking when multiple Drawer instances render simultaneously. CupertinoPane exposes the container element via the paneEl property—use pane.paneEl instead of the global query.

Update lines 87-88, 107-108, and 122-123 to replace document.querySelector(".pane") with pane.paneEl.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte around lines 87-88,
107-108 and 122-123, the code uses document.querySelector(".pane") which can
return the wrong element when multiple Drawer instances exist; replace those
global DOM queries with the CupertinoPane instance's paneEl property
(pane.paneEl) and cast/guard it as an HTMLElement where needed so each Drawer
uses its own container element instead of the first .pane in the document.

Comment on lines +256 to +264
// Provide showVeriffModal context to child components
setContext("showVeriffModal", {
get value() {
return showVeriffModal;
},
set value(v: boolean) {
showVeriffModal = v;
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

setContext cannot be called inside onMount — this will throw a runtime error.

In Svelte 5, setContext must be called synchronously during component initialization, not inside lifecycle callbacks. Move this call outside of onMount:

+// Provide showVeriffModal context to child components
+setContext("showVeriffModal", {
+    get value() {
+        return showVeriffModal;
+    },
+    set value(v: boolean) {
+        showVeriffModal = v;
+    },
+});
+
 onMount(async () => {
     globalState = getContext<() => GlobalState>("globalState")();
     // handle verification logic + sec user data in the store

-    // Provide showVeriffModal context to child components
-    setContext("showVeriffModal", {
-        get value() {
-            return showVeriffModal;
-        },
-        set value(v: boolean) {
-            showVeriffModal = v;
-        },
-    });
-
     // Check hardware key support first
     await checkHardwareKeySupport();
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte around lines
256-264, the setContext call is currently inside onMount which throws at runtime
in Svelte 5; move the entire setContext invocation out of onMount so it runs
synchronously during component initialization, keeping the same context object
shape (getter/setter or reactive store) and accessors, and if any values depend
on mount-time data, initialize placeholders synchronously and update them inside
onMount instead.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte (1)

51-80: Replace pane?.destroy() with pane?.hide() in the backdrop handler to keep the pane instance reusable

The separate open/close effect (lines 82–94) uses present() to show the pane and hide() to close it, expecting the pane instance to survive multiple opens and closes. However, onBackdropTap calls pane?.destroy(), which removes the instance from the DOM. When isPaneOpen is next toggled to true, calling present() on a destroyed instance will fail or silently do nothing.

Use hide() instead to close the UI while keeping the instance alive for reuse:

        events: {
            onBackdropTap: () => {
-                pane?.destroy();
+                pane?.hide();
                 isPaneOpen = false;
            },
        },

This preserves the "hide on close, destroy on unmount" semantics, with the cleanup function (lines 75–79) handling final disposal on component unmount.

♻️ Duplicate comments (2)
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte (1)

70-85: Add an accessible label to the copy button.

As previously noted, the button lacks an aria-label or title attribute to indicate its purpose to users and screen readers.

 <Button.Icon
     icon={Copy01Icon}
     iconColor={"white"}
     strokeWidth={2}
+    aria-label="Copy eName to clipboard"
     onclick={async () => {
infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (1)

263-284: setContext cannot be called inside onMount—this will throw at runtime in Svelte 5.

As previously flagged, setContext must be called synchronously during component initialization. Move both setContext calls outside of onMount:

 let handleContinue: () => Promise<void> = $state(async () => {});

+// Provide showVeriffModal context to child components
+setContext("showVeriffModal", {
+    get value() {
+        return showVeriffModal;
+    },
+    set value(v: boolean) {
+        showVeriffModal = v;
+    },
+});
+
+// Provide verification data context for selfie page
+setContext("verifyData", {
+    get person() {
+        return person;
+    },
+    get document() {
+        return document;
+    },
+    get websocketData() {
+        return websocketData;
+    },
+});
+
 onMount(async () => {
     globalState = getContext<() => GlobalState>("globalState")();
     // handle verification logic + sec user data in the store

-    // Provide showVeriffModal context to child components
-    setContext("showVeriffModal", {
-        ...
-    });
-
-    // Provide verification data context for selfie page
-    setContext("verifyData", {
-        ...
-    });
-
     // Check hardware key support first
     await checkHardwareKeySupport();
🧹 Nitpick comments (7)
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte (1)

144-144: Consider externalizing the version string.

The version is hardcoded in the template. To maintain consistency and reduce manual updates, consider importing it from package.json or a configuration file.

For example, in the script section:

import packageJson from '../../../../package.json';
const version = packageJson.version;

Then in the template:

-            Version v0.5.0.0
+            Version v{version}
infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte (1)

101-105: Consider keeping cancel handlers sync or widening drawer callback types

All three cancel handlers are marked async solely to await goto("/main"), and their callbacks are passed into drawers as props. If those props are still typed as () => void, returning Promise<void> can cause TypeScript friction and the awaited result is not otherwise used. You could either:

  • keep the handlers synchronous and call void goto("/main"), or
  • update the drawer prop types to accept () => void | Promise<void> if you explicitly want async handlers.

This is non‑blocking but would make the signatures and typing clearer; a quick TS/Svelte typecheck of the drawer components should confirm everything is consistent.

Also applies to: 122-126, 139-142

infrastructure/eid-wallet/src/routes/(auth)/verify/passport/+page.svelte (1)

5-7: Consider using Tailwind classes instead of inline styles.

For consistency with the project's styling approach, consider replacing inline style with Tailwind utility classes:

-<div class="fixed inset-0 bg-white z-50 overflow-y-auto" style="padding-top: 70px; padding-left: 24px; padding-right: 24px;">
+<div class="fixed inset-0 bg-white z-50 overflow-y-auto pt-[70px] px-6">
infrastructure/eid-wallet/src/routes/(auth)/verify/selfie/+page.svelte (1)

5-7: Consider using Tailwind classes instead of inline styles.

Same suggestion as for passport/+page.svelte—use Tailwind utilities for consistency:

-<div class="fixed inset-0 bg-white z-50 overflow-y-auto" style="padding-top: 70px; padding-left: 24px; padding-right: 24px;">
+<div class="fixed inset-0 bg-white z-50 overflow-y-auto pt-[70px] px-6">
infrastructure/eid-wallet/src/routes/(auth)/verify/store.ts (1)

14-16: Avoid any types—define or reuse proper interfaces.

The Person and Document types are already defined in +page.svelte. Extract them to a shared types file and use them here for type safety:

-export const verificationPerson = writable<any>(null);
-export const verificationDocument = writable<any>(null);
+export const verificationPerson = writable<Person | null>(null);
+export const verificationDocument = writable<Document | null>(null);

This prevents silent type errors and improves IDE support across components consuming these stores.

infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (1)

167-173: Consider a single source of truth for verification data.

The SSE data is stored in both local variables (person, document, websocketData) and writable stores (verificationPerson, etc.). While currently synchronized, this dual storage could lead to drift if updated independently elsewhere. Consider using only the stores and deriving local reads from them:

$: person = $verificationPerson;
$: document = $verificationDocument;

Or remove local variables entirely and use stores throughout.

infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte (1)

195-215: Reuse stopCamera() and use idiomatic Svelte event binding for the back button

The “go back” button currently:

  • Duplicates the track‑stopping loop already encapsulated in stopCamera().
  • Uses onclick={…} rather than Svelte’s on:click={…} pattern, which is more idiomatic and consistent with the rest of the codebase.

You can simplify and centralize the camera shutdown logic like this:

-            <button
-                onclick={() => {
-                    if (stream) {
-                        for (const track of stream.getTracks()) {
-                            track.stop();
-                        }
-                    }
-                    goto("/verify/passport");
-                }}
+            <button
+                on:click={() => {
+                    stopCamera();
+                    goto("/verify/passport");
+                }}
                 class="cursor-pointer self-start flex items-center gap-2 text-sm text-gray-600 hover:text-gray-800"
             >

This keeps camera cleanup logic in one place and aligns with standard Svelte event syntax.

Please ensure stopCamera() is safe to call when stream is undefined or already stopped (current implementation guards on stream, so this refactor should be behavior‑preserving).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0d5313a and 3c9482a.

📒 Files selected for processing (13)
  • infrastructure/eid-wallet/package.json (2 hunks)
  • infrastructure/eid-wallet/src-tauri/tauri.conf.json (3 hunks)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte (2 hunks)
  • infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte (3 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte (3 hunks)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte (2 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/+page.svelte (5 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/passport/+page.svelte (1 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/selfie/+page.svelte (1 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/document-type.svelte (1 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte (6 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte (4 hunks)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/store.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • infrastructure/eid-wallet/src/routes/(auth)/verify/steps/document-type.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (10)
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte (4)

1-20: LGTM!

The imports are well-organized and the context setup follows standard Svelte patterns.


22-30: LGTM!

State declarations are well-structured for both the delete confirmation flow and the hidden tap functionality.


55-57: LGTM!

Proper cleanup of the timeout on component unmount prevents potential memory leaks.


110-112: LGTM!

The effect hook properly sets the page title for the settings view.

infrastructure/eid-wallet/src/routes/(app)/scan-qr/+page.svelte (2)

101-105: Cancel on Auth/Signing drawers now always redirects to /main

Closing the drawer and hard‑navigating to /main makes the cancel path uniform and guarantees the page’s onDestroy cleanup runs. Please double‑check with product/UX that this is desired even when the scan flow was initiated from a route other than the main dashboard, as it changes any previous “go back to where I came from” behavior.

Also applies to: 122-126


139-142: Reveal cancel now goes to /main instead of using history.back()

Switching from window.history.back() to goto("/main") simplifies navigation and matches the new dashboard‑centric cancel behavior. Confirm this doesn’t regress flows where reveal might be deep‑linked from non‑main routes (e.g. you truly always want to land on the dashboard after cancelling reveal).

infrastructure/eid-wallet/package.json (1)

3-3: Version bump looks correct.

The version increment from 0.4.0 to 0.5.0 aligns with the corresponding update in tauri.conf.json, maintaining consistency across the project configuration.

infrastructure/eid-wallet/src-tauri/tauri.conf.json (1)

4-4: Version updates are consistent.

The version bump to 0.5.0 matches package.json. The Android versionCode increment from 12 to 20 is a notable jump—ensure this is intentional and won't cause issues with staged rollouts or version tracking in the Play Store.

Also applies to: 31-31

infrastructure/eid-wallet/src/lib/ui/Drawer/Drawer.svelte (1)

108-116: Static global .pane styling looks coherent with the new lifecycle

The consolidated positioning/background/overflow rules on :global(.pane) align with the present()/hide() lifecycle and should give a stable, centered bottom‑sheet across uses. No functional issues from this change.

infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte (1)

22-24: Selfie capture → loading → results state flow looks consistent

The combination of:

  • $state variables (showResults, loading),
  • SSE‑driven $status/$verifStep watcher to flip showResults,
  • camera lifecycle (requestCameraPermission on mount and stopCamera in both capture and unmount), and
  • the {#if !load && !showResults} / {:else if load && !showResults} / {:else if showResults} branches

forms a clear state machine: capture → verification spinner → results + continue/claim/retry button. Transitions appear internally coherent and should avoid camera leaks or stuck spinners under normal flows.

Also applies to: 48-53, 55-62, 64-97, 256-317

Comment on lines +48 to 52
async function cancelDelete() {
isDeleteConfirmationOpen = false;
isFinalConfirmationOpen = false;
await goto("/main");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Unexpected navigation on cancel.

When a user clicks "Cancel" on a delete confirmation dialog, they expect to remain on the settings page, not be navigated away to /main. This creates a confusing user experience.

Consider removing the navigation:

 async function cancelDelete() {
     isDeleteConfirmationOpen = false;
     isFinalConfirmationOpen = false;
-    await goto("/main");
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function cancelDelete() {
isDeleteConfirmationOpen = false;
isFinalConfirmationOpen = false;
await goto("/main");
}
async function cancelDelete() {
isDeleteConfirmationOpen = false;
isFinalConfirmationOpen = false;
}
🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte around lines
48-52, the cancelDelete function currently closes the dialogs then navigates to
"/main", which causes unexpected navigation; remove the navigation call (delete
the await goto("/main") line) so cancelDelete only sets isDeleteConfirmationOpen
and isFinalConfirmationOpen to false, and ensure any focus or state reset needed
on the settings page is performed after closing dialogs rather than navigating
away.

Comment on lines +59 to 108
async function handleVersionTap() {
const now = Date.now();

// Reset if more than 3s between taps
if (now - lastTapTime > 3000) {
tapCount = 0;
}

tapCount++;
lastTapTime = now;

// Show feedback after 5 taps
if (tapCount >= 5) {
retryMessage = `Taps: ${tapCount}/10`;
}

// Trigger hidden action at 10 taps
if (tapCount === 10) {
isRetrying = true;
retryMessage = "Retrying eVault profile setup...";

try {
await globalState.vaultController.retryProfileCreation();
retryMessage =
"✅ eVault profile setup completed successfully!";

// Clear previous timeout if exists
if (timeoutId) clearTimeout(timeoutId);

timeoutId = setTimeout(() => {
tapCount = 0;
retryMessage = "";
isRetrying = false;
}, 3000);
} catch (error) {
console.error("Failed to retry eVault profile setup:", error);
retryMessage =
"❌ Failed to setup eVault profile. Check console for details.";

// Clear previous timeout if exists
if (timeoutId) clearTimeout(timeoutId);

timeoutId = setTimeout(() => {
tapCount = 0;
retryMessage = "";
isRetrying = false;
}, 5000);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add null check for vaultController.

Before calling retryProfileCreation(), there's no validation that vaultController exists on the globalState object. This could result in a runtime error if the controller is not initialized.

Add a guard to validate the controller exists:

 // Trigger hidden action at 10 taps
 if (tapCount === 10) {
+    if (!globalState.vaultController) {
+        retryMessage = "❌ Vault controller not available.";
+        setTimeout(() => {
+            tapCount = 0;
+            retryMessage = "";
+        }, 3000);
+        return;
+    }
+
     isRetrying = true;
     retryMessage = "Retrying eVault profile setup...";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function handleVersionTap() {
const now = Date.now();
// Reset if more than 3s between taps
if (now - lastTapTime > 3000) {
tapCount = 0;
}
tapCount++;
lastTapTime = now;
// Show feedback after 5 taps
if (tapCount >= 5) {
retryMessage = `Taps: ${tapCount}/10`;
}
// Trigger hidden action at 10 taps
if (tapCount === 10) {
isRetrying = true;
retryMessage = "Retrying eVault profile setup...";
try {
await globalState.vaultController.retryProfileCreation();
retryMessage =
"✅ eVault profile setup completed successfully!";
// Clear previous timeout if exists
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
tapCount = 0;
retryMessage = "";
isRetrying = false;
}, 3000);
} catch (error) {
console.error("Failed to retry eVault profile setup:", error);
retryMessage =
"❌ Failed to setup eVault profile. Check console for details.";
// Clear previous timeout if exists
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
tapCount = 0;
retryMessage = "";
isRetrying = false;
}, 5000);
}
}
}
async function handleVersionTap() {
const now = Date.now();
// Reset if more than 3s between taps
if (now - lastTapTime > 3000) {
tapCount = 0;
}
tapCount++;
lastTapTime = now;
// Show feedback after 5 taps
if (tapCount >= 5) {
retryMessage = `Taps: ${tapCount}/10`;
}
// Trigger hidden action at 10 taps
if (tapCount === 10) {
if (!globalState.vaultController) {
retryMessage = "❌ Vault controller not available.";
setTimeout(() => {
tapCount = 0;
retryMessage = "";
}, 3000);
return;
}
isRetrying = true;
retryMessage = "Retrying eVault profile setup...";
try {
await globalState.vaultController.retryProfileCreation();
retryMessage =
"✅ eVault profile setup completed successfully!";
// Clear previous timeout if exists
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
tapCount = 0;
retryMessage = "";
isRetrying = false;
}, 3000);
} catch (error) {
console.error("Failed to retry eVault profile setup:", error);
retryMessage =
"❌ Failed to setup eVault profile. Check console for details.";
// Clear previous timeout if exists
if (timeoutId) clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
tapCount = 0;
retryMessage = "";
isRetrying = false;
}, 5000);
}
}
}
🤖 Prompt for AI Agents
In infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte around lines
59-108, add a null/undefined guard for globalState.vaultController before
calling retryProfileCreation to avoid runtime errors; if the controller is
missing, set an appropriate retryMessage, reset isRetrying and tapCount,
optionally console.error the missing controller, clear any existing timeout, and
return early; otherwise proceed to await retryProfileCreation as existing code
does.

Comment on lines +233 to +247
<button
onclick={() => {
stopCamera();
goto("/verify");
}}
class="cursor-pointer self-start flex items-center gap-2 text-sm text-gray-600 hover:text-gray-800"
>
<Button.Icon
icon={ArrowLeft01Icon}
iconColor="currentColor"
strokeWidth={2}
class="w-4 h-4"
/>
<span>go back</span>
</button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find Button component definition
fd -t f -i "button" --extension svelte | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 1108


🏁 Script executed:

# Search for Button.Icon component in the codebase
rg -t svelte "Button\.Icon|export.*Icon" --max-count=20

Repository: MetaState-Prototype-Project/prototype

Length of output: 111


🏁 Script executed:

# Look for Button component in common locations
git ls-files | grep -i button | grep -E "\.(svelte|ts|js)$"

Repository: MetaState-Prototype-Project/prototype

Length of output: 1777


🏁 Script executed:

cat infrastructure/eid-wallet/src/lib/ui/Button/ButtonIcon.svelte

Repository: MetaState-Prototype-Project/prototype

Length of output: 5727


🏁 Script executed:

# Verify how the back button is actually used in passport.svelte and see if there's text content
cat -n infrastructure/eid-wallet/src/routes/\(auth\)/verify/steps/passport.svelte | sed -n '233,247p'

Repository: MetaState-Prototype-Project/prototype

Length of output: 845


Remove nested button elements.

Button.Icon renders as a <button> element, creating nested interactive elements when wrapped in another <button>. This is invalid HTML and causes accessibility issues.

The outer <button> wrapper should be removed and the onclick handler and styling applied directly to Button.Icon. However, since Button.Icon doesn't render text children, you'll need to either:

  1. Use Button.Icon alone without the "go back" text:
-<button
-    onclick={() => {
-        stopCamera();
-        goto("/verify");
-    }}
-    class="cursor-pointer self-start flex items-center gap-2 text-sm text-gray-600 hover:text-gray-800"
->
-    <Button.Icon
-        icon={ArrowLeft01Icon}
-        iconColor="currentColor"
-        strokeWidth={2}
-        class="w-4 h-4"
-    />
-    <span>go back</span>
-</button>
+<Button.Icon
+    icon={ArrowLeft01Icon}
+    iconColor="currentColor"
+    strokeWidth={2}
+    class="w-4 h-4 cursor-pointer self-start"
+    onclick={() => {
+        stopCamera();
+        goto("/verify");
+    }}
+/>
  1. Or restructure using a <div> or anchor element that groups the icon and text without creating nested buttons.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/verify/steps/passport.svelte
around lines 233-247, the current markup nests Button.Icon (which renders a
<button>) inside another <button>; remove the outer <button> to avoid nested
interactive elements and instead wrap the icon and text in a non-button
container (e.g., <div> or <a>) or move the click handler onto Button.Icon and
keep the "go back" text as a sibling element outside the inner button; ensure
the click handler still calls stopCamera() and navigates to "/verify", preserve
the existing classes on the new wrapper or apply them to Button.Icon, and make
the wrapper accessible by adding role="button", tabindex="0" and keyboard
handlers for Enter/Space if you choose a non-anchor element.

Comment on lines 99 to 191
async function getApplicationPublicKey(): Promise<string> {
if (!globalState) throw new Error("Global state is not defined");
const keyManager = globalState.keyService.getKeyManagerForContext("verification");
const publicKey = await keyManager.getPublicKey("default");
return publicKey;
}

async function handleContinue() {
if ($status !== "approved" && $status !== "duplicate") {
return verifStep.set(0);
}
if (!globalState) throw new Error("Global state is not defined");
if (!$verificationPerson || !$verificationDocument) {
console.error("Missing verification data");
return;
}

loading = true;

try {
globalState.userController.user = {
name: capitalize(
`${$verificationPerson.firstName.value} ${$verificationPerson.lastName.value ?? ""}`,
),
"Date of Birth": new Date($verificationPerson.dateOfBirth.value).toDateString(),
"ID submitted":
$verificationDocument.type.value === "passport"
? `Passport - ${$verificationDocument.country.value}`
: $verificationDocument.type.value === "drivers_license"
? `Driving License - ${$verificationDocument.country.value}`
: `ID Card - ${$verificationDocument.country.value}`,
"Document Number": $verificationDocument.number.value,
};
globalState.userController.document = {
"Valid From": new Date($verificationDocument.validFrom.value).toDateString(),
"Valid Until": new Date($verificationDocument.validUntil.value).toDateString(),
"Verified On": new Date().toDateString(),
};
globalState.userController.isFake = false;

if ($status === "duplicate") {
// For duplicate case, skip provision and resolve the existing eVault URI
const existingW3id = $verificationWebsocketData?.w3id;
if (!existingW3id) {
throw new Error("No w3id provided for duplicate eVault");
}

// Resolve the eVault URI from the registry
const response = await axios.get(
new URL(
`resolve?w3id=${existingW3id}`,
PUBLIC_REGISTRY_URL,
).toString(),
);
// Skip profile creation for duplicates by setting status directly
globalState.vaultController.profileCreationStatus = "success";
// For duplicates, just set the vault without triggering profile creation
globalState.vaultController.vault = {
uri: response.data.uri,
ename: existingW3id,
};
} else {
// Normal flow for approved status
const {
data: { token: registryEntropy },
} = await axios.get(
new URL("/entropy", PUBLIC_REGISTRY_URL).toString(),
);
const { data } = await axios.post(
new URL("/provision", PUBLIC_PROVISIONER_URL).toString(),
{
registryEntropy,
namespace: uuidv4(),
verificationId: $verificaitonId,
publicKey: await getApplicationPublicKey(),
},
);
if (data.success === true) {
// Set vault in controller - this will trigger profile creation with retry logic
globalState.vaultController.vault = {
uri: data.uri,
ename: data.w3id,
};
}
}

// Go straight to register WITHOUT the 10-second delay
goto("/register");
} catch (error) {
console.error("Failed to provision:", error);
loading = false;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find the file and understand the current implementation
cd infrastructure/eid-wallet && find . -name "selfie.svelte" -type f

Repository: MetaState-Prototype-Project/prototype

Length of output: 127


🏁 Script executed:

# Search for API endpoint definitions and contracts
fd -e ts -e js -e svelte . | xargs grep -l "entropy\|provision\|resolve" | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 1415


🏁 Script executed:

# Look for backend code or services that handle these endpoints
fd -type d -name "*api*" -o -name "*services*" -o -name "*backend*" | head -10

Repository: MetaState-Prototype-Project/prototype

Length of output: 251


🏁 Script executed:

# Check for tests that might reveal the API contracts
fd -e test.ts -e test.js -e spec.ts -o -name "*.test.*" -o -name "*.spec.*" | xargs grep -l "provision\|entropy\|resolve" 2>/dev/null | head -10

Repository: MetaState-Prototype-Project/prototype

Length of output: 311


🏁 Script executed:

# Search for PUBLIC_REGISTRY_URL and PUBLIC_PROVISIONER_URL definitions
grep -r "PUBLIC_REGISTRY_URL\|PUBLIC_PROVISIONER_URL" infrastructure/eid-wallet/src --include="*.ts" --include="*.js" --include="*.svelte"

Repository: MetaState-Prototype-Project/prototype

Length of output: 3474


🏁 Script executed:

# Look for provision endpoint implementation
grep -r "provision" infrastructure --include="*.ts" --include="*.js" -l | grep -E "(routes|api|server)" | head -10

Repository: MetaState-Prototype-Project/prototype

Length of output: 199


🏁 Script executed:

# Look for entropy endpoint implementation
grep -r "entropy" infrastructure --include="*.ts" --include="*.js" -l | grep -E "(routes|api|server)" | head -10

Repository: MetaState-Prototype-Project/prototype

Length of output: 63


🏁 Script executed:

# Look for resolve endpoint in registry
grep -r "resolve" infrastructure --include="*.ts" --include="*.js" -l | grep -E "(routes|api|server|registry)" | head -10

Repository: MetaState-Prototype-Project/prototype

Length of output: 531


🏁 Script executed:

# Examine the evault-core server for endpoint implementations
cat -n infrastructure/evault-core/src/core/http/server.ts | head -100

Repository: MetaState-Prototype-Project/prototype

Length of output: 4034


🏁 Script executed:

# Check mock registry server for response contracts
cat -n infrastructure/evault-core/src/test-utils/mock-registry-server.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 3805


🏁 Script executed:

# Look for /provision endpoint specifically
grep -r "provision" infrastructure/evault-core/src --include="*.ts" -A 5 -B 2 | head -60

Repository: MetaState-Prototype-Project/prototype

Length of output: 5219


🏁 Script executed:

# Check the evault controller for how it handles responses
grep -r "success" infrastructure/eid-wallet/src/lib/global/controllers/evault.ts -B 3 -A 3

Repository: MetaState-Prototype-Project/prototype

Length of output: 3348


🏁 Script executed:

# Look at the full /provision endpoint implementation in server.ts
grep -n "provision" infrastructure/evault-core/src/core/http/server.ts | head -20

Repository: MetaState-Prototype-Project/prototype

Length of output: 435


🏁 Script executed:

# Find and view the /provision endpoint full implementation
sed -n '200,400p' infrastructure/evault-core/src/core/http/server.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 6283


🏁 Script executed:

# Search for /entropy endpoint
grep -n "entropy" infrastructure --include="*.ts" --include="*.js" -r

Repository: MetaState-Prototype-Project/prototype

Length of output: 5371


🏁 Script executed:

# Check ProvisioningService to understand response structure
find infrastructure -name "ProvisioningService*" -type f

Repository: MetaState-Prototype-Project/prototype

Length of output: 292


🏁 Script executed:

# Find the /entropy endpoint implementation
grep -n "/entropy" infrastructure/evault-core/src/core/http/server.ts -A 30 | head -50

Repository: MetaState-Prototype-Project/prototype

Length of output: 63


🏁 Script executed:

# Look at the full /provision endpoint response
sed -n '512,650p' infrastructure/evault-core/src/core/http/server.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 5046


🏁 Script executed:

# Check ProvisioningService implementation for response structure
cat -n infrastructure/evault-core/src/services/ProvisioningService.ts | head -200

Repository: MetaState-Prototype-Project/prototype

Length of output: 8881


🏁 Script executed:

# Look for where registry entropy endpoint is defined
find infrastructure -type f \( -name "*.ts" -o -name "*.js" \) -exec grep -l "\/entropy" {} \; | grep -E "(registry|server|routes)" | head -5

Repository: MetaState-Prototype-Project/prototype

Length of output: 63


🏁 Script executed:

# Check the rest of ProvisioningService error handling
sed -n '200,250p' infrastructure/evault-core/src/services/ProvisioningService.ts

Repository: MetaState-Prototype-Project/prototype

Length of output: 409


🏁 Script executed:

# Look for /entropy endpoint in registry or other services
find infrastructure -type f \( -name "*.ts" -o -name "*.js" \) -exec grep -l "get.*entropy\|\/entropy" {} \; | head -10

Repository: MetaState-Prototype-Project/prototype

Length of output: 121


🏁 Script executed:

# Verify the exact response structure by checking how selfie.svelte uses response.data
grep -n "response.data" infrastructure/eid-wallet/src/routes/\(auth\)/verify/steps/selfie.svelte -B 2 -A 2

Repository: MetaState-Prototype-Project/prototype

Length of output: 328


🏁 Script executed:

# Get the complete handleContinue function to see the full flow
sed -n '99,191p' infrastructure/eid-wallet/src/routes/\(auth\)/verify/steps/selfie.svelte

Repository: MetaState-Prototype-Project/prototype

Length of output: 3957


Only navigate to /register once vault resolution/provision has actually succeeded

In handleContinue, goto("/register") is executed unconditionally at the end of the try block:

  • For the "approved" path, if /entropy or /provision fails, or if data.success !== true, the vault is never set but navigation still occurs, leaving the downstream registration flow in a broken state.
  • For the "duplicate" path, if the registry resolve call returns an unexpected response (e.g., missing uri field), vault setup fails silently but navigation proceeds.

This makes failures difficult to detect in the UI and breaks downstream flows that assume a populated vault.

Consider gating navigation on confirmed success and clearing loading on failure, for example:

 async function handleContinue() {
-    loading = true;
+    loading = true;
 
     try {
+        let hasVault = false;
+
         if ($status === "duplicate") {
             const response = await axios.get(
                 new URL(
                     `resolve?w3id=${existingW3id}`,
                     PUBLIC_REGISTRY_URL,
                 ).toString(),
             );
             globalState.vaultController.profileCreationStatus = "success";
             globalState.vaultController.vault = {
                 uri: response.data.uri,
                 ename: existingW3id,
             };
+            hasVault = true;
         } else {
             const {
                 data: { token: registryEntropy },
             } = await axios.get(
                 new URL("/entropy", PUBLIC_REGISTRY_URL).toString(),
             );
             const { data } = await axios.post(
                 new URL("/provision", PUBLIC_PROVISIONER_URL).toString(),
                 {
                     registryEntropy,
                     namespace: uuidv4(),
                     verificationId: $verificaitonId,
                     publicKey: await getApplicationPublicKey(),
                 },
             );
             if (data.success === true) {
                 globalState.vaultController.vault = {
                     uri: data.uri,
                     ename: data.w3id,
                 };
+                hasVault = true;
             }
         }
 
-        // Go straight to register WITHOUT the 10-second delay
-        goto("/register");
+        // Only navigate when vault is confirmed
+        if (hasVault) {
+            goto("/register");
+        } else {
+            loading = false;
+        }
     } catch (error) {
         console.error("Failed to provision:", error);
         loading = false;
     }
 }

This keeps the user on the results screen when provisioning or resolution fails instead of navigating into a broken registration state.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
in infrastructure/eid-wallet/src/routes/(auth)/verify/steps/selfie.svelte around
lines 99 to 191, the code unconditionally calls goto("/register") even if vault
resolution/provision failed; change the control flow so navigation only happens
after confirming the vault was successfully set: validate the registry resolve
response contains response.data.uri before assigning vault for the duplicate
branch and abort/throw (or return) with loading = false on missing data; for the
approved branch confirm the POST /provision returned data.success === true and
data.uri/data.w3id exist before assigning vault and only then call
goto("/register"); ensure any failure paths set loading = false and do not
navigate so the user remains on the result screen for retry or error display.

@coodos coodos closed this Dec 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant