Compare commits
2 Commits
master
...
reduce-ani
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ac06d57e82 | ||
|
|
9da9b7aad6 |
12
.github/workflows/e2e-ios.yml
vendored
12
.github/workflows/e2e-ios.yml
vendored
@ -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/
|
||||
|
||||
263
docs/superpowers/plans/2026-05-20-prompt-refactor.md
Normal file
263
docs/superpowers/plans/2026-05-20-prompt-refactor.md
Normal 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.
|
||||
104
docs/superpowers/specs/2026-05-20-prompt-refactor-design.md
Normal file
104
docs/superpowers/specs/2026-05-20-prompt-refactor-design.md
Normal 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.
|
||||
Loading…
Reference in New Issue
Block a user