Memo inbox + receiving-address labeling & filtering - #134
Draft
zmanian wants to merge 37 commits into
Draft
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the received-output test out of detail_includes_output so the inbox query (Task 2+) and the detail path share one definition that cannot drift. Add #[derive(Clone)] to TxOutput to support test spread syntax. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `get_received_memos` (with optional substring query wired for Task 3) and `ReceivedMemo` struct to the wallet sync layer. Only external inbound shielded outputs with non-empty `Memo::Text` are returned; change outputs (internal key scope) and empty/arbitrary memos are excluded by combining `is_received_output` with `decode_text_memo`. Re-exported from `wallet::sync` at the correct visibility levels for the FRB layer in Task 5. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add get_received_memos_search to exercise query filtering: two external inbound txs with memos "Invoice 1042 paid" and "thanks for lunch" verify that query=None returns 2, "invoice" matches 1 (case-insensitive), "LUNCH" matches 1, and "zzz" returns 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `memo_output_key: Option<String>` to `pub(crate) struct TransactionDetail` in `rust/src/wallet/sync/transactions.rs`. In `get_transaction_detail`, the existing `find_map` over `visible_outputs` is extended to also capture which output carried the text memo, producing a key formatted as `"<pool>:<index>"` (e.g. `"3:0"` for Orchard output 0). `None` when no text memo is found. Two focused unit tests are added: - `detail_memo_output_key_set_when_memo_present` — asserts the key matches the pool and index of the memo-bearing output. - `detail_memo_output_key_none_when_no_text_memo` — asserts `None` for a tx with no text memo. The FRB-facing `TransactionDetail` in `api/sync.rs` is not touched; it maps fields explicitly and is unchanged (Task 5 handles the FRB layer). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add ReceivedMemo FRB struct and get_received_memos wrapper to api/sync.rs that delegates to wallet_sync::get_received_memos. Add memo_output_key field to FRB TransactionDetail struct and its get_transaction_detail mapping. Regenerate all FRB bindings (frb_generated.rs and Dart generated files). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pure-function helper that builds the canonical hide-key string
("txidHex:pool:index") from either the ReceivedMemo field set or
the pre-formatted memoOutputKey from TransactionDetail, ensuring
both sources produce identical strings for the same output.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds `kHiddenMemosKey` constant, a `HiddenMemosNotifier` Riverpod `Notifier` backed by `AppSecureStore`, and a TDD test suite covering hide, restore, account isolation, JSON round-trip persistence, and self-initialization. The provider self-initializes: `build()` kicks off `load()` fire-and- forget so watchers automatically receive persisted state without an explicit call. A mutation-generation guard prevents a late initial load from clobbering newer in-memory state. Persistence goes through `HiddenMemosState.toSerializable()` rather than reaching into private fields. Tests inject `AppSecureStore.testing` with an in-memory secure-storage fake. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add MemoRepository abstraction and memoRepositoryProvider seam so
widget tests can override the data source without FFI.
- Add receivedMemosProvider (FutureProvider.family<MemoQuery>) that
returns [] when appSecurityProvider.requiresUnlock is true (clears on
lock).
- Add MemosTab and MemoRow widgets in
lib/src/features/activity/widgets/memos_tab.dart with debounced search
(350ms), empty states ("No memos yet" vs "No memos match"), and
navigation to the existing ActivityTransactionStatusScreen.
- Add "All | Memos" segmented control to ActivityScreen; All tab
behaviour is unchanged.
- 7 widget tests in test/features/activity/memos_tab_test.dart covering
list rendering, search narrowing, distinct empty states, lock-clear,
and memo truncation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds hide/restore actions on inbox memo rows via hiddenMemosProvider. Each inbox row shows a hide icon button; a Hidden segment in the tab header lists hidden memos with a restore action. When all memos are hidden the inbox shows a distinct "All memos hidden — see Hidden" state. Strengthens the existing amount assertion in memos_tab_test.dart to check the exact ZecAmount.activity string instead of a loose textContaining. Extracts the shared InMemorySecureStorage test fake to test/helpers/in_memory_secure_storage.dart (used by both activity test files) to prevent drift. Keeps the tappable memo content/amount areas showing the desktop pointer cursor even when a trailing Hide/Restore button is present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add MemoDetailSection ConsumerWidget that checks hiddenMemosProvider and renders a "Memo hidden" placeholder with a Restore affordance when the memo's hide-key is in the hidden set; otherwise renders the memo text. When not hidden (or memoOutputKey is null), the non-hidden display is unchanged: the expand/collapse toggle stays in the message block's titleTrailing (beside the "Message" heading) exactly as before, with isMemoHidden computed reactively in the screen build via ref.watch(hiddenMemosProvider). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Harden per-memo ValueKeys (hide/restore buttons, memo text/amount) to use the full txid:pool:index identifier instead of txid alone, so keys stay unique when a single transaction carries multiple received memo outputs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… addresses Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lists external (key_scope=0) receiving addresses the user has actually been shown (exposed_at_height IS NOT NULL), excluding the gap-limit lookahead pool, newest-first by diversifier_index_be. Marks the account default address. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add AccountAddress struct and list_account_addresses wrapper to rust/src/api/wallet.rs, mirroring the existing get_unified_address idiom (catch + keys::parse_network). Regenerate all FRB bindings; lib/src/rust/api/wallet.dart now exposes listAccountAddresses and class AccountAddress with address (String) and isDefault (bool). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add kAddressLabelsKey to app_secure_store.dart, implement AddressLabelsNotifier/AddressLabelsState mirroring HiddenMemosNotifier (mutation-generation guard, build-time microtask load, persist via writePlain), and full test suite covering set, blank-clears, remove, per-account isolation, JSON round-trip, and self-init without explicit load(). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracts AddressNameField (ConsumerStatefulWidget) that binds to addressLabelsProvider for pre-fill, persist on submit/blur, and clear-to-remove semantics. Places the field on the Receive screen below the shielded address block, guarded to the shielded type and a non-null address. Address renewal re-keys the widget so the controller resets to the new address's label automatically. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add addressRepositoryProvider + addressListProvider mirroring the memoRepositoryProvider seam pattern for testability without FFI - Add MyAddressesScreen listing per-account addresses with italic "Unnamed" for unlabeled addresses and an inline Rename action persisting via addressLabelsProvider - Add "My Addresses" row to the Account section of SettingsScreen following the onMyAddresses callback threading pattern - Register /settings/my-addresses GoRoute in app.dart - Widget tests cover: two-row render + italic Unnamed, save-persists, cancel-reverts The rename field's FocusNode is owned by _AddressRowState (created in initState, requestFocus on edit entry, disposed in dispose) rather than allocated inline in build, avoiding a leaked FocusNode per rename session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ter plan Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add `to_address: Option<String>` to `ReceivedMemo` and populate it from
`output.to_address` in `get_received_memos`. Extend the existing test to
assert the Some path ("u-my-receiver") and add a fixture row for the None
path (inbound output with no to_address stored).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add pub to_address: Option<String> to the FRB ReceivedMemo struct and map it from the wallet-layer struct in get_received_memos. Regenerated frb_generated.rs and Dart bindings so ReceivedMemo.toAddress (String?) is available to Dart callers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the private _truncateAddress logic from my_addresses_screen.dart into a shared lib/src/features/activity/address_display.dart module so the Memos tab address filter (Task 3b) can reuse it without duplication. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a DropdownButton address filter inside the memos data branch. The filter derives distinct toAddress values from the current (search-filtered) memo set, applies to both inbox and hidden views, resets via post-frame callback when the selected address leaves the set, and shows "No memos for this address" when the filtered list is empty. Dropdown is hidden when fewer than two distinct addresses are present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This branch ships three stacked features building on each other. Specs/plans under
docs/superpowers/.1. Received memo inbox (read-only, with hide/restore)
A Memos tab on the Activity screen listing received Zcash text memos for the active account, with per-memo hide/restore for spam/abuse.
All · Memossegmented control; received text memos only (sent/change/self excluded).decode_text_memo, neverLIKEon the raw memo BLOB).AppSecureStoreplain KV, wiped on reset); a Hidden view restores; hidden memos redacted (with Restore) in the transaction detail screen.txid:pool:indexidentifiers persisted.2. Receiving-address labeling
Name your own receiving addresses, stored locally per-account; managed inline on the Receive screen and in a new My Addresses screen (Settings → My addresses).
list_account_addresses: external, exposed receiving UAs only (key_scope = 0 AND exposed_at_height IS NOT NULL), excluding the gap-limit lookahead pool; newest-first.addressLabelsProvider: local per-account labels keyed by address string (mirrorshiddenMemosProvider); wiped on reset, not seed-recoverable.3. Filter memos by receiving address
The Memos tab gains a receiving-address dropdown that narrows the inbox to memos received at a chosen one of your addresses — shown by label when named (from feature 2).
to_addresssurfaced onReceivedMemo(Rust → FRB → Dart); same string as the labels, so it both labels and filters with zero translation.to_addressmemos appear only under "All".Test Plan
cd rust && cargo test— 94 lib tests pass.fvm flutter test test/features/activity/ test/features/receive/ test/features/settings/my_addresses_screen_test.dart test/providers/hidden_memos_provider_test.dart test/providers/address_labels_provider_test.dart).fvm flutter analyze— no issues in changed files (vendoredrust_builder/cargokiterrors are pre-existing/unrelated).Known follow-ups (non-blocking)
AppTextField; empty-active-account edge in the address list.🤖 Generated with Claude Code