Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
966dffd
docs(agents): document class member ordering convention
ethicnology Jun 15, 2026
8080703
refactor(import_watch_only): sanitize user-facing error messages
ethicnology Jun 15, 2026
3c2e0db
refactor(broadcast_signed_tx): sanitize user-facing error messages
ethicnology Jun 15, 2026
c3d9073
fix(labels): stop leaking raw error detail in label messages
ethicnology Jun 15, 2026
5b39725
chore(l10n): add localized error messages and remove dead keys
ethicnology Jun 15, 2026
0eaca5b
feat: add Result<T> pattern and Failure base class
wired-pasteque Jun 17, 2026
6296977
refactor: sanitize user-facing error messages
wired-pasteque Jun 17, 2026
f06cae1
chore: add localized error messages
wired-pasteque Jun 17, 2026
4314bae
refactor(pin_code): adopt generic Result and sealed PinCodeFailure
ethicnology Jun 18, 2026
48f8c9e
docs: standardize Result/Failure error-handling pattern
ethicnology Jun 18, 2026
ef246e2
refactor(import_watch_only): adopt sealed Failure and Result
ethicnology Jun 18, 2026
15379be
refactor(broadcast_signed_tx): adopt sealed Failure and Result
ethicnology Jun 18, 2026
3a37039
refactor(labels): adopt sealed Failure and Result
ethicnology Jun 18, 2026
6f9337c
refactor(pin_code): rename failure extension to _failure_l10n
ethicnology Jun 18, 2026
04c6ebd
docs: align failure docs with _l10n naming
ethicnology Jun 18, 2026
476e69d
refactor(labels): drop unused LabelFailure variants
ethicnology Jun 18, 2026
4373284
refactor(import_watch_only): extract parse into a use-case
ethicnology Jun 18, 2026
df3f7db
test(import_watch_only): cover xpub use-case sanitization
ethicnology Jun 18, 2026
6a48439
refactor(import_watch_only): drop unused unexpected failure
ethicnology Jun 19, 2026
9fa6bce
refactor(labels): surface note-delete failure, drop double-log
ethicnology Jun 19, 2026
9b0e6a7
docs: use a value object for the member-ordering example
ethicnology Jun 19, 2026
6174362
refactor(app_unlock): adopt generic Result and sealed AppUnlockFailure
wired-pasteque Jun 18, 2026
ab30345
chore(l10n): add localized error messages
wired-pasteque Jun 18, 2026
8ebfce1
test(app_unlock): add unit tests for use-cases and repository
wired-pasteque Jun 18, 2026
3b005dd
refactor(recoverbull): sanitize errors with sealed failures and a Res…
ethicnology Jun 22, 2026
4f890be
refactor(bitcoin_price): sanitize user-facing error messages
wired-pasteque Jun 22, 2026
24050f0
chore(l10n): add localized error messages
wired-pasteque Jun 22, 2026
1f2143c
fix(recoverbull): effective guards, preserve key-server detail, per-o…
ethicnology Jun 22, 2026
863211b
refactor(backup_settings): sanitize user-facing error messages
wired-pasteque Jun 22, 2026
f29d4aa
fix: log.warning, and SnackBarUtils for backup_settings
wired-pasteque Jun 22, 2026
6622e1a
Merge pull request #2328 from SatoshiPortal/sanitize-backup-settings
ethicnology Jun 22, 2026
5179781
Merge pull request #2331 from SatoshiPortal/sanitize-bitcoin-price-er…
ethicnology Jun 22, 2026
a80d830
Merge pull request #2330 from SatoshiPortal/sanitize-recoverbull
ethicnology Jun 23, 2026
ebac771
refactor(electrum_settings): sanitize user-facing error messages
wired-pasteque Jun 23, 2026
7a9d793
chore(l10n): remove not used electrum error messages
wired-pasteque Jun 23, 2026
3561851
test(electrum): verify no raw exception leaks from electrum use-cases
wired-pasteque Jun 23, 2026
aea0cf0
fix: prevent stuck loading states in use cases and fix minor issues
wired-pasteque Jun 23, 2026
1f595b5
Merge pull request #2334 from SatoshiPortal/sanitize-electrum-setting…
ethicnology Jun 23, 2026
7c10ea6
refactor(mempool_settings): sanitize user-facing error messages
wired-pasteque Jun 24, 2026
1497e6d
chore(l10n): add localized error messages
wired-pasteque Jun 24, 2026
08b064d
test(mempool_settings): add unit tests
wired-pasteque Jun 24, 2026
4274a6f
fix: sanitize fees bridge, clean up dead l10n key, and other minor fixes
wired-pasteque Jun 24, 2026
91ce605
fix: simplify mempool failure layer by extending core failure directly
wired-pasteque Jun 24, 2026
3245ecd
fix: use static message in mempool url builder bridge to prevent logM…
wired-pasteque Jun 24, 2026
845b7cd
refactor(bip85_entropy): sanitize errors with sealed failures and a R…
wired-pasteque Jun 24, 2026
135078b
fix: proper error propagation in ARK use-cases, private fields, trans…
wired-pasteque Jun 25, 2026
2c5ad7b
Merge pull request #2342 from SatoshiPortal/sanitize-bip85-entropy-er…
ethicnology Jun 25, 2026
60159c2
Merge pull request #2339 from SatoshiPortal/sanitize-mempool-settings…
ethicnology Jun 25, 2026
2a7c1c8
refactor(all_seed_view): sanitize user-facing error messages
wired-pasteque Jun 23, 2026
0c1abbf
chore(l10n): add localized error messages
wired-pasteque Jun 23, 2026
c872962
test(seed): verify no raw exception leaks from seed use-cases
wired-pasteque Jun 23, 2026
e70a92a
fix: keep log.severe in seed repository error paths
wired-pasteque Jun 23, 2026
399c691
fix: re-add delete guard and fix wallet-failure handling
wired-pasteque Jun 23, 2026
ac4c8b7
Merge pull request #2332 from SatoshiPortal/sanitize-all-seed-view-er…
ethicnology Jun 25, 2026
99db9ee
Merge origin/develop into 1895-sanitize-all-user-facing-error-messages
ethicnology Jun 25, 2026
7b8c555
fix(test): use default-wallet seed in bip85 derivation check
ethicnology Jun 25, 2026
b609c95
Merge remote-tracking branch 'origin/develop' into 1895-sanitize-all-…
ethicnology Jun 25, 2026
b6b1fb3
refactor(replace_by_fee): sanitize user-facing error messages
wired-pasteque Jun 25, 2026
ca67263
chore: remove dead localization keys and clean up cubit
wired-pasteque Jun 25, 2026
2a87325
refactor(import_mnemonic): sanitize user-facing error messages
wired-pasteque Jun 25, 2026
608dcd3
chore(l10n): add localized error messages
wired-pasteque Jun 25, 2026
73e7a6b
test(import_mnemonic): add unit tests
wired-pasteque Jun 25, 2026
dbdf37a
fix: add cubit guard tests and translate empty label error
wired-pasteque Jun 25, 2026
fe197c0
Merge pull request #2346 from SatoshiPortal/refactor-import-mnemonic-…
ethicnology Jun 25, 2026
c0cb31f
Merge pull request #2344 from SatoshiPortal/refactor-replace-by-fee-e…
ethicnology Jun 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Hard rules:
8. **Shared value objects go in `lib/core/primitives/`** (planned per [FEATURES.md](FEATURES.md) — folder doesn't exist yet at audit time, and `Secret`/`Address`/`Amount`/`Fingerprint` are not yet extracted as primitives). When you create a value object that more than one feature would reasonably use, put it in `lib/core/primitives/` from the start — that's how the primitives layer gets built. Before creating one, grep `lib/` for an existing class with the same intent.
9. **Prefer rich domain models** — methods that enforce invariants, not anemic DTOs mirroring DB rows. (Note: [Fowler's anemic-domain anti-pattern](https://www.martinfowler.com/bliki/AnemicDomainModel.html) is widely cited but [not universal](https://blog.inf.ed.ac.uk/sapm/2014/02/04/the-anaemic-domain-model-is-no-anti-pattern-its-a-solid-design/). This is our preference, aligned with ARCHITECTURE.md's "Anemic Domain Models" pitfall.)
10. **No raw colors.** Always pull from the theme. If the user describes a color in plain language, pick the closest theme color that works in both light and dark mode. Don't edit theme files unprompted.
11. **Errors — one sealed family per feature.** Define a sealed `<feature>_error.dart` in `domain/`. Map foreign errors at every boundary; never leak another layer's or feature's error type. **Each error exposes a `toTranslated(BuildContext)`** (dominant convention, 36 uses — `sell_error.dart`, `bitbox_errors.dart`, `replace_by_fee/errors.dart`; not `toTranslation`/`String get message`) returning a localized, user-safe message via `AppLocalizations` (the `context.loc` extension; see the UI Kit "no hardcoded user-facing strings" rule) — never the raw exception or dev detail, which stays in logs. The `sealed` switch makes a missing user message a compile error. **Call it UI-side only** — it takes a `BuildContext`; the bloc holds the `<Feature>Error` in state, the widget renders `error.toTranslated(context)`. A `BuildContext` in `presentation/` is a smell. **The end user never sees a dev string:** the catch-all variant (`unexpected`/`unknown`) returns a **generic localized** message, never the raw `message` — `unexpected: (message) => message` leaks dev detail; log it, show a generic string. Prefer returning a `Result`/sealed outcome for expected, recoverable errors at the repository boundary (`throw` only for programmer errors) — officially "a recommendation, but not a requirement" ([data-layer case study](https://docs.flutter.dev/app-architecture/case-study/data-layer)); don't mass-migrate existing exception code. **Legacy (don't replicate):** some hexagonal features split errors per layer (`domain_errors.dart` / `application_errors.dart` / `presentation_errors.dart`), and `fund_exchange` uses feature-prefixed singular (`fund_exchange_<layer>_error.dart`). Converge to one family per feature over time.
11. **Failures — one sealed family per feature, `*Failure` not `*Error`.** Three words, kept apart: **`Exception`** = thrown infra (data layer, caught at the boundary); **`Error`** = a `dart:core` programmer bug, never caught (→ Sentry); **`Failure`** = a modeled, recoverable **value** in `domain/`. Define a sealed `<Feature>Failure extends Failure` in `domain/<feature>_failure.dart`, **Flutter-free**; cross-cutting modes (network, storage-locked, not-found, timeout, auth, device, insufficient-funds) come from the shared `sealed CoreFailure` in `lib/core/failures/` (alongside the `Failure` base; the legacy `lib/core/errors/` is the graveyard until emptied). Map foreign errors at the boundary; never leak another layer's or feature's type. **Translation is a presentation extension, not a method on the failure:** `presentation/<feature>_failure_l10n.dart` exposes `toTranslated(BuildContext)` — the only place `context.loc` / `flutter` touches a failure (keeps `domain/`+`data/` Flutter-free); the `sealed` switch makes a missing user message a compile error. **The end user never sees a dev string:** the catch-all variant returns a **generic localized** message (`oopsSomethingWentWrong`), never the raw `logMessage` (logged at the boundary, for us only) — `unexpected: (m) => m` leaks dev detail. **Propagation:** repositories return `Result<T, F extends Failure>` (variants `Ok`/`Err`, generic over `F` so consumers never cast, `@useResult`; helpers `fold`/`map`/`mapErr`); `throw` only for `dart:core` bugs ([Flutter Result](https://docs.flutter.dev/app-architecture/design-patterns/result)). The **repository** is the one `try/catch` boundary (or the feature **use-case** when wrapping a shared core repo that still throws); use-cases forward/compose `Result`s; the bloc `switch`es and holds the typed `<Feature>Failure` in state (no `BuildContext` in `presentation/` logic); the UI renders `failure.toTranslated(context)`. **Migration (#1895) is sanctioned and staged** — bring a feature fully in line when you touch it, one feature per PR. **Legacy (don't replicate, converge per-feature):** `BullException` + hardcoded English; `<Feature>Error` naming; `toTranslated` *on* the error; per-layer splits (`domain_errors.dart`/`application_errors.dart`/…); `fund_exchange`'s feature-prefixed singular.
12. **Acyclic feature graph.** Check [FEATURES.md](FEATURES.md) before adding a dependency.
13. **Keep the dependency graph live.** Any PR that adds a feature, removes a feature, or changes which other features it consumes via `public/` facades **must** update the mermaid graph in [FEATURES.md](FEATURES.md) in the same commit. The graph is documentation only if it matches the code — if you change one without the other, both become useless.
14. **Folders justify their existence — files don't justify folders.** A tiny piece of code is one file with a role suffix, not a folder of one file:
Expand All @@ -69,7 +69,7 @@ Hard rules:

**Exception (melos migration):** `packages/` and `features/` are intentionally pre-created with `.gitkeep` as reserved workspace homes for the in-progress monorepo migration — do not remove them or treat them as a rule-#14 violation. See the Monorepo / melos section.

15. **Enforce with the compiler, not hope.** Repository contracts → `abstract interface class` (forbids `extends`, forces `implements`); lock finer capabilities with `@Deprecated.implement()` / `.instantiate()` / `.extend()` (Dart 3.10+). Error families and multi-case states → `sealed` (a missing `switch` case is a compile error). `Result`-returning repo methods → annotate `@useResult` (`package:meta`) so a discarded result warns. Cross-feature and `data/`-from-`presentation/` bans → a rule in the first-party analyzer plugin system (Dart 3.10+, the supported successor to `custom_lint`); under melos, package boundaries + the `implementation_imports` lint make them hard errors. All available in the current toolchain (Flutter 3.41/3.44 bundle Dart 3.11/3.12) — no point-release dependency; nothing new in 3.41/3.44 is required.
15. **Enforce with the compiler, not hope.** Repository contracts → `abstract interface class` (forbids `extends`, forces `implements`); lock finer capabilities with `@Deprecated.implement()` / `.instantiate()` / `.extend()` (Dart 3.10+). Failure families and multi-case states → `sealed` (a missing `switch` case is a compile error). `Result`-returning repo methods → annotate `@useResult` (`package:meta`) so a discarded result warns. Cross-feature and `data/`-from-`presentation/` bans → a rule in the first-party analyzer plugin system (Dart 3.10+, the supported successor to `custom_lint`); under melos, package boundaries + the `implementation_imports` lint make them hard errors. All available in the current toolchain (Flutter 3.41/3.44 bundle Dart 3.11/3.12) — no point-release dependency; nothing new in 3.41/3.44 is required.

When a request would break these rules, explain why and propose a compliant alternative. Don't silently comply.

Expand Down Expand Up @@ -114,7 +114,7 @@ Codified from a sweep of the actual codebase. ARCHITECTURE.md is silent on most
| Event | `<feature>_event.dart` | `send_event.dart` |
| Facade | `<feature>_facade.dart` | `labels_facade.dart` |
| Locator | `<feature>_locator.dart` | `wallet_locator.dart` |
| Errors | per rule #11 | |
| Failure (family) | `<feature>_failure.dart` (domain) + `<feature>_failure_l10n.dart` (presentation) | per rule #11 |

**Classes** — `PascalCase`, matching local quirks:

Expand All @@ -129,10 +129,34 @@ Codified from a sweep of the actual codebase. ARCHITECTURE.md is silent on most
- **State / Event**: `<Feature>State` / `<Feature>Event`, sealed with freezed when there are multiple cases.
- **Facade**: `<Feature>Facade` (`LabelsFacade`).
- **Watcher** (when introduced): `<Subject>Watcher`.
- **Error**: one sealed `<Feature>Error` family per feature (see rule #11).
- **Failure**: one sealed `<Feature>Failure` family per feature in `domain/<feature>_failure.dart` (base `Failure`); never `<Feature>Error` — `Error` is reserved for `dart:core` bugs. Translation extension `<Feature>FailureL10n` in `presentation/`. See rule #11.

When the same role exists with two names in the codebase, use the dominant one and flag the outlier as legacy. Don't rename outliers in unrelated PRs — that breaks atomic commits.

### Member ordering

Inside a class, declare members in this order, with a blank line between each group:

1. **Instance fields** — the object's state. Reading these first tells you *what the thing is*.
2. **Constructor(s)** — how it's built, on top of the fields you just read.
3. **Methods** — its behaviour, including `@override`s.

```dart
class Amount {
final BigInt sats;

Amount(this.sats) {
if (sats < BigInt.zero) throw ArgumentError('Amount cannot be negative');
}

bool get isDust => sats < BigInt.from(546);
}
```

This is fields-first on purpose: our domain, value, and error types are defined by their state, so surfacing it first reads most naturally (and matches the field-first habit of most other languages). It is a soft convention — the analyzer does not enforce member order, so keep it consistent by hand and in review.

**Do not enable the `sort_constructors_first` lint.** It enforces the *opposite* order (constructor before fields); turning it on would fight this convention and churn every class. Widgets, whose constructor is effectively their public API, may keep the constructor first if that reads better there — don't reformat existing widgets to satisfy this rule.

## UI Kit — reuse first, build the kit as you go

The codebase is migrating toward a real UI Kit. The skeleton already lives in [`lib/core/widgets/`](lib/core/widgets/) (`buttons/`, `cards/`, `inputs/`, `lists/`, `dropdown/`, `bottom_sheet/`, `text/`, `selectors/`, …) and theme tokens in [`lib/core/themes/`](lib/core/themes/). Today the same widgets get re-implemented per feature (`_SaveButton`, `_ConfirmButton`, `_BottomButtons` …) — stop adding to that pile.
Expand Down
Loading