Skip to content

Commit 954c83e

Browse files
pietro909claude
andcommitted
feat: hide bootstrap status messages behind triple-tap easter egg
Status messages and commit hash during wallet initialization are now hidden by default. Tap the loading logo three times (within 600ms between taps) to toggle them on/off. This keeps the boot animation clean for normal users while preserving debug visibility for developers and testers. Implementation: - Add statusRevealed state (default false) to gate text and commit hash render - Add tap counter with 600ms timeout to track triple-tap gesture - Make logo tappable by adding pointerEvents: auto wrapper - Change text and commit hash conditions to require statusRevealed Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
1 parent 9300408 commit 954c83e

4 files changed

Lines changed: 1284 additions & 15 deletions

File tree

BOOT_ERROR.agents.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Plan: Retry prompt when first wallet data load fails
2+
3+
## Context
4+
5+
After commit 0b4da778 fixed the loading-screen deadlock by setting `dataReady = true` in a `finally` block, a failed first `reloadWallet` silently falls through to an empty wallet with no error or retry path. We want to keep the user on the boot loading screen with a retry prompt instead.
6+
7+
## Approach
8+
9+
Replace the "always set dataReady on failure" strategy with an explicit error state that holds the loading gate open and shows a retry UI.
10+
11+
### 1. Add `loadError` state to WalletContext (`src/providers/wallet.tsx`)
12+
13+
- New state: `const [loadError, setLoadError] = useState<string | null>(null)`
14+
- Add to `WalletContextProps` interface, default context value, and provider value
15+
- Expose `dismissLoadError` function that sets `dataReady = true` (continue to empty wallet)
16+
17+
### 2. Modify `reloadWallet` catch/finally (`src/providers/wallet.tsx:~308-318`)
18+
19+
- **catch block**: On first load (`!hasLoadedOnce.current`), set `loadError` with a user-facing message, call `clearLoadingStatus()`, but do NOT set `dataReady = true` or `hasLoadedOnce = true`. This keeps `shouldHoldOnLoading` true so the user stays on the loading screen.
20+
- **finally block**: Remove entirely — the catch now handles the first-load failure, and the try block already handles success.
21+
- On retry: `reloadWallet` clears `loadError` at entry before trying again.
22+
23+
### 3. New component: `BootError` (`src/components/BootError.tsx`)
24+
25+
A full-screen overlay (matching LoadingLogo's visual layer) that shows:
26+
- Centered layout using inline styles matching CenterScreen's pattern (flex column, centered, max-width 18rem)
27+
- Themed background (`var(--ion-background-color)`) covering the full viewport like LoadingLogo's portal
28+
- Error text: `Text` component with the error message
29+
- "Retry" button: primary `Button` with `loading` state while retrying
30+
- "Continue anyway" button: secondary/clear `Button` that calls `dismissLoadError`
31+
32+
Rendered via `createPortal` to `document.body` (same as LoadingLogo) so it sits at the same z-layer.
33+
34+
### 4. Wire up in App.tsx (`src/App.tsx:~385-392`)
35+
36+
Replace the boot animation block:
37+
```tsx
38+
{bootAnimActive ? (
39+
loadError ? (
40+
<BootError />
41+
) : (
42+
<LoadingLogo ... />
43+
)
44+
) : null}
45+
```
46+
47+
Pull `loadError`, `reloadWallet`, `dismissLoadError` from `WalletContext`.
48+
49+
## Files to modify
50+
51+
| File | Change |
52+
|---|---|
53+
| `src/providers/wallet.tsx` | Add `loadError` state, modify `reloadWallet` catch/finally, add `dismissLoadError`, update interface + provider value |
54+
| `src/components/BootError.tsx` | **New file** — retry prompt component |
55+
| `src/App.tsx` | Destructure new context fields, conditionally render `BootError` vs `LoadingLogo` |
56+
57+
## Verification
58+
59+
1. `pnpm build` — no type errors
60+
2. `pnpm lint && pnpm format:check` — clean
61+
3. Manual test: temporarily inject `throw new Error('test')` at the top of `reloadWallet`'s try block, verify the retry prompt appears on boot, retry works, "continue anyway" proceeds to empty wallet

E2E_FAILING.md

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# E2E Test Failures on `feat/loading-status-messages`
2+
3+
## Root Cause
4+
5+
The branch changed `shouldHoldOnLoading` in `App.tsx:223` from:
6+
7+
```js
8+
// master:
9+
const shouldHoldOnLoading = hasStoredWallet && !initialized && authState !== 'locked'
10+
// branch:
11+
const shouldHoldOnLoading = hasStoredWallet && (!initialized || !dataReady) && authState !== 'locked'
12+
```
13+
14+
This keeps the loading screen visible (and `comp = null` — no page content rendered) until `reloadWallet()` completes and sets `dataReady = true`. On master, the wallet page renders immediately after `initialized` becomes true.
15+
16+
Two consequences for tests:
17+
18+
1. **Wallet readiness takes longer**`createWallet()` and `restoreWallet()` waited for `text=Send` with a 30s timeout. On this branch, `Send` only appears after `reloadWallet` finishes (extra seconds). If `reloadWallet` fails, a `BootError` overlay ("Continue anyway" / "Retry") blocks the UI instead.
19+
20+
2. **`reloadWallet` failure on first load blocks the app** — On master, failures were silently ignored. On this branch, `loadError` is set and `BootError` renders as a full-screen overlay. Tests don't handle this.
21+
22+
## Fixes Applied
23+
24+
### 1. `waitForWalletPage` helper (`src/test/e2e/utils.ts`)
25+
26+
Handles both the normal path (`Send` visible) and the error path (`Continue anyway` visible):
27+
28+
```ts
29+
export async function waitForWalletPage(page: Page, timeout = 60000): Promise<void> {
30+
const sendBtn = page.getByText('Send', { exact: true })
31+
const continueBtn = page.getByText('Continue anyway')
32+
await sendBtn.or(continueBtn).first().waitFor({ state: 'visible', timeout })
33+
if (await continueBtn.isVisible()) {
34+
await continueBtn.click()
35+
await sendBtn.waitFor({ state: 'visible', timeout: 30000 })
36+
}
37+
}
38+
```
39+
40+
Used in `createWallet()`, `restoreWallet()`, and `delegate.test.ts` (post-reload wait).
41+
42+
### 2. `waitForPaymentReceived` timeout (`src/test/e2e/utils.ts`)
43+
44+
Increased from default 30s to 60s. The Boltz reverse-swap claim path can exceed 30s on slow CI runners.
45+
46+
## Remaining Issue: `restore.test.ts` Lightning payment not detected
47+
48+
### Symptom
49+
50+
`waitForPaymentReceived` times out even at 60s. The page snapshot shows the QrCode/Receive page is correctly displaying the Lightning invoice, but "Payment received!" never appears.
51+
52+
### Key finding: `exec` vs `execSync`
53+
54+
The test uses fire-and-forget `exec()` to pay the Lightning invoice via lnd:
55+
```js
56+
exec(`docker exec lnd lncli --network=regtest payinvoice ${invoice} --force`)
57+
```
58+
59+
- **`execSync`** (blocking): payment works instantly, test passes
60+
- **`exec`** (fire-and-forget): payment never completes, test times out
61+
62+
On master, fire-and-forget `exec` works fine. On this branch, it does not.
63+
64+
### Cascading failure
65+
66+
Each failed attempt leaves an IN_FLIGHT HTLC in lnd that never settles. After several failed runs, lnd accumulates stuck HTLCs that block all new payments on the channel. This makes subsequent runs fail even more reliably.
67+
68+
### Theories investigated but not confirmed
69+
70+
- **Navigation effect re-firing**: The `wallet.tsx:256-278` effect navigates to `Pages.Wallet` when `initialized && dataReady`. If `initialized` flickers (status ping returning false temporarily), the effect could re-fire and bounce the user off the QrCode page, unmounting the payment listener. However, the same effect exists on master.
71+
72+
- **QrCode component unmount**: If `shouldHoldOnLoading` becomes true mid-test (e.g., `initialized` flickers), `comp = null` destroys all page content including the QrCode component and its `listenForPayments` handler. The swap's `waitAndClaim` promise and the VTXO_UPDATE listener are lost. This is unique to the branch since master's `shouldHoldOnLoading` is less likely to re-trigger.
73+
74+
- **`exec` child process blocking on stdout pipe**: Unlikely given the small output size of `payinvoice`.
75+
76+
### Suggested next step
77+
78+
Change the `exec` call at `restore.test.ts:44` (and similar in `swap.test.ts:40`, `nostr.test.ts:84`) to use `execAsync` so the payment is confirmed before waiting for wallet detection. The test already imports `execAsync = promisify(exec)`.
79+
80+
Alternatively, investigate why `exec` (fire-and-forget) works on master but not on this branch — likely related to the `initialized` state flickering causing component unmounts via `shouldHoldOnLoading`.

0 commit comments

Comments
 (0)