Skip to content

feat: streamline onboarding — remove carousel, add dismissible PWA install banner - #375

Merged
Kukks merged 20 commits into
nextfrom
wt-streamline-onboarding-20260305
Mar 11, 2026
Merged

Kukks merged 20 commits into
nextfrom
wt-streamline-onboarding-20260305

Conversation

@sahilc0

@sahilc0 sahilc0 commented Mar 6, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Streamlines the new-user experience by removing the 4-step onboarding carousel and adding a new dismissible PWA install banner on the wallet page.

What changed:

  1. Onboarding carousel removed — New users now go directly from the welcome screen → wallet creation → wallet page. No more multi-step "Welcome to Arkade" slideshow. The Init screen is redesigned with an animated wallet icon, stagger animations, and streamlined copy.

  2. New DismissibleBanner component — A reusable animated banner with:

    • Framer Motion enter/exit animations (ease-out-quint, y:-24, scale:0.97)
    • Horizontal swipe-to-dismiss gesture on touch devices
    • Layout animation for smooth sibling reflow
    • prefers-reduced-motion support (disables layout, drag, popLayout)
    • 44px minimum touch targets
  3. PWA install banner — Shows on mobile only (iOS/Android), gated behind:

    • pwaCanInstall() — service worker support, not standalone
    • isIOS() || isAndroid() — mobile platforms only
    • Not already installed (usePwaInstalled() hook)
    • Not previously dismissed (persisted in config.dismissedBanners)
    • On Android: native install prompt via beforeinstallprompt
    • On iOS: instruction-only banner (no programmatic install API exists)
  4. Password nudge upgraded — The old CreatePasswordWarning (AlertBox) replaced with DismissibleBanner. Added nudgeVisible/nudgeCheckComplete to context for PWA banner priority coordination. Fixed error handling on async password check (.catch/.finally).

  5. Announcement/PSA system preserved — Announcement.tsx, announcements.tsx provider, AnnouncementProvider, psaMessage, AlertBox.tsx (for InfoBox) all kept intact from master per team request. All announcements currently inactive.

Files

New

  • src/components/DismissibleBanner.tsx — Reusable animated dismissible banner
  • src/lib/pwa.ts — PWA install utilities (hooks, beforeinstallprompt capture)

Modified

  • src/screens/Wallet/Index.tsx — PWA banner integration + announcement system
  • src/screens/Init/Init.tsx — Redesigned welcome screen (animated icon, stagger)
  • src/screens/Init/Password.tsx — Navigate to InitConnect, fix stale restoring flag
  • src/screens/Init/Restore.tsx — Navigate to InitConnect
  • src/providers/nudge.tsx — DismissibleBanner, error handling, nudgeVisible/checkComplete
  • src/providers/navigation.tsx — Remove Onboard from Pages enum
  • src/providers/config.tsx — Add dismissedBanners: [] to defaults
  • src/lib/types.ts — Add dismissedBanners: string[] to Config
  • src/ionic.css — Add --purple10 color variable
  • src/App.tsx — Route to Init instead of Onboard for new wallets
  • src/test/setup.ts — Add matchMedia mock
  • src/test/e2e/utils.ts — Remove onboard steps from test helpers

Deleted (~11,000 lines)

  • src/screens/Wallet/Onboard.tsx — 4-step carousel screen
  • src/icons/Onboard1-4.tsx — SVG illustrations for carousel
  • src/components/Bullet.tsx, StepBars.tsx — Carousel UI components
  • Associated test files

Test plan

  • Build passes (bun run build)
  • Prettier + ESLint pass
  • New wallet creation flow: Init → Create → Wallet (no carousel)
  • Restore wallet flow: Init → Restore → Wallet (no carousel)
  • PWA banner shows on mobile (tested via Cloudflare Pages deploy)
  • PWA banner hidden on desktop
  • PWA banner dismisses and persists dismissal
  • Password nudge shows with DismissibleBanner when applicable
  • Announcement system intact (all inactive, renderable via env var)
  • PSA message display works via VITE_PSA_MESSAGE env var
  • iOS: instruction-only banner (no Install button, clear Safari share instructions)
  • Android: Install button triggers native beforeinstallprompt

Summary by CodeRabbit

Release Notes

  • New Features

    • Added dismissible banner UI for PWA installation prompts with support for drag-to-dismiss and reduced-motion preferences.
    • Improved Init screen layout with motion animations.
  • Bug Fixes

    • Simplified wallet initialization flow by removing conditional navigation paths.
  • Removed Features

    • Removed multi-step onboarding flow.
    • Removed unused UI components from navigation.

@sahilc0
sahilc0 requested review from bordalix and pietro909 March 6, 2026 19:04
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Mar 6, 2026 •

Copy link
Copy Markdown

Deploying wallet-bitcoin with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8a482ba
Status: ✅  Deploy successful!
Preview URL: https://0a71f727.wallet-bitcoin.pages.dev
Branch Preview URL: https://wt-streamline-onboarding-202.wallet-bitcoin.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Mar 6, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • next-version

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8a602b46-0db7-4e77-a189-7607872dafc0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR removes the multi-step onboarding flow from the wallet initialization and integrates PWA installation prompting directly into the Wallet screen. The initialization path is simplified to always navigate to Init instead of conditionally navigating to Onboard. Related UI components and icons are deleted, and a new DismissibleBanner component is introduced to support dismissible notifications with drag interactions.

Changes

Cohort / File(s) Summary
Onboarding Flow Removal
src/screens/Wallet/Onboard.tsx, src/icons/Onboard2.tsx, src/icons/Onboard4.tsx, src/components/StepBars.tsx, src/providers/navigation.tsx
Removed the entire Onboard screen component, associated SVG icon components, step indicator UI, and navigation routing for the onboarding flow.
PWA Installation Integration
src/lib/pwa.ts, src/screens/Wallet/Index.tsx, src/App.tsx
Added PWA installation detection hook, prompt utilities, and integrated dismissible PWA install banner into Wallet screen; simplified wallet initialization to always navigate to Init.
Dismissible Banner Component
src/components/DismissibleBanner.tsx, src/ionic.css
Introduced new DismissibleBanner component with drag-to-dismiss and reduced-motion support; added purple10 CSS variable for styling.
Nudge Provider Refactor
src/providers/nudge.tsx
Refactored nudge context to use DismissibleBanner with LogoIconAnimated; exposed nudgeVisible and nudgeCheckComplete state; updated dismissal handling.
Init Screen & Navigation Updates
src/screens/Init/Init.tsx, src/screens/Init/Password.tsx, src/screens/Init/Restore.tsx
Updated Init screen with Framer Motion-based layout and reduced-motion support; changed post-wallet-creation navigation from InitSuccess to InitConnect across multiple init paths.
Configuration & Type Updates
src/lib/types.ts, src/providers/config.tsx
Added dismissedBanners array to Config type and default config for tracking dismissed banner state.
Removed Components
src/components/Bullet.tsx
Removed unused Bullet badge component.
Test Updates
src/test/components/*.test.tsx, src/test/screens/wallet/onboard.test.tsx, src/test/e2e/utils.ts, src/test/setup.ts
Deleted tests for removed components (Bullet, Onboard, AlertBox); updated e2e test utils to skip removed onboarding steps; enhanced window.matchMedia mock in test setup.

Sequence Diagram

sequenceDiagram
    actor User
    participant App as App/Wallet
    participant PWA as PWA API
    participant Config as Config Context
    participant UI as DismissibleBanner

    User->>App: Navigate to Wallet
    App->>PWA: Check canPromptInstall()
    App->>App: Evaluate device type & install status
    App->>Config: Read dismissedBanners
    
    alt Banner not dismissed && can install
        App->>UI: Render PWA install banner
        Note over UI: Show with drag-to-dismiss
        
        alt User clicks action
            User->>UI: Tap install button
            UI->>PWA: promptPwaInstall()
            PWA->>PWA: Show install prompt
            User->>PWA: Accept/Dismiss prompt
        else User drags to dismiss
            User->>UI: Drag horizontally > 100px
            UI->>App: onDismiss callback
        end
        
        App->>Config: Update dismissedBanners
    else Banner dismissed or cannot install
        App->>UI: Skip banner rendering
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • pietro909
  • bordalix
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the main changes: removing the onboarding carousel and adding a dismissible PWA install banner, which aligns with the primary objectives of the changeset.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch wt-streamline-onboarding-20260305

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.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Mar 6, 2026 •

Copy link
Copy Markdown

Deploying wallet-mutinynet with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8a482ba
Status: ✅  Deploy successful!
Preview URL: https://952878b0.arkade-wallet.pages.dev
Branch Preview URL: https://wt-streamline-onboarding-202.arkade-wallet.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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)
src/screens/Init/Password.tsx (1)

33-39: ⚠️ Potential issue | 🟠 Major

Reset restoring on the password path.

src/screens/Init/Connect.tsx:34-40 now uses initInfo.restoring to decide whether to wait for arkadeLightning and run restoreSwaps(). Because both branches keep ...initInfo, a stale restoring: true from an earlier restore attempt can leak into this fresh-wallet flow after the new Pages.InitConnect navigation.

💡 Proposed fix
       .then(({ password, passkeyId }) => {
         updateWallet({ ...wallet, lockedByBiometrics: true, passkeyId })
-        setInitInfo({ ...initInfo, password })
+        setInitInfo({ ...initInfo, password, restoring: false })
         navigate(Pages.InitConnect)
       })
@@
   const handleContinue = () => {
     const pass = password ? password : defaultPassword
-    setInitInfo({ ...initInfo, password: pass })
+    setInitInfo({ ...initInfo, password: pass, restoring: false })
     navigate(Pages.InitConnect)
   }

Also applies to: 43-46

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/screens/Init/Password.tsx` around lines 33 - 39, When registering a new
user via registerUserBiometrics (which calls registerUser and then
updateWallet/setInitInfo and navigate to Pages.InitConnect), clear any stale
restore state by ensuring setInitInfo includes restoring: false (e.g.,
setInitInfo({ ...initInfo, password, restoring: false })). Update both places
where setInitInfo is called after registerUser (and the other branch around
lines 43-46) so the new initInfo explicitly sets restoring to false to avoid
carrying over a previous restore attempt.
🧹 Nitpick comments (1)
src/lib/types.ts (1)

23-23: Type banner IDs centrally.

dismissedBanners: string[] leaves IDs like 'pwa-install' unchecked across config writers/readers, so a typo silently breaks persistence. A shared BannerId union/enum would let TypeScript catch that.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/types.ts` at line 23, Replace the loose string array type for
dismissed banners with a centralized BannerId type: declare a BannerId union (or
enum) listing all valid banner IDs (e.g., "pwa-install", etc.) in the same
module and change the dismissedBanners declaration from dismissedBanners:
string[] to dismissedBanners: BannerId[]; then update any code that reads/writes
dismissedBanners (parsers, serializers, defaults, tests) to use BannerId values
so TypeScript will catch typos and invalid IDs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/DismissibleBanner.tsx`:
- Around line 109-140: The reduced-motion path still triggers layout reflow
because AnimatePresence is given mode='popLayout' and the motion.div always has
layout; update the component to conditionally disable layout and the popLayout
mode when prefersReduced is true: pass mode={prefersReduced ? undefined :
'popLayout'} to AnimatePresence and only include the layout prop on motion.div
when prefersReduced is false (also ensure initial/animate/exit props that
perform transforms are skipped when prefersReduced is true); locate the
AnimatePresence and motion.div usage to apply these conditional props
(references: AnimatePresence, mode='popLayout', motion.div, layout,
prefersReduced, handleDragEnd).

In `@src/lib/pwa.ts`:
- Around line 51-59: The promptPwaInstall function currently discards the native
prompt outcome and leaves callers unable to react; update promptPwaInstall (and
its use) to return the awaited p.userChoice result (i.e., await p.prompt();
const outcome = await p.userChoice; return outcome) so callers can inspect
outcome.outcome and act (hide/retarget UI). Also ensure banner visibility logic
uses canPromptInstall() (which checks _deferredPrompt) or inspects the returned
outcome, rather than relying on pwaCanInstall(), so the banner is hidden when
the one-shot prompt has been consumed.

In `@src/screens/Wallet/Index.tsx`:
- Around line 39-43: The banner currently shows an actionable "Install" button
even when the browser cannot programmatically prompt installation; update the
Wallet/Index.tsx UI so the action button is rendered only when
canPromptInstall() returns true (keep using showPwaBanner, pwaCanInstall(),
isIOS(), isAndroid(), pwaInstalled, dismissed and pwaDescription as before), and
when canPromptInstall() is false render a non-actionable instruction-only banner
(no navigator.share fallback or other misleading install action) so iOS/manual
flows remain informational; ensure any existing click handlers that call
prompt() are only attached/used when canPromptInstall() is true.

In `@src/test/setup.ts`:
- Around line 8-20: The global matchMedia mock (window.matchMedia) is a
persistent vi.fn() whose call history can leak between tests; before restoring
spies you should clear all mocks. In the test setup update the teardown or
before/after hook to call vi.clearAllMocks() (in addition to
vi.restoreAllMocks()) so that window.matchMedia and other mocks have their call
history reset between tests; locate the code around the matchMedia definition
and ensure vi.clearAllMocks() runs in the test lifecycle (e.g., afterEach or
beforeEach) alongside vi.restoreAllMocks().

---

Outside diff comments:
In `@src/screens/Init/Password.tsx`:
- Around line 33-39: When registering a new user via registerUserBiometrics
(which calls registerUser and then updateWallet/setInitInfo and navigate to
Pages.InitConnect), clear any stale restore state by ensuring setInitInfo
includes restoring: false (e.g., setInitInfo({ ...initInfo, password, restoring:
false })). Update both places where setInitInfo is called after registerUser
(and the other branch around lines 43-46) so the new initInfo explicitly sets
restoring to false to avoid carrying over a previous restore attempt.

---

Nitpick comments:
In `@src/lib/types.ts`:
- Line 23: Replace the loose string array type for dismissed banners with a
centralized BannerId type: declare a BannerId union (or enum) listing all valid
banner IDs (e.g., "pwa-install", etc.) in the same module and change the
dismissedBanners declaration from dismissedBanners: string[] to
dismissedBanners: BannerId[]; then update any code that reads/writes
dismissedBanners (parsers, serializers, defaults, tests) to use BannerId values
so TypeScript will catch typos and invalid IDs.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7b509551-0900-4934-bdb7-23dd5e033128

📥 Commits

Reviewing files that changed from the base of the PR and between ee6162d and f5d347d.

📒 Files selected for processing (29)
  • src/App.tsx
  • src/components/AlertBox.tsx
  • src/components/Announcement.tsx
  • src/components/Bullet.tsx
  • src/components/DismissibleBanner.tsx
  • src/components/StepBars.tsx
  • src/icons/Onboard1.tsx
  • src/icons/Onboard2.tsx
  • src/icons/Onboard3.tsx
  • src/icons/Onboard4.tsx
  • src/index.tsx
  • src/ionic.css
  • src/lib/constants.ts
  • src/lib/pwa.ts
  • src/lib/types.ts
  • src/providers/announcements.tsx
  • src/providers/config.tsx
  • src/providers/navigation.tsx
  • src/providers/nudge.tsx
  • src/screens/Init/Init.tsx
  • src/screens/Init/Password.tsx
  • src/screens/Init/Restore.tsx
  • src/screens/Wallet/Index.tsx
  • src/screens/Wallet/Onboard.tsx
  • src/test/components/AlertBox.test.tsx
  • src/test/components/Bullet.test.tsx
  • src/test/e2e/utils.ts
  • src/test/screens/wallet/onboard.test.tsx
  • src/test/setup.ts
💤 Files with no reviewable changes (13)
  • src/lib/constants.ts
  • src/icons/Onboard2.tsx
  • src/test/components/Bullet.test.tsx
  • src/test/e2e/utils.ts
  • src/components/Bullet.tsx
  • src/icons/Onboard4.tsx
  • src/components/Announcement.tsx
  • src/screens/Wallet/Onboard.tsx
  • src/test/components/AlertBox.test.tsx
  • src/components/AlertBox.tsx
  • src/test/screens/wallet/onboard.test.tsx
  • src/components/StepBars.tsx
  • src/providers/announcements.tsx

Comment thread src/components/DismissibleBanner.tsx Resolved
Comment thread src/lib/pwa.ts Resolved
Comment thread src/screens/Wallet/Index.tsx Resolved
Comment thread src/test/setup.ts
Comment on lines +8 to +20
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})

Copy link
Copy Markdown
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:

cat -n src/test/setup.ts

Repository: arkade-os/wallet

Length of output: 1158


🌐 Web query:

Vitest vi.restoreAllMocks vi.clearAllMocks behavior difference

💡 Result:

vi.clearAllMocks() vs vi.restoreAllMocks() in Vitest

  • vi.clearAllMocks(): calls .mockClear() on all spies/mocks, meaning it clears call history (calls, instances, results) without changing the current mock implementation. [1]

  • vi.restoreAllMocks(): restores the original (real) implementations, but only for spies created with vi.spyOn(). It does not clear call history and does not reset the mock implementation state (unlike calling .mockRestore() on an individual spy). It also does not affect automocked mocks. [1]

Important Vitest v4+ behavior note

In Vitest 4, vi.restoreAllMocks() no longer resets spy state and only restores manually created vi.spyOn spies (automocks are not affected). This also impacts the restoreMocks config behavior. [2]

Quick rule of thumb

  • Want to keep the mocking in place but remove “was called” pollution between tests? Use vi.clearAllMocks().
  • Want to undo vi.spyOn(obj, 'method') and put the real method back? Use vi.restoreAllMocks().

Sources: [1] [2]


Reset the global matchMedia mock between tests.

window.matchMedia is a module-scoped vi.fn(), but vi.restoreAllMocks() only restores vi.spyOn() spies and does not clear mock call history. That lets matchMedia call counts leak between tests. Add vi.clearAllMocks() to clear all mocks before restoring, so each test starts clean.

🧪 Minimal cleanup fix
 afterEach(() => {
+  vi.clearAllMocks()
   vi.restoreAllMocks()
 })

Also applies to: 29-30

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/setup.ts` around lines 8 - 20, The global matchMedia mock
(window.matchMedia) is a persistent vi.fn() whose call history can leak between
tests; before restoring spies you should clear all mocks. In the test setup
update the teardown or before/after hook to call vi.clearAllMocks() (in addition
to vi.restoreAllMocks()) so that window.matchMedia and other mocks have their
call history reset between tests; locate the code around the matchMedia
definition and ensure vi.clearAllMocks() runs in the test lifecycle (e.g.,
afterEach or beforeEach) alongside vi.restoreAllMocks().

@sahilc0
sahilc0 force-pushed the wt-streamline-onboarding-20260305 branch from f5d347d to 3032c6f Compare March 6, 2026 19:29
Comment thread src/lib/pwa.ts
Comment on lines +11 to +31
export function usePwaInstalled(): boolean {
const [installed, setInstalled] = useState(() => pwaIsInstalled())

useEffect(() => {
const mq = window.matchMedia('(display-mode: standalone)')
const handleChange = (e: MediaQueryListEvent) => {
if (e.matches) setInstalled(true)
}
mq.addEventListener('change', handleChange)

const handleInstall = () => setInstalled(true)
window.addEventListener('appinstalled', handleInstall)

return () => {
mq.removeEventListener('change', handleChange)
window.removeEventListener('appinstalled', handleInstall)
}
}, [])

return installed
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@Kukks you spent lot of time on this already, is this code extra needed?

@sahilc0 sahilc0 changed the title feat: streamline onboarding, add dismissible PWA install banner feat: streamline onboarding — remove carousel, add dismissible PWA install banner Mar 6, 2026
@sahilc0

sahilc0 commented Mar 6, 2026 •

Copy link
Copy Markdown
Contributor Author

@tiero rebased and addressed comments in slack, waiting on coderabbit to fix any changed but feel free to review again

PR description updated accordingly

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
src/lib/pwa.ts (1)

53-59: ⚠️ Potential issue | 🟠 Major

Return the prompt outcome to enable callers to react appropriately.

This issue was flagged in a previous review. promptPwaInstall() discards the userChoice outcome. After the user dismisses the native Android prompt, _deferredPrompt is nulled but the banner may still be visible until manually dismissed. Returning the outcome allows the caller to auto-dismiss the banner when the user accepts or declines.

,

Suggested fix
-export const promptPwaInstall = async () => {
-  if (!_deferredPrompt) return
+export const promptPwaInstall = async (): Promise<'accepted' | 'dismissed' | null> => {
+  if (!_deferredPrompt) return null
   const p = _deferredPrompt
   _deferredPrompt = null
   await p.prompt()
-  await p.userChoice
+  return (await p.userChoice).outcome
 }

Then in Wallet/Index.tsx, use the outcome to auto-dismiss:

onClick: async () => {
  const outcome = await promptPwaInstall()
  if (outcome) dismissPwaBanner()
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/pwa.ts` around lines 53 - 59, promptPwaInstall currently discards the
PWA install prompt outcome; change it to await p.userChoice and return that
result so callers can react (e.g., auto-dismiss the banner). Locate the
promptPwaInstall function and modify it to capture the value from await
p.userChoice (after calling await p.prompt()) and return that value; keep the
existing _deferredPrompt nulling behavior but ensure the userChoice promise
result is forwarded to the caller.
🧹 Nitpick comments (1)
src/screens/Wallet/Index.tsx (1)

104-106: Consider auto-dismissing the banner after the native prompt is consumed.

After the user accepts or declines the native install prompt, the banner remains visible until manually dismissed. If promptPwaInstall() is updated to return the outcome (per the related comment in pwa.ts), you could auto-dismiss here:

onClick: async () => {
  const outcome = await promptPwaInstall()
  if (outcome) dismissPwaBanner()
}

This provides a smoother UX on Android where the native prompt is one-shot.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/screens/Wallet/Index.tsx` around lines 104 - 106, Update the onClick
handler in Wallet/Index.tsx to await promptPwaInstall() and auto-dismiss the
banner when the native prompt is consumed: call promptPwaInstall() (now
returning an outcome) inside an async handler, capture the returned outcome, and
invoke dismissPwaBanner() when the outcome indicates the prompt was
shown/consumed; also handle/rethrow errors as needed instead of swallowing
silently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/lib/pwa.ts`:
- Around line 53-59: promptPwaInstall currently discards the PWA install prompt
outcome; change it to await p.userChoice and return that result so callers can
react (e.g., auto-dismiss the banner). Locate the promptPwaInstall function and
modify it to capture the value from await p.userChoice (after calling await
p.prompt()) and return that value; keep the existing _deferredPrompt nulling
behavior but ensure the userChoice promise result is forwarded to the caller.

---

Nitpick comments:
In `@src/screens/Wallet/Index.tsx`:
- Around line 104-106: Update the onClick handler in Wallet/Index.tsx to await
promptPwaInstall() and auto-dismiss the banner when the native prompt is
consumed: call promptPwaInstall() (now returning an outcome) inside an async
handler, capture the returned outcome, and invoke dismissPwaBanner() when the
outcome indicates the prompt was shown/consumed; also handle/rethrow errors as
needed instead of swallowing silently.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9747f77f-7c7b-42af-90b5-a71012291a97

📥 Commits

Reviewing files that changed from the base of the PR and between f5d347d and 9bbeba1.

📒 Files selected for processing (24)
  • src/App.tsx
  • src/components/Bullet.tsx
  • src/components/DismissibleBanner.tsx
  • src/components/StepBars.tsx
  • src/icons/Onboard1.tsx
  • src/icons/Onboard2.tsx
  • src/icons/Onboard3.tsx
  • src/icons/Onboard4.tsx
  • src/ionic.css
  • src/lib/pwa.ts
  • src/lib/types.ts
  • src/providers/config.tsx
  • src/providers/navigation.tsx
  • src/providers/nudge.tsx
  • src/screens/Init/Init.tsx
  • src/screens/Init/Password.tsx
  • src/screens/Init/Restore.tsx
  • src/screens/Wallet/Index.tsx
  • src/screens/Wallet/Onboard.tsx
  • src/test/components/AlertBox.test.tsx
  • src/test/components/Bullet.test.tsx
  • src/test/e2e/utils.ts
  • src/test/screens/wallet/onboard.test.tsx
  • src/test/setup.ts
💤 Files with no reviewable changes (9)
  • src/test/e2e/utils.ts
  • src/screens/Wallet/Onboard.tsx
  • src/test/components/AlertBox.test.tsx
  • src/test/screens/wallet/onboard.test.tsx
  • src/test/components/Bullet.test.tsx
  • src/icons/Onboard4.tsx
  • src/components/Bullet.tsx
  • src/icons/Onboard2.tsx
  • src/components/StepBars.tsx
🚧 Files skipped from review as they are similar to previous changes (8)
  • src/screens/Init/Password.tsx
  • src/components/DismissibleBanner.tsx
  • src/ionic.css
  • src/App.tsx
  • src/lib/types.ts
  • src/providers/navigation.tsx
  • src/test/setup.ts
  • src/providers/config.tsx

sahilc0 and others added 6 commits March 10, 2026 15:19
Remove the 4-step onboarding carousel (Onboard.tsx) and all supporting
components (StepBars, Bullet, AlertBox, Announcement, Onboard1-4 SVGs).
New users now go directly from Init → InitConnect → Wallet instead of
through the carousel. The announcement/PSA system is also removed as it
interrupted the flow without providing value.

Files removed:
- src/screens/Wallet/Onboard.tsx (carousel screen)
- src/components/AlertBox.tsx, Announcement.tsx, Bullet.tsx, StepBars.tsx
- src/icons/Onboard1-4.tsx (~10,800 lines of SVG illustrations)
- src/providers/announcements.tsx (PSA announcement system)
- Associated tests for removed components

Files modified:
- src/App.tsx: route to Init instead of Onboard for new wallets
- src/screens/Init/*.tsx: navigate to InitConnect instead of InitSuccess
- src/providers/navigation.tsx: remove Onboard from Pages enum
- src/index.tsx: remove AnnouncementProvider wrapper
- src/test/e2e/utils.ts: remove onboard steps from createWallet helper
Add a new DismissibleBanner component used for both the PWA install
prompt and the password creation nudge. The banner features Framer Motion
enter/exit animations (EASE_OUT_QUINT, y:-24, scale:0.97), horizontal
swipe-to-dismiss gesture, layout animation for smooth sibling reflow,
and prefers-reduced-motion support with 44px touch targets.

PWA install banner:
- Mobile-only gating (isIOS || isAndroid) — hidden on desktop
- Platform-specific descriptions (iOS share sheet vs Android install)
- Module-level beforeinstallprompt capture for native install prompt
- Respects persistent dismissed state via config.dismissedBanners

Password nudge:
- Replaced old AlertBox with DismissibleBanner (expanded layout)
- Added nudgeVisible/nudgeCheckComplete to context for PWA banner
  priority coordination (PWA banner waits for nudge check)
- Fixed error handling: .catch/.finally on async password check

Config: replaced announcementsSeen with dismissedBanners (existing
configs handled gracefully via ?? [] fallbacks).

New files:
- src/components/DismissibleBanner.tsx

Modified:
- src/screens/Wallet/Index.tsx (PWA banner integration)
- src/providers/nudge.tsx (DismissibleBanner + error handling)
- src/lib/pwa.ts (usePwaInstalled, beforeinstallprompt, promptPwaInstall)
- src/lib/types.ts, src/providers/config.tsx (dismissedBanners)
- src/ionic.css (--purple10 color variable)
- src/test/setup.ts (matchMedia mock)
- Password.tsx: reset restoring flag when navigating to InitConnect
  to prevent stale state from a previous restore attempt
- DismissibleBanner.tsx: disable layout animation and popLayout mode
  when prefers-reduced-motion is active
- Wallet/Index.tsx: only show Install action button when native
  prompt is available (Android); iOS shows instruction-only banner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update the iOS PWA install banner description to reference "the share
icon in Safari's toolbar" instead of generic "the share button" so
users know exactly where to look.
promptPwaInstall() now returns 'accepted' | 'dismissed' | null instead
of void. On Android, the banner auto-dismisses after the user interacts
with the native install prompt, rather than staying visible.
Fire hapticSubtle() on button taps (Dismiss, Install, Set password)
and on swipe-to-dismiss gesture, consistent with all other interactive
elements in the app.
@sahilc0
sahilc0 force-pushed the wt-streamline-onboarding-20260305 branch from 1deef8f to caa3f4c Compare March 10, 2026 20:20
@sahilc0
sahilc0 changed the base branch from master to next March 10, 2026 20:20
sahilc0 added 8 commits March 10, 2026 15:20
…ffects

Redesign the Init screen with a rich onboarding animation:

- OnboardingLogo: SVG cross-fade between Arcade, Space Invader, and Heart
  shapes with deep squash-stretch bounces using Framer Motion async
  animation controls. Final bounce flies logo to header position via FLIP.
- PixelSplash: burst of purple pixel particles on each bounce
- PixelSunrise: pixelated purple gradient at screen top (fixed position)
- Three bullet points with outlined icon components (Bolt, Globe, ShieldCheck)
  replacing the old paragraph text
- Reduced motion: all animations skip, content renders instantly

Files: OnboardingLogo.tsx, PixelSplash.tsx, PixelSunrise.tsx,
BoltOutline.tsx, GlobeOutline.tsx, ShieldCheckOutline.tsx,
Init.tsx, ionic.css
- PixelSunrise: replace pixelated grid with smooth radial-gradient,
  remove all pixel dissolution code
- Init: move content to bottom of screen (flex-end + paddingBottom),
  stack logo above title with optical alignment to bullet icons
- Status bar: add apple-mobile-web-app-capable for PWA bleed-through,
  set theme-color to #8771BC (computed gradient top on white),
  fix manifest.json theme_color mismatch
- Dynamic theme-color useEffect on Init screen for Safari tinting
- Always render content in DOM (hidden until ready) so logoTargetRef
  position is stable during fly animation instead of shifting on mount
- Portal OnboardingLogo and PixelSunrise to document.body so they
  escape IonPage's max-width: 640px container on desktop
- Add color-scheme light/dark to :root and .ion-palette-dark
- Add color-scheme meta tag and body background-color for Safari
- Sunrise animation synced with fly via onFlyStart callback
- Snappier bounce physics: deeper squash, higher overshoot, faster timings
Logo morph animation:
- Rewrite OnboardingLogo to use pixel-shapes.ts grid system instead of
  opacity crossfade between SVG path components
- Arcade "A" always renders as clean SVG paths (never pixelated), while
  Invader and Heart shapes use pixel rects with CSS transform transitions
- Spring-based bounce recovery (stiffness: 600, damping: 18, mass: 0.6)
  replaces scripted multi-step keyframes for natural overshoot feel
- Morph + bounce happen simultaneously — shape swap at squash peak
- Faster timing: 180ms initial pause, 60ms between morphs, ~1s total
- Portal to document.body so logo escapes IonPage max-width on desktop

Pixel splash particles:
- Increase from 12-17 to 20-28 particles per burst
- Wider velocity range (25-105) and more size variation (2.5-6.5px)

Skip loading screen for new users:
- New users (no wallet in storage) bypass ASP + JS capability gates,
  going straight to Init where the logo morph serves as intro visual
- Wallet provider reads from localStorage synchronously via lazy
  useState initializer, eliminating one-frame Loading flash
- "Create wallet" button stays disabled until ASP resolves

Revert status bar changes:
- All theme-color, color-scheme, and apple-mobile-web-app changes from
  this branch reverted — will be handled in a separate PR
- Remove temp replay button and associated state

Files: OnboardingLogo.tsx, PixelSplash.tsx, Init.tsx, App.tsx,
       wallet.tsx, index.html, manifest.json, ionic.css, index.css
walletLoaded is now always true (synchronous init), so the setter is
dead code. Replace useState with a constant to satisfy eslint.
@sahilc0
sahilc0 requested a review from Kukks March 10, 2026 20:23
Comment thread src/screens/Init/Init.tsx Resolved
tiero and others added 6 commits March 11, 2026 01:01
Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
…5' into wt-onboarding-delight-20260306

# Conflicts:
#	src/screens/Init/Init.tsx
feat(onboarding): animated logo morph with spring physics
Rollup's module concatenation was dropping the default export definition
while keeping the JSX references, causing 'PageTransition is not defined'
at runtime. Named exports are handled more reliably.
…page)

The onboarding flow now goes Init → InitConnect → Wallet directly,
skipping the InitSuccess page. The createWallet() and restoreWallet()
helpers were waiting for "Your new wallet is live!" / clicking
"Go to wallet" which no longer exist, causing all Playwright tests
to hang indefinitely.
@Kukks
Kukks merged commit ae97cf0 into next Mar 11, 2026
5 checks passed
pietro909 added a commit that referenced this pull request Mar 12, 2026
* Use next branches

* Implements chain swaps

* fixes

* Use new SDK storage

* Update integration to Contract Manager

* App starting, payments received

* Use correct import

* Use new SDK storage

* wip

* SW working

* using ws

* Latest SDK

* workaround for build

* Fix build

* Rollback unneeeded changes, clean up code

* Address Rabbit comment

* Set flag in unsub

* Fix tx history rendering issue

* Update package.json, lock file, worker

* Run migration only if needed

* Update lock and worker

* Use next releases

* Clear setInterval on re-init to prevent leaking duplicate intervals

* Implements delegators UI (#319)

* implements delegators UI

* rabbit fixes

* dumb down + rename to delegates

* revert changes

* add unit tests

* add e2e test

* rabbit fixes

* fixes

* Upgrade latest sdk

* fixes

* Ignore md helpers

* Stop pinging wallet when locked

* Allow the page to force activation of a newly installed worker.

* Handle failure in getCommitmentTxIds

* Add defensive parsing of messages for stale service workers

* Upgrade sdk and boltz-swap

* fix Indexer constructor in wallet provider

* Use event.waitUntil

* update dependencies

* bug fix

* update dependencies

* default delegator

* fix URL

* delegator

* make delegator URL configurable

* Wire up delegates

* Centralize compressed-xonly conversion, hook in delegates

* Trigger reload on delegate on/off

* Delegates on by default

* Use persisted preference for delegates

* Fix type in wallet

* Remove workspace file

* Fix formatting

* Enforce use of delegate upon restore

* Use correct delegates port, update e2e test

* Remove log

* Update Fulmine 0.3.15 and port mapping

* Change fulmine port in setup script

* fix server persistency

* Use delegate url from network

* Remove unused VITE_DELEGATOR_URL

* fix delegate server

* feat: asset support (#318)

* Add assets integration design document

Design for integrating Arkade SDK asset system (ts-sdk PR #279) into the wallet,
covering import, receive, send, mint, reissue, and burn flows.

* Add assets integration implementation plan

16-task step-by-step plan covering SDK install, types, providers, navigation,
BIP21, all asset screens, send/receive modifications, and tx history updates.

* feat: upgrade @arkade-os/sdk to 0.3.13

Upgrade from 0.3.12 to 0.3.13, the latest published version.
The asset branch (github:arkade-os/ts-sdk#asset) does not exist yet,
so using the latest npm release as the foundation for asset integration.

* feat: add asset types and importedAssets to config

* feat: add asset flow state to FlowContext

* feat: add asset balances and metadata cache to WalletContext

* feat: register asset pages in navigation with placeholders

* feat: add BIP21 asset URI encoding and decoding

* feat: add Assets app card to Apps index

* feat: implement Assets list screen

* feat: implement asset import screen

* feat: implement asset detail screen

* feat: implement mint and mint success screens

* feat: implement reissue and burn screens

* feat: add asset support to receive flow

* feat: add asset support to send flow

* feat: show asset info in transaction history and coin control

* refactor: replace as-any casts with proper SDK types

* feat: install @arkade-os/sdk from asset branch (louisinger/wallet-sdk#asset)

* refactor: use proper SDK types for transaction assets

ArkTransaction now includes assets field from SDK changes,
removing the last as-any cast in asp.ts.

* chore: gitignore docs/plans

* format + ts-sdk from gh repo

* allow build

* remove useless tests

* fixes

* fixes

* UI fixes

* bump ts-sdk

* feat(mint): add optional control asset selector

Generated with Claude Code
via Happy

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* fix: display asset amounts with proper decimals and convert unit inputs to raw

- Add formatAssetAmount utility for raw→human-readable display
- Convert user-entered units to raw integers in Burn, Reissue, and Send flows
- Apply decimal formatting across all asset display surfaces

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* style: fix prettier formatting in Burn and Reissue

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* refactor: address code review issues across asset feature

- Skip BTC limits for asset receives in Amount screen
- Fix test mocks with missing context properties
- Format raw supply display in MintSuccess and Detail screens
- Type assetMetadataCache as Map<string, AssetDetails> instead of any
- Extract shared assetInputStyle to src/lib/styles.ts
- Extract AssetAvatar component to deduplicate avatar pattern (~10 locations)
- Fix stale closure in Detail.tsx useEffect
- Format asset amounts in Vtxos debug view with metadata lookup
- Fix default decimals from 0 to 8 in MintSuccess
- Fix Burn placeholder to show formatted balance

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* fix: apply decimal unit conversion to all asset amount inputs and displays

- Add formatAssetAmount() to format.ts for display (raw → human units)
- Mint: convert user units to raw via Decimal.mul(amount, 10^decimals)
- Send form: convert input to raw, display available balance in units
- Burn: convert input to raw, show balance in human-readable units
- Reissue: convert input to raw using decimals metadata
- All display locations (tx list, asset list, detail, send confirm/success)
  now apply formatAssetAmount to show correct decimal representation
- No trailing zeros thanks to Intl.NumberFormat maximumFractionDigits

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* persist asset metada

* clean

* various UI fixes

* add playwright tests

* fix asset e2e tests

* fixes

* fix invoice bip21 encoding

* fix Apps/Asset/receive

* simplify asset import

* UI fix + reuse Input component

* Merge remote-tracking branch origin/master into assets

* Show asset ticker instead of SATS on InputAmount component when receiving an asset

* Also fix amount when user has Fiat Only on Settings / Display Preferences

* revert changes to InputWithScanner

* new InputAssetId component

* dry code

* tests for lib/assets

* Mint: hide dropdown if knownAsset.length <= 0

* allow to mint control asset

* feat: add isIssuance helper to detect issuance txs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show issuance label and green assets in tx list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show issuance direction and positive asset amounts in tx detail

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add issuance transaction rendering test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add burn tx handling, remove asset prefix, move asset icon right

- Add isBurn helper (self-send with amount=0 and negative asset amounts)
- Update isIssuance to check for positive asset amounts
- Remove +/- prefix from asset display (SDK now returns signed amounts)
- Show "Burn" label and direction for burn transactions
- Move asset icon to right side of amount in tx list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* handle Issuance and Burn tx in transaction history

* Fix restoration

* fix test

* fix mint tests

* update to latest ts-sdk master

* fix playwright tests

* not:cloned-input selector

* verified | approved asset flow

* playwright: use reduced motion

* move upgrade logic to updateConfig

* simplify mint form

* fix send all btc

* remove ctrlDecimals

* remove fuji app

* show asset amount in Receive/Success

* receive/success fix

* rework receive/success

* flxShrink mint form

* add link to Asset app

* add assets settings to toggle service + hide assets in send if inactive (#353)

* add assets settings to toggle service + hide assets in send if inactive

* nit: 'enabled' instead of 'connected'

* add test + fix tests

* update boltz config

* fixes

* fix: revert boltz config and add missing enableAssets in reissue test

- Revert [[currencies.lnds]] back to [currencies.lnd] in boltz TOML
  config. The array syntax broke Boltz's LND connection, causing all
  Lightning swap e2e tests to fail.
- Add missing enableAssets() call to the reissue test, which was
  omitted when asset toggle support was added in ea958fd.

* fix: increase timeout for multi-swap e2e tests

The restore and nostr swap tests perform multiple swap operations
(reverse + submarine) plus wallet restore, exceeding the default 30s
test timeout. Increase to 90s to give these complex flows enough time.

* fix: increase action timeout and add balance wait for swap tests

The submarine swap creation is async and can take variable time,
especially after a reverse swap. Increase actionTimeout from 30s to
60s to accommodate slow swap creation, and add test.setTimeout(120s)
for multi-swap tests. Also verify wallet balance is visible before
attempting the send flow in the restore test.

* fix: revert actionTimeout to 30s and verify balance in nostr test

Increasing actionTimeout to 60s made the nostr swap test worse by
keeping failed swap connections alive longer, preventing retries from
getting a clean Boltz state. Revert to 30s and ensure balance is
verified by exact match before submarine swap.

* chore: retrigger CI

* ci: trigger workflows for PRs targeting next branch

* fix: revert Boltz LND config to singular [currencies.lnd] format

The boltz/boltz:ark image does not support the plural [[currencies.lnds]]
array syntax. This broke all Lightning swap tests (receive, send, refund).
Keep pull_policy: always to ensure fresh images.

* chore: retrigger CI

* fix: fund Boltz core wallet for chain swaps in regtest setup

The ARK-to-BTC chain swap direction requires Boltz to have BTC in its
core Bitcoin wallet. Without funding, createArkToBtcSwap hangs/fails,
breaking "send to onchain address" and "send to onchain via swap" tests.

* chore: format setup.mjs

* fix: create Bitcoin Core 'core' wallet before funding for Boltz chain swaps

* fix: create and fund Boltz core wallet before Boltz starts

Move wallet creation before Boltz backend startup so the wallet exists
and has funds when Boltz initializes. Mine an extra block to confirm.

* fix: fall back to collaborative exit when chain swap creation fails

When createArkToBtcSwap returns null or throws, navigate to SendDetails
without a pendingSwap. The Details page already handles this case by
using collaborativeExitWithFees as the fallback for BTC address sends.

* revert: remove Boltz wallet funding from setup (breaks Boltz startup)

The wallet funding caused Boltz to fail to start. Instead, the Form.tsx
fallback to collaborative exit handles onchain sends when chain swaps
are unavailable.

* fix: update onchain send test assertions for collaborative exit fees

The chain swap path (Boltz ARK→BTC) is unavailable in regtest, so the
app falls back to collaborative exit. This changes the fee from 275
(Boltz swap fee) to 200 (ASP onchain output fee from fees intent).
Also makes fees intent/clear calls synchronous to prevent race conditions.

* fix: remove unused exec import from send test

* fix: disable delegate in e2e tests (no delegator service in CI)

The delegate feature defaults to true but requires a delegator service
at localhost:7004. Since CI has no delegator, wallet initialization hangs
causing all tests to timeout. Use VITE_DELEGATE_ENABLED=false in the
Playwright config to disable delegation in the test environment.

* chore: format all files with prettier after merge

* ci: add workflow_dispatch trigger to enable manual CI runs

* fix: enable fulmine delegator and fix port mapping for e2e tests

- Add FULMINE_DELEGATOR_ENABLED=true and FULMINE_DELEGATOR_FEE=350
- Fix port mapping to expose delegator on port 7002
- Aligns fulmine config with next branch

* fix server persistency

---------

Co-authored-by: louisinger <louis@vulpem.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>

* Delete data on IndexedDB when reset (#366)

* fix delegate dev server

* fix tests

* update dependencies

* Use mutinynet server for testing

* Fix setup 7001

* E2E fixes, config, delegates url (#369)

* Fix e2e delegate test

* Log commit from SW to verify updates

* Ensure config has all the expected fields

* Fix delegates url resolution

* Missing type, harden config parsing

* reduce tests waiting for maybeLater

* skip swap restore test

* disable chain swap on receive

* Upgrade SDK, boltz, use 30s ServiceWorker timeout (#371)

* SDK Version next 8

* Use SW custom timeout

* Update SW

* Use sdk 0.4.0 and boltz-swap 0.3.0

* Update service worker

* Update service worker 2

* fix receive fractional assets (#373)

* fix receive fractional assets

* fix bip21 in asset receive

* disable mint if balance < dust

* New asset card (#374)

* New asset card

* lint

* General fixes (#382)

* dry code

* remove console.log

* fix throw with fractional sats

* refactor Asset send input amount

* fix keyboard to work with assets

* Add vmempool.space explorer links for offchain txs and assets (#380)

* Add vmempool.space explorer links for offchain txs and assets

- Add vmempool.space URL configuration with VITE_VMEMPOOL_URL env var
- Add helper functions for offchain tx and asset URLs in explorers.ts
- Update Table component to support clickable rows with onClick handlers
- Update Details component to accept wallet, assetId, and isOffchainTx props
- Make Transaction ID and Asset ID fields clickable in transaction details
- Open offchain txs (redeemTxid) in vmempool.space
- Open onchain txs (boardingTxid) in mempool.space
- Open assets in vmempool.space asset explorer
- Only show vmempool links for mainnet by default (configurable via env var)

* fix: keep tap-to-copy, add external link icon for explorer links

* fix: add aria-label to ExternalLinkIcon, hide link when URL unavailable

* fixes

---------

Co-authored-by: OpenClaw Agent <agent@openclaw.dev>
Co-authored-by: louisinger <louisinger@protonmail.com>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>

* fix max button with large amounts

* fix issuing of assets with new control asset

* Replace sleep with wait for new vtxo

* fix input amount

* Don't block app on swap init error (#383)

* Don't block the UX if swaps service isn't available and log errors

* Update SW

* Add tests

* Add tests

* lint

* fix nostr backup of swaps

* fix SVGs

* fix boltz env

* fix tests

* lint

* fix tests

* remove console.log

* update pnpm-lock

* refactor: simplify Hero component and remove LearnMore section (#395)

* fix(pwa): fix swipe-back navigation for iOS standalone mode

The iOS edge-swipe gesture was broken in the PWA: it navigated to wrong
screens, activated when it shouldn't (showing white pages), and caused
double animations. This was due to browser history entries being out of
sync with internal navigation state.

Replaces the old push-every-navigation model with 1:1 browser history
sync — browser entries only exist when there's real back navigation.
When the back stack is empty, iOS disables the swipe gesture entirely.

Key changes:
- navigation.tsx: stable popstate handler via refs (no phantom entries),
  root navigations clear browser history via history.go(-n), forward
  navigations push 1:1 to browser history
- options.tsx: Settings sub-navigation now pushes browser history entries
  so swipe-back works within Settings pages, coordinates with main nav
  via subNavHandler for cleanup on tab switches
- Distinguishes swipe vs back-button via isButtonBack flag to suppress
  Framer Motion animation on swipe (browser already animated it)
- Adds overscroll-behavior: none to prevent rubber-banding

Note: this is a pragmatic PWA workaround — relies on browser history
depth controlling iOS gesture activation. Will be properly solved when
migrating to Capacitor (native WKWebView has no Safari gestures).

* fix(nav): address CodeRabbit review feedback

- Pop blocked entries (InitConnect/Loading) from backStack to prevent
  infinite loop when user repeatedly presses back
- Guard historyDepth from going negative in Settings sub-navigation

* fix(nav): address second round of CodeRabbit feedback

- goBack delegates to subNavHandler when main backStack is empty
- navigateToOption resets historyDepth when returning to Menu

* update ts-sdk dependency

* fix double swap when claiming reverse swap

* fix(ui): match iOS/Android status bar color to app theme (#334)

* fix(ui): match iOS/Android status bar color to app theme

The status bar stayed white in dark mode because:
- The theme-color meta tag was hardcoded to #000000
- Safari doesn't reliably support media queries on theme-color
- The html element had no background-color, so the safe area defaulted to white

Fix: inline script in <head> reads the stored theme from localStorage
and sets theme-color + ion-palette-dark class before first paint.
applyTheme() keeps it in sync on manual toggle. Root element background
now follows --ion-background-color so the safe area fills correctly.

* fix(ui): add dev-only logging to theme bootstrap catch block

Address CodeRabbit nitpick: log theme init failures on localhost
so broken localStorage/config parsing is visible during development.

* fix(ios): status bar color matching and content overlap for iOS 26+ PWA

iOS 26+ ignores the theme-color meta tag — it derives the status bar
color from the actual rendered content at the top of the page. Add a
position:fixed body::before pseudo-element that sits behind the
transparent status bar and uses var(--ion-background-color), so iOS
samples the correct theme color instantly on theme switch.

Push .ion-page below the safe area with top: env(safe-area-inset-top)
using ion-app > .ion-page selector (higher specificity than Ionic's
core .ion-page { top: 0 } which loads later in the cascade).

Also fix manifest.json theme_color (#fff) and improve inline theme
bootstrap comments in index.html.

* style: fix prettier formatting in ionic.css

* fix(ui): match theme enum casing in bootstrap script

The Themes enum uses capitalized values ('Dark', 'Light', 'Auto') but
the inline bootstrap script compared against lowercase. This caused
the dark-mode flash prevention to fail when theme was explicitly set.

Also makes the iOS status bar comment version-agnostic per review.

---------

Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>

* Upgrade ts-sdk 0.4.2 and boltz-swap 0.3.1 (#399)

* Fix send balance when sending btc with assets (#401)

* fix max balance when sending btc with assets

* fix keyboard when sending assets

* style: rebrand Assets to Arkade Mint + redesign app cards (#398)

* style(assets): rebrand Assets to Arkade Mint with pixel coin icon

- Rename "Assets" to "Arkade Mint" across all asset screens (list, settings, mint success)
- Replace placeholder "A" icon with Mario-style pixel coin in Arkade purple
- New icon matches size/format of other app icons (55px/78px)

* style(apps): redesign app cards with subtle surface treatment

Replace Shadow border+borderPurple cards with whisper-surface style:
- Subtle background tint instead of gray bg + harsh purple border
- Tactile press state with scale(0.97) + background darken
- Touch events matching Button component pattern (mouse + touch)
- Icon aligned to top-left, description nested under title
- Inline accessibility (role, tabIndex, keyboard handler)

src/screens/Apps/Index.tsx

* style: fix prettier formatting

* fix(test): update test selectors after Assets → Arkade Mint rename

The app card testId changed from 'app-assets' to 'app-arkade-mint' and
the header/settings text changed accordingly.

* fix(test): update 'Back to Assets' → 'Back to Arkade Mint' in tests

MintSuccess button text was renamed but tests still referenced old text.

---------

Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: Pietro Grandi <dev@pietro.uno>

* Upgrade ts-sdk@0.4.3 and boltz-swap@0.3.2 (#402)

* Upgrade ts-sdk@0.4.3 and boltz-swap@0.3.2

* Update SW

* feat: streamline onboarding — remove carousel, add dismissible PWA install banner (#375)

* feat(onboarding): remove onboarding carousel, skip straight to wallet

Remove the 4-step onboarding carousel (Onboard.tsx) and all supporting
components (StepBars, Bullet, AlertBox, Announcement, Onboard1-4 SVGs).
New users now go directly from Init → InitConnect → Wallet instead of
through the carousel. The announcement/PSA system is also removed as it
interrupted the flow without providing value.

Files removed:
- src/screens/Wallet/Onboard.tsx (carousel screen)
- src/components/AlertBox.tsx, Announcement.tsx, Bullet.tsx, StepBars.tsx
- src/icons/Onboard1-4.tsx (~10,800 lines of SVG illustrations)
- src/providers/announcements.tsx (PSA announcement system)
- Associated tests for removed components

Files modified:
- src/App.tsx: route to Init instead of Onboard for new wallets
- src/screens/Init/*.tsx: navigate to InitConnect instead of InitSuccess
- src/providers/navigation.tsx: remove Onboard from Pages enum
- src/index.tsx: remove AnnouncementProvider wrapper
- src/test/e2e/utils.ts: remove onboard steps from createWallet helper

* feat(wallet): add dismissible PWA install banner and password nudge

Add a new DismissibleBanner component used for both the PWA install
prompt and the password creation nudge. The banner features Framer Motion
enter/exit animations (EASE_OUT_QUINT, y:-24, scale:0.97), horizontal
swipe-to-dismiss gesture, layout animation for smooth sibling reflow,
and prefers-reduced-motion support with 44px touch targets.

PWA install banner:
- Mobile-only gating (isIOS || isAndroid) — hidden on desktop
- Platform-specific descriptions (iOS share sheet vs Android install)
- Module-level beforeinstallprompt capture for native install prompt
- Respects persistent dismissed state via config.dismissedBanners

Password nudge:
- Replaced old AlertBox with DismissibleBanner (expanded layout)
- Added nudgeVisible/nudgeCheckComplete to context for PWA banner
  priority coordination (PWA banner waits for nudge check)
- Fixed error handling: .catch/.finally on async password check

Config: replaced announcementsSeen with dismissedBanners (existing
configs handled gracefully via ?? [] fallbacks).

New files:
- src/components/DismissibleBanner.tsx

Modified:
- src/screens/Wallet/Index.tsx (PWA banner integration)
- src/providers/nudge.tsx (DismissibleBanner + error handling)
- src/lib/pwa.ts (usePwaInstalled, beforeinstallprompt, promptPwaInstall)
- src/lib/types.ts, src/providers/config.tsx (dismissedBanners)
- src/ionic.css (--purple10 color variable)
- src/test/setup.ts (matchMedia mock)

* fix: address CodeRabbit review feedback

- Password.tsx: reset restoring flag when navigating to InitConnect
  to prevent stale state from a previous restore attempt
- DismissibleBanner.tsx: disable layout animation and popLayout mode
  when prefers-reduced-motion is active
- Wallet/Index.tsx: only show Install action button when native
  prompt is available (Android); iOS shows instruction-only banner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(wallet): clarify iOS PWA banner instruction text

Update the iOS PWA install banner description to reference "the share
icon in Safari's toolbar" instead of generic "the share button" so
users know exactly where to look.

* fix(pwa): return prompt outcome and auto-dismiss banner after install

promptPwaInstall() now returns 'accepted' | 'dismissed' | null instead
of void. On Android, the banner auto-dismisses after the user interacts
with the native install prompt, rather than staying visible.

* fix(banner): add haptic feedback to DismissibleBanner interactions

Fire hapticSubtle() on button taps (Dismiss, Install, Set password)
and on swipe-to-dismiss gesture, consistent with all other interactive
elements in the app.

* feat(onboarding): add animated SVG logo morph sequence with delight effects

Redesign the Init screen with a rich onboarding animation:

- OnboardingLogo: SVG cross-fade between Arcade, Space Invader, and Heart
  shapes with deep squash-stretch bounces using Framer Motion async
  animation controls. Final bounce flies logo to header position via FLIP.
- PixelSplash: burst of purple pixel particles on each bounce
- PixelSunrise: pixelated purple gradient at screen top (fixed position)
- Three bullet points with outlined icon components (Bolt, Globe, ShieldCheck)
  replacing the old paragraph text
- Reduced motion: all animations skip, content renders instantly

Files: OnboardingLogo.tsx, PixelSplash.tsx, PixelSunrise.tsx,
BoltOutline.tsx, GlobeOutline.tsx, ShieldCheckOutline.tsx,
Init.tsx, ionic.css

* style(onboarding): fix Prettier formatting in OnboardingLogo and Init

* style(onboarding): smooth radial gradient, layout + status bar fixes

- PixelSunrise: replace pixelated grid with smooth radial-gradient,
  remove all pixel dissolution code
- Init: move content to bottom of screen (flex-end + paddingBottom),
  stack logo above title with optical alignment to bullet icons
- Status bar: add apple-mobile-web-app-capable for PWA bleed-through,
  set theme-color to #8771BC (computed gradient top on white),
  fix manifest.json theme_color mismatch
- Dynamic theme-color useEffect on Init screen for Safari tinting

* style(onboarding): fix Prettier formatting in Init

* fix(onboarding): stable fly target, gradient portal, dark mode prep

- Always render content in DOM (hidden until ready) so logoTargetRef
  position is stable during fly animation instead of shifting on mount
- Portal OnboardingLogo and PixelSunrise to document.body so they
  escape IonPage's max-width: 640px container on desktop
- Add color-scheme light/dark to :root and .ion-palette-dark
- Add color-scheme meta tag and body background-color for Safari
- Sunrise animation synced with fly via onFlyStart callback
- Snappier bounce physics: deeper squash, higher overshoot, faster timings

* style(onboarding): fix Prettier formatting in Init.tsx

* feat(onboarding): pixel morph animation, spring physics, loading bypass

Logo morph animation:
- Rewrite OnboardingLogo to use pixel-shapes.ts grid system instead of
  opacity crossfade between SVG path components
- Arcade "A" always renders as clean SVG paths (never pixelated), while
  Invader and Heart shapes use pixel rects with CSS transform transitions
- Spring-based bounce recovery (stiffness: 600, damping: 18, mass: 0.6)
  replaces scripted multi-step keyframes for natural overshoot feel
- Morph + bounce happen simultaneously — shape swap at squash peak
- Faster timing: 180ms initial pause, 60ms between morphs, ~1s total
- Portal to document.body so logo escapes IonPage max-width on desktop

Pixel splash particles:
- Increase from 12-17 to 20-28 particles per burst
- Wider velocity range (25-105) and more size variation (2.5-6.5px)

Skip loading screen for new users:
- New users (no wallet in storage) bypass ASP + JS capability gates,
  going straight to Init where the logo morph serves as intro visual
- Wallet provider reads from localStorage synchronously via lazy
  useState initializer, eliminating one-frame Loading flash
- "Create wallet" button stays disabled until ASP resolves

Revert status bar changes:
- All theme-color, color-scheme, and apple-mobile-web-app changes from
  this branch reverted — will be handled in a separate PR
- Remove temp replay button and associated state

Files: OnboardingLogo.tsx, PixelSplash.tsx, Init.tsx, App.tsx,
       wallet.tsx, index.html, manifest.json, ionic.css, index.css

* fix(wallet): remove unused setWalletLoaded setter

walletLoaded is now always true (synchronous init), so the setter is
dead code. Replace useState with a constant to satisfy eslint.

* Apply suggestion from @tiero

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>

* fix: use named export for PageTransition to fix Rollup tree-shaking

Rollup's module concatenation was dropping the default export definition
while keeping the JSX references, causing 'PageTransition is not defined'
at runtime. Named exports are handled more reliably.

* fix: update e2e helpers for new onboarding flow (no more InitSuccess page)

The onboarding flow now goes Init → InitConnect → Wallet directly,
skipping the InitSuccess page. The createWallet() and restoreWallet()
helpers were waiting for "Your new wallet is live!" / clicking
"Go to wallet" which no longer exist, causing all Playwright tests
to hang indefinitely.

---------

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* perf(transactions): virtualise TransactionsList with @tanstack/react-virtual (#394)

* test: add smoke test for TransactionsList with large tx history

Reproduces the reported lag on wallets with several thousand transactions.
TransactionsList renders every row into the DOM at once with no windowing;
render time scales linearly (~1.8ms/tx in jsdom), so:
  100 txs  →   471ms  ✓
  500 txs  → 1,475ms  ✗
 1000 txs  → 2,258ms  ✗
 3000 txs  → 5,477ms  ✗
 5000 txs  → 9,206ms  ✗

The fix is to virtualise the list (e.g. @tanstack/react-virtual).
This test will serve as the regression gate once that is in place.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* perf(transactions): virtualise TransactionsList with @tanstack/react-virtual

Fixes the wallet freeze / extreme lag reported for wallets with several
thousand transactions.

Root cause
----------
TransactionsList rendered every Tx into the DOM unconditionally via a plain
.map() call.  With no windowing, render time scaled linearly with the
transaction count (~1.8 ms/tx in jsdom, worse in a real browser due to
layout and paint):

  Before fix (all DOM nodes mounted):
    100 txs  →    471 ms
    500 txs  →  1 475 ms
   1000 txs  →  2 258 ms
   3000 txs  →  5 477 ms
   5000 txs  →  9 206 ms

Fix
---
Replace the plain map() with @tanstack/react-virtual's useVirtualizer.
Only the rows that fit in the viewport (+ a 5-row overscan buffer above
and below) are ever mounted in the DOM.  The total DOM node count is now
O(visible rows) ≈ 15–25 regardless of wallet history size.

  After fix (virtual rows only):
    100 txs  →  4 ms ✓
    500 txs  →  3 ms ✓
   1000 txs  →  4 ms ✓
   3000 txs  →  4 ms ✓
   5000 txs  →  3 ms ✓

The list renders in a self-contained scrollable container
(height: calc(100dvh - 380px), min-height: 200px) so the balance/buttons
section above it remains static — a common pattern in mobile wallet UIs.

Smoke test update
-----------------
- Added a DOM-count assertion: at most MAX_DOM_ROWS (20) rows may be
  mounted at any one time, ensuring virtualisation cannot regress silently.
- Retained the per-count render-time assertions (≤ 500 ms) as a belt-and-
  braces guard.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* feat(TransactionsList): add ArrowUp/ArrowDown keyboard navigation with virtualizer scroll-sync

Add focusedIndex state and handleListKeyDown on the parentRef scroll
container. Arrow key presses call virtualizer.scrollToIndex(next) then
requestAnimationFrame to focus the target Focusable row once it is
mounted. Existing Enter → handleClick and Escape → focusOnOuterShell
behaviour is preserved; Tab is unchanged.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* fix(TransactionsList): guard focusRow and focusOnFirstRow against empty/out-of-range indices

Add an early-return bounds check in focusRow (index < 0 || index >= txs.length)
to prevent virtualizer.scrollToIndex and key(txs[index]) from executing on
invalid indices. Move the txs.length === 0 guard before setFocused(true) in
focusOnFirstRow so focused is never set true when there are no rows to navigate.
The focusRow guard also covers the edge case in handleListKeyDown where
txs.length === 0 would produce a -1 next index.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* remove transactions smoke test

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* fix(keyboard): sync focusedIndex with actual DOM focus on row focus events (#397)

* Initial plan

* fix(keyboard): sync focusedIndex with actual DOM focus on row focus events

Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

* chore: remove accidentally committed package-lock.json

Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

* fix: hide virtualizer scrollbar and address review feedback

- Hide scrollbar on transaction list (reported by @Kukks)
- Remove package-lock.json from .gitignore (feedback from @pietro909)
- Remove redundant setFocusedIndex calls (CodeRabbit nitpick)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* fix: restore keyboard navigation on Apps list (#404)

The app card redesign (#398) replaced the Focusable wrapper with a plain
div, losing the focus ring styling and keyboard nav flow from #283.
Re-wrap each App card in Focusable to restore focus indicator and
Enter/Space activation.

* fix: dynamic row height for virtualised transaction list (#405)

Rows with asset info (e.g. "-14.7 BB") are taller than the 61px
estimate, causing overlap. Enable measureElement so @tanstack/react-virtual
measures actual DOM heights instead of relying on the fixed estimate.

* Support undefined amounts and invalid invoices (#403)

* fix sending to onchain address when having assets (#406)

* remove tooltip from Text

* hotfix

* Fix coin list with assets (#407)

* remove tooltip from Text

* show amounts in separate line in coin list

* fix InfoLine flash show (#408)

* show loading in coin control (#409)

* remove link from assets app

* fix: dark mode banner button text invisible (#391998 on dark bg)

- Switch DismissibleBanner button color from --purple to --purpletext
- Add --purpletext dark mode override (#9678f7) for better contrast

* fix: transaction list requires two clicks to navigate

The onFocus handler on virtualised rows called setState (setFocused,
setFocusedIndex), triggering a React re-render that destroyed the
clicked DOM element before the click event completed. Convert both
to refs since they're only used in event handlers, not render output.

* Upgrade ts-sdk@0.4.4 and boltz-swap@0.3.3 (#411)

* Fix receive amount in fiat only

* Fix receive amount in fiat only

* Fix collab exit (#413)

---------

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>
Co-authored-by: louisinger <louis@vulpem.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Co-authored-by: louisinger <41042567+louisinger@users.noreply.github.com>
Co-authored-by: OpenClaw Agent <agent@openclaw.dev>
Co-authored-by: louisinger <louisinger@protonmail.com>
Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Co-authored-by: Sahil Chaturvedi <sahilchaturvedi@protonmail.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Kukks added a commit that referenced this pull request Mar 13, 2026
* Use next branches

* Implements chain swaps

* fixes

* Use new SDK storage

* Update integration to Contract Manager

* App starting, payments received

* Use correct import

* Use new SDK storage

* wip

* SW working

* using ws

* Latest SDK

* workaround for build

* Fix build

* Rollback unneeeded changes, clean up code

* Address Rabbit comment

* Set flag in unsub

* Fix tx history rendering issue

* Update package.json, lock file, worker

* Run migration only if needed

* Update lock and worker

* Use next releases

* Clear setInterval on re-init to prevent leaking duplicate intervals

* Implements delegators UI (#319)

* implements delegators UI

* rabbit fixes

* dumb down + rename to delegates

* revert changes

* add unit tests

* add e2e test

* rabbit fixes

* fixes

* Upgrade latest sdk

* fixes

* Ignore md helpers

* Stop pinging wallet when locked

* Allow the page to force activation of a newly installed worker.

* Handle failure in getCommitmentTxIds

* Add defensive parsing of messages for stale service workers

* Upgrade sdk and boltz-swap

* fix Indexer constructor in wallet provider

* Use event.waitUntil

* update dependencies

* bug fix

* update dependencies

* default delegator

* fix URL

* delegator

* make delegator URL configurable

* Wire up delegates

* Centralize compressed-xonly conversion, hook in delegates

* Trigger reload on delegate on/off

* Delegates on by default

* Use persisted preference for delegates

* Fix type in wallet

* Remove workspace file

* Fix formatting

* Enforce use of delegate upon restore

* Use correct delegates port, update e2e test

* Remove log

* Update Fulmine 0.3.15 and port mapping

* Change fulmine port in setup script

* fix server persistency

* Use delegate url from network

* Remove unused VITE_DELEGATOR_URL

* fix delegate server

* feat: asset support (#318)

* Add assets integration design document

Design for integrating Arkade SDK asset system (ts-sdk PR #279) into the wallet,
covering import, receive, send, mint, reissue, and burn flows.

* Add assets integration implementation plan

16-task step-by-step plan covering SDK install, types, providers, navigation,
BIP21, all asset screens, send/receive modifications, and tx history updates.

* feat: upgrade @arkade-os/sdk to 0.3.13

Upgrade from 0.3.12 to 0.3.13, the latest published version.
The asset branch (github:arkade-os/ts-sdk#asset) does not exist yet,
so using the latest npm release as the foundation for asset integration.

* feat: add asset types and importedAssets to config

* feat: add asset flow state to FlowContext

* feat: add asset balances and metadata cache to WalletContext

* feat: register asset pages in navigation with placeholders

* feat: add BIP21 asset URI encoding and decoding

* feat: add Assets app card to Apps index

* feat: implement Assets list screen

* feat: implement asset import screen

* feat: implement asset detail screen

* feat: implement mint and mint success screens

* feat: implement reissue and burn screens

* feat: add asset support to receive flow

* feat: add asset support to send flow

* feat: show asset info in transaction history and coin control

* refactor: replace as-any casts with proper SDK types

* feat: install @arkade-os/sdk from asset branch (louisinger/wallet-sdk#asset)

* refactor: use proper SDK types for transaction assets

ArkTransaction now includes assets field from SDK changes,
removing the last as-any cast in asp.ts.

* chore: gitignore docs/plans

* format + ts-sdk from gh repo

* allow build

* remove useless tests

* fixes

* fixes

* UI fixes

* bump ts-sdk

* feat(mint): add optional control asset selector

Generated with Claude Code
via Happy

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* fix: display asset amounts with proper decimals and convert unit inputs to raw

- Add formatAssetAmount utility for raw→human-readable display
- Convert user-entered units to raw integers in Burn, Reissue, and Send flows
- Apply decimal formatting across all asset display surfaces

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* style: fix prettier formatting in Burn and Reissue

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* refactor: address code review issues across asset feature

- Skip BTC limits for asset receives in Amount screen
- Fix test mocks with missing context properties
- Format raw supply display in MintSuccess and Detail screens
- Type assetMetadataCache as Map<string, AssetDetails> instead of any
- Extract shared assetInputStyle to src/lib/styles.ts
- Extract AssetAvatar component to deduplicate avatar pattern (~10 locations)
- Fix stale closure in Detail.tsx useEffect
- Format asset amounts in Vtxos debug view with metadata lookup
- Fix default decimals from 0 to 8 in MintSuccess
- Fix Burn placeholder to show formatted balance

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* fix: apply decimal unit conversion to all asset amount inputs and displays

- Add formatAssetAmount() to format.ts for display (raw → human units)
- Mint: convert user units to raw via Decimal.mul(amount, 10^decimals)
- Send form: convert input to raw, display available balance in units
- Burn: convert input to raw, show balance in human-readable units
- Reissue: convert input to raw using decimals metadata
- All display locations (tx list, asset list, detail, send confirm/success)
  now apply formatAssetAmount to show correct decimal representation
- No trailing zeros thanks to Intl.NumberFormat maximumFractionDigits

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* persist asset metada

* clean

* various UI fixes

* add playwright tests

* fix asset e2e tests

* fixes

* fix invoice bip21 encoding

* fix Apps/Asset/receive

* simplify asset import

* UI fix + reuse Input component

* Merge remote-tracking branch origin/master into assets

* Show asset ticker instead of SATS on InputAmount component when receiving an asset

* Also fix amount when user has Fiat Only on Settings / Display Preferences

* revert changes to InputWithScanner

* new InputAssetId component

* dry code

* tests for lib/assets

* Mint: hide dropdown if knownAsset.length <= 0

* allow to mint control asset

* feat: add isIssuance helper to detect issuance txs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show issuance label and green assets in tx list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show issuance direction and positive asset amounts in tx detail

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add issuance transaction rendering test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add burn tx handling, remove asset prefix, move asset icon right

- Add isBurn helper (self-send with amount=0 and negative asset amounts)
- Update isIssuance to check for positive asset amounts
- Remove +/- prefix from asset display (SDK now returns signed amounts)
- Show "Burn" label and direction for burn transactions
- Move asset icon to right side of amount in tx list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* handle Issuance and Burn tx in transaction history

* Fix restoration

* fix test

* fix mint tests

* update to latest ts-sdk master

* fix playwright tests

* not:cloned-input selector

* verified | approved asset flow

* playwright: use reduced motion

* move upgrade logic to updateConfig

* simplify mint form

* fix send all btc

* remove ctrlDecimals

* remove fuji app

* show asset amount in Receive/Success

* receive/success fix

* rework receive/success

* flxShrink mint form

* add link to Asset app

* add assets settings to toggle service + hide assets in send if inactive (#353)

* add assets settings to toggle service + hide assets in send if inactive

* nit: 'enabled' instead of 'connected'

* add test + fix tests

* update boltz config

* fixes

* fix: revert boltz config and add missing enableAssets in reissue test

- Revert [[currencies.lnds]] back to [currencies.lnd] in boltz TOML
  config. The array syntax broke Boltz's LND connection, causing all
  Lightning swap e2e tests to fail.
- Add missing enableAssets() call to the reissue test, which was
  omitted when asset toggle support was added in ea958fd.

* fix: increase timeout for multi-swap e2e tests

The restore and nostr swap tests perform multiple swap operations
(reverse + submarine) plus wallet restore, exceeding the default 30s
test timeout. Increase to 90s to give these complex flows enough time.

* fix: increase action timeout and add balance wait for swap tests

The submarine swap creation is async and can take variable time,
especially after a reverse swap. Increase actionTimeout from 30s to
60s to accommodate slow swap creation, and add test.setTimeout(120s)
for multi-swap tests. Also verify wallet balance is visible before
attempting the send flow in the restore test.

* fix: revert actionTimeout to 30s and verify balance in nostr test

Increasing actionTimeout to 60s made the nostr swap test worse by
keeping failed swap connections alive longer, preventing retries from
getting a clean Boltz state. Revert to 30s and ensure balance is
verified by exact match before submarine swap.

* chore: retrigger CI

* ci: trigger workflows for PRs targeting next branch

* fix: revert Boltz LND config to singular [currencies.lnd] format

The boltz/boltz:ark image does not support the plural [[currencies.lnds]]
array syntax. This broke all Lightning swap tests (receive, send, refund).
Keep pull_policy: always to ensure fresh images.

* chore: retrigger CI

* fix: fund Boltz core wallet for chain swaps in regtest setup

The ARK-to-BTC chain swap direction requires Boltz to have BTC in its
core Bitcoin wallet. Without funding, createArkToBtcSwap hangs/fails,
breaking "send to onchain address" and "send to onchain via swap" tests.

* chore: format setup.mjs

* fix: create Bitcoin Core 'core' wallet before funding for Boltz chain swaps

* fix: create and fund Boltz core wallet before Boltz starts

Move wallet creation before Boltz backend startup so the wallet exists
and has funds when Boltz initializes. Mine an extra block to confirm.

* fix: fall back to collaborative exit when chain swap creation fails

When createArkToBtcSwap returns null or throws, navigate to SendDetails
without a pendingSwap. The Details page already handles this case by
using collaborativeExitWithFees as the fallback for BTC address sends.

* revert: remove Boltz wallet funding from setup (breaks Boltz startup)

The wallet funding caused Boltz to fail to start. Instead, the Form.tsx
fallback to collaborative exit handles onchain sends when chain swaps
are unavailable.

* fix: update onchain send test assertions for collaborative exit fees

The chain swap path (Boltz ARK→BTC) is unavailable in regtest, so the
app falls back to collaborative exit. This changes the fee from 275
(Boltz swap fee) to 200 (ASP onchain output fee from fees intent).
Also makes fees intent/clear calls synchronous to prevent race conditions.

* fix: remove unused exec import from send test

* fix: disable delegate in e2e tests (no delegator service in CI)

The delegate feature defaults to true but requires a delegator service
at localhost:7004. Since CI has no delegator, wallet initialization hangs
causing all tests to timeout. Use VITE_DELEGATE_ENABLED=false in the
Playwright config to disable delegation in the test environment.

* chore: format all files with prettier after merge

* ci: add workflow_dispatch trigger to enable manual CI runs

* fix: enable fulmine delegator and fix port mapping for e2e tests

- Add FULMINE_DELEGATOR_ENABLED=true and FULMINE_DELEGATOR_FEE=350
- Fix port mapping to expose delegator on port 7002
- Aligns fulmine config with next branch

* fix server persistency

---------

Co-authored-by: louisinger <louis@vulpem.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>

* Delete data on IndexedDB when reset (#366)

* fix delegate dev server

* fix tests

* update dependencies

* Use mutinynet server for testing

* Fix setup 7001

* E2E fixes, config, delegates url (#369)

* Fix e2e delegate test

* Log commit from SW to verify updates

* Ensure config has all the expected fields

* Fix delegates url resolution

* Missing type, harden config parsing

* reduce tests waiting for maybeLater

* skip swap restore test

* disable chain swap on receive

* Upgrade SDK, boltz, use 30s ServiceWorker timeout (#371)

* SDK Version next 8

* Use SW custom timeout

* Update SW

* Use sdk 0.4.0 and boltz-swap 0.3.0

* Update service worker

* Update service worker 2

* fix receive fractional assets (#373)

* fix receive fractional assets

* fix bip21 in asset receive

* disable mint if balance < dust

* New asset card (#374)

* New asset card

* lint

* General fixes (#382)

* dry code

* remove console.log

* fix throw with fractional sats

* refactor Asset send input amount

* fix keyboard to work with assets

* Add vmempool.space explorer links for offchain txs and assets (#380)

* Add vmempool.space explorer links for offchain txs and assets

- Add vmempool.space URL configuration with VITE_VMEMPOOL_URL env var
- Add helper functions for offchain tx and asset URLs in explorers.ts
- Update Table component to support clickable rows with onClick handlers
- Update Details component to accept wallet, assetId, and isOffchainTx props
- Make Transaction ID and Asset ID fields clickable in transaction details
- Open offchain txs (redeemTxid) in vmempool.space
- Open onchain txs (boardingTxid) in mempool.space
- Open assets in vmempool.space asset explorer
- Only show vmempool links for mainnet by default (configurable via env var)

* fix: keep tap-to-copy, add external link icon for explorer links

* fix: add aria-label to ExternalLinkIcon, hide link when URL unavailable

* fixes

---------

Co-authored-by: OpenClaw Agent <agent@openclaw.dev>
Co-authored-by: louisinger <louisinger@protonmail.com>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>

* fix max button with large amounts

* fix issuing of assets with new control asset

* Replace sleep with wait for new vtxo

* fix input amount

* Don't block app on swap init error (#383)

* Don't block the UX if swaps service isn't available and log errors

* Update SW

* Add tests

* Add tests

* lint

* fix nostr backup of swaps

* fix SVGs

* fix boltz env

* fix tests

* lint

* fix tests

* remove console.log

* update pnpm-lock

* refactor: simplify Hero component and remove LearnMore section (#395)

* feat(ui): navbar redesign + lucide icon migration (WIP)

- Migrate ~40 custom SVG icons to lucide-react (outlined, strokeWidth 1.75)
- Redesign bottom navbar: transparent bg, 28px icons, 10px labels, 49px height
- Add frosted glass blur on header + status bar area (backdrop-filter)
- Three navbar variants with floating dev switcher: border, blur, pill
- Pill variant: Bitkey-style floating capsule with 2 icons, settings in header
- Hairline border CSS variable (0.5px retina, 1px standard)
- 44px touch targets, touch-action manipulation, prefers-reduced-motion support
- Allow trycloudflare.com in vite dev server

* feat(ui): custom pill navbar with settings gear morph

Replace Ionic's ion-tab-bar with a custom floating pill navbar:

- Hide ion-tab-bar (visibility: hidden), use pure React PillNavbar component
- Pill: fixed bottom-center, 999px border-radius, 3-layer shadow composition
- Wallet + Apps tabs with lucide-react icons (size 24, strokeWidth 2)
- Active tab: color + background highlight, inactive: muted color
- Settings gear icon in header with morph animation to X on settings page
- Gear/X vertically centered in 4rem header band, aligned with header title
- Pill hidden on Settings tab, visible on Wallet/Apps
- Dark mode: pill bg #1C1C1E for contrast against #101010 page
- Eased gradient fade (160px, 8-stop) above pill for smooth content cutoff
- shape-rendering: geometricPrecision for crisp SVG icons
- margin-based centering (not translateX) to avoid sub-pixel blur
- 44px tap targets, touch-action: manipulation, prefers-reduced-motion

* fix(pwa): fix swipe-back navigation for iOS standalone mode

The iOS edge-swipe gesture was broken in the PWA: it navigated to wrong
screens, activated when it shouldn't (showing white pages), and caused
double animations. This was due to browser history entries being out of
sync with internal navigation state.

Replaces the old push-every-navigation model with 1:1 browser history
sync — browser entries only exist when there's real back navigation.
When the back stack is empty, iOS disables the swipe gesture entirely.

Key changes:
- navigation.tsx: stable popstate handler via refs (no phantom entries),
  root navigations clear browser history via history.go(-n), forward
  navigations push 1:1 to browser history
- options.tsx: Settings sub-navigation now pushes browser history entries
  so swipe-back works within Settings pages, coordinates with main nav
  via subNavHandler for cleanup on tab switches
- Distinguishes swipe vs back-button via isButtonBack flag to suppress
  Framer Motion animation on swipe (browser already animated it)
- Adds overscroll-behavior: none to prevent rubber-banding

Note: this is a pragmatic PWA workaround — relies on browser history
depth controlling iOS gesture activation. Will be properly solved when
migrating to Capacitor (native WKWebView has no Safari gestures).

* fix(nav): address CodeRabbit review feedback

- Pop blocked entries (InitConnect/Loading) from backStack to prevent
  infinite loop when user repeatedly presses back
- Guard historyDepth from going negative in Settings sub-navigation

* fix(nav): address second round of CodeRabbit feedback

- goBack delegates to subNavHandler when main backStack is empty
- navigateToOption resets historyDepth when returning to Menu

* update ts-sdk dependency

* fix double swap when claiming reverse swap

* fix(ui): match iOS/Android status bar color to app theme (#334)

* fix(ui): match iOS/Android status bar color to app theme

The status bar stayed white in dark mode because:
- The theme-color meta tag was hardcoded to #000000
- Safari doesn't reliably support media queries on theme-color
- The html element had no background-color, so the safe area defaulted to white

Fix: inline script in <head> reads the stored theme from localStorage
and sets theme-color + ion-palette-dark class before first paint.
applyTheme() keeps it in sync on manual toggle. Root element background
now follows --ion-background-color so the safe area fills correctly.

* fix(ui): add dev-only logging to theme bootstrap catch block

Address CodeRabbit nitpick: log theme init failures on localhost
so broken localStorage/config parsing is visible during development.

* fix(ios): status bar color matching and content overlap for iOS 26+ PWA

iOS 26+ ignores the theme-color meta tag — it derives the status bar
color from the actual rendered content at the top of the page. Add a
position:fixed body::before pseudo-element that sits behind the
transparent status bar and uses var(--ion-background-color), so iOS
samples the correct theme color instantly on theme switch.

Push .ion-page below the safe area with top: env(safe-area-inset-top)
using ion-app > .ion-page selector (higher specificity than Ionic's
core .ion-page { top: 0 } which loads later in the cascade).

Also fix manifest.json theme_color (#fff) and improve inline theme
bootstrap comments in index.html.

* style: fix prettier formatting in ionic.css

* fix(ui): match theme enum casing in bootstrap script

The Themes enum uses capitalized values ('Dark', 'Light', 'Auto') but
the inline bootstrap script compared against lowercase. This caused
the dark-mode flash prevention to fail when theme was explicitly set.

Also makes the iOS status bar comment version-agnostic per review.

---------

Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>

* Upgrade ts-sdk 0.4.2 and boltz-swap 0.3.1 (#399)

* Fix send balance when sending btc with assets (#401)

* fix max balance when sending btc with assets

* fix keyboard when sending assets

* style: fix Prettier formatting in App.tsx and index.css

* style: rebrand Assets to Arkade Mint + redesign app cards (#398)

* style(assets): rebrand Assets to Arkade Mint with pixel coin icon

- Rename "Assets" to "Arkade Mint" across all asset screens (list, settings, mint success)
- Replace placeholder "A" icon with Mario-style pixel coin in Arkade purple
- New icon matches size/format of other app icons (55px/78px)

* style(apps): redesign app cards with subtle surface treatment

Replace Shadow border+borderPurple cards with whisper-surface style:
- Subtle background tint instead of gray bg + harsh purple border
- Tactile press state with scale(0.97) + background darken
- Touch events matching Button component pattern (mouse + touch)
- Icon aligned to top-left, description nested under title
- Inline accessibility (role, tabIndex, keyboard handler)

src/screens/Apps/Index.tsx

* style: fix prettier formatting

* fix(test): update test selectors after Assets → Arkade Mint rename

The app card testId changed from 'app-assets' to 'app-arkade-mint' and
the header/settings text changed accordingly.

* fix(test): update 'Back to Assets' → 'Back to Arkade Mint' in tests

MintSuccess button text was renamed but tests still referenced old text.

---------

Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: Pietro Grandi <dev@pietro.uno>

* Upgrade ts-sdk@0.4.3 and boltz-swap@0.3.2 (#402)

* Upgrade ts-sdk@0.4.3 and boltz-swap@0.3.2

* Update SW

* feat: streamline onboarding — remove carousel, add dismissible PWA install banner (#375)

* feat(onboarding): remove onboarding carousel, skip straight to wallet

Remove the 4-step onboarding carousel (Onboard.tsx) and all supporting
components (StepBars, Bullet, AlertBox, Announcement, Onboard1-4 SVGs).
New users now go directly from Init → InitConnect → Wallet instead of
through the carousel. The announcement/PSA system is also removed as it
interrupted the flow without providing value.

Files removed:
- src/screens/Wallet/Onboard.tsx (carousel screen)
- src/components/AlertBox.tsx, Announcement.tsx, Bullet.tsx, StepBars.tsx
- src/icons/Onboard1-4.tsx (~10,800 lines of SVG illustrations)
- src/providers/announcements.tsx (PSA announcement system)
- Associated tests for removed components

Files modified:
- src/App.tsx: route to Init instead of Onboard for new wallets
- src/screens/Init/*.tsx: navigate to InitConnect instead of InitSuccess
- src/providers/navigation.tsx: remove Onboard from Pages enum
- src/index.tsx: remove AnnouncementProvider wrapper
- src/test/e2e/utils.ts: remove onboard steps from createWallet helper

* feat(wallet): add dismissible PWA install banner and password nudge

Add a new DismissibleBanner component used for both the PWA install
prompt and the password creation nudge. The banner features Framer Motion
enter/exit animations (EASE_OUT_QUINT, y:-24, scale:0.97), horizontal
swipe-to-dismiss gesture, layout animation for smooth sibling reflow,
and prefers-reduced-motion support with 44px touch targets.

PWA install banner:
- Mobile-only gating (isIOS || isAndroid) — hidden on desktop
- Platform-specific descriptions (iOS share sheet vs Android install)
- Module-level beforeinstallprompt capture for native install prompt
- Respects persistent dismissed state via config.dismissedBanners

Password nudge:
- Replaced old AlertBox with DismissibleBanner (expanded layout)
- Added nudgeVisible/nudgeCheckComplete to context for PWA banner
  priority coordination (PWA banner waits for nudge check)
- Fixed error handling: .catch/.finally on async password check

Config: replaced announcementsSeen with dismissedBanners (existing
configs handled gracefully via ?? [] fallbacks).

New files:
- src/components/DismissibleBanner.tsx

Modified:
- src/screens/Wallet/Index.tsx (PWA banner integration)
- src/providers/nudge.tsx (DismissibleBanner + error handling)
- src/lib/pwa.ts (usePwaInstalled, beforeinstallprompt, promptPwaInstall)
- src/lib/types.ts, src/providers/config.tsx (dismissedBanners)
- src/ionic.css (--purple10 color variable)
- src/test/setup.ts (matchMedia mock)

* fix: address CodeRabbit review feedback

- Password.tsx: reset restoring flag when navigating to InitConnect
  to prevent stale state from a previous restore attempt
- DismissibleBanner.tsx: disable layout animation and popLayout mode
  when prefers-reduced-motion is active
- Wallet/Index.tsx: only show Install action button when native
  prompt is available (Android); iOS shows instruction-only banner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(wallet): clarify iOS PWA banner instruction text

Update the iOS PWA install banner description to reference "the share
icon in Safari's toolbar" instead of generic "the share button" so
users know exactly where to look.

* fix(pwa): return prompt outcome and auto-dismiss banner after install

promptPwaInstall() now returns 'accepted' | 'dismissed' | null instead
of void. On Android, the banner auto-dismisses after the user interacts
with the native install prompt, rather than staying visible.

* fix(banner): add haptic feedback to DismissibleBanner interactions

Fire hapticSubtle() on button taps (Dismiss, Install, Set password)
and on swipe-to-dismiss gesture, consistent with all other interactive
elements in the app.

* feat(onboarding): add animated SVG logo morph sequence with delight effects

Redesign the Init screen with a rich onboarding animation:

- OnboardingLogo: SVG cross-fade between Arcade, Space Invader, and Heart
  shapes with deep squash-stretch bounces using Framer Motion async
  animation controls. Final bounce flies logo to header position via FLIP.
- PixelSplash: burst of purple pixel particles on each bounce
- PixelSunrise: pixelated purple gradient at screen top (fixed position)
- Three bullet points with outlined icon components (Bolt, Globe, ShieldCheck)
  replacing the old paragraph text
- Reduced motion: all animations skip, content renders instantly

Files: OnboardingLogo.tsx, PixelSplash.tsx, PixelSunrise.tsx,
BoltOutline.tsx, GlobeOutline.tsx, ShieldCheckOutline.tsx,
Init.tsx, ionic.css

* style(onboarding): fix Prettier formatting in OnboardingLogo and Init

* style(onboarding): smooth radial gradient, layout + status bar fixes

- PixelSunrise: replace pixelated grid with smooth radial-gradient,
  remove all pixel dissolution code
- Init: move content to bottom of screen (flex-end + paddingBottom),
  stack logo above title with optical alignment to bullet icons
- Status bar: add apple-mobile-web-app-capable for PWA bleed-through,
  set theme-color to #8771BC (computed gradient top on white),
  fix manifest.json theme_color mismatch
- Dynamic theme-color useEffect on Init screen for Safari tinting

* style(onboarding): fix Prettier formatting in Init

* fix(onboarding): stable fly target, gradient portal, dark mode prep

- Always render content in DOM (hidden until ready) so logoTargetRef
  position is stable during fly animation instead of shifting on mount
- Portal OnboardingLogo and PixelSunrise to document.body so they
  escape IonPage's max-width: 640px container on desktop
- Add color-scheme light/dark to :root and .ion-palette-dark
- Add color-scheme meta tag and body background-color for Safari
- Sunrise animation synced with fly via onFlyStart callback
- Snappier bounce physics: deeper squash, higher overshoot, faster timings

* style(onboarding): fix Prettier formatting in Init.tsx

* feat(onboarding): pixel morph animation, spring physics, loading bypass

Logo morph animation:
- Rewrite OnboardingLogo to use pixel-shapes.ts grid system instead of
  opacity crossfade between SVG path components
- Arcade "A" always renders as clean SVG paths (never pixelated), while
  Invader and Heart shapes use pixel rects with CSS transform transitions
- Spring-based bounce recovery (stiffness: 600, damping: 18, mass: 0.6)
  replaces scripted multi-step keyframes for natural overshoot feel
- Morph + bounce happen simultaneously — shape swap at squash peak
- Faster timing: 180ms initial pause, 60ms between morphs, ~1s total
- Portal to document.body so logo escapes IonPage max-width on desktop

Pixel splash particles:
- Increase from 12-17 to 20-28 particles per burst
- Wider velocity range (25-105) and more size variation (2.5-6.5px)

Skip loading screen for new users:
- New users (no wallet in storage) bypass ASP + JS capability gates,
  going straight to Init where the logo morph serves as intro visual
- Wallet provider reads from localStorage synchronously via lazy
  useState initializer, eliminating one-frame Loading flash
- "Create wallet" button stays disabled until ASP resolves

Revert status bar changes:
- All theme-color, color-scheme, and apple-mobile-web-app changes from
  this branch reverted — will be handled in a separate PR
- Remove temp replay button and associated state

Files: OnboardingLogo.tsx, PixelSplash.tsx, Init.tsx, App.tsx,
       wallet.tsx, index.html, manifest.json, ionic.css, index.css

* fix(wallet): remove unused setWalletLoaded setter

walletLoaded is now always true (synchronous init), so the setter is
dead code. Replace useState with a constant to satisfy eslint.

* Apply suggestion from @tiero

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>

* fix: use named export for PageTransition to fix Rollup tree-shaking

Rollup's module concatenation was dropping the default export definition
while keeping the JSX references, causing 'PageTransition is not defined'
at runtime. Named exports are handled more reliably.

* fix: update e2e helpers for new onboarding flow (no more InitSuccess page)

The onboarding flow now goes Init → InitConnect → Wallet directly,
skipping the InitSuccess page. The createWallet() and restoreWallet()
helpers were waiting for "Your new wallet is live!" / clicking
"Go to wallet" which no longer exist, causing all Playwright tests
to hang indefinitely.

---------

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* perf(transactions): virtualise TransactionsList with @tanstack/react-virtual (#394)

* test: add smoke test for TransactionsList with large tx history

Reproduces the reported lag on wallets with several thousand transactions.
TransactionsList renders every row into the DOM at once with no windowing;
render time scales linearly (~1.8ms/tx in jsdom), so:
  100 txs  →   471ms  ✓
  500 txs  → 1,475ms  ✗
 1000 txs  → 2,258ms  ✗
 3000 txs  → 5,477ms  ✗
 5000 txs  → 9,206ms  ✗

The fix is to virtualise the list (e.g. @tanstack/react-virtual).
This test will serve as the regression gate once that is in place.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* perf(transactions): virtualise TransactionsList with @tanstack/react-virtual

Fixes the wallet freeze / extreme lag reported for wallets with several
thousand transactions.

Root cause
----------
TransactionsList rendered every Tx into the DOM unconditionally via a plain
.map() call.  With no windowing, render time scaled linearly with the
transaction count (~1.8 ms/tx in jsdom, worse in a real browser due to
layout and paint):

  Before fix (all DOM nodes mounted):
    100 txs  →    471 ms
    500 txs  →  1 475 ms
   1000 txs  →  2 258 ms
   3000 txs  →  5 477 ms
   5000 txs  →  9 206 ms

Fix
---
Replace the plain map() with @tanstack/react-virtual's useVirtualizer.
Only the rows that fit in the viewport (+ a 5-row overscan buffer above
and below) are ever mounted in the DOM.  The total DOM node count is now
O(visible rows) ≈ 15–25 regardless of wallet history size.

  After fix (virtual rows only):
    100 txs  →  4 ms ✓
    500 txs  →  3 ms ✓
   1000 txs  →  4 ms ✓
   3000 txs  →  4 ms ✓
   5000 txs  →  3 ms ✓

The list renders in a self-contained scrollable container
(height: calc(100dvh - 380px), min-height: 200px) so the balance/buttons
section above it remains static — a common pattern in mobile wallet UIs.

Smoke test update
-----------------
- Added a DOM-count assertion: at most MAX_DOM_ROWS (20) rows may be
  mounted at any one time, ensuring virtualisation cannot regress silently.
- Retained the per-count render-time assertions (≤ 500 ms) as a belt-and-
  braces guard.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* feat(TransactionsList): add ArrowUp/ArrowDown keyboard navigation with virtualizer scroll-sync

Add focusedIndex state and handleListKeyDown on the parentRef scroll
container. Arrow key presses call virtualizer.scrollToIndex(next) then
requestAnimationFrame to focus the target Focusable row once it is
mounted. Existing Enter → handleClick and Escape → focusOnOuterShell
behaviour is preserved; Tab is unchanged.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* fix(TransactionsList): guard focusRow and focusOnFirstRow against empty/out-of-range indices

Add an early-return bounds check in focusRow (index < 0 || index >= txs.length)
to prevent virtualizer.scrollToIndex and key(txs[index]) from executing on
invalid indices. Move the txs.length === 0 guard before setFocused(true) in
focusOnFirstRow so focused is never set true when there are no rows to navigate.
The focusRow guard also covers the edge case in handleListKeyDown where
txs.length === 0 would produce a -1 next index.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* remove transactions smoke test

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* fix(keyboard): sync focusedIndex with actual DOM focus on row focus events (#397)

* Initial plan

* fix(keyboard): sync focusedIndex with actual DOM focus on row focus events

Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

* chore: remove accidentally committed package-lock.json

Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

* fix: hide virtualizer scrollbar and address review feedback

- Hide scrollbar on transaction list (reported by @Kukks)
- Remove package-lock.json from .gitignore (feedback from @pietro909)
- Remove redundant setFocusedIndex calls (CodeRabbit nitpick)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* fix: adapt e2e tests for pill navbar navigation

- Add data-testid to PillNavbar buttons and settings header button
- Fix settings button: only toggle-close from Settings main menu,
  always navigate to Settings main from sub-pages
- Remove duplicate testIds from hidden IonTabBar
- Update createWalletWithPassword to close settings via button
- Remove unused pwaIsInstalled import

* fix: restore settings-icon-light testId lost in lucide-react migration

The switch from custom SVG to lucide-react dropped the data-testid
attribute, causing all e2e asset tests that call enableAssets() to
time out.

* fix: resolve e2e test failures from navbar redesign

- Lower settings button z-index below ion-header so sub-page aux buttons
  remain clickable (fixes keyboard swap button interception)
- Add pointer-events: none to hidden morph icons to prevent invisible
  elements from intercepting clicks
- Use testId selectors in init.test.ts to avoid strict mode violations
  from duplicate text nodes (pill navbar + Ionic tab bar)
- Scope settings-icon-light click in enableAssets to ion-header to avoid
  matching the navbar's duplicate settings icon

* fix: remove ion-header scope from settings icon test selector

The navbar redesign moved the settings icon out of ion-header into a
standalone button. The e2e test scoped its click to ion-header, which
no longer contains the icon.

* fix: resolve e2e strict mode violation for settings icon

Two elements matched settings-icon-light testId: the global settings
button and the app Header's aux icon. Add data-testid='header-aux-btn'
to the Header component's aux button and use that in the test.

* fix: force-click app settings icon covered by global settings button

The global header-settings-btn (position: absolute, z-index: 9) covers
the app Header's aux button in the same top-right position. Use
force: true to bypass Playwright's actionability check.

* fix: hide global settings button on app sub-pages

The global header-settings-btn (position: absolute, z-index: 9) was
covering app-specific Header aux buttons on sub-pages like Arkade Mint.
Hide the global button when the current page is not a root tab page
(Wallet, Apps, or Settings), since sub-pages render their own Header.

* style: fix formatting and rebuild worker after merge

* fix: navigate to wallet tab before clicking settings in e2e tests

The global settings button is hidden (display:none) on sub-pages.
Tests calling getNsec/resetWallet may be on a sub-page after a
send/swap, so navigate to wallet tab first to ensure the button
is visible.

* fix: conditionally navigate to wallet tab before settings in e2e

The PillNavbar is hidden on settings pages, so tab-wallet click
would fail. Use navigateToSettings helper that only clicks
tab-wallet when visible, then clicks tab-settings.

---------

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Pietro Grandi <dev@pietro.uno>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>
Co-authored-by: pietro909 <2094604+pietro909@users.noreply.github.com>
Co-authored-by: louisinger <louis@vulpem.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Co-authored-by: louisinger <41042567+louisinger@users.noreply.github.com>
Co-authored-by: OpenClaw Agent <agent@openclaw.dev>
Co-authored-by: louisinger <louisinger@protonmail.com>
Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
@coderabbitai coderabbitai Bot mentioned this pull request Mar 23, 2026
TaprootFreak pushed a commit to TaprootFreak/wallet that referenced this pull request Mar 27, 2026
* Use next branches

* Implements chain swaps

* fixes

* Use new SDK storage

* Update integration to Contract Manager

* App starting, payments received

* Use correct import

* Use new SDK storage

* wip

* SW working

* using ws

* Latest SDK

* workaround for build

* Fix build

* Rollback unneeeded changes, clean up code

* Address Rabbit comment

* Set flag in unsub

* Fix tx history rendering issue

* Update package.json, lock file, worker

* Run migration only if needed

* Update lock and worker

* Use next releases

* Clear setInterval on re-init to prevent leaking duplicate intervals

* Implements delegators UI (arkade-os#319)

* implements delegators UI

* rabbit fixes

* dumb down + rename to delegates

* revert changes

* add unit tests

* add e2e test

* rabbit fixes

* fixes

* Upgrade latest sdk

* fixes

* Ignore md helpers

* Stop pinging wallet when locked

* Allow the page to force activation of a newly installed worker.

* Handle failure in getCommitmentTxIds

* Add defensive parsing of messages for stale service workers

* Upgrade sdk and boltz-swap

* fix Indexer constructor in wallet provider

* Use event.waitUntil

* update dependencies

* bug fix

* update dependencies

* default delegator

* fix URL

* delegator

* make delegator URL configurable

* Wire up delegates

* Centralize compressed-xonly conversion, hook in delegates

* Trigger reload on delegate on/off

* Delegates on by default

* Use persisted preference for delegates

* Fix type in wallet

* Remove workspace file

* Fix formatting

* Enforce use of delegate upon restore

* Use correct delegates port, update e2e test

* Remove log

* Update Fulmine 0.3.15 and port mapping

* Change fulmine port in setup script

* fix server persistency

* Use delegate url from network

* Remove unused VITE_DELEGATOR_URL

* fix delegate server

* feat: asset support (arkade-os#318)

* Add assets integration design document

Design for integrating Arkade SDK asset system (ts-sdk PR arkade-os#279) into the wallet,
covering import, receive, send, mint, reissue, and burn flows.

* Add assets integration implementation plan

16-task step-by-step plan covering SDK install, types, providers, navigation,
BIP21, all asset screens, send/receive modifications, and tx history updates.

* feat: upgrade @arkade-os/sdk to 0.3.13

Upgrade from 0.3.12 to 0.3.13, the latest published version.
The asset branch (github:arkade-os/ts-sdk#asset) does not exist yet,
so using the latest npm release as the foundation for asset integration.

* feat: add asset types and importedAssets to config

* feat: add asset flow state to FlowContext

* feat: add asset balances and metadata cache to WalletContext

* feat: register asset pages in navigation with placeholders

* feat: add BIP21 asset URI encoding and decoding

* feat: add Assets app card to Apps index

* feat: implement Assets list screen

* feat: implement asset import screen

* feat: implement asset detail screen

* feat: implement mint and mint success screens

* feat: implement reissue and burn screens

* feat: add asset support to receive flow

* feat: add asset support to send flow

* feat: show asset info in transaction history and coin control

* refactor: replace as-any casts with proper SDK types

* feat: install @arkade-os/sdk from asset branch (louisinger/wallet-sdk#asset)

* refactor: use proper SDK types for transaction assets

ArkTransaction now includes assets field from SDK changes,
removing the last as-any cast in asp.ts.

* chore: gitignore docs/plans

* format + ts-sdk from gh repo

* allow build

* remove useless tests

* fixes

* fixes

* UI fixes

* bump ts-sdk

* feat(mint): add optional control asset selector

Generated with Claude Code
via Happy

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* fix: display asset amounts with proper decimals and convert unit inputs to raw

- Add formatAssetAmount utility for raw→human-readable display
- Convert user-entered units to raw integers in Burn, Reissue, and Send flows
- Apply decimal formatting across all asset display surfaces

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* style: fix prettier formatting in Burn and Reissue

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* refactor: address code review issues across asset feature

- Skip BTC limits for asset receives in Amount screen
- Fix test mocks with missing context properties
- Format raw supply display in MintSuccess and Detail screens
- Type assetMetadataCache as Map<string, AssetDetails> instead of any
- Extract shared assetInputStyle to src/lib/styles.ts
- Extract AssetAvatar component to deduplicate avatar pattern (~10 locations)
- Fix stale closure in Detail.tsx useEffect
- Format asset amounts in Vtxos debug view with metadata lookup
- Fix default decimals from 0 to 8 in MintSuccess
- Fix Burn placeholder to show formatted balance

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* fix: apply decimal unit conversion to all asset amount inputs and displays

- Add formatAssetAmount() to format.ts for display (raw → human units)
- Mint: convert user units to raw via Decimal.mul(amount, 10^decimals)
- Send form: convert input to raw, display available balance in units
- Burn: convert input to raw, show balance in human-readable units
- Reissue: convert input to raw using decimals metadata
- All display locations (tx list, asset list, detail, send confirm/success)
  now apply formatAssetAmount to show correct decimal representation
- No trailing zeros thanks to Intl.NumberFormat maximumFractionDigits

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* persist asset metada

* clean

* various UI fixes

* add playwright tests

* fix asset e2e tests

* fixes

* fix invoice bip21 encoding

* fix Apps/Asset/receive

* simplify asset import

* UI fix + reuse Input component

* Merge remote-tracking branch origin/master into assets

* Show asset ticker instead of SATS on InputAmount component when receiving an asset

* Also fix amount when user has Fiat Only on Settings / Display Preferences

* revert changes to InputWithScanner

* new InputAssetId component

* dry code

* tests for lib/assets

* Mint: hide dropdown if knownAsset.length <= 0

* allow to mint control asset

* feat: add isIssuance helper to detect issuance txs

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show issuance label and green assets in tx list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: show issuance direction and positive asset amounts in tx detail

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* test: add issuance transaction rendering test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat: add burn tx handling, remove asset prefix, move asset icon right

- Add isBurn helper (self-send with amount=0 and negative asset amounts)
- Update isIssuance to check for positive asset amounts
- Remove +/- prefix from asset display (SDK now returns signed amounts)
- Show "Burn" label and direction for burn transactions
- Move asset icon to right side of amount in tx list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* handle Issuance and Burn tx in transaction history

* Fix restoration

* fix test

* fix mint tests

* update to latest ts-sdk master

* fix playwright tests

* not:cloned-input selector

* verified | approved asset flow

* playwright: use reduced motion

* move upgrade logic to updateConfig

* simplify mint form

* fix send all btc

* remove ctrlDecimals

* remove fuji app

* show asset amount in Receive/Success

* receive/success fix

* rework receive/success

* flxShrink mint form

* add link to Asset app

* add assets settings to toggle service + hide assets in send if inactive (arkade-os#353)

* add assets settings to toggle service + hide assets in send if inactive

* nit: 'enabled' instead of 'connected'

* add test + fix tests

* update boltz config

* fixes

* fix: revert boltz config and add missing enableAssets in reissue test

- Revert [[currencies.lnds]] back to [currencies.lnd] in boltz TOML
  config. The array syntax broke Boltz's LND connection, causing all
  Lightning swap e2e tests to fail.
- Add missing enableAssets() call to the reissue test, which was
  omitted when asset toggle support was added in ea958fd.

* fix: increase timeout for multi-swap e2e tests

The restore and nostr swap tests perform multiple swap operations
(reverse + submarine) plus wallet restore, exceeding the default 30s
test timeout. Increase to 90s to give these complex flows enough time.

* fix: increase action timeout and add balance wait for swap tests

The submarine swap creation is async and can take variable time,
especially after a reverse swap. Increase actionTimeout from 30s to
60s to accommodate slow swap creation, and add test.setTimeout(120s)
for multi-swap tests. Also verify wallet balance is visible before
attempting the send flow in the restore test.

* fix: revert actionTimeout to 30s and verify balance in nostr test

Increasing actionTimeout to 60s made the nostr swap test worse by
keeping failed swap connections alive longer, preventing retries from
getting a clean Boltz state. Revert to 30s and ensure balance is
verified by exact match before submarine swap.

* chore: retrigger CI

* ci: trigger workflows for PRs targeting next branch

* fix: revert Boltz LND config to singular [currencies.lnd] format

The boltz/boltz:ark image does not support the plural [[currencies.lnds]]
array syntax. This broke all Lightning swap tests (receive, send, refund).
Keep pull_policy: always to ensure fresh images.

* chore: retrigger CI

* fix: fund Boltz core wallet for chain swaps in regtest setup

The ARK-to-BTC chain swap direction requires Boltz to have BTC in its
core Bitcoin wallet. Without funding, createArkToBtcSwap hangs/fails,
breaking "send to onchain address" and "send to onchain via swap" tests.

* chore: format setup.mjs

* fix: create Bitcoin Core 'core' wallet before funding for Boltz chain swaps

* fix: create and fund Boltz core wallet before Boltz starts

Move wallet creation before Boltz backend startup so the wallet exists
and has funds when Boltz initializes. Mine an extra block to confirm.

* fix: fall back to collaborative exit when chain swap creation fails

When createArkToBtcSwap returns null or throws, navigate to SendDetails
without a pendingSwap. The Details page already handles this case by
using collaborativeExitWithFees as the fallback for BTC address sends.

* revert: remove Boltz wallet funding from setup (breaks Boltz startup)

The wallet funding caused Boltz to fail to start. Instead, the Form.tsx
fallback to collaborative exit handles onchain sends when chain swaps
are unavailable.

* fix: update onchain send test assertions for collaborative exit fees

The chain swap path (Boltz ARK→BTC) is unavailable in regtest, so the
app falls back to collaborative exit. This changes the fee from 275
(Boltz swap fee) to 200 (ASP onchain output fee from fees intent).
Also makes fees intent/clear calls synchronous to prevent race conditions.

* fix: remove unused exec import from send test

* fix: disable delegate in e2e tests (no delegator service in CI)

The delegate feature defaults to true but requires a delegator service
at localhost:7004. Since CI has no delegator, wallet initialization hangs
causing all tests to timeout. Use VITE_DELEGATE_ENABLED=false in the
Playwright config to disable delegation in the test environment.

* chore: format all files with prettier after merge

* ci: add workflow_dispatch trigger to enable manual CI runs

* fix: enable fulmine delegator and fix port mapping for e2e tests

- Add FULMINE_DELEGATOR_ENABLED=true and FULMINE_DELEGATOR_FEE=350
- Fix port mapping to expose delegator on port 7002
- Aligns fulmine config with next branch

* fix server persistency

---------

Co-authored-by: louisinger <louis@vulpem.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>

* Delete data on IndexedDB when reset (arkade-os#366)

* fix delegate dev server

* fix tests

* update dependencies

* Use mutinynet server for testing

* Fix setup 7001

* E2E fixes, config, delegates url (arkade-os#369)

* Fix e2e delegate test

* Log commit from SW to verify updates

* Ensure config has all the expected fields

* Fix delegates url resolution

* Missing type, harden config parsing

* reduce tests waiting for maybeLater

* skip swap restore test

* disable chain swap on receive

* Upgrade SDK, boltz, use 30s ServiceWorker timeout (arkade-os#371)

* SDK Version next 8

* Use SW custom timeout

* Update SW

* Use sdk 0.4.0 and boltz-swap 0.3.0

* Update service worker

* Update service worker 2

* fix receive fractional assets (arkade-os#373)

* fix receive fractional assets

* fix bip21 in asset receive

* disable mint if balance < dust

* New asset card (arkade-os#374)

* New asset card

* lint

* General fixes (arkade-os#382)

* dry code

* remove console.log

* fix throw with fractional sats

* refactor Asset send input amount

* fix keyboard to work with assets

* Add vmempool.space explorer links for offchain txs and assets (arkade-os#380)

* Add vmempool.space explorer links for offchain txs and assets

- Add vmempool.space URL configuration with VITE_VMEMPOOL_URL env var
- Add helper functions for offchain tx and asset URLs in explorers.ts
- Update Table component to support clickable rows with onClick handlers
- Update Details component to accept wallet, assetId, and isOffchainTx props
- Make Transaction ID and Asset ID fields clickable in transaction details
- Open offchain txs (redeemTxid) in vmempool.space
- Open onchain txs (boardingTxid) in mempool.space
- Open assets in vmempool.space asset explorer
- Only show vmempool links for mainnet by default (configurable via env var)

* fix: keep tap-to-copy, add external link icon for explorer links

* fix: add aria-label to ExternalLinkIcon, hide link when URL unavailable

* fixes

---------

Co-authored-by: OpenClaw Agent <agent@openclaw.dev>
Co-authored-by: louisinger <louisinger@protonmail.com>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>

* fix max button with large amounts

* fix issuing of assets with new control asset

* Replace sleep with wait for new vtxo

* fix input amount

* Don't block app on swap init error (arkade-os#383)

* Don't block the UX if swaps service isn't available and log errors

* Update SW

* Add tests

* Add tests

* lint

* fix nostr backup of swaps

* fix SVGs

* fix boltz env

* fix tests

* lint

* fix tests

* remove console.log

* update pnpm-lock

* refactor: simplify Hero component and remove LearnMore section (arkade-os#395)

* feat(ui): navbar redesign + lucide icon migration (WIP)

- Migrate ~40 custom SVG icons to lucide-react (outlined, strokeWidth 1.75)
- Redesign bottom navbar: transparent bg, 28px icons, 10px labels, 49px height
- Add frosted glass blur on header + status bar area (backdrop-filter)
- Three navbar variants with floating dev switcher: border, blur, pill
- Pill variant: Bitkey-style floating capsule with 2 icons, settings in header
- Hairline border CSS variable (0.5px retina, 1px standard)
- 44px touch targets, touch-action manipulation, prefers-reduced-motion support
- Allow trycloudflare.com in vite dev server

* feat(ui): custom pill navbar with settings gear morph

Replace Ionic's ion-tab-bar with a custom floating pill navbar:

- Hide ion-tab-bar (visibility: hidden), use pure React PillNavbar component
- Pill: fixed bottom-center, 999px border-radius, 3-layer shadow composition
- Wallet + Apps tabs with lucide-react icons (size 24, strokeWidth 2)
- Active tab: color + background highlight, inactive: muted color
- Settings gear icon in header with morph animation to X on settings page
- Gear/X vertically centered in 4rem header band, aligned with header title
- Pill hidden on Settings tab, visible on Wallet/Apps
- Dark mode: pill bg #1C1C1E for contrast against #101010 page
- Eased gradient fade (160px, 8-stop) above pill for smooth content cutoff
- shape-rendering: geometricPrecision for crisp SVG icons
- margin-based centering (not translateX) to avoid sub-pixel blur
- 44px tap targets, touch-action: manipulation, prefers-reduced-motion

* fix(pwa): fix swipe-back navigation for iOS standalone mode

The iOS edge-swipe gesture was broken in the PWA: it navigated to wrong
screens, activated when it shouldn't (showing white pages), and caused
double animations. This was due to browser history entries being out of
sync with internal navigation state.

Replaces the old push-every-navigation model with 1:1 browser history
sync — browser entries only exist when there's real back navigation.
When the back stack is empty, iOS disables the swipe gesture entirely.

Key changes:
- navigation.tsx: stable popstate handler via refs (no phantom entries),
  root navigations clear browser history via history.go(-n), forward
  navigations push 1:1 to browser history
- options.tsx: Settings sub-navigation now pushes browser history entries
  so swipe-back works within Settings pages, coordinates with main nav
  via subNavHandler for cleanup on tab switches
- Distinguishes swipe vs back-button via isButtonBack flag to suppress
  Framer Motion animation on swipe (browser already animated it)
- Adds overscroll-behavior: none to prevent rubber-banding

Note: this is a pragmatic PWA workaround — relies on browser history
depth controlling iOS gesture activation. Will be properly solved when
migrating to Capacitor (native WKWebView has no Safari gestures).

* fix(nav): address CodeRabbit review feedback

- Pop blocked entries (InitConnect/Loading) from backStack to prevent
  infinite loop when user repeatedly presses back
- Guard historyDepth from going negative in Settings sub-navigation

* fix(nav): address second round of CodeRabbit feedback

- goBack delegates to subNavHandler when main backStack is empty
- navigateToOption resets historyDepth when returning to Menu

* update ts-sdk dependency

* fix double swap when claiming reverse swap

* fix(ui): match iOS/Android status bar color to app theme (arkade-os#334)

* fix(ui): match iOS/Android status bar color to app theme

The status bar stayed white in dark mode because:
- The theme-color meta tag was hardcoded to #000000
- Safari doesn't reliably support media queries on theme-color
- The html element had no background-color, so the safe area defaulted to white

Fix: inline script in <head> reads the stored theme from localStorage
and sets theme-color + ion-palette-dark class before first paint.
applyTheme() keeps it in sync on manual toggle. Root element background
now follows --ion-background-color so the safe area fills correctly.

* fix(ui): add dev-only logging to theme bootstrap catch block

Address CodeRabbit nitpick: log theme init failures on localhost
so broken localStorage/config parsing is visible during development.

* fix(ios): status bar color matching and content overlap for iOS 26+ PWA

iOS 26+ ignores the theme-color meta tag — it derives the status bar
color from the actual rendered content at the top of the page. Add a
position:fixed body::before pseudo-element that sits behind the
transparent status bar and uses var(--ion-background-color), so iOS
samples the correct theme color instantly on theme switch.

Push .ion-page below the safe area with top: env(safe-area-inset-top)
using ion-app > .ion-page selector (higher specificity than Ionic's
core .ion-page { top: 0 } which loads later in the cascade).

Also fix manifest.json theme_color (#fff) and improve inline theme
bootstrap comments in index.html.

* style: fix prettier formatting in ionic.css

* fix(ui): match theme enum casing in bootstrap script

The Themes enum uses capitalized values ('Dark', 'Light', 'Auto') but
the inline bootstrap script compared against lowercase. This caused
the dark-mode flash prevention to fail when theme was explicitly set.

Also makes the iOS status bar comment version-agnostic per review.

---------

Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>

* Upgrade ts-sdk 0.4.2 and boltz-swap 0.3.1 (arkade-os#399)

* Fix send balance when sending btc with assets (arkade-os#401)

* fix max balance when sending btc with assets

* fix keyboard when sending assets

* style: fix Prettier formatting in App.tsx and index.css

* style: rebrand Assets to Arkade Mint + redesign app cards (arkade-os#398)

* style(assets): rebrand Assets to Arkade Mint with pixel coin icon

- Rename "Assets" to "Arkade Mint" across all asset screens (list, settings, mint success)
- Replace placeholder "A" icon with Mario-style pixel coin in Arkade purple
- New icon matches size/format of other app icons (55px/78px)

* style(apps): redesign app cards with subtle surface treatment

Replace Shadow border+borderPurple cards with whisper-surface style:
- Subtle background tint instead of gray bg + harsh purple border
- Tactile press state with scale(0.97) + background darken
- Touch events matching Button component pattern (mouse + touch)
- Icon aligned to top-left, description nested under title
- Inline accessibility (role, tabIndex, keyboard handler)

src/screens/Apps/Index.tsx

* style: fix prettier formatting

* fix(test): update test selectors after Assets → Arkade Mint rename

The app card testId changed from 'app-assets' to 'app-arkade-mint' and
the header/settings text changed accordingly.

* fix(test): update 'Back to Assets' → 'Back to Arkade Mint' in tests

MintSuccess button text was renamed but tests still referenced old text.

---------

Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: Pietro Grandi <dev@pietro.uno>

* Upgrade ts-sdk@0.4.3 and boltz-swap@0.3.2 (arkade-os#402)

* Upgrade ts-sdk@0.4.3 and boltz-swap@0.3.2

* Update SW

* feat: streamline onboarding — remove carousel, add dismissible PWA install banner (arkade-os#375)

* feat(onboarding): remove onboarding carousel, skip straight to wallet

Remove the 4-step onboarding carousel (Onboard.tsx) and all supporting
components (StepBars, Bullet, AlertBox, Announcement, Onboard1-4 SVGs).
New users now go directly from Init → InitConnect → Wallet instead of
through the carousel. The announcement/PSA system is also removed as it
interrupted the flow without providing value.

Files removed:
- src/screens/Wallet/Onboard.tsx (carousel screen)
- src/components/AlertBox.tsx, Announcement.tsx, Bullet.tsx, StepBars.tsx
- src/icons/Onboard1-4.tsx (~10,800 lines of SVG illustrations)
- src/providers/announcements.tsx (PSA announcement system)
- Associated tests for removed components

Files modified:
- src/App.tsx: route to Init instead of Onboard for new wallets
- src/screens/Init/*.tsx: navigate to InitConnect instead of InitSuccess
- src/providers/navigation.tsx: remove Onboard from Pages enum
- src/index.tsx: remove AnnouncementProvider wrapper
- src/test/e2e/utils.ts: remove onboard steps from createWallet helper

* feat(wallet): add dismissible PWA install banner and password nudge

Add a new DismissibleBanner component used for both the PWA install
prompt and the password creation nudge. The banner features Framer Motion
enter/exit animations (EASE_OUT_QUINT, y:-24, scale:0.97), horizontal
swipe-to-dismiss gesture, layout animation for smooth sibling reflow,
and prefers-reduced-motion support with 44px touch targets.

PWA install banner:
- Mobile-only gating (isIOS || isAndroid) — hidden on desktop
- Platform-specific descriptions (iOS share sheet vs Android install)
- Module-level beforeinstallprompt capture for native install prompt
- Respects persistent dismissed state via config.dismissedBanners

Password nudge:
- Replaced old AlertBox with DismissibleBanner (expanded layout)
- Added nudgeVisible/nudgeCheckComplete to context for PWA banner
  priority coordination (PWA banner waits for nudge check)
- Fixed error handling: .catch/.finally on async password check

Config: replaced announcementsSeen with dismissedBanners (existing
configs handled gracefully via ?? [] fallbacks).

New files:
- src/components/DismissibleBanner.tsx

Modified:
- src/screens/Wallet/Index.tsx (PWA banner integration)
- src/providers/nudge.tsx (DismissibleBanner + error handling)
- src/lib/pwa.ts (usePwaInstalled, beforeinstallprompt, promptPwaInstall)
- src/lib/types.ts, src/providers/config.tsx (dismissedBanners)
- src/ionic.css (--purple10 color variable)
- src/test/setup.ts (matchMedia mock)

* fix: address CodeRabbit review feedback

- Password.tsx: reset restoring flag when navigating to InitConnect
  to prevent stale state from a previous restore attempt
- DismissibleBanner.tsx: disable layout animation and popLayout mode
  when prefers-reduced-motion is active
- Wallet/Index.tsx: only show Install action button when native
  prompt is available (Android); iOS shows instruction-only banner

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(wallet): clarify iOS PWA banner instruction text

Update the iOS PWA install banner description to reference "the share
icon in Safari's toolbar" instead of generic "the share button" so
users know exactly where to look.

* fix(pwa): return prompt outcome and auto-dismiss banner after install

promptPwaInstall() now returns 'accepted' | 'dismissed' | null instead
of void. On Android, the banner auto-dismisses after the user interacts
with the native install prompt, rather than staying visible.

* fix(banner): add haptic feedback to DismissibleBanner interactions

Fire hapticSubtle() on button taps (Dismiss, Install, Set password)
and on swipe-to-dismiss gesture, consistent with all other interactive
elements in the app.

* feat(onboarding): add animated SVG logo morph sequence with delight effects

Redesign the Init screen with a rich onboarding animation:

- OnboardingLogo: SVG cross-fade between Arcade, Space Invader, and Heart
  shapes with deep squash-stretch bounces using Framer Motion async
  animation controls. Final bounce flies logo to header position via FLIP.
- PixelSplash: burst of purple pixel particles on each bounce
- PixelSunrise: pixelated purple gradient at screen top (fixed position)
- Three bullet points with outlined icon components (Bolt, Globe, ShieldCheck)
  replacing the old paragraph text
- Reduced motion: all animations skip, content renders instantly

Files: OnboardingLogo.tsx, PixelSplash.tsx, PixelSunrise.tsx,
BoltOutline.tsx, GlobeOutline.tsx, ShieldCheckOutline.tsx,
Init.tsx, ionic.css

* style(onboarding): fix Prettier formatting in OnboardingLogo and Init

* style(onboarding): smooth radial gradient, layout + status bar fixes

- PixelSunrise: replace pixelated grid with smooth radial-gradient,
  remove all pixel dissolution code
- Init: move content to bottom of screen (flex-end + paddingBottom),
  stack logo above title with optical alignment to bullet icons
- Status bar: add apple-mobile-web-app-capable for PWA bleed-through,
  set theme-color to #8771BC (computed gradient top on white),
  fix manifest.json theme_color mismatch
- Dynamic theme-color useEffect on Init screen for Safari tinting

* style(onboarding): fix Prettier formatting in Init

* fix(onboarding): stable fly target, gradient portal, dark mode prep

- Always render content in DOM (hidden until ready) so logoTargetRef
  position is stable during fly animation instead of shifting on mount
- Portal OnboardingLogo and PixelSunrise to document.body so they
  escape IonPage's max-width: 640px container on desktop
- Add color-scheme light/dark to :root and .ion-palette-dark
- Add color-scheme meta tag and body background-color for Safari
- Sunrise animation synced with fly via onFlyStart callback
- Snappier bounce physics: deeper squash, higher overshoot, faster timings

* style(onboarding): fix Prettier formatting in Init.tsx

* feat(onboarding): pixel morph animation, spring physics, loading bypass

Logo morph animation:
- Rewrite OnboardingLogo to use pixel-shapes.ts grid system instead of
  opacity crossfade between SVG path components
- Arcade "A" always renders as clean SVG paths (never pixelated), while
  Invader and Heart shapes use pixel rects with CSS transform transitions
- Spring-based bounce recovery (stiffness: 600, damping: 18, mass: 0.6)
  replaces scripted multi-step keyframes for natural overshoot feel
- Morph + bounce happen simultaneously — shape swap at squash peak
- Faster timing: 180ms initial pause, 60ms between morphs, ~1s total
- Portal to document.body so logo escapes IonPage max-width on desktop

Pixel splash particles:
- Increase from 12-17 to 20-28 particles per burst
- Wider velocity range (25-105) and more size variation (2.5-6.5px)

Skip loading screen for new users:
- New users (no wallet in storage) bypass ASP + JS capability gates,
  going straight to Init where the logo morph serves as intro visual
- Wallet provider reads from localStorage synchronously via lazy
  useState initializer, eliminating one-frame Loading flash
- "Create wallet" button stays disabled until ASP resolves

Revert status bar changes:
- All theme-color, color-scheme, and apple-mobile-web-app changes from
  this branch reverted — will be handled in a separate PR
- Remove temp replay button and associated state

Files: OnboardingLogo.tsx, PixelSplash.tsx, Init.tsx, App.tsx,
       wallet.tsx, index.html, manifest.json, ionic.css, index.css

* fix(wallet): remove unused setWalletLoaded setter

walletLoaded is now always true (synchronous init), so the setter is
dead code. Replace useState with a constant to satisfy eslint.

* Apply suggestion from @tiero

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>

* fix: use named export for PageTransition to fix Rollup tree-shaking

Rollup's module concatenation was dropping the default export definition
while keeping the JSX references, causing 'PageTransition is not defined'
at runtime. Named exports are handled more reliably.

* fix: update e2e helpers for new onboarding flow (no more InitSuccess page)

The onboarding flow now goes Init → InitConnect → Wallet directly,
skipping the InitSuccess page. The createWallet() and restoreWallet()
helpers were waiting for "Your new wallet is live!" / clicking
"Go to wallet" which no longer exist, causing all Playwright tests
to hang indefinitely.

---------

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* perf(transactions): virtualise TransactionsList with @tanstack/react-virtual (arkade-os#394)

* test: add smoke test for TransactionsList with large tx history

Reproduces the reported lag on wallets with several thousand transactions.
TransactionsList renders every row into the DOM at once with no windowing;
render time scales linearly (~1.8ms/tx in jsdom), so:
  100 txs  →   471ms  ✓
  500 txs  → 1,475ms  ✗
 1000 txs  → 2,258ms  ✗
 3000 txs  → 5,477ms  ✗
 5000 txs  → 9,206ms  ✗

The fix is to virtualise the list (e.g. @tanstack/react-virtual).
This test will serve as the regression gate once that is in place.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* perf(transactions): virtualise TransactionsList with @tanstack/react-virtual

Fixes the wallet freeze / extreme lag reported for wallets with several
thousand transactions.

Root cause
----------
TransactionsList rendered every Tx into the DOM unconditionally via a plain
.map() call.  With no windowing, render time scaled linearly with the
transaction count (~1.8 ms/tx in jsdom, worse in a real browser due to
layout and paint):

  Before fix (all DOM nodes mounted):
    100 txs  →    471 ms
    500 txs  →  1 475 ms
   1000 txs  →  2 258 ms
   3000 txs  →  5 477 ms
   5000 txs  →  9 206 ms

Fix
---
Replace the plain map() with @tanstack/react-virtual's useVirtualizer.
Only the rows that fit in the viewport (+ a 5-row overscan buffer above
and below) are ever mounted in the DOM.  The total DOM node count is now
O(visible rows) ≈ 15–25 regardless of wallet history size.

  After fix (virtual rows only):
    100 txs  →  4 ms ✓
    500 txs  →  3 ms ✓
   1000 txs  →  4 ms ✓
   3000 txs  →  4 ms ✓
   5000 txs  →  3 ms ✓

The list renders in a self-contained scrollable container
(height: calc(100dvh - 380px), min-height: 200px) so the balance/buttons
section above it remains static — a common pattern in mobile wallet UIs.

Smoke test update
-----------------
- Added a DOM-count assertion: at most MAX_DOM_ROWS (20) rows may be
  mounted at any one time, ensuring virtualisation cannot regress silently.
- Retained the per-count render-time assertions (≤ 500 ms) as a belt-and-
  braces guard.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* feat(TransactionsList): add ArrowUp/ArrowDown keyboard navigation with virtualizer scroll-sync

Add focusedIndex state and handleListKeyDown on the parentRef scroll
container. Arrow key presses call virtualizer.scrollToIndex(next) then
requestAnimationFrame to focus the target Focusable row once it is
mounted. Existing Enter → handleClick and Escape → focusOnOuterShell
behaviour is preserved; Tab is unchanged.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* fix(TransactionsList): guard focusRow and focusOnFirstRow against empty/out-of-range indices

Add an early-return bounds check in focusRow (index < 0 || index >= txs.length)
to prevent virtualizer.scrollToIndex and key(txs[index]) from executing on
invalid indices. Move the txs.length === 0 guard before setFocused(true) in
focusOnFirstRow so focused is never set true when there are no rows to navigate.
The focusRow guard also covers the edge case in handleListKeyDown where
txs.length === 0 would produce a -1 next index.

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* remove transactions smoke test

https://claude.ai/code/session_01LDxnFdhREuPcK3yPCkQUMU

* fix(keyboard): sync focusedIndex with actual DOM focus on row focus events (arkade-os#397)

* Initial plan

* fix(keyboard): sync focusedIndex with actual DOM focus on row focus events

Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

* chore: remove accidentally committed package-lock.json

Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: tiero <3596602+tiero@users.noreply.github.com>

* fix: hide virtualizer scrollbar and address review feedback

- Hide scrollbar on transaction list (reported by @Kukks)
- Remove package-lock.json from .gitignore (feedback from @pietro909)
- Remove redundant setFocusedIndex calls (CodeRabbit nitpick)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

* fix: adapt e2e tests for pill navbar navigation

- Add data-testid to PillNavbar buttons and settings header button
- Fix settings button: only toggle-close from Settings main menu,
  always navigate to Settings main from sub-pages
- Remove duplicate testIds from hidden IonTabBar
- Update createWalletWithPassword to close settings via button
- Remove unused pwaIsInstalled import

* fix: restore settings-icon-light testId lost in lucide-react migration

The switch from custom SVG to lucide-react dropped the data-testid
attribute, causing all e2e asset tests that call enableAssets() to
time out.

* fix: resolve e2e test failures from navbar redesign

- Lower settings button z-index below ion-header so sub-page aux buttons
  remain clickable (fixes keyboard swap button interception)
- Add pointer-events: none to hidden morph icons to prevent invisible
  elements from intercepting clicks
- Use testId selectors in init.test.ts to avoid strict mode violations
  from duplicate text nodes (pill navbar + Ionic tab bar)
- Scope settings-icon-light click in enableAssets to ion-header to avoid
  matching the navbar's duplicate settings icon

* fix: remove ion-header scope from settings icon test selector

The navbar redesign moved the settings icon out of ion-header into a
standalone button. The e2e test scoped its click to ion-header, which
no longer contains the icon.

* fix: resolve e2e strict mode violation for settings icon

Two elements matched settings-icon-light testId: the global settings
button and the app Header's aux icon. Add data-testid='header-aux-btn'
to the Header component's aux button and use that in the test.

* fix: force-click app settings icon covered by global settings button

The global header-settings-btn (position: absolute, z-index: 9) covers
the app Header's aux button in the same top-right position. Use
force: true to bypass Playwright's actionability check.

* fix: hide global settings button on app sub-pages

The global header-settings-btn (position: absolute, z-index: 9) was
covering app-specific Header aux buttons on sub-pages like Arkade Mint.
Hide the global button when the current page is not a root tab page
(Wallet, Apps, or Settings), since sub-pages render their own Header.

* style: fix formatting and rebuild worker after merge

* fix: navigate to wallet tab before clicking settings in e2e tests

The global settings button is hidden (display:none) on sub-pages.
Tests calling getNsec/resetWallet may be on a sub-page after a
send/swap, so navigate to wallet tab first to ensure the button
is visible.

* fix: conditionally navigate to wallet tab before settings in e2e

The PillNavbar is hidden on settings pages, so tab-wallet click
would fail. Use navigateToSettings helper that only clicks
tab-wallet when visible, then clicks tab-settings.

---------

Signed-off-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Pietro Grandi <dev@pietro.uno>
Co-authored-by: João Bordalo <bordalix@users.noreply.github.com>
Co-authored-by: pietro909 <2094604+pietro909@users.noreply.github.com>
Co-authored-by: louisinger <louis@vulpem.com>
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Co-authored-by: louisinger <41042567+louisinger@users.noreply.github.com>
Co-authored-by: OpenClaw Agent <agent@openclaw.dev>
Co-authored-by: louisinger <louisinger@protonmail.com>
Co-authored-by: Marco Argentieri <3596602+tiero@users.noreply.github.com>
Co-authored-by: Sahil Chaturvedi <sahilc0@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
@bordalix
bordalix deleted the wt-streamline-onboarding-20260305 branch May 18, 2026 16:16
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.

3 participants