Skip to content

Commit 7d53b31

Browse files
authored
refactor(app): migrate to shared/features structure and harden popup flows (#178)
* refactor: modal config, settings flow, features/shared structure, folder rules - Modal screens: helpers (modalFlow, slideFromRight, modalWithBlur, cardFade, fullScreenModal, modalTransparent), remove redundant defaults, fix transaction detail header (headerShown: true) - Settings flow: add _layout as group flow, slideFromRight animation, fix drawer nav path (no leading slash) - Camera: fix scan overlay corners (inline styles for consistency) - Folder structure: add .cursor/rules/folder-structure.mdc, update AGENTS.md and code-quality architecture boundaries - Features/shared migration: components, hooks, helper, stores, providers moved to features/ and shared/ Made-with: Cursor * refactor: consolidate utils, reorganize shared/ui - Migrate utils/nip17 → shared/lib/nostr/nip17, remove duplicate - Consolidate map utils (utils/ + features/map/utils/) → shared/lib/map/ - Move hero-transition → shared/providers/hero-transition/ - Move transfer → shared/blocks/transfer/ - Remove utils/ and features/map/utils/ directories - docs: update folder-structure (utils deprecated, providers/blocks rules) - docs: fix theme-system-architecture (helper/themeEngine → shared/lib/themeEngine) Made-with: Cursor * refactor(features): consolidate transactions imports across screens Made-with: Cursor * refactor(popup): migrate action sheets to HeroUI BottomSheet - Replace react-native-actions-sheet with HeroUI BottomSheet for profile switcher, emoji picker, and button handler - Redesign profile switcher to scrollable-with-snap-points layout: Select profile header, scrollable list, fixed footer with Generate new account and Import nsec - Add named popup functions (profileSwitcherPopup, emojiPickerPopup, buttonHandlerPopup) aligned with popups.ts API - Add actionSheetTypes and bridge for custom sheet payloads - Metro resolver for heroui-native; PopupHost handles custom sheets Made-with: Cursor * refactor: consolidate popup imports and minor formatting - Group popup imports in SendTokenScreen and UserMessagesScreen - Simplify ImportNsec return in profileSwitcher Made-with: Cursor * refactor(popup): unify actionable sheets under button-handler Migrate remaining actionable popup button sheets to buttonHandlerPopup, add nested pushSheet support for SendToken emoji flow, and fix bottom-sheet reopen behavior by syncing close events reliably in PopupHost. Made-with: Cursor * refactor(popup): unify sheet headers, content layout, and fix formatting - Add SheetHeader and SheetContent for consistent action sheet layout - Remove horizontal padding from profile-style sheets (px-0) - Fix import nsec form field padding (move Description outside TextField) - Use AmountFormatter for profile selector balances to match wallet header - Add IMPORT_NSEC_LABEL constant to avoid duplication Made-with: Cursor * docs(rules): align rule docs and paths with shared/ layout - Update popup, code-quality, zustand, secure-storage, text, theme, git rules - Fix globs and path refs: helper/* → shared/lib/*, components/* → shared/* - Replace DevPopupPanel refs with Wallet __DEV__ test buttons - Update knip.json ignoreIssues paths - Fix @module JSDoc in shared/ui, engine, utils, colorUtils - Add @fileoverview to profileSessionOrchestrator Made-with: Cursor * refactor(settings): remove storage screen, add widget target - Remove SettingsStorageScreen and storage route from settings flow - Add iOS widget target (Info.plist, Swift entry, expo-target.config) - Update settings layout, keyring screen, and explore screen - Update importNsec sheet and .gitignore Made-with: Cursor * fix(profile): scope profile stores by pubkey instead of account index - createProfileScopedStorage() now uses active profile pubkey for keys - Key format: {name}:profile:{pubkey} for all profiles (no index collision) - Add migrateProfileScopedKeys() one-time migration from index-based keys - clearAllProfileScopedData() now accepts pubkeys instead of indexes - Wire migration in MigrationGate (runs on every launch, idempotent) - Fixes imported profiles seeing swap/other data from wrong accounts Made-with: Cursor * fix(popup): move PopupHost outside account-scoped providers and guard CocoManager cleanup race Move PopupHost outside AccountScopedProviders so it is never unmounted during profile switches, preventing the BottomSheet from being torn down mid-animation which would leave a native overlay blocking touches. Add pendingCleanup tracking to CocoManager so initialize() awaits any in-flight cleanup() before proceeding, avoiding race conditions during hot reload or rapid profile switches. Made-with: Cursor * feat(settings): add storage inventory screen and debug lib - Add storage route and SettingsStorageScreen for debug inspection - Add shared/lib/debug/storageInventory for AsyncStorage, SecureStore, coco DB - Refactor payments: skeleton loading, display contacts before decryption - Update assets: optimize images, remove unused blue.jpg and initializing.png Made-with: Cursor * refactor: theme tokens and component styling across features - Update theme system architecture docs - Apply semantic tokens to feed, mint, payments, settings, transactions, user, wallet - Avatar gradient and profile switcher updates - Wallet header and mint balance display refinements Made-with: Cursor * refactor(settings): remove Recovery section and Free Reserved Proofs from dev menu Made-with: Cursor * refactor(popup): add payment status toasts and refactor modal config - Add PaymentStatusToast, CompactToast, usePaymentStatusListener - Add paymentStatusStore and parsePaymentError - Refactor popup engine, bridge, PopupHost - Update receive/send/wallet screens - Add .easignore Made-with: Cursor * refactor(popup): split popups by domain, rename profile-switcher, update docs - Split popups.ts into popups/ domain modules (copy, payment, token, etc.) - Rename profileSwitcher/ to profile-switcher/ for kebab-case consistency - Extract liveSheetTypes.ts for circular import fix - Update popup-toast-sheet-guidelines.mdc with current structure - Remove withSheetProvider HOC and app/message dead code Made-with: Cursor * refactor(send,receive): transfer chain, history entry hook, popup config - Extract AnimatedCheckpointDot, simplify TransferStepChain - Add useReceiveHistoryEntry hook for receive flow - Refactor HistoryEntryTimeline, ReceiveTokenScreen, MeltQuoteScreen - Add send popup exports to popups barrel Made-with: Cursor * Update widgets.swift * fix(profile): comprehensive profile scoping audit and safety improvements Close SQLite connections on CocoManager cleanup, capture accountIndex at render time in SendTokenScreen, add cancellation guard to payment status listener, clear payment status store before profile reload, gate profile-scoped store hydration on profileStore readiness, guard CocoManager access from popup host, pass accountIndex as prop to NostrNDKProvider, document rehydrateProfileStores as reserved, and add periodic safety/security audit rule for profile-scoped code. Made-with: Cursor * fix(profile): redesign profile management with native restart and nuclear delete Profile switching, creation, import, and deletion were unreliable because Updates.reloadAsync() silently fails in production (no expo-updates config), the WalletScreen action queue lost actions when navigation/focus failed, FullWindowOverlay ghost windows captured touches after JS reload, and deleteAccount called clearAllSecureData which nuked the root mnemonic causing new identities to appear instead of deleting. - Add react-native-restart for reliable native app restarts - Create appRestart.ts with priority chain (DevSettings → RNRestart → Updates) - Rewrite profileSessionOrchestrator with AsyncStorage-based transition guard, registered controls pattern, and direct orchestrator calls from drawer - Delete profileActionStore and remove WalletScreen action queue - Disable FullWindowOverlay on PopupHost to prevent ghost windows - Add clearPerProfileSecureData() that preserves root mnemonic - Nuclear deleteAllProfiles: CocoManager.completeReset + clearAllSecureData + AsyncStorage.clear + Redux purge — nothing survives - DeleteScreen always calls deleteAllProfiles for full wipe - Register transition controls and key derivation in app/_layout.tsx Made-with: Cursor * fix(profile): unblock first-session derivation and pin import vectors Profile creation could fail on the first session after a fresh wipe because NostrKeysProvider generated and stored the root mnemonic during initialization, but getKeysForAccount still depended on stale hook state and returned null until a later refresh or relaunch. This makes account derivation fall back to reading SecureStore directly and refreshes the mnemonic after generation so create flows work immediately. It also adds a dev-only deterministic mnemonic override for fresh wallets via Expo config and simplifies the key derivation test suite around the one root mnemonic and imported nsec path the app actually relies on, including pinned Cashu values for imported profiles. Made-with: Cursor * chore(dev): simplify debug mnemonic override The dev mnemonic override did not need the extra Expo config plumbing. Use EXPO_PUBLIC_DEBUG_MNEMONIC directly in secureStorage and keep the package script focused on restarting Expo with a clean cache. Made-with: Cursor * fix(popup): render sheets above native modals Re-enable HeroUI's full-window overlay for PopupHost so button-handler sheets stack above modal and form-sheet presentations. Update the popup and profile-safety docs to preserve that layering behavior while still tearing sheets down before restart. Made-with: Cursor * chore(redux): mark legacy migration files as deprecated Rename the remaining Redux migration files to explicit deprecated paths so their migration-only role is obvious. Trim dead legacy exports at the same time so knip stays clean without hiding real issues. Made-with: Cursor * refactor(feed): prune legacy UI and relocate shared primitives Remove unused feed image-overlay, stories, and note components while moving shared container and scroll offset helpers to clearer homes. This keeps exports aligned with the current feature structure and trims dead code surfaced by the cleanup tooling. Made-with: Cursor * style: normalize wrapped imports and declarations Apply formatting cleanup to wrapped imports, exports, and type declarations so generated and hand-edited files stay consistent with the current code style. Made-with: Cursor
1 parent d78d988 commit 7d53b31

483 files changed

Lines changed: 20567 additions & 19293 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.cursor/rules/code-quality.mdc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ You are a senior React Native + Expo engineer. Ship the requested change with th
1111

1212
## Execution sequence
1313

14-
1. **Search first** — find existing patterns in `components/`, `hooks/`, `helper/` before creating anything new. Investigate deeply. Be 100% sure before implementing.
14+
1. **Search first** — find existing patterns in `features/`, `shared/` before creating anything new. Investigate deeply. Be 100% sure before implementing.
1515
2. **Reuse first** — extend existing functions, hooks, components. Smallest possible change.
1616
3. **No assumptions** — only use: files you've read, user messages, tool results. Missing info? Search, then ask.
1717
4. **Challenge ideas** — if you see flaws, risks, or better approaches, say so directly. Do not auto-agree.
@@ -20,16 +20,16 @@ You are a senior React Native + Expo engineer. Ship the requested change with th
2020
## Before you code
2121

2222
- Identify: screen/flow, state sources, async boundaries, loading/error states, navigation implications.
23-
- Light scan of relevant files in `app/`, `components/`, `hooks/`, `helper/`. Do NOT audit the whole repo.
23+
- Light scan of relevant files in `app/`, `features/`, `shared/`. Do NOT audit the whole repo.
2424
- If editing an existing file, run `git diff <file>` first. Restore any lost JSDoc.
2525

2626
## Architecture boundaries
2727

2828
- `app/` — routing + orchestration only. Screens stay thin.
29-
- `components/ui/` — primitives. `components/blocks/` — composed product UI.
30-
- `hooks/coco/` — compose coco-cashu-react hooks for UX needs. Never reimplement coco internals.
31-
- `helper/coco/` — non-React coco integration glue.
32-
- `stores/` — Zustand. Check `.cursor/rules/zustand-store-scoping.mdc` for scope rules before adding or modifying any store.
29+
- `features/` — domain modules. Screens + components + hooks per domain.
30+
- `shared/` — cross-cutting UI, hooks, stores, lib. See `.cursor/rules/folder-structure.mdc`.
31+
- `shared/lib/cashu/` — coco integration. Compose coco-cashu-react hooks for UX needs. Never reimplement coco internals.
32+
- `shared/stores/` — Zustand. Check `.cursor/rules/zustand-store-scoping.mdc` for scope rules before adding or modifying any store.
3333
- `coco-cashu-core` is the source of truth for cashu types. Import from coco, not `@cashu/cashu-ts`.
3434

3535
## Naming
@@ -44,7 +44,7 @@ You are a senior React Native + Expo engineer. Ship the requested change with th
4444
1. React / React Native
4545
2. Expo / Expo Router
4646
3. Third-party (alphabetized)
47-
4. Internal absolute (coco-cashu-core → coco-cashu-react → @/helper/coco → @/hooks/coco → rest, alphabetized)
47+
4. Internal absolute (coco-cashu-core → coco-cashu-react → @/shared/lib/cashu → @/shared/hooks → rest, alphabetized)
4848
5. Relative (./ then ../, alphabetized)
4949

5050
Remove unused imports.

.cursor/rules/folder-structure.mdc

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
---
2+
description: Folder structure, layer boundaries, and where to place new code. Read before adding files outside existing patterns.
3+
globs:
4+
- "**/*"
5+
alwaysApply: false
6+
---
7+
8+
# Folder Structure
9+
10+
Organize by domain, not file type. Code lives near its consumers. Top-level names describe what the app does.
11+
12+
## Layers
13+
14+
| Layer | Purpose | Rule |
15+
|-------|---------|------|
16+
| `app/` | Expo Router routes only. Thin wrappers. | Keep as-is. No business logic. |
17+
| `features/` | Domain modules. Screens + components + hooks + types. | One feature = one domain. Barrel export via `index.ts`. |
18+
| `shared/` | Cross-cutting primitives. UI, hooks, stores, providers, lib. | Used by 3+ features. No feature-specific logic. |
19+
| `config/` | App-level config (non-navigation). | |
20+
| `navigation/` | Navigation infra (flowLayoutOptions, nativeTabs). | Near `app/` but shared. |
21+
| `shared/lib/popup/sheets/` | Custom action sheet content (profile-switcher, emoji-picker, button-handler). | Colocated with popup API. Triggered via `showActionSheet` from anywhere. |
22+
| `redux/` | Legacy. Migrate to Zustand over time. | Do not add new code. |
23+
24+
## Feature layout
25+
26+
```
27+
features/<domain>/
28+
screens/ # Screen components
29+
components/ # Domain-specific UI
30+
hooks/ # Domain-specific hooks
31+
lib/ # Domain business logic (no UI)
32+
index.ts # Public barrel — export only what others need
33+
```
34+
35+
## Where new code goes
36+
37+
- **New screen for existing flow** → `features/<domain>/screens/`, wire in `app/(flow)/`.
38+
- **New hook used by one feature** → `features/<domain>/hooks/`.
39+
- **New hook used by 3+ features** → `shared/hooks/`.
40+
- **Stateless util, no UI** → `shared/lib/` (or `features/<domain>/lib/` if domain-specific).
41+
- **Primitive UI (View, Text, Button)** → `shared/ui/primitives/`.
42+
- **Composed UI (AmountFormatter, QRCode)** → `shared/ui/composed/`.
43+
- **Provider / context** → `shared/providers/`.
44+
- **Domain-specific composed blocks** (transfer, claim, pending) → `shared/blocks/<domain>/`.
45+
46+
## Colocation
47+
48+
- If 70%+ of importers are in one folder, move the file there.
49+
- Domain-specific logic belongs in the feature that owns it. Do not centralize "just in case."
50+
51+
## Must / Must not
52+
53+
- **Must** use barrel exports (`index.ts`) for feature public API. Internals stay internal.
54+
- **Must not** put screens in `components/screens/` — they belong in `features/<domain>/screens/`.
55+
- **Must not** put domain logic in `helper/` — use `shared/lib/` or `features/<domain>/lib/`.
56+
- **Must not** use `utils/` — stateless utils go in `shared/lib/` or `features/<domain>/lib/`.
57+
- **Must not** create god files. Decompose when a file exceeds ~500 LOC or exports 5+ unrelated things.
58+
- **Must not** put providers or domain blocks in `shared/ui/` — use `shared/providers/` or `shared/blocks/`.

.cursor/rules/git-github-workflow.mdc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ Derive from diff paths first, then feature intent. Pick **one** primary scope pe
8787

8888
| Scope | Covers |
8989
|---|---|
90-
| `ui` | `components/ui/*`, general visual/layout, pill styling, skeleton, text |
90+
| `ui` | `shared/ui/*`, general visual/layout, pill styling, skeleton, text |
9191
| `app` | `app/*` top-level, offline shell, launch, widget target, release automation |
9292
| `camera` | QR scanning, camera permissions, camera lock |
9393
| `nfc` | NFC payment flow, NFC rollback, NFC tap |
@@ -98,19 +98,19 @@ Derive from diff paths first, then feature intent. Pick **one** primary scope pe
9898
| `rebalance` | Rebalance chain cards, step grouping |
9999
| `transactions` | Transaction screens, source/P2PK details |
100100
| `keyring` | P2PK key regeneration, keyring settings |
101-
| `keys` | Key derivation, NIP-06, NUT-13, `helper/keyDerivation*` |
101+
| `keys` | Key derivation, NIP-06, NUT-13, `shared/lib/nostr/keyDerivation*` |
102102
| `profile` | Account session isolation, multi-profile, profile switch |
103103
| `settings` | Settings pages, dev mode, preferences |
104104
| `home` | Dashboard widgets, native headers |
105105
| `explore` | Explore tab, wallet health modal |
106106
| `map` | Map flow, BTC Map, map performance |
107107
| `lists` | LegendList, virtualized feeds |
108-
| `popup` | `helper/popup/*`, toast/sheet system |
108+
| `popup` | `shared/lib/popup/*`, toast/sheet system |
109109
| `theme` | Theme engine, HeroUI semantics, `themeEngine.ts` |
110-
| `providers` | `providers/*`, NDK, ThemeProvider |
111-
| `store` | `stores/*`, Zustand stores |
112-
| `hooks` | `hooks/*` |
113-
| `blocks` | `components/blocks/*` |
110+
| `providers` | `shared/providers/*`, NDK, ThemeProvider |
111+
| `store` | `shared/stores/*`, Zustand stores |
112+
| `hooks` | `shared/hooks/*` |
113+
| `blocks` | `shared/blocks/*` |
114114
| `tx` | Transaction timeline, history rerenders |
115115
| `widget` | iOS/Android widget target |
116116
| `repo` | Repo-level docs, commit guidance |

.cursor/rules/popup-toast-sheet-guidelines.mdc

Lines changed: 63 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
---
22
description: Popup and toast system conventions, ownership, and copy patterns. Use when adding or changing popup behavior.
33
globs:
4-
- "helper/popup/**"
5-
- "components/blocks/popup/**"
6-
- "stores/popupStore.ts"
4+
- "shared/lib/popup/**"
5+
- "shared/blocks/popup/**"
6+
- "shared/stores/runtime/popupStore.ts"
77
alwaysApply: false
88
---
99

1010
# Popup System
1111

12-
Sovran uses a centralized popup system for all user-facing notifications. Every popup in the app is a named function in `helper/popup/popups.ts`, called from anywhere without React context.
12+
Sovran uses a centralized popup system for all user-facing notifications. Every popup in the app is a named function in `shared/lib/popup/popups/`, called from anywhere without React context.
1313

1414
**Always import from the barrel:**
1515

1616
```tsx
17-
import { copyPopup, sendSuccessPopup, fmt } from '@/helper/popup';
17+
import { copyPopup, sendSuccessPopup, fmt, profileSwitcherPopup, emojiPickerPopup, buttonHandlerPopup } from '@/shared/lib/popup';
1818
```
1919

2020
---
@@ -33,31 +33,64 @@ A detached bottom sheet (HeroUI `BottomSheet`). Shows an **icon**, **title**, op
3333

3434
A popup becomes a sheet when it has **buttons** or **`variant: 'sheet'`** is set explicitly.
3535

36+
### Action sheets (custom sheet content)
37+
38+
Action sheets are a popup variant with **custom content** instead of the standard icon/title/buttons layout. Use the named popup functions (same pattern as `copyPopup`, `sendSuccessPopup`, etc.):
39+
40+
| Function | Payload | Use case |
41+
|----------|---------|----------|
42+
| `profileSwitcherPopup` | `{ onSwitchProfile, onAddProfile, onImportProfile }` | Profile list + import nsec |
43+
| `emojiPickerPopup` | `{ token: string }` | Encode token as emoji, copy to clipboard |
44+
| `buttonHandlerPopup` | `{ buttons: ButtonHandlerButton[] }` | Dynamic button actions |
45+
46+
**Example:**
47+
48+
```tsx
49+
import { profileSwitcherPopup } from '@/shared/lib/popup';
50+
51+
profileSwitcherPopup({
52+
onSwitchProfile: (accountIndex) => { ... },
53+
onAddProfile: () => { ... },
54+
onImportProfile: (npubNumber) => { ... },
55+
});
56+
```
57+
58+
PopupHost renders all sheets (standard + action) in one place. No separate registration or sheet provider.
59+
60+
`PopupHost` sheets must keep HeroUI's default `FullWindowOverlay` behavior enabled so action sheets render above native-stack modals and form sheets. If you need to avoid stale iOS overlay windows during a profile transition, destroy the sheet before restarting rather than disabling the overlay layer globally.
61+
3662
---
3763

3864
## File structure
3965

4066
```
41-
helper/popup/
42-
index.ts Barrel — the only import path external code should use
43-
engine.tsx popup() function, runtime error matching, toast/sheet routing
44-
bridge.ts showToast/showSheet, type definitions, toast manager registration
45-
format.ts fmt tagged template, PopupTextSegment, flattenSegments
46-
icons.tsx PopupIcon type, resolvePopupIcon, CUSTOM_ICONS registry
47-
popups.ts All named popup functions (the source of truth for copy)
48-
49-
stores/popupStore.ts Zustand store for sheet open/close state
50-
components/blocks/popup/PopupHost.tsx Renders toasts + sheets (mounted in app layout)
67+
shared/lib/popup/
68+
index.ts Barrel — the only import path external code should use
69+
engine.tsx popup() function, runtime error matching, toast/sheet routing
70+
bridge.ts showToast/showSheet/showActionSheet, type definitions
71+
actionSheetTypes.ts ActionSheetPayloads type for custom sheets
72+
format.ts fmt tagged template, PopupTextSegment, flattenSegments
73+
icons.tsx PopupIcon type, resolvePopupIcon, CUSTOM_ICONS registry
74+
liveSheetTypes.ts LiveSheetConfig, LiveSheetStatus (live-updating sheet state)
75+
parsePaymentError.ts Parses coco/cashu errors for payment status toast
76+
CompactToast.tsx Standard toast component (internal, used by bridge)
77+
PaymentStatusToast.tsx Payment status custom toast (internal)
78+
PaymentStatusIcon.tsx Animated icon for payment status (internal)
79+
popups/ Named popup functions by domain (copy, payment, token, etc.)
80+
81+
shared/stores/runtime/popupStore.ts Zustand store for sheet open/close state
82+
shared/blocks/popup/PopupHost.tsx Renders toasts + standard sheets + action sheets
83+
shared/lib/popup/sheets/ Action sheet content (profile-switcher, emoji-picker, button-handler)
5184
```
5285

5386
### Roles
5487

5588
| File | Owns | Consumers |
5689
|------|------|-----------|
57-
| `popups.ts` | Every popup's copy, type, and icon | All app code |
58-
| `engine.tsx` | Routing logic (toast vs sheet) + runtime error matching | `popups.ts` only |
59-
| `bridge.ts` | Toast manager ref + Zustand store connection | `engine.tsx`, `PopupHost` |
60-
| `format.ts` | Amount formatting for popup text | `popups.ts`, `engine.tsx`, `PopupHost` |
90+
| `popups/` | Every popup's copy, type, and icon (by domain) | All app code |
91+
| `engine.tsx` | Routing logic (toast vs sheet) + runtime error matching | `popups/` only |
92+
| `bridge.ts` | Toast manager ref + showActionSheet (internal) + Zustand store connection | `popups/`, `PopupHost` |
93+
| `format.ts` | Amount formatting for popup text | `popups/`, `engine.tsx`, `PopupHost` |
6194
| `icons.tsx` | Icon resolution (emoji/monicon/custom) | `PopupHost` |
6295
| `popupStore.ts` | Sheet open/close state | `bridge.ts`, `PopupHost` |
6396
| `PopupHost.tsx` | Rendering (toasts via HeroUI, sheets via BottomSheet) | Mounted once in `app/_layout.tsx` |
@@ -66,9 +99,9 @@ components/blocks/popup/PopupHost.tsx Renders toasts + sheets (mounted in app
6699

67100
## Adding a new popup
68101

69-
### 1. Add a named function in `popups.ts`
102+
### 1. Add a named function in `popups/`
70103

71-
Every popup is a named export. No free-form strings at callsites.
104+
Every popup is a named export in the appropriate domain file. No free-form strings at callsites.
72105

73106
```ts
74107
export function myNewPopup(overrides?: TextOverrides): void {
@@ -100,7 +133,7 @@ Three formats, in order of preference:
100133
| Format | Syntax | When to use |
101134
|--------|--------|-------------|
102135
| **Monicon** | `'icon:mdi:check-circle'` | Default choice. Themed, consistent, sharp. Browse at [icones.js.org](https://icones.js.org). |
103-
| **Custom** | `'custom:nfc-success'` | Animated or complex SVG. Must be registered in `icons.tsx` `CUSTOM_ICONS`. |
136+
| **Custom** | `'custom:X'` | Animated or complex SVG. Must be registered in `icons.tsx` `CUSTOM_ICONS`. |
104137
| **Emoji** | `'emoji:🎉'` | Only when the emoji is genuinely more expressive than any icon. Rare. |
105138

106139
Icon naming conventions already used in the codebase:
@@ -111,7 +144,7 @@ Icon naming conventions already used in the codebase:
111144
| Keys | `solar:key-bold`, `mdi:key-alert`, `mdi:key-remove` |
112145
| Mints | `mdi:bank-check`, `mdi:bank-off`, `mdi:bank-remove` |
113146
| Wallet | `mdi:wallet-outline`, `mdi:wallet-check`, `mdi:wallet-plus` |
114-
| NFC | `mdi:nfc`, `mdi:nfc-off`, `custom:nfc-success` |
147+
| NFC | `mdi:nfc`, `mdi:nfc-off`, `mdi:send-check` |
115148
| Camera | `mdi:camera-check`, `mdi:camera-off`, `mdi:camera-lock` |
116149
| Errors | `mdi:alert-circle`, `mdi:alert-circle-outline` |
117150
| Info | `mdi:information-outline`, `mdi:help-circle-outline` |
@@ -120,9 +153,9 @@ Icon naming conventions already used in the codebase:
120153
| QR | `mdi:qrcode-remove` |
121154
| Links | `mdi:link-off` |
122155

123-
### 4. Add to DevPopupPanel
156+
### 4. Verify in dev mode
124157

125-
Add an entry to the `SECTIONS` array in `components/blocks/DevPopupPanel.tsx` so it can be visually verified. Put it in the matching category section.
158+
Use the `__DEV__` test buttons on the Wallet screen to visually verify copy, icons, and timing after any change to `popups/`.
126159

127160
---
128161

@@ -150,7 +183,7 @@ Overrides spread **after** the defaults, so they win. This means callers can ove
150183
Use the `fmt` tagged template to embed formatted amounts inline. Objects with `{ amount, unit }` are rendered as `<AmountFormatter>` in sheets and stringified via `formatAmount()` in toasts.
151184

152185
```ts
153-
import { fmt } from '@/helper/popup';
186+
import { fmt } from '@/shared/lib/popup';
154187

155188
nfcPaymentSentPopup({
156189
text: fmt`${{ amount: 500, unit: 'sat' }} sent`,
@@ -163,7 +196,7 @@ For plain interpolations, `fmt` stringifies normally:
163196

164197
```ts
165198
modelSwitchedPopup({ modelName: 'GPT-4o' });
166-
// inside popups.ts:
199+
// inside popups/:
167200
popup({ message: `Switched to ${params.modelName}`, ... });
168201
```
169202

@@ -201,7 +234,7 @@ Pass `duration` (ms) to auto-close a sheet. A progress bar renders at the bottom
201234
```ts
202235
nfcPaymentSentPopup({
203236
text: fmt`${{ amount: 100, unit: 'sat' }} sent`,
204-
icon: 'custom:nfc-success',
237+
icon: 'icon:mdi:send-check',
205238
duration: 2600,
206239
});
207240
```
@@ -231,10 +264,10 @@ nfcPaymentSentPopup({
231264

232265
### Avoid duplication
233266

234-
Before adding a new popup, search `popups.ts` for similar ones. The file is organized by section (Copy, Funds, NFC, Token Status, etc.). If a popup already exists for that scenario, reuse it with overrides rather than creating a near-duplicate.
267+
Before adding a new popup, search `popups/` for similar ones. Popups are organized by domain (copy, payment, token, mint, etc.). If a popup already exists for that scenario, reuse it with overrides rather than creating a near-duplicate.
235268

236269
---
237270

238271
## Testing
239272

240-
The `DevPopupPanel` (`components/blocks/DevPopupPanel.tsx`) shows every popup in the system organized by category. Use it to visually verify copy, icons, and timing after any change to `popups.ts`.
273+
The Wallet screen has `__DEV__` test buttons for standard sheets and action sheets. Use them to visually verify copy, icons, and timing after any change to `popups.ts`.

0 commit comments

Comments
 (0)