Compare commits

...

2 Commits

Author SHA1 Message Date
Ivan Vershigora
ac06d57e82 fix: always 2026-06-05 17:18:31 +01:00
Ivan Vershigora
9da9b7aad6 tst: reduce animations ios simulator during e2e tests 2026-06-05 17:18:31 +01:00
3 changed files with 375 additions and 4 deletions

View File

@ -194,9 +194,6 @@ jobs:
mkdir -p ios/build/Build/Products/Release-iphonesimulator
tar -xzf BlueWallet.app.tar.gz -C ios/build/Build/Products/Release-iphonesimulator
- name: Disable simulator animations
run: defaults write com.apple.iphonesimulator SlowMotionAnimation -bool NO
# Pre-boot simulator so first detox launchApp lands warm.
- name: Pre-boot iOS simulator
run: |
@ -210,6 +207,13 @@ jobs:
xcrun simctl bootstatus "$UDID" -b
xcrun simctl launch "$UDID" com.apple.springboard >/dev/null 2>&1 || true
# Cut animations so detox sync stays steady on slow CI VMs; Reduce Motion makes reanimated skip to final value.
- name: Disable simulator animations
run: |
defaults write com.apple.iphonesimulator SlowMotionAnimation -bool NO
xcrun simctl spawn booted defaults write com.apple.Accessibility ReduceMotionEnabled -bool true
xcrun simctl spawn booted notifyutil -p com.apple.Accessibility.ReduceMotionStatusDidChange
- name: Run detox tests
timeout-minutes: 360
run: |
@ -223,7 +227,7 @@ jobs:
--artifacts-location ./artifacts
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
if: failure()
if: always()
with:
name: e2e-ios-videos
path: ./artifacts/

View File

@ -0,0 +1,263 @@
# Prompt Helper Refactor Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Replace `helpers/prompt`'s 7 fragile positional arguments with `prompt(title, text, options?)`.
**Architecture:** `title` and `text` stay positional (passed at every call site). The 5 trailing optional args move into a `PromptOptions` object. Helper internals are unchanged — only the source of the values changes. All 9 affected call sites migrate. TypeScript verifies completeness.
**Tech Stack:** React Native, TypeScript, `react-native-prompt-android`, Jest.
**Spec:** `docs/superpowers/specs/2026-05-20-prompt-refactor-design.md`
**Do NOT commit.** Per user instruction this branch is left uncommitted. Every task ends with verification, not a commit.
**Compilation note:** After Task 1 alone, `tsc` fails — the helper signature no longer matches its callers. This is expected. The codebase compiles again only after Task 2 finishes. Run `tsc` verification in Task 3, not before.
---
### Task 1: Refactor the `helpers/prompt` helper
**Files:**
- Modify: `helpers/prompt.ts` (entire file)
- [ ] **Step 1: Rewrite `helpers/prompt.ts`**
Replace the full file contents with:
```ts
import { Platform } from 'react-native';
import prompt from 'react-native-prompt-android';
import loc from '../loc';
type PromptOptions = {
cancelable?: boolean;
type?: PromptType | PromptTypeIOS | PromptTypeAndroid;
destructive?: boolean;
continueButtonText?: string;
defaultValue?: string;
};
export default (title: string, text: string, options: PromptOptions = {}): Promise<string> => {
const { cancelable = true, destructive = false, continueButtonText = loc._.ok, defaultValue } = options;
let { type = 'secure-text' } = options;
const keyboardType = type === 'numeric' ? 'numeric' : 'default';
if (Platform.OS === 'ios' && type === 'numeric') {
// `react-native-prompt-android` on ios does not support numeric input
type = 'plain-text';
}
return new Promise((resolve, reject) => {
const buttons: Array<PromptButton> = cancelable
? [
{
text: loc._.cancel,
onPress: () => {
reject(Error('Cancel Pressed'));
},
style: 'cancel',
},
{
text: continueButtonText,
onPress: password => {
console.log('OK Pressed');
resolve(password);
},
style: destructive ? 'destructive' : 'default',
},
]
: [
{
text: continueButtonText,
onPress: password => {
console.log('OK Pressed');
resolve(password);
},
},
];
const message = defaultValue !== undefined ? '' : text;
prompt(title, message, buttons, {
type,
cancelable,
// @ts-ignore suppressed because its supported only on ios and is absent from type definitions
keyboardType,
...(defaultValue !== undefined && { defaultValue }),
});
});
};
```
Notes:
- `PromptType`, `PromptTypeIOS`, `PromptTypeAndroid`, `PromptButton` are global ambient types from `react-native-prompt-android/index.d.ts` — no import needed (same as before).
- `type` uses `let` because the iOS-numeric workaround reassigns it.
- The `// @ts-ignore` comment and message-blanking behavior are carried over unchanged.
- [ ] **Step 2: Verify the file type-checks in isolation**
Run: `npx tsc --noEmit -p tsconfig.json 2>&1 | grep "helpers/prompt"`
Expected: no output (the helper file itself has no type errors). Call-site errors elsewhere are expected at this stage — they are fixed in Task 2.
---
### Task 2: Migrate the 9 call sites
**Files:**
- Modify: `screen/transactions/TransactionStatus.tsx:680`
- Modify: `screen/wallets/WalletDetails.tsx:157`, `screen/wallets/WalletDetails.tsx:489`
- Modify: `screen/wallets/addMultisigStep2.tsx:289`, `screen/wallets/addMultisigStep2.tsx:300`
- Modify: `screen/wallets/PaymentCodesList.tsx:137`, `screen/wallets/PaymentCodesList.tsx:248`
- Modify: `screen/lnd/lnurlPay.tsx:143`
- Modify: `blue_modules/start-and-decrypt.ts:26`
The 3 two-arg call sites (`ImportWalletDiscovery.tsx:109`, `addMultisigStep2.tsx:362`, `ViewEditMultisigCosigners.tsx:422`) need no change.
- [ ] **Step 1: `screen/transactions/TransactionStatus.tsx`**
Replace:
```ts
const newMemo = await prompt(loc.send.details_note_placeholder, '', true, 'plain-text', false, undefined, currentMemo);
```
with:
```ts
const newMemo = await prompt(loc.send.details_note_placeholder, '', { type: 'plain-text', defaultValue: currentMemo });
```
- [ ] **Step 2: `screen/wallets/WalletDetails.tsx` — delete confirmation prompt**
Replace:
```ts
const walletBalanceConfirmation = await prompt(
loc.wallets.details_delete_wallet,
loc.formatString(loc.wallets.details_del_wb_q, { balance }),
true,
'numeric',
true,
loc.wallets.details_delete,
);
```
with:
```ts
const walletBalanceConfirmation = await prompt(
loc.wallets.details_delete_wallet,
loc.formatString(loc.wallets.details_del_wb_q, { balance }),
{ type: 'numeric', destructive: true, continueButtonText: loc.wallets.details_delete },
);
```
- [ ] **Step 3: `screen/wallets/WalletDetails.tsx` — rename prompt**
Replace:
```ts
const newName = await prompt(loc.wallets.add_wallet_name, '', true, 'plain-text', false, undefined, wallet.getLabel());
```
with:
```ts
const newName = await prompt(loc.wallets.add_wallet_name, '', { type: 'plain-text', defaultValue: wallet.getLabel() });
```
- [ ] **Step 4: `screen/wallets/addMultisigStep2.tsx` — fingerprint prompt**
Replace:
```ts
fp = await prompt(loc.multisig.input_fp, loc.multisig.input_fp_explain, true, 'plain-text');
```
with:
```ts
fp = await prompt(loc.multisig.input_fp, loc.multisig.input_fp_explain, { type: 'plain-text' });
```
- [ ] **Step 5: `screen/wallets/addMultisigStep2.tsx` — path prompt**
Replace:
```ts
path = await prompt(
loc.multisig.input_path,
loc.formatString(loc.multisig.input_path_explain, { default: getPath() }),
true,
'plain-text',
);
```
with:
```ts
path = await prompt(
loc.multisig.input_path,
loc.formatString(loc.multisig.input_path_explain, { default: getPath() }),
{ type: 'plain-text' },
);
```
- [ ] **Step 6: `screen/wallets/PaymentCodesList.tsx` — rename prompt**
Replace:
```ts
const newName = await prompt(loc.bip47.rename, loc.bip47.provide_name, true, 'plain-text');
```
with:
```ts
const newName = await prompt(loc.bip47.rename, loc.bip47.provide_name, { type: 'plain-text' });
```
- [ ] **Step 7: `screen/wallets/PaymentCodesList.tsx` — add contact prompt**
Replace:
```ts
const newPc = await prompt(loc.bip47.add_contact, loc.bip47.provide_payment_code, true, 'plain-text');
```
with:
```ts
const newPc = await prompt(loc.bip47.add_contact, loc.bip47.provide_payment_code, { type: 'plain-text' });
```
- [ ] **Step 8: `screen/lnd/lnurlPay.tsx` — comment prompt**
Replace:
```ts
comment = await prompt('Comment', '', false, 'plain-text');
```
with:
```ts
comment = await prompt('Comment', '', { cancelable: false, type: 'plain-text' });
```
- [ ] **Step 9: `blue_modules/start-and-decrypt.ts` — password prompt**
Replace:
```ts
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, false);
```
with:
```ts
password = await prompt((retry && loc._.bad_password) || loc._.enter_password, loc._.storage_is_encrypted, { cancelable: false });
```
---
### Task 3: Verify
**Files:** none (verification only)
- [ ] **Step 1: TypeScript + lint**
Run: `npm run lint`
Expected: PASS. No errors. In particular, no `prompt` arity or argument-type errors. If `tsc` reports an error at a `prompt(` call, a call site was missed or malformed — fix it.
- [ ] **Step 2: Unit tests**
Run: `npm run unit`
Expected: PASS. `tests/unit/transaction-status.test.tsx` mocks `helpers/prompt` as a `jest.fn`, so its behavior is unaffected by the signature change.
- [ ] **Step 3: Sanity grep for stale positional calls**
Run: `grep -rn "prompt(" --include='*.ts' --include='*.tsx' --include='*.js' screen blue_modules | grep -v "react-native-prompt" | grep -E "prompt\([^)]*,\s*(true|false)\s*,"`
Expected: no output. Any match is a call site still passing a positional boolean — migrate it.
---
## Out of scope
- Anchor support (not supported by `react-native-prompt-android` or RN `Alert.prompt`).
- Removing the `react-native-prompt-android` dependency (it provides the native Android prompt).
- Committing — branch stays uncommitted per user instruction.

View File

@ -0,0 +1,104 @@
# Design: refactor `helpers/prompt`
Date: 2026-05-20
Branch: `wallet-details`
Origin: BlueWallet/BlueWallet PR #8301 review thread (`screen/wallets/WalletDetails.tsx:489`).
## Problem
`helpers/prompt` takes 7 positional arguments:
```ts
(title, text, isCancelable, type, isOKDestructive, continueButtonText, defaultInputValue)
```
The trailing 5 optional args are fragile. In PR #8301 a code-quality bot miscounted the
arity ("only accepts 6 args"), and reviewer @marcosrdz called the helper "so fragile" and
asked for a clearer interface. A positional `undefined` placeholder is needed today just to
reach `defaultInputValue` (see `WalletDetails.tsx:489`).
## Investigated and rejected
- **Drop `react-native-prompt-android`.** Not possible. The package ships native Android
Java code (`RNPromptModule.java`, `NativeModules.PromptAndroid`) and is the only Android
prompt implementation. RN's `Alert.prompt` is iOS-only.
- **Add an `anchor` option** (reviewer suggestion). Not possible. Neither
`react-native-prompt-android` nor RN `Alert.prompt` accept an anchor parameter. Out of
scope; report this finding back on the PR thread.
## Design
Keep `title` and `text` positional (passed at every call site). Move the 5 fragile
trailing args into an optional `options` object.
### New signature
```ts
type PromptOptions = {
cancelable?: boolean; // default true
type?: PromptType | PromptTypeIOS | PromptTypeAndroid; // default 'secure-text'
destructive?: boolean; // default false — OK button uses 'destructive' style
continueButtonText?: string; // default loc._.ok
defaultValue?: string; // prefills the input field
};
export default (title: string, text: string, options?: PromptOptions): Promise<string>
```
Renames (aligning with the lib's own `PromptOptions` field names):
| old positional param | new options field |
| -------------------- | ----------------- |
| `isCancelable` | `cancelable` |
| `type` | `type` |
| `isOKDestructive` | `destructive` |
| `continueButtonText` | `continueButtonText` |
| `defaultInputValue` | `defaultValue` |
### Internals
Logic unchanged. Destructure with defaults at the top, handling `options` being
`undefined`:
```ts
const { cancelable = true, type = 'secure-text', destructive = false,
continueButtonText = loc._.ok, defaultValue } = options ?? {};
```
Everything downstream (button array build, `keyboardType` derivation, iOS
`numeric`→`plain-text` workaround, message-blanking when `defaultValue` is set) reads
these locals instead of positional params. No behavior change.
## Call site migration
12 call sites. 3 are two-arg calls — untouched. 9 migrate trailing args into an object.
| # | File:line | Change |
|---|-----------|--------|
| 1 | `screen/transactions/TransactionStatus.tsx:680` | `{ type: 'plain-text', defaultValue: currentMemo }` |
| 2 | `screen/wallets/WalletDetails.tsx:157` | `{ type: 'numeric', destructive: true, continueButtonText: loc.wallets.details_delete }` |
| 3 | `screen/wallets/WalletDetails.tsx:489` | `{ type: 'plain-text', defaultValue: wallet.getLabel() }` |
| 4 | `screen/wallets/ImportWalletDiscovery.tsx:109` | unchanged (2-arg) |
| 5 | `screen/wallets/addMultisigStep2.tsx:289` | `{ type: 'plain-text' }` |
| 6 | `screen/wallets/addMultisigStep2.tsx:300` | `{ type: 'plain-text' }` |
| 7 | `screen/wallets/addMultisigStep2.tsx:362` | unchanged (2-arg) |
| 8 | `screen/wallets/PaymentCodesList.tsx:137` | `{ type: 'plain-text' }` |
| 9 | `screen/wallets/PaymentCodesList.tsx:248` | `{ type: 'plain-text' }` |
| 10 | `screen/wallets/ViewEditMultisigCosigners.tsx:422` | unchanged (2-arg) |
| 11 | `screen/lnd/lnurlPay.tsx:143` | `{ cancelable: false, type: 'plain-text' }` |
| 12 | `blue_modules/start-and-decrypt.ts:26` | `{ cancelable: false }` |
Note: sites 5/6/8/9 pass `isCancelable: true` today, which is the default — the
`{ type: 'plain-text' }` object omits it.
## Testing
- `tests/unit/transaction-status.test.tsx:114` mocks the helper as a `jest.fn` — no change.
- Verify: `npm run lint` (ESLint + `tsc`) and `npm run unit` pass.
- TypeScript catches any missed/malformed call site at compile time.
## Out of scope
- Anchor support.
- Removing `react-native-prompt-android`.
- Any change to prompt UI/behavior.