fix(folio): gate submit-balance property on Home route landing

totalBalance is only freshly computed when AccountCards are visible on
Home; off-Home landings return the carrier and would false-fire the
property, latching always(next(F)) to false and masking the real
double-submit bug. Skip vacuously when route is not "home".
This commit is contained in:
pj committed 2026-05-31 22:14:38 +05:30
1 parent 52865a1d7b
commit 4c7c22ced6
2 files changed
+10 -3

No files matched your search

+7 -2
View File
@@ -39,14 +39,19 @@ export function parseTypedAmount(text: string | undefined | null): number {
// When the last action is a tap (or double-tap) on the transaction Submit
// button, the absolute change in total balance must equal the amount the
// user typed. A double-submit lands two transactions and shifts the balance
// by 2x the typed amount, tripping this check.
// by 2x the typed amount, tripping this check. The route gate skips steps
// whose landing screen is not Home: totalBalance is only freshly computed
// from visible AccountCards on Home, so off-Home comparisons would read a
// stale carrier value and false-fire.
export function submitChangesBalanceByTypedAmount(args: {
route: string | null;
lastAction: { kind?: string; on?: string | object } | null;
typedAmount: number;
prevTotalBalance: number;
currTotalBalance: number;
}): boolean {
const { lastAction, typedAmount, prevTotalBalance, currTotalBalance } = args;
const { route, lastAction, typedAmount, prevTotalBalance, currTotalBalance } = args;
if (route !== "home") return true;
if (lastAction == null) return true;
if (lastAction.kind !== "Tap" && lastAction.kind !== "DoubleTap") return true;
const on = lastAction.on;
+3 -1
View File
@@ -98,10 +98,12 @@ const newAccountBalanceIsZero = always(
// Property 2: a tap on TxnSubmit must move the total balance by exactly the
// typed amount. A double-submit lands two transactions, so the balance shifts
// by twice the typed amount and the check fires.
// by twice the typed amount and the check fires. The route gate inside the
// predicate skips off-Home landings where totalBalance.current is the carrier.
const submitMovesBalanceByTypedAmount = always(
next(() =>
submitChangesBalanceByTypedAmount({
route: route.current,
lastAction: lastAction.current,
typedAmount: parseTypedAmount(txnAmountField.previous?.text),
prevTotalBalance: totalBalance.previous ?? 0,