Skip to content

feat: sync first-run key bootstrap and add key regeneration flow#44

Merged
grunch merged 11 commits into
mainfrom
feat/key-regeneration-first-launch-backup
Mar 21, 2026
Merged

feat: sync first-run key bootstrap and add key regeneration flow#44
grunch merged 11 commits into
mainfrom
feat/key-regeneration-first-launch-backup

Conversation

@arkanoider

@arkanoider arkanoider commented Mar 20, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • align first-launch key bootstrap so settings.toml
    sec_privkey is derived from the DB identity key (mnemonic index 0), keeping DB and settings in sync
  • add a Settings -> Generate New Keys workflow with confirm + backup popups, role-aware key persistence (
    sec_privkey for user mode, admin_privkey for admin mode), and simplified popup state handling
  • improve popup rendering/layout to prevent boundary glitches and update docs for startup/config, key management, settings behavior, and TUI flow

Test plan

  • cargo fmt --all
  • cargo clippy --all-targets --all-features
  • cargo test
  • cargo build
  • Manual test: first launch shows backup popup overlay and persisted
    sec_privkey matches DB identity key
  • Manual test: Settings -> Generate New Keys updates the correct key field, shows mnemonic backup popup, and requires restart

Notes

  • Includes UI changes in backup/confirmation popups and related key-handler state transitions.

Summary by CodeRabbit

  • New Features

    • "Generate New Keys" in Settings for User & Admin with backup-mnemonic popup; "View Seed Words" option added.
    • Backup popup shown as an overlay on first launch and after rotations; restart required to apply rotated keys when indicated.
    • Portable install mode: colocated settings file is preferred and must not contain placeholders.
  • Bug Fixes

    • Admin key confirmation popup rendering fixed to avoid overflow.
  • Documentation

    • Startup, settings, and key-management docs updated to describe new bootstrapping, key derivation, rotation, and backup UX.

@coderabbitai

coderabbitai Bot commented Mar 20, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Boot sequence changed to initialize the SQLite DB first, derive identity keys from the DB, then initialize/load settings (possibly generating a colocated settings.toml). Adds UI-driven key-rotation flows (confirm + backup popups), async tasks for rotating/persisting keys and seed-word loading, and an in-process soft reload to apply rotated keys.

Changes

Cohort / File(s) Summary
Docs
README.md, docs/KEY_MANAGEMENT.md, docs/SETTINGS_ANALYSIS.md, docs/STARTUP_AND_CONFIG.md, docs/TUI_INTERFACE.md
Rewrote first-run/config precedence, documented DB-derived nsec_privkey, backup popup on first-run and rotations, added Generate New Keys flows and portable-install placeholder validation.
Boot & Settings API
src/main.rs, src/settings.rs
DB-first boot: db::init_db() → derive identity keys → init_settings(identity_keys) now returns InitSettingsResult { settings, did_generate_new_settings_file }; settings generation uses embedded template and prefers colocated settings.toml.
Database / Models
src/models.rs
Added User::from_mnemonic, User::delete_all, User::replace_all_atomic and replace_all_in_tx to support atomic replacement of the user identity row from a mnemonic.
Async Channels & Tasks
src/ui/key_handler/async_tasks.rs, src/ui/key_handler/mod.rs
Added AppChannels and create_app_channels(); centralized spawn_* helpers (send/take order, key rotation, seed-word load, Mostro refresh, add-relay); apply_pending_key_reload for soft reload; re-exported from key_handler mod.
UI State & Modes
src/ui/app_state.rs, src/ui/draw.rs, src/ui/mod.rs
Added ConfirmGenerateNewKeys and BackupNewKeys(Zeroizing<String>) UiModes; custom Clone that zeroizes mnemonic clones; new AppState flags backup_requires_restart and pending_key_reload; draw now renders generate/backup overlays; new generate_keys_popup module.
Popup Rendering
src/ui/generate_keys_popup.rs, src/ui/admin_key_confirm.rs
New renderers for confirm/backup popups; backup formats mnemonic into 6-word rows; admin confirmation rendering changed to avoid wrapping and enforce fixed rows.
Key Handler & Navigation
src/ui/key_handler/... (enter_handlers.rs, esc_handlers.rs, navigation.rs, confirmation.rs, user_handlers.rs, admin_handlers.rs)
Enter/Esc handlers extended for confirm/backup flows, mnemonic generation, deterministic nsec derivation, spawn key-rotation/seed-load tasks, set pending_key_reload; many inline tokio::spawn usages moved to async_tasks helpers; navigation blocked in confirm/backup modes; several handler APIs changed to accept EnterKeyContext.
Runtime & Messaging
src/main.rs, src/util/order_utils/fetch_scheduler.rs
Replaced manual channel construction with create_app_channels(); mostro_pubkey shared via Arc<Mutex<PublicKey>>; message listener is kept as an abortable JoinHandle; start_fetch_scheduler now accepts shared pubkey and returns order/dispute task handles.
Settings UI
src/ui/tabs/settings_tab.rs
Added "View Seed Words" and "Generate New Keys" menu entries for Admin/User; updated option counts and indices.
Order utils / send_new_order
src/util/order_utils/send_new_order.rs
send_new_order signature simplified: takes owned FormState and &SqlitePool, removed unused Settings parameter.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as Mostrix UI
    participant Main as main.rs
    participant DB as SQLite
    participant Settings as settings.rs
    participant Backup as Backup Popup

    Note over Main,Settings: First-run bootstrap with DB-derived keys

    User->>UI: Start app
    UI->>Main: launch
    Main->>DB: db::init_db()
    Main->>DB: User::get_identity_keys()
    DB-->>Main: identity keys
    Main->>Settings: init_settings(Some(identity keys))
    Settings->>Settings: generate settings.toml (derive nsec from DB keys)
    Settings-->>Main: InitSettingsResult(did_generate_new_settings_file=true)
    Main->>DB: User::get() (mnemonic)
    DB-->>Main: mnemonic
    Main->>Backup: show BackupNewKeys(mnemonic)
    Backup->>User: display 12-word mnemonic (save & dismiss)
Loading
sequenceDiagram
    actor User
    participant UI as Mostrix UI
    participant Handler as KeyHandler
    participant Tasks as Async Tasks
    participant DB as SQLite
    participant Settings as settings.rs
    participant Main as main.rs

    Note over UI,Tasks: Runtime key rotation flow

    User->>UI: Select "Generate New Keys"
    UI->>Handler: show ConfirmGenerateNewKeys
    User->>UI: Confirm (Enter/Y)
    UI->>Handler: generate mnemonic & derive nsec
    Handler->>Tasks: spawn_key_rotation_task(mnemonic, derived_nsec)
    Tasks->>DB: User::replace_all_atomic(mnemonic)
    DB-->>Tasks: success
    Tasks->>Settings: load_settings_from_disk()
    Tasks->>Settings: update nsec_privkey/admin_privkey
    Tasks->>Settings: save_settings()
    Tasks-->>Handler: send Ok(mnemonic) via key_rotation channel
    Handler->>UI: BackupNewKeys(mnemonic) (set backup_requires_restart=true)
    User->>UI: dismiss backup -> app.pending_key_reload=true
    UI->>Main: main triggers apply_pending_key_reload => reload settings, abort/respawn listeners, reset session
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • grunch
  • AndreaDiazCorreia
  • Catrya

"🐇 I hopped in at dawn with keys in my paw,
I spun twelve words tidy, then synced them with the law,
DB and settings now tango, no longer askew,
Save your mnemonic snug — then back to fields we chew! 🥕"

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: sync first-run key bootstrap and add key regeneration flow' clearly summarizes the two main changes: synchronizing first-run key bootstrap between DB and settings, and introducing a key regeneration workflow. It directly relates to the changeset's primary objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/key-regeneration-first-launch-backup

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
src/ui/key_handler/esc_handlers.rs (1)

121-124: Consider confirming ESC dismissal for mnemonic backup.

Line 121 lets BackupNewKeys close immediately on Esc. A secondary confirmation (e.g., “I saved my mnemonic”) would reduce accidental loss of the only displayed backup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/key_handler/esc_handlers.rs` around lines 121 - 124, The ESC handler
for UiMode::BackupNewKeys currently closes the mnemonic popup immediately;
instead require an explicit confirmation before dismissing—modify the branch
handling UiMode::BackupNewKeys in esc_handlers.rs to, on first ESC press,
transition to a new confirmation state (e.g., UiMode::ConfirmMnemonicDismiss or
set a flag on the app like app.awaiting_mnemonic_confirm) and only set app.mode
= default_mode when the user explicitly confirms (e.g., via a confirm action or
a second ESC mapped to confirm in the new state); update related UI rendering
and input handling to show the “I saved my mnemonic” confirmation prompt and to
handle the confirm/cancel actions accordingly so accidental dismissal is
prevented.
src/ui/constants.rs (1)

5-6: Move runtime config defaults out of ui::constants.

Line 5 defines a settings/bootstrap default in a UI-focused module. If src/settings.rs depends on this, it introduces avoidable cross-layer coupling. Consider relocating it to a shared config/settings constants module.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/constants.rs` around lines 5 - 6, MOSTRO_STAGING_PUBKEY is a
runtime/bootstrap default living in the UI module which creates cross-layer
coupling; move the constant out of src/ui/constants.rs into a new or existing
shared settings/constants module (e.g., src/settings/constants.rs or add to
src/settings.rs), export it with the same name (MOSTRO_STAGING_PUBKEY) and
update all references/imports (including settings-related code that currently
depends on ui::constants::MOSTRO_STAGING_PUBKEY) to import it from the new
shared module so UI code still compiles but bootstrap/settings code no longer
depends on the UI layer.
src/settings.rs (1)

204-227: Remove unused npub computation.

The _npub variable is computed via to_bech32() in both branches but never used. This wastes CPU cycles and allocations. Simplify to only derive nsec.

Proposed fix
-    // On first launch, derive the user `nsec` from the database identity/index-0 key
-    // (generated from the mnemonic stored in `users`), so DB keys and settings match.
-    let (nsec, _npub) =
-        if let Some(identity) = identity_keys {
-            let sk = identity.secret_key();
-            let nsec = sk.to_bech32().map_err(|e| {
-                anyhow::anyhow!("Failed to encode identity Nostr secret key: {}", e)
-            })?;
-            let npub = identity.public_key().to_bech32().map_err(|e| {
-                anyhow::anyhow!("Failed to encode identity Nostr public key: {}", e)
-            })?;
-            (nsec, npub)
-        } else {
-            // Fallback: preserve older behavior if identity keys aren't provided.
-            let keys = Keys::generate();
-            let sk = keys.secret_key();
-            let nsec = sk.to_bech32().map_err(|e| {
-                anyhow::anyhow!("Failed to encode generated Nostr secret key: {}", e)
-            })?;
-            let npub = keys.public_key().to_bech32().map_err(|e| {
-                anyhow::anyhow!("Failed to encode generated Nostr public key: {}", e)
-            })?;
-            (nsec, npub)
-        };
+    // On first launch, derive the user `nsec` from the database identity/index-0 key
+    // (generated from the mnemonic stored in `users`), so DB keys and settings match.
+    let nsec = if let Some(identity) = identity_keys {
+        identity.secret_key().to_bech32().map_err(|e| {
+            anyhow::anyhow!("Failed to encode identity Nostr secret key: {}", e)
+        })?
+    } else {
+        // Fallback: preserve older behavior if identity keys aren't provided.
+        Keys::generate().secret_key().to_bech32().map_err(|e| {
+            anyhow::anyhow!("Failed to encode generated Nostr secret key: {}", e)
+        })?
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/settings.rs` around lines 204 - 227, The code is computing an unused
`_npub` (via `to_bech32()` on `identity.public_key()` and `keys.public_key()`)
which wastes work; update the initialization that currently binds `(nsec,
_npub)` to only derive and return `nsec` and remove all `to_bech32()` calls for
public keys. Specifically, in the branches that use `identity_keys` (where
`identity.secret_key()` is converted to `nsec`) and the fallback that calls
`Keys::generate()` (where `keys.secret_key()` is converted to `nsec`), drop the
`npub`/`_npub` computation and simplify the binding to just `let nsec = ...` so
only `nsec` is produced.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@README.md`:
- Around line 35-43: Clarify the precedence and which file Mostrix reads/writes
after first run by adding an explicit rule: if a settings.toml is present next
to the executable (the "colocated settings.toml"), Mostrix uses and updates that
file; otherwise it uses and updates ~/.mostrix/settings.toml. Reference the two
symbols settings.toml (colocated) and ~/.mostrix/settings.toml in the same
paragraph so operators know which file takes precedence and which file to edit.

In `@src/main.rs`:
- Around line 205-208: The code currently drops errors from User::get(&pool)
when init.did_generate_new_settings_file is true, so the first-run backup flow
can be skipped; change the if-let to explicitly handle the Err case: call
User::get(&pool).await with a match (or try/if let_err), and on Ok(user) set
app.mode = UiMode::BackupNewKeys(user.mnemonic), but on Err log the error and
still transition into the backup flow (e.g., set app.mode to a backup/error UI
or BackupNewKeys with a safe fallback) so the mnemonic backup path is not
silently skipped; update the block around init.did_generate_new_settings_file,
User::get, and app.mode accordingly.

In `@src/models.rs`:
- Around line 67-69: The delete_all call is non-atomic and can leave the users
table empty if the subsequent insert (from User::new in
src/ui/key_handler/enter_handlers.rs) fails; change this to an atomic replace by
performing the DELETE and INSERT inside a single sqlx transaction. Implement
either a new API like User::replace_all_atomic(pool: &SqlitePool, new_user:
&User) that begins a sqlx::Transaction, runs the DELETE and then the INSERT(s)
and commits (rolling back on error), or change delete_all to accept a &mut
sqlx::Transaction so the handler can start a transaction, call delete_all(&mut
tx) and then do the User insert(s) before committing; ensure errors propagate so
failures rollback and identity is not lost.

In `@src/ui/app_state.rs`:
- Around line 40-43: UiMode::BackupNewKeys currently stores mnemonic as an
ordinary String (and UiMode derives Clone), which allows sensitive seed words to
be duplicated in memory; replace the String variant with a zeroizing secret type
(e.g., zeroize::Zeroizing<String> or secrecy::SecretString) in the UiMode enum
(BackupNewKeys) and update any code that constructs or reads this variant to use
the secret wrapper, ensuring the enum's Clone/Copy behavior is adjusted so
secrets are not trivially cloned (either avoid deriving Clone for UiMode or
implement Clone to forbid/clamp cloning of secret variants) and that secrets are
explicitly zeroized/dropped when done.

In `@src/ui/generate_keys_popup.rs`:
- Around line 59-93: The comment says wrapping must be disabled but the mnemonic
Paragraph enables it; remove the .wrap(...) call on the Paragraph created for
mnemonic_lines (the Paragraph::new(mnemonic_lines) rendered to chunks[3]) so it
renders without wrapping, ensuring mnemonic_lines is displayed as a single
space-separated line and preventing clipping artefacts.

In `@src/ui/key_handler/enter_handlers.rs`:
- Around line 197-231: The code currently spawns a background task that deletes
the old user, writes a new User (User::delete_all, User::new) and updates
settings (crate::settings::load_settings_from_disk / save_settings) but
immediately sets app.mode = UiMode::BackupNewKeys(mnemonic) before those
operations complete; change this so the rotation is committed before showing the
backup: perform the DB/settings work synchronously (await the operations) or
await the tokio::spawn JoinHandle and check its result, only setting app.mode =
UiMode::BackupNewKeys(mnemonic) if User::new and save_settings succeeded; also
avoid deleting the existing user before ensuring the new row is created (create
new first or wrap in a transaction) and log/propagate failures so the UI does
not show the mnemonic on failure.
- Around line 234-237: The current branch handling UiMode::BackupNewKeys only
resets app.mode to default_mode but leaves the in-memory Client and admin key
material unchanged, allowing continued operation under stale identity; fix by
enforcing a required restart or replacing in-memory credentials: after closing
the backup popup (UiMode::BackupNewKeys), either (A) set a restart-required flag
on the app (e.g., app.must_restart or app.restart_required) and ensure all
action handlers check this flag and block normal operations until a restart, or
(B) trigger an immediate shutdown/quit flow (e.g., call the app shutdown/restart
routine) so the process exits and restarts, or (C) reload/replace the in-memory
Client/admin key material from disk (refresh the Client instance) before
returning to default_mode; pick one approach and update the
UiMode::BackupNewKeys handling and any central action dispatchers to respect the
restart_required flag or shutdown trigger so the user cannot continue using
stale credentials.

---

Nitpick comments:
In `@src/settings.rs`:
- Around line 204-227: The code is computing an unused `_npub` (via
`to_bech32()` on `identity.public_key()` and `keys.public_key()`) which wastes
work; update the initialization that currently binds `(nsec, _npub)` to only
derive and return `nsec` and remove all `to_bech32()` calls for public keys.
Specifically, in the branches that use `identity_keys` (where
`identity.secret_key()` is converted to `nsec`) and the fallback that calls
`Keys::generate()` (where `keys.secret_key()` is converted to `nsec`), drop the
`npub`/`_npub` computation and simplify the binding to just `let nsec = ...` so
only `nsec` is produced.

In `@src/ui/constants.rs`:
- Around line 5-6: MOSTRO_STAGING_PUBKEY is a runtime/bootstrap default living
in the UI module which creates cross-layer coupling; move the constant out of
src/ui/constants.rs into a new or existing shared settings/constants module
(e.g., src/settings/constants.rs or add to src/settings.rs), export it with the
same name (MOSTRO_STAGING_PUBKEY) and update all references/imports (including
settings-related code that currently depends on
ui::constants::MOSTRO_STAGING_PUBKEY) to import it from the new shared module so
UI code still compiles but bootstrap/settings code no longer depends on the UI
layer.

In `@src/ui/key_handler/esc_handlers.rs`:
- Around line 121-124: The ESC handler for UiMode::BackupNewKeys currently
closes the mnemonic popup immediately; instead require an explicit confirmation
before dismissing—modify the branch handling UiMode::BackupNewKeys in
esc_handlers.rs to, on first ESC press, transition to a new confirmation state
(e.g., UiMode::ConfirmMnemonicDismiss or set a flag on the app like
app.awaiting_mnemonic_confirm) and only set app.mode = default_mode when the
user explicitly confirms (e.g., via a confirm action or a second ESC mapped to
confirm in the new state); update related UI rendering and input handling to
show the “I saved my mnemonic” confirmation prompt and to handle the
confirm/cancel actions accordingly so accidental dismissal is prevented.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 435e4e68-845b-43cb-abfb-4efd024e1be3

📥 Commits

Reviewing files that changed from the base of the PR and between 380f662 and d5aa179.

📒 Files selected for processing (19)
  • README.md
  • docs/KEY_MANAGEMENT.md
  • docs/SETTINGS_ANALYSIS.md
  • docs/STARTUP_AND_CONFIG.md
  • docs/TUI_INTERFACE.md
  • src/main.rs
  • src/models.rs
  • src/settings.rs
  • src/ui/admin_key_confirm.rs
  • src/ui/app_state.rs
  • src/ui/constants.rs
  • src/ui/draw.rs
  • src/ui/generate_keys_popup.rs
  • src/ui/key_handler/enter_handlers.rs
  • src/ui/key_handler/esc_handlers.rs
  • src/ui/key_handler/mod.rs
  • src/ui/key_handler/navigation.rs
  • src/ui/mod.rs
  • src/ui/tabs/settings_tab.rs

Comment thread README.md Outdated
Comment thread src/main.rs
Comment thread src/models.rs
Comment thread src/ui/app_state.rs
Comment thread src/ui/generate_keys_popup.rs Outdated
Comment thread src/ui/key_handler/enter_handlers.rs Outdated
Comment thread src/ui/key_handler/enter_handlers.rs Outdated
Comment thread src/ui/generate_keys_popup.rs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
src/main.rs (1)

505-535: ⚠️ Potential issue | 🟠 Major

Refresh admin key material during the soft reload.

This reload only rebuilds client from nsec_privkey. admin_chat_keys and admin_chat_pubkey are computed once before the loop and never refreshed here, so after Generate New Keys in admin mode the dispute/chat flows keep using the old admin key until a full process restart.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 505 - 535, The reload path only rebuilds client
from nsec_privkey but doesn't refresh admin_chat_keys and admin_chat_pubkey, so
recompute those values when you perform the soft reload (the same place you
create new_client and call PublicKey::from_str on
latest_settings.mostro_pubkey); after creating new_client and before spawning
message_listener_handle, derive admin_chat_keys and admin_chat_pubkey again from
the updated nsec_privkey (and any config used originally) and assign them to the
same variables used by the running app so subsequent admin/chat flows use the
new keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.rs`:
- Around line 486-503: The abort of message_listener_handle currently happens
before validating and fully constructing the replacement session, which can
leave the app without a listener on error; change the flow in the pending key
reload path so you first call crate::settings::load_settings_from_disk(), parse
latest_settings.nsec_privkey into Keys, construct
Client::new(new_identity_keys), and await new_client.add_relay(...) for all
relays to validate the new session successfully, and only then call
message_listener_handle.abort() and swap to the new runtime; alternatively, if
you prefer to keep the abort earlier, ensure you restart the previous listener
on every error path (where new_client.add_relay fails) and set app.mode
(UiMode::OperationResult(OperationResult::Error(...))) accordingly to avoid
leaving the app without a listener.

In `@src/ui/generate_keys_popup.rs`:
- Around line 59-92: The mnemonic_lines is rendered as one fixed-length row (via
Layout/Constraint::Length and Paragraph::new) which causes clipping for long
12-word seeds; update the UI code that builds mnemonic_lines and the
Layout/chunks usage in generate_keys_popup.rs to split the mnemonic into
multiple rows (e.g., two rows of 6 words each) or compute the required height
from mnemonic.split_whitespace().len() and use Constraint::Length dynamically
instead of the hard-coded Constraint::Length(2) for the mnemonic chunk; then
render multiple Paragraphs or a single Paragraph with newline-separated word
groups so the full seed is always visible (adjust chunks index references such
as chunks[3] accordingly).

In `@src/ui/key_handler/async_tasks.rs`:
- Around line 167-178: The current flow calls
crate::models::User::replace_all_atomic(...) which commits the new mnemonic to
the DB before crate::settings::save_settings(&s) updates the settings file, so a
file-write failure leaves DB and settings.toml out of sync; change the sequence
to guarantee atomicity across both stores by using a coordinated commit: open a
DB transaction instead of calling replace_all_atomic (or add a new
replace_all_transaction API), write the new settings to a temporary file (not
overwriting settings.toml), then if the temp file write succeeds commit the DB
transaction and atomically rename the temp file to replace settings.toml; if any
step fails rollback the DB transaction and remove the temp file. Update the code
around User::replace_all_atomic, load_settings_from_disk, and save_settings to
implement this transactional pattern (or add helper functions like
replace_all_with_settings_commit) so DB commit only happens after file is safely
written and rename completed.
- Around line 19-30: Background tasks currently call SETTINGS.get() (OnceLock)
and therefore keep using the old snapshot after a soft-reload; instead stop
reading SETTINGS inside the task and use the runtime-provided settings: change
the task signature to accept a &Settings (or a SettingsRef) passed in by the
reloader/context, and propagate that instance into calls like
crate::util::send_new_order and any other calls that currently use
SETTINGS.get(); update code paths in async_tasks.rs (the block that reads
SETTINGS and the similar block at lines 52-66) to remove SETTINGS.get() lookups,
use the passed settings reference, and send errors via result_tx as before so
the task uses the currently swapped settings.

In `@src/ui/key_handler/enter_handlers.rs`:
- Around line 166-171: The ConfirmGenerateNewKeys popup
(UiMode::ConfirmGenerateNewKeys) isn't wired to the global 'y'/'n' shortcuts;
update the enter key handler so that when app.mode is
UiMode::ConfirmGenerateNewKeys you call the same confirmation helpers as
Enter/Esc do—invoke handle_confirm_key(...) for the YES case and
handle_cancel_key(...) for the NO case (or forward the selected_button boolean
into those functions) so 'y' and 'n' trigger the same code path as Enter/Esc; do
the same change for the other occurrence around the UiMode match at the 663-678
region so both instances reference handle_confirm_key and handle_cancel_key from
confirmation.rs.

---

Duplicate comments:
In `@src/main.rs`:
- Around line 505-535: The reload path only rebuilds client from nsec_privkey
but doesn't refresh admin_chat_keys and admin_chat_pubkey, so recompute those
values when you perform the soft reload (the same place you create new_client
and call PublicKey::from_str on latest_settings.mostro_pubkey); after creating
new_client and before spawning message_listener_handle, derive admin_chat_keys
and admin_chat_pubkey again from the updated nsec_privkey (and any config used
originally) and assign them to the same variables used by the running app so
subsequent admin/chat flows use the new keys.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eec9c88b-1208-4859-8e1a-b40f11fe37b2

📥 Commits

Reviewing files that changed from the base of the PR and between d5aa179 and b7c5dff.

📒 Files selected for processing (12)
  • README.md
  • src/main.rs
  • src/models.rs
  • src/ui/app_state.rs
  • src/ui/draw.rs
  • src/ui/generate_keys_popup.rs
  • src/ui/key_handler/async_tasks.rs
  • src/ui/key_handler/confirmation.rs
  • src/ui/key_handler/enter_handlers.rs
  • src/ui/key_handler/esc_handlers.rs
  • src/ui/key_handler/mod.rs
  • src/ui/key_handler/user_handlers.rs
✅ Files skipped from review due to trivial changes (2)
  • src/ui/draw.rs
  • README.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/models.rs
  • src/ui/app_state.rs

Comment thread src/main.rs Outdated
Comment thread src/ui/generate_keys_popup.rs Outdated
Comment thread src/ui/key_handler/async_tasks.rs Outdated
Comment thread src/ui/key_handler/async_tasks.rs Outdated
Comment thread src/ui/key_handler/enter_handlers.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

♻️ Duplicate comments (4)
src/main.rs (2)

491-568: ⚠️ Potential issue | 🟠 Major

Validate the replacement session before aborting the listener.

Line 493 aborts the only listen_for_order_messages task before the new settings, keys, relays, and Mostro pubkey are all known-good. Any failure later in this branch leaves the old runtime without a message listener.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 491 - 568, Do not abort message_listener_handle
before confirming the replacement session is valid: first call
crate::settings::load_settings_from_disk(), parse latest_settings.nsec_privkey
into Keys, instantiate Client::new, add relays via new_client.add_relay, parse
latest_settings.mostro_pubkey with PublicKey::from_str and (optionally) call
new_client.connect(), and spawn a new listen_for_order_messages task to verify
it starts successfully; only after all of those succeed should you abort the old
message_listener_handle, swap in client, mostro_pubkey, message_listener_handle,
and clear_runtime_session_state, leaving the existing listener running on any
failure so the old runtime remains operational.

196-207: ⚠️ Potential issue | 🟠 Major

Admin key reload is still incomplete.

This soft-reload path only reparses latest_settings.nsec_privkey and swaps client. admin_chat_keys / admin_chat_pubkey are computed once at startup and never refreshed, so the admin “Generate New Keys” flow still keeps admin chat send/fetch on stale credentials after the “Keys reloaded” success state.

Also applies to: 295-299, 497-541

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 196 - 207, The admin key reload path only reparses
latest_settings.nsec_privkey and swaps client, but never recomputes
admin_keys/admin_chat_pubkey so the admin chat still uses stale credentials;
update the reload logic to re-run Keys::parse on settings.admin_privkey and
recompute admin_keys and admin_chat_pubkey (using Keys::public_key) whenever
settings are reloaded and the client is swapped, and ensure the new
admin_keys/admin_chat_pubkey are stored/assigned to the same location the rest
of the code reads from (the same variable/field that was initialized at
startup).
src/ui/key_handler/async_tasks.rs (2)

222-233: ⚠️ Potential issue | 🔴 Critical

User key rotation can still commit only half of the new identity.

Per src/models.rs:72-103, User::replace_all_atomic() commits the new mnemonic to SQLite before this code reaches save_settings(). If the settings write fails, the DB and settings.toml diverge immediately, which breaks the new sync guarantee.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/key_handler/async_tasks.rs` around lines 222 - 233, The current flow
may leave DB and settings.toml inconsistent because
User::replace_all_atomic(mnemonic, &pool) writes to SQLite before
save_settings(&s) completes; make the update atomic by writing settings and DB
in a rollback-safe sequence: load current settings with
load_settings_from_disk(), keep a copy of the original settings, update
s.nsec_privkey/admin_privkey and call save_settings(&s) first, then call
User::replace_all_atomic(mnemonic.clone(), &pool); if replace_all_atomic fails,
restore the original settings via save_settings(&original_s) to revert the file;
use the same local variables (s, original_s, mnemonic, derived_nsec,
is_user_mode) and functions (load_settings_from_disk, save_settings,
User::replace_all_atomic) to implement this rollback pattern.

66-95: ⚠️ Potential issue | 🟠 Major

These order tasks still read the pre-reload settings snapshot.

The soft-reload path in src/main.rs:491-541 only swaps the local client and disk-backed settings; SETTINGS never changes. These spawners still pull from that OnceLock, so any setting-dependent behavior in send_new_order / take_order will keep using the original snapshot after a key reload.

Also applies to: 97-137

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/key_handler/async_tasks.rs` around lines 66 - 95,
spawn_send_new_order_task (and the sibling spawn for take_order) currently reads
the immutable OnceLock SETTINGS inside the spawned task, so tasks keep using the
pre-reload snapshot; change this so the task obtains the up-to-date settings at
execution time instead of calling SETTINGS.get(): either (A) add a new parameter
to spawn_send_new_order_task/spawn_take_order_task that accepts a
settings-provider (e.g., Arc<RwLock<Settings>> or a closure) which main updates
on soft-reload and use that inside the async block when calling
send_new_order/take_order, or (B) remove the SETTINGS.get() call from the task
and call the disk-backed loader function that main swaps (e.g.,
crate::config::load_current_settings or equivalent) inside the async block so
send_new_order and take_order receive the current settings; update both
spawn_send_new_order_task and the 97-137 sibling accordingly and ensure you pass
the obtained settings into crate::util::send_new_order / take_order.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/main.rs`:
- Around line 491-568: Do not abort message_listener_handle before confirming
the replacement session is valid: first call
crate::settings::load_settings_from_disk(), parse latest_settings.nsec_privkey
into Keys, instantiate Client::new, add relays via new_client.add_relay, parse
latest_settings.mostro_pubkey with PublicKey::from_str and (optionally) call
new_client.connect(), and spawn a new listen_for_order_messages task to verify
it starts successfully; only after all of those succeed should you abort the old
message_listener_handle, swap in client, mostro_pubkey, message_listener_handle,
and clear_runtime_session_state, leaving the existing listener running on any
failure so the old runtime remains operational.
- Around line 196-207: The admin key reload path only reparses
latest_settings.nsec_privkey and swaps client, but never recomputes
admin_keys/admin_chat_pubkey so the admin chat still uses stale credentials;
update the reload logic to re-run Keys::parse on settings.admin_privkey and
recompute admin_keys and admin_chat_pubkey (using Keys::public_key) whenever
settings are reloaded and the client is swapped, and ensure the new
admin_keys/admin_chat_pubkey are stored/assigned to the same location the rest
of the code reads from (the same variable/field that was initialized at
startup).

In `@src/ui/key_handler/async_tasks.rs`:
- Around line 222-233: The current flow may leave DB and settings.toml
inconsistent because User::replace_all_atomic(mnemonic, &pool) writes to SQLite
before save_settings(&s) completes; make the update atomic by writing settings
and DB in a rollback-safe sequence: load current settings with
load_settings_from_disk(), keep a copy of the original settings, update
s.nsec_privkey/admin_privkey and call save_settings(&s) first, then call
User::replace_all_atomic(mnemonic.clone(), &pool); if replace_all_atomic fails,
restore the original settings via save_settings(&original_s) to revert the file;
use the same local variables (s, original_s, mnemonic, derived_nsec,
is_user_mode) and functions (load_settings_from_disk, save_settings,
User::replace_all_atomic) to implement this rollback pattern.
- Around line 66-95: spawn_send_new_order_task (and the sibling spawn for
take_order) currently reads the immutable OnceLock SETTINGS inside the spawned
task, so tasks keep using the pre-reload snapshot; change this so the task
obtains the up-to-date settings at execution time instead of calling
SETTINGS.get(): either (A) add a new parameter to
spawn_send_new_order_task/spawn_take_order_task that accepts a settings-provider
(e.g., Arc<RwLock<Settings>> or a closure) which main updates on soft-reload and
use that inside the async block when calling send_new_order/take_order, or (B)
remove the SETTINGS.get() call from the task and call the disk-backed loader
function that main swaps (e.g., crate::config::load_current_settings or
equivalent) inside the async block so send_new_order and take_order receive the
current settings; update both spawn_send_new_order_task and the 97-137 sibling
accordingly and ensure you pass the obtained settings into
crate::util::send_new_order / take_order.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 90bdff55-855e-4941-bad3-cc0513573a6f

📥 Commits

Reviewing files that changed from the base of the PR and between b7c5dff and b2ed8f9.

📒 Files selected for processing (7)
  • README.md
  • src/main.rs
  • src/settings.rs
  • src/ui/key_handler/async_tasks.rs
  • src/ui/key_handler/enter_handlers.rs
  • src/ui/key_handler/mod.rs
  • src/ui/tabs/settings_tab.rs
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/ui/tabs/settings_tab.rs
  • src/ui/key_handler/mod.rs
  • README.md

…snapshot; they always use current runtime settings loaded from disk at action time.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/ui/key_handler/enter_handlers.rs (1)

405-410: ⚠️ Potential issue | 🟡 Minor

Don't use the settings helper's "quit app" path for a pubkey parse error.

handle_enter_settings_mode() uses false to mean "exit Mostrix". If this parse ever disagrees with validate_mostro_pubkey, Line 409 will terminate the app instead of showing an error popup.

Suggested fix
                 let new_pubkey = match PublicKey::from_str(&key_string) {
                     Ok(pk) => pk,
                     Err(e) => {
-                        log::error!("Invalid pubkey after confirmation: {}", e);
-                        return false;
+                        app.mode = UiMode::OperationResult(OperationResult::Error(format!(
+                            "Invalid Mostro pubkey after confirmation: {}",
+                            e
+                        )));
+                        return true;
                     }
                 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/key_handler/enter_handlers.rs` around lines 405 - 410, The code
currently treats a PublicKey::from_str parse error inside
handle_enter_settings_mode() as a quit signal by returning false; instead, on
Err(e) from PublicKey::from_str(&key_string) you should surface the parse error
via the settings UI error popup (the same UX path used by
validate_mostro_pubkey) and return true to remain in settings, not false; locate
the match on PublicKey::from_str in handle_enter_settings_mode(), replace the
log::error + return false branch with logic that logs the error, invokes the
settings error-display helper (or the same popup routine used by
validate_mostro_pubkey), and returns true so the app does not exit.
♻️ Duplicate comments (1)
src/main.rs (1)

296-300: ⚠️ Potential issue | 🟠 Major

Reload the in-memory admin signer too.

The new Admin “Generate New Keys” flow persists admin_privkey, but admin_chat_keys is still computed once at startup and this reload branch only rebuilds the user Client from nsec_privkey. After an admin rotation, chat sends and admin-message classification keep using the old admin key until the process is restarted. Reparse latest_settings.admin_privkey here and refresh admin_chat_pubkey, or force a full restart for admin-key rotations.

Also applies to: 493-580

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.rs` around lines 296 - 300, The admin key rotation code currently
only rebuilds the user Client from nsec_privkey but leaves admin_chat_keys and
admin_chat_pubkey stale; update the reload path to reparse
latest_settings.admin_privkey and recreate admin_chat_keys (and
derive/admin_chat_pubkey) when app.user_role == UserRole::Admin instead of
reusing the old admin_keys, or alternatively trigger a full restart; locate the
variables admin_chat_keys, admin_keys, latest_settings.admin_privkey and
admin_chat_pubkey in the reload logic and ensure admin_chat_keys is reassigned
from the new admin_privkey so chat sends and admin-message classification use
the rotated key.
🧹 Nitpick comments (1)
src/ui/key_handler/user_handlers.rs (1)

81-83: TODO: Invoice input for buy orders is pending.

The comment indicates that invoice handling for buy orders (taking sell) is not yet implemented.

Would you like me to help design the invoice input flow for buy orders, or open an issue to track this task?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/key_handler/user_handlers.rs` around lines 81 - 83, The code leaves
invoice handling unimplemented for buy orders by setting let invoice = None; —
implement an input flow so buy (taking sell) orders prompt for an invoice and
populate the invoice variable instead of leaving it None: detect the order side
in the user handlers in user_handlers.rs (where invoice is declared), if the
order is a buy ask the user for invoice text (or select a file) via the existing
input/prompt utilities, validate non-empty input, convert it to the expected
invoice type and assign it to invoice; if validation fails, return an error or
re-prompt. If you prefer tracking rather than implementing now, create an issue
referencing this TODO and link it in the comment above the invoice variable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/ui/key_handler/enter_handlers.rs`:
- Around line 412-421: The handler updates ctx.current_mostro_pubkey but leaves
a stale by-value ctx.mostro_pubkey used for admin/dispute actions; fix by
removing the by-value mostro_pubkey from the EnterKeyContext (or stop using it)
and make all admin/dispute pathways read the live key from
ctx.current_mostro_pubkey.lock() at dispatch time (update any usages of
ctx.mostro_pubkey to acquire the mutex and clone the key before sending), and
keep spawn_refresh_mostro_info_task unchanged to use the new_pubkey.

In `@src/util/order_utils/fetch_scheduler.rs`:
- Around line 42-45: The scheduler currently captures and pins the original
Client in the spawned order/dispute loops so soft reloads can't replace it;
change start_fetch_scheduler to accept a swappable client (e.g.,
Arc<Mutex<Client>> or a client-factory closure) instead of consuming a concrete
Client and update the internal order/dispute loops to read the client on each
poll from that shared handle (or call the factory) so they use the new runtime
session after reload; alternatively, keep the signature but modify
FetchSchedulerResult to include explicit abort/restart controls (e.g.,
AbortHandle/RestartHandle) that main can call to stop and recreate the loops;
update all references to Client clones in the order/dispute loop spawn sites and
ensure current_mostro_pubkey usage remains Arc<Mutex<PublicKey>> as before.

---

Outside diff comments:
In `@src/ui/key_handler/enter_handlers.rs`:
- Around line 405-410: The code currently treats a PublicKey::from_str parse
error inside handle_enter_settings_mode() as a quit signal by returning false;
instead, on Err(e) from PublicKey::from_str(&key_string) you should surface the
parse error via the settings UI error popup (the same UX path used by
validate_mostro_pubkey) and return true to remain in settings, not false; locate
the match on PublicKey::from_str in handle_enter_settings_mode(), replace the
log::error + return false branch with logic that logs the error, invokes the
settings error-display helper (or the same popup routine used by
validate_mostro_pubkey), and returns true so the app does not exit.

---

Duplicate comments:
In `@src/main.rs`:
- Around line 296-300: The admin key rotation code currently only rebuilds the
user Client from nsec_privkey but leaves admin_chat_keys and admin_chat_pubkey
stale; update the reload path to reparse latest_settings.admin_privkey and
recreate admin_chat_keys (and derive/admin_chat_pubkey) when app.user_role ==
UserRole::Admin instead of reusing the old admin_keys, or alternatively trigger
a full restart; locate the variables admin_chat_keys, admin_keys,
latest_settings.admin_privkey and admin_chat_pubkey in the reload logic and
ensure admin_chat_keys is reassigned from the new admin_privkey so chat sends
and admin-message classification use the rotated key.

---

Nitpick comments:
In `@src/ui/key_handler/user_handlers.rs`:
- Around line 81-83: The code leaves invoice handling unimplemented for buy
orders by setting let invoice = None; — implement an input flow so buy (taking
sell) orders prompt for an invoice and populate the invoice variable instead of
leaving it None: detect the order side in the user handlers in user_handlers.rs
(where invoice is declared), if the order is a buy ask the user for invoice text
(or select a file) via the existing input/prompt utilities, validate non-empty
input, convert it to the expected invoice type and assign it to invoice; if
validation fails, return an error or re-prompt. If you prefer tracking rather
than implementing now, create an issue referencing this TODO and link it in the
comment above the invoice variable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b9832d55-35b5-4beb-9a4e-26a9809ec88b

📥 Commits

Reviewing files that changed from the base of the PR and between b2ed8f9 and a87a206.

📒 Files selected for processing (8)
  • src/main.rs
  • src/ui/generate_keys_popup.rs
  • src/ui/key_handler/async_tasks.rs
  • src/ui/key_handler/confirmation.rs
  • src/ui/key_handler/enter_handlers.rs
  • src/ui/key_handler/mod.rs
  • src/ui/key_handler/user_handlers.rs
  • src/util/order_utils/fetch_scheduler.rs
✅ Files skipped from review due to trivial changes (1)
  • src/ui/key_handler/async_tasks.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/ui/key_handler/confirmation.rs
  • src/ui/key_handler/mod.rs

Comment thread src/ui/key_handler/enter_handlers.rs
Comment thread src/util/order_utils/fetch_scheduler.rs

@mostronatorcoder mostronatorcoder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review ✅

Well-structured implementation for key bootstrap synchronization and key regeneration flow.

Security Positives

Aspect Implementation
Memory safety Zeroizing<String> for mnemonics ✅
Atomic DB update replace_all_in_tx uses SQL transaction ✅
Atomic file write Write to .tmp-{nanos}, then fs::rename
Rollback on failure If tx.commit() fails, removes tmp file ✅

Architecture

  1. First-launch syncinit_settings() now receives identity keys from DB, ensuring settings.toml nsec_privkey matches the DB mnemonic-derived key

  2. Key rotation flow:

    • User mode: Updates both DB (atomic tx) and settings.toml (atomic rename)
    • Admin mode: Only updates admin_privkey in settings.toml
    • Shows backup popup with mnemonic before allowing restart
  3. Channel-based async tasks — Clean separation with AppChannels struct

Documentation ✅

Updated STARTUP_AND_CONFIG.md, KEY_MANAGEMENT.md, SETTINGS_ANALYSIS.md, TUI_INTERFACE.md to reflect new behaviors.

CI ✅

  • cargo fmt
  • cargo clippy
  • cargo test
  • cargo build
  • MSRV 1.90.0 ✅

Summary

LGTM. The implementation correctly handles the security-sensitive key regeneration flow with proper atomicity guarantees and memory cleanup.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/ui/key_handler/admin_handlers.rs (1)

25-30: Silent failure on mutex poison leaves UI in waiting state.

If current_mostro_pubkey.lock() fails (poisoned mutex), the function returns early without updating app.mode, leaving the UI stuck in WaitingTakeDispute with no error feedback to the user.

Consider setting an error mode before returning:

🛠️ Suggested improvement
     let current_mostro_pubkey = if let Ok(active_pubkey) = ctx.current_mostro_pubkey.lock() {
         *active_pubkey
     } else {
         log::warn!("Failed to get runtime Mostro pubkey after confirmation");
+        app.mode = UiMode::OperationResult(OperationResult::Error(
+            "Internal error: failed to access runtime state".to_string(),
+        ));
         return;
     };

The same pattern applies to execute_add_solver_action (lines 74-79) and execute_finalize_dispute_action (lines 110-115).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/ui/key_handler/admin_handlers.rs` around lines 25 - 30, When the mutex
lock on ctx.current_mostro_pubkey fails the code currently logs a warning and
returns, leaving the UI stuck in WaitingTakeDispute; change the error path to
update the UI state before returning by setting app.mode to an error state (e.g.
AppMode::Error or a suitable error variant) and populate any available error
message field so the user sees feedback, then return; apply the same change to
the mutex-failure branches in execute_add_solver_action and
execute_finalize_dispute_action where current_mostro_pubkey.lock() is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/ui/key_handler/admin_handlers.rs`:
- Around line 25-30: When the mutex lock on ctx.current_mostro_pubkey fails the
code currently logs a warning and returns, leaving the UI stuck in
WaitingTakeDispute; change the error path to update the UI state before
returning by setting app.mode to an error state (e.g. AppMode::Error or a
suitable error variant) and populate any available error message field so the
user sees feedback, then return; apply the same change to the mutex-failure
branches in execute_add_solver_action and execute_finalize_dispute_action where
current_mostro_pubkey.lock() is used.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ee891390-03c0-4cad-9f9e-97a1a2034d64

📥 Commits

Reviewing files that changed from the base of the PR and between a87a206 and d8a1eb8.

📒 Files selected for processing (10)
  • src/main.rs
  • src/models.rs
  • src/ui/key_handler/admin_handlers.rs
  • src/ui/key_handler/async_tasks.rs
  • src/ui/key_handler/confirmation.rs
  • src/ui/key_handler/enter_handlers.rs
  • src/ui/key_handler/mod.rs
  • src/util/order_utils/fetch_scheduler.rs
  • src/util/order_utils/mod.rs
  • src/util/order_utils/send_new_order.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/util/order_utils/fetch_scheduler.rs
  • src/ui/key_handler/confirmation.rs
  • src/models.rs
  • src/ui/key_handler/mod.rs

@grunch grunch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@grunch grunch merged commit c899ed8 into main Mar 21, 2026
11 checks passed
@grunch grunch deleted the feat/key-regeneration-first-launch-backup branch March 21, 2026 01:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants