ADR-0083: Review-card money is detected metadata, never a confirmed payment
Context
Unmatched rows store no parsed amount. The card nevertheless took the first currency token in the raw text and rendered it in the amount role beside the source label. A balance-first notification therefore showed the balance as if it were the payment, a fee or multi-amount notification picked an arbitrary token, and a long value was constrained to one line and clipped at 320 dp/200%. Release builds also showed the debug-only long-press copy hint while the copy action and export were absent.
Decision
The card renders a monetary value only when the raw text has exactly one
currency amount and no balance/limit/fee cue (balance, balances, baki,
saldo, sisa, available, aval, outstanding, limit, limits, bal,
fee, fees, caj, minimum payment, bayaran minimum, had kredit,
amount due, tersedia). That value comes from the existing Currency token
catalog and AmountParser with exact-fraction validation, is formatted from
Long minor units by MoneyFormatter, and sits under a Detected in
notification eyebrow — never under a payment label or beside the source as a
confirmed amount.
A currency token is paired with at most one adjacent number. A left number that
is a card suffix, a reference (ref, txn, transaction, card, ending,
acct, account, inv, bill, no, id) or a leading-zero tag (0475), or
a date/time fragment (on/at before it, / or : adjacent), is ignored; the
right number is then the candidate. A number adjacent to / or : is never
money, so RM 11/08/2026 and RM 09:30 show nothing. Two real adjacent numbers
(12.50 USD 30 days, RM24.90-RM5.00) are ambiguous; a dash before a bare
integer after the amount is a prose separator/count (24.90 RM - 2 items keeps
the left amount), while a signed decimal on the right stays ambiguous. Currency
tokens and numeric runs require complete word and number boundaries, so letters,
combining marks or digits inside a word or reference number (2 hours 30,
ref123 USD, RM123_abc, 1 234.56) cannot fabricate money. A
whitespace-separated leading sign (- RM10.00) is preserved; conflicting signs
are ambiguous.
The cue check is deliberately global and conservative: a valid payment amount may be omitted when the same notification also mentions a balance or fee. Omission is preferred to presenting a balance or a rounded value as the payment. Several amount-like tokens show a "possible amounts" notice, a balance/limit/fee mention shows an ambiguity notice, and anything else shows no money. The UI does not run a second payment parser or infer which token is the transaction.
The metadata block takes the card's full width between the source identity and
the raw text, so a complete currency-aware value wraps instead of clipping at
320 dp/200%. A zero-width break opportunity after each thousands separator lets
a Long-maximum value wrap; the displayed digits and MoneyFormatter output stay
unchanged. Raw notification text, stored masking guidance, create/dismiss and
the JSON export contract are unchanged.
The long-press copy hint and its semantics label are gated by the same
showDiagnostics flag as the copy action and export. Release builds expose
neither the instruction nor the diagnostics action; debug builds keep the
existing snackbar feedback.
Consequences
A user can no longer mistake a balance or the first of several amounts for the payment, and long values stay readable at large text. Review cards lose a prominent amount for ambiguous notifications, which is deliberate: the raw text and the editor retry remain the source of truth. The policy is screen-local, reuses the named amount/supporting roles, and needs no schema or clipboard change.