[ui] #36: auto-open the unlock-saved-key flow from storage when an nsec session is locked

Follow-up to the locked-nsec detection: clicking "Unlock Storage" on a locked
persisted-nsec session (no in-memory signer after a page reload) now auto-opens the
login modal straight to its existing unlock-saved-key (passphrase) flow, so the user
re-unlocks in one step instead of hunting for the Login menu. Once unlocked, the
signer is active app-wide and re-opening the storage pane auto-unlocks.

- applicationBootstrap: add a side-effect-free openLoginModal service to
  profileModalServices (app.loginModalController.openModal — no add-account callback;
  idempotently ensures the modal is initialized first).
- ProfileStorageController: reportLockedNsecSession({ autoOpenLogin }) opens the modal
  only from the user-initiated unlock click, never from the passive pane render (no
  surprise popups).
- Tests extended to 8 (auto-open fires on click; stays closed on passive render).

Build + lint clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.8 committed 2026-06-30 09:35:40 -04:00
1 parent a669040740
commit 35bb1bc576
4 files changed
+77 -9

No files matched your search

+22 -3
View File
@@ -517,7 +517,7 @@ export class ProfileStorageController {
return meta;
}
reportLockedNsecSession() {
reportLockedNsecSession({ autoOpenLogin = false } = {}) {
userLogger.info(
"[storage-unlock] saved nsec key is locked (no in-memory signer after reload); prompting re-unlock.",
);
@@ -527,6 +527,25 @@ export class ProfileStorageController {
error.code = "storage-unlock-locked-nsec-session";
this.setStorageUnlockFailureState(error);
this.mainController.showError(this.getStorageUnlockFailureMessage(error));
// When the user actively clicked "Unlock Storage", open the login modal straight
// to its existing unlock-saved-key (passphrase) flow so they can re-unlock in one
// step. After they unlock, the signer is active app-wide and re-opening the storage
// pane auto-unlocks. We don't auto-open from the passive pane render (would be
// surprising); the status text + button still guide them there.
if (autoOpenLogin) {
const openLoginModal = this.mainController.services?.openLoginModal;
if (typeof openLoginModal === "function") {
try {
openLoginModal();
} catch (openError) {
devLogger.warn(
"[ProfileModal] Failed to open login modal for saved-key unlock:",
openError,
);
}
}
}
}
async requestStorageUnlockPermissions() {
@@ -570,7 +589,7 @@ export class ProfileStorageController {
if (!signer) {
if (this.getLockedStoredNsecSession(pubkey)) {
this.reportLockedNsecSession();
this.reportLockedNsecSession({ autoOpenLogin: true });
return;
}
this.mainController.showError("No active signer found. Please login.");
@@ -647,7 +666,7 @@ export class ProfileStorageController {
// A restored-but-locked persisted nsec session can leave a decrypt-less stub
// signer; route it to the re-unlock guidance rather than the generic message.
if (this.getLockedStoredNsecSession(pubkey)) {
this.reportLockedNsecSession();
this.reportLockedNsecSession({ autoOpenLogin: true });
return;
}
const missingDecryptError = new Error(