Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"permissions": {
"deny": [
"Edit(scripts/**)",
"Write(scripts/**)",
"Edit(scripts/safe-cargo.sh)",
"Write(scripts/safe-cargo.sh)",
"Edit(.github/workflows/**)",
"Write(.github/workflows/**)"
]
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Read and follow all instructions in [CLAUDE.md](./CLAUDE.md) before starting any task.
79 changes: 56 additions & 23 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,16 +75,62 @@ fn remove_selected_utxos(&mut self, context: Option<&AppContext>, selected: &BTr
- **docs/ai-design** should contain architecture, technical design and manual testing scenarios files, grouped in subdirectories prefixed with ISO-formatted date
- end-user documentation is in a separate repo: https://github.com/dashpay/docs/tree/HEAD/docs/user/network/dash-evo-tool , published at https://docs.dash.org/en/stable/docs/user/network/dash-evo-tool/

### Core Module Structure
### System Layers (top → bottom)

- **UI (`ui/`)** — Screens and reusable components (`ui/components/`). No business logic. Returns `AppAction`s.
- **App (`app.rs`)** — `AppState`: owns all screens, polls task results each frame, dispatches to visible screen. Bridges UI and backend.
- **Backend Tasks (`backend_task/`)** — Async business logic, one submodule per domain (identity, wallet, contract, etc.). Talks to SDK/DB via `AppContext`, sends results back via channel.
- **Context (`context/`)** — `AppContext`: shared state — network config, SDK client, database, wallets, settings cache (split into submodules: `identity_db.rs`, `wallet_lifecycle.rs`, `settings_db.rs`, etc.). Glue between layers.
- **Model (`model/`)** — Pure data types (amounts, fees, settings, wallet/identity models). No side effects, no IO.
- **Database (`database/`)** — SQLite persistence (rusqlite), one module per domain. Typed CRUD, no business decisions.
- **Platform Integration (`spv/`, `components/core_zmq_listener`)** — External system interfaces (SPV sync, ZMQ events).

### Layer Rules

**Model rules** (ideal target for new code):
- UI never calls SDK or database directly — always through `BackendTask`
- Backend tasks receive `AppContext`, do async work, return typed results
- Models are shared across all layers — pure data types, no IO
- Database modules are pure data access — no business logic or domain decisions
- Context is the glue: UI reads from it, backend tasks operate through it
- Data types shared between layers belong in `model/`, not in `ui/` or `database/`

**In practice**, the codebase has established patterns that differ from the model:
- UI may **read** from DB through `AppContext` wrapper methods (e.g., `app_context.load_local_qualified_identities()`)
- UI may **write** to DB in `display_task_result()` for caching backend results
- `Wallet` (`model/wallet/`) mixes data, DB writes, and RPC calls — this is intentional
Comment on lines +98 to +101
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “In practice” exceptions list doesn’t mention that there is already direct app_context.db.* usage in UI code (both reads and writes), beyond wrapper methods and beyond display_task_result() (e.g., src/ui/dashpay/contact_details.rs writes via save_contact_private_info, and src/ui/identities/identities_screen.rs deletes identities directly). If the intent is “avoid introducing new direct DB calls from UI”, consider documenting this as an existing legacy pattern here (or clarifying the anti-pattern wording) so the guidance matches what contributors will find in the codebase.

Copilot uses AI. Check for mistakes.
- Some data types live in `ui/` and are imported by `backend_task/`
- Database methods occasionally contain domain logic (e.g., contest state derivation)

These are accepted. Do not refactor existing code to match the model rules.

### Standard Flows

**User action → async work → result displayed:**
```
Screen::ui() → AppAction::BackendTask(task)
→ tokio::spawn → AppContext::run_backend_task()
→ sender.send(TaskResult::Success(result))
→ AppState::update() polls → Screen::display_task_result()
```

**UI needs fresh data on construction/refresh:**
```
Screen::new() or refresh() → app_context.read_wrapper_method()
→ returns cached or DB-read data (read-only, no writes)
```

**Backend task fetches + persists data:**
```
BackendTask variant → AppContext::run_*_task()
→ SDK/RPC call → persist results to DB → return typed result
→ Screen::display_task_result() updates in-memory state only
```

- **app.rs** - `AppState`: owns all screens, polls task results each frame, dispatches to visible screen
- **ui/** - Screens and reusable components (`ui/components/`)
- **backend_task/** - Async business logic, one submodule per domain (identity, wallet, contract, etc.)
- **model/** - Data types (amounts, fees, settings, wallet/identity models)
- **database/** - SQLite persistence (rusqlite), one module per domain
- **context/** - `AppContext`: network config, SDK client, database, wallets, settings cache (split into submodules: `identity_db.rs`, `wallet_lifecycle.rs`, `settings_db.rs`, etc.)
- **spv/** - Simplified Payment Verification for light wallet support
- **components/core_zmq_listener** - Real-time Dash Core event listening via ZMQ
**Anti-patterns (do not add new instances):**
- `app_context.db.save_*()` / `db.delete_*()` from UI code
- `tokio::spawn` in UI bypassing the `BackendTask` system
- Business logic (signing, filtering, state derivation) in UI or database layers

### Key Dependencies

Expand All @@ -105,20 +151,7 @@ See `.env.example` for network configuration options.

## App Task System (Critical Pattern)

The UI and async backend communicate through an action/channel pattern:

1. **Screens return `AppAction`** from their `ui()` method (e.g., `AppAction::BackendTask(task)`)
2. **`AppState` spawns a tokio task** that calls `app_context.run_backend_task(task, sender)`
3. **`AppContext::run_backend_task()`** matches on the `BackendTask` enum and dispatches to domain-specific async methods
4. **Results come back** via tokio MPSC channel as `TaskResult` (Success/Error/Refresh)
5. **Main `update()` loop** polls `task_result_receiver.try_recv()` each frame and routes results to the visible screen's `display_task_result()`

```
Screen::ui() → AppAction::BackendTask(task)
→ tokio::spawn → AppContext::run_backend_task()
→ sender.send(TaskResult::Success(result))
→ AppState::update() polls receiver → Screen::display_task_result()
```
The UI and async backend communicate through the action/channel pattern described in Standard Flows above.

**Backend task enums**: `BackendTask` has variants like `IdentityTask(IdentityTask)`, `WalletTask(WalletTask)`, `TokenTask(Box<TokenTask>)`, etc. Each sub-enum has its own variants and corresponding `run_*_task()` method. Results are `BackendTaskSuccessResult` with 50+ typed variants.

Expand Down
Loading