Skip to content

Add nsec keys tab and rename shares to FROST shares#285

Merged
kwsantiago merged 7 commits intomainfrom
nsec-tab
Feb 27, 2026
Merged

Add nsec keys tab and rename shares to FROST shares#285
kwsantiago merged 7 commits intomainfrom
nsec-tab

Conversation

@wksantiago
Copy link
Contributor

@wksantiago wksantiago commented Feb 26, 2026

Summary

  • Add dedicated Nsec Keys tab showing imported nsec keys with expandable cards
  • Rename Shares sidebar tab to FROST Shares for clarity
  • Nsec import now navigates to Nsec Keys screen instead of FROST Shares
  • ExportNcryptsec highlights Nsec Keys tab

Closes #284

Summary by CodeRabbit

  • New Features

    • Added NSEC Keys management UI in main navigation: view, activate, export, and delete keys with details (created, last-used, signature count) and two-step delete confirmation.
    • Sidebar shows NSEC key count; "Shares" renamed to "FROST Shares".
    • Successful key imports navigate to the NSEC Keys screen and show a success toast.
    • Back/navigation flows improved to return to NSEC Keys from import/export when appropriate.
  • Bug Fixes

    • Ensured scanner/state cleanup when entering/exiting NSEC Keys and refreshed related lists correctly.
  • Chores

    • Unified NPUB truncation and timestamp formatting across key and share views.

@coderabbitai
Copy link

coderabbitai bot commented Feb 26, 2026

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

Adds a new NsecKeys UI and navigation flow: listing, activation, export, two‑step delete; integrates NsecKeys into message routing, sidebar badge, import/delete synchronization, and shared formatting helpers. Public Screen enum gains NsecKeys variant.

Changes

Cohort / File(s) Summary
Core app & messages
keep-desktop/src/app.rs, keep-desktop/src/message.rs
Adds NavigateNsecKeys and Nsec-related Message variants (ToggleNsecKeyDetails, RequestDeleteNsecKey, ConfirmDeleteNsecKey, CancelDeleteNsecKey); routes Nsec messages to new handlers, updates Debug arms, and wires new App state fields (import_return_to_nsec, cached counts).
Nsec keys UI module
keep-desktop/src/screen/nsec_keys.rs
New public UI: NsecKeyEntry and NsecKeysScreen with constructors and view_content; implements list rendering, expand/collapse, activate/export actions, and two-step delete confirmation.
Screen routing & helpers
keep-desktop/src/screen/mod.rs, keep-desktop/src/screen/layout.rs
Adds pub mod nsec_keys, Screen::NsecKeys(...) variant, format_timestamp and truncate_npub helpers; extends view/with_sidebar signatures to accept nsec_count; adds NavItem::NsecKeys and sidebar badge; renames "Shares" label to "FROST Shares".
Related screen tweaks
keep-desktop/src/screen/export_ncryptsec.rs, keep-desktop/src/screen/shares.rs
Replaced inline NPUB truncation with truncate_npub; moved timestamp formatting to shared format_timestamp; adjusted ncryptsec truncation logic.
State & lifecycle
keep-desktop/src/app.rs (continued)
Tracks and invalidates cached share/nsec counts on lock/clear and on related transitions; records return-to-Nsec flag for import/back navigation; implements deletion flow that cleans vault/relay and refreshes UI.

Sequence Diagram(s)

sequenceDiagram
  participant User as User
  participant UI as UI (Iced)
  participant App as App (keep-desktop::app)
  participant Vault as Vault (key storage)
  participant FS as Filesystem

  rect rgba(200,230,255,0.5)
    User->>UI: Click "Nsec Keys" (NavigateNsecKeys)
    UI->>App: Message(NavigateNsecKeys)
    App->>App: stop scanner, reset feedback, clear caches
    App->>Vault: Read key records
    Vault-->>App: KeyRecord list
    App->>UI: Render NsecKeysScreen(keys, active)
  end

  rect rgba(200,255,200,0.5)
    User->>UI: Click "Delete" (RequestDeleteNsecKey)
    UI->>App: Message(RequestDeleteNsecKey)
    App->>UI: Show confirmation row (set delete_confirm)
    User->>UI: Confirm delete (ConfirmDeleteNsecKey)
    UI->>App: Message(ConfirmDeleteNsecKey)
    App->>FS: Remove key files / update configs
    FS-->>App: Success/Err
    App->>Vault: Refresh keys / refresh shares if needed
    Vault-->>App: Updated key list
    App->>UI: Update NsecKeysScreen + Toast(success/err)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop to the keys in a moonlit nook,

NPUBs trimmed tidy — peek at my book.
Activate, export, then a careful delete,
Two hops to confirm before my small feat.
Hooray for keys — soft paws, light beat.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main changes: adding a dedicated Nsec Keys tab and renaming the Shares tab to FROST Shares, which directly aligns with the primary objectives of the PR.
Linked Issues check ✅ Passed The PR fully implements both coding requirements from issue #284: a new Nsec Keys tab with expandable cards displaying imported keys, and renaming the Shares tab to FROST Shares in the navigation.
Out of Scope Changes check ✅ Passed All changes are directly related to the linked issue objectives: new Nsec Keys UI component, navigation integration, state management for nsec keys, and label renaming. No unrelated changes detected.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch nsec-tab

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.

@wksantiago wksantiago self-assigned this Feb 26, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
keep-desktop/src/screen/nsec_keys.rs (1)

46-55: Consider extracting truncated_npub to avoid duplication.

This truncation logic is duplicated from Identity::truncated_npub in message.rs (lines 31-40). Consider extracting this into a shared utility function to maintain consistency and reduce duplication.

♻️ Suggested refactor

Create a shared helper function in a common module (e.g., utils.rs or within message.rs):

pub fn truncate_npub(npub: &str) -> String {
    if !npub.is_ascii() || npub.len() <= 20 {
        return npub.to_string();
    }
    format!(
        "{}...{}",
        &npub[..12],
        &npub[npub.len() - 6..]
    )
}

Then use this from both Identity::truncated_npub and NsecKeyEntry::truncated_npub.

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

In `@keep-desktop/src/screen/nsec_keys.rs` around lines 46 - 55, Extract the
duplicated truncation logic into a single helper function (e.g., pub fn
truncate_npub(npub: &str) -> String) placed in a shared module (such as a new
utils.rs or the existing message.rs) and replace both
NsecKeyEntry::truncated_npub and Identity::truncated_npub to call this helper;
ensure the helper uses the same conditions (ascii check and length <= 20) and
the same slicing (first 12 chars + "..." + last 6 chars) so both truncated_npub
implementations delegate to truncate_npub(npub).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@keep-desktop/src/screen/nsec_keys.rs`:
- Around line 46-55: Extract the duplicated truncation logic into a single
helper function (e.g., pub fn truncate_npub(npub: &str) -> String) placed in a
shared module (such as a new utils.rs or the existing message.rs) and replace
both NsecKeyEntry::truncated_npub and Identity::truncated_npub to call this
helper; ensure the helper uses the same conditions (ascii check and length <=
20) and the same slicing (first 12 chars + "..." + last 6 chars) so both
truncated_npub implementations delegate to truncate_npub(npub).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 543691c and 32a1627.

📒 Files selected for processing (5)
  • keep-desktop/src/app.rs
  • keep-desktop/src/message.rs
  • keep-desktop/src/screen/layout.rs
  • keep-desktop/src/screen/mod.rs
  • keep-desktop/src/screen/nsec_keys.rs

@wksantiago
Copy link
Contributor Author

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Feb 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@keep-desktop/src/app.rs`:
- Around line 1874-1891: The match over delete_result currently ignores the None
case, leaving the NsecKeys delete_confirm stale and giving no user feedback when
the vault is locked/unavailable; update the None arm to mirror the Err branch
for the Nsec delete flow: if let Screen::NsecKeys(s) = &mut self.screen {
s.delete_confirm = None; } and call self.set_toast with a clear message (e.g.,
friendly_err for a locked-vault error or a specific "vault locked/unavailable"
message) so users get feedback when delete_result is None; keep the existing
Some(Ok(())) behavior that removes relay files, refreshes shares, and shows the
success toast.
- Around line 1630-1633: The badge count is only computed from the current
screen (nsec_count using match on self.screen -> Screen::NsecKeys(s) and
s.keys), so it disappears when you're off the Nsec screen; change the logic to
derive the count from the underlying persistent nsec key store instead of the
active screen. Replace the match that sets nsec_count with a call or inline
check that reads the canonical nsec key collection (e.g. a new helper like
get_nsec_key_count() that inspects the module/field that holds imported keys)
and returns Some(len) when keys exist, otherwise None, while leaving the
Screen::NsecKeys branch as a fallback if needed (use self.<nsec_store_or_field>
or add an accessor if none exists).
- Around line 2357-2361: The call to resolve_active_share(&shares) is running
before the Nsec keys screen is rendered and can overwrite an active nsec
identity; to fix this, prevent resolve_active_share from switching the active
identity during an Nsec import by either (A) moving the
resolve_active_share(&shares) call to after set_nsec_keys_screen() so the
UI/state for NsecKeys is established before any share-based active-identity
resolution, or (B) add a guard inside resolve_active_share (or around its
invocation) to skip switching when the newly rendered screen will be the Nsec
keys flow (use the presence of NsecKeys state or a flag set by
set_nsec_keys_screen()). Update the code paths that call
refresh_identities(&shares), set_nsec_keys_screen(), and
resolve_active_share(&shares) accordingly so Nsec imports never get overwritten
by FROST share resolution.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b0041b and 1746bd5.

📒 Files selected for processing (3)
  • keep-desktop/src/app.rs
  • keep-desktop/src/screen/layout.rs
  • keep-desktop/src/screen/nsec_keys.rs

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
keep-desktop/src/app.rs (1)

1812-1819: ⚠️ Potential issue | 🟠 Major

Preserve active Nsec identity when refreshing shares.

refresh_shares() still unconditionally calls resolve_active_share(&shares) (Line 1815), which is share-only resolution. If the active identity is Nsec, this can overwrite active_share_hex during refresh paths (including Nsec delete success path at Line 1894).

Suggested patch
     fn refresh_shares(&mut self) {
         let shares = self.current_shares();
+        let nsec_keys = self.current_nsec_keys();
         self.cached_share_count = shares.len();
-        self.cached_nsec_count = self.current_nsec_keys().len();
-        self.resolve_active_share(&shares);
+        self.cached_nsec_count = nsec_keys.len();
+        let active_is_nsec = self
+            .active_share_hex
+            .as_deref()
+            .is_some_and(|hex| nsec_keys.iter().any(|k| k.pubkey_hex == hex));
+        if !active_is_nsec {
+            self.resolve_active_share(&shares);
+        }
         self.refresh_identities(&shares);
         if matches!(self.screen, Screen::NsecKeys(_)) {
             self.set_nsec_keys_screen();

Also applies to: 1888-1895

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

In `@keep-desktop/src/app.rs` around lines 1812 - 1819, In refresh_shares(), avoid
overwriting a currently active Nsec identity by not calling
resolve_active_share(&shares) when the UI is showing or the active identity is
an Nsec; instead only call resolve_active_share when the active screen/identity
is a share (e.g. when !matches!(self.screen, Screen::NsecKeys(_)) or inside the
Screen::ShareList branch). Ensure active_share_hex is left unchanged for Nsec
flows (preserve its value during the NsecKeys path) and keep the existing
set_nsec_keys_screen()/refresh_identities(&shares) behavior for Nsec refreshes.
🧹 Nitpick comments (1)
keep-desktop/src/app.rs (1)

1768-1785: Reset import_return_to_nsec during lock to avoid stale nav state.

Small state-hygiene improvement: clear the import return flag in do_lock() alongside the cached counters.

Suggested patch
     fn do_lock(&mut self) -> Task<Message> {
         self.stop_scanner();
         self.handle_disconnect_relay();
         self.stop_bunker();
@@
         self.identities.clear();
+        self.import_return_to_nsec = false;
         self.cached_share_count = 0;
         self.cached_nsec_count = 0;
         self.identity_switcher_open = false;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keep-desktop/src/app.rs` around lines 1768 - 1785, In do_lock(), after
resetting cached_nsec_count (and before identity_switcher_open), reset the
import return flag to avoid stale navigation state by setting
self.import_return_to_nsec = false; (i.e. add this assignment in the do_lock
method alongside the other state clears such as cached_share_count and
cached_nsec_count).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@keep-desktop/src/app.rs`:
- Around line 1812-1819: In refresh_shares(), avoid overwriting a currently
active Nsec identity by not calling resolve_active_share(&shares) when the UI is
showing or the active identity is an Nsec; instead only call
resolve_active_share when the active screen/identity is a share (e.g. when
!matches!(self.screen, Screen::NsecKeys(_)) or inside the Screen::ShareList
branch). Ensure active_share_hex is left unchanged for Nsec flows (preserve its
value during the NsecKeys path) and keep the existing
set_nsec_keys_screen()/refresh_identities(&shares) behavior for Nsec refreshes.

---

Nitpick comments:
In `@keep-desktop/src/app.rs`:
- Around line 1768-1785: In do_lock(), after resetting cached_nsec_count (and
before identity_switcher_open), reset the import return flag to avoid stale
navigation state by setting self.import_return_to_nsec = false; (i.e. add this
assignment in the do_lock method alongside the other state clears such as
cached_share_count and cached_nsec_count).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1746bd5 and cbb18b4.

📒 Files selected for processing (1)
  • keep-desktop/src/app.rs

@kwsantiago kwsantiago merged commit 02f872e into main Feb 27, 2026
9 checks passed
@kwsantiago kwsantiago deleted the nsec-tab branch February 27, 2026 02:12
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.

importing nsec it does not show as being imported

2 participants