feat(e2ee): on-demand unlock UX — header lock icon + clickable placeholder#412
Merged
Conversation
The web build relies on the autocomplete + form-submit pattern to trigger password-manager save prompts, but the Tauri webview doesn't reliably fire that detection. Adds a SaveToPasswordManagerButton next to Copy + Regenerate in BackupPassphraseDialog that calls navigator.credentials.store() (W3C Credential Management API; supported on Chromium-based webviews including WebView2) and falls back to clipboard + hint when the API is unavailable. Gated on isTauri() && USE_V6_KEYS — the button is hidden on the web (where auto-detection already works) and in V4 backup-code mode (where the formatted restore input deliberately opts out of PM autofill). i18n: 4 new keys under settings.encryption.* translated to all 33 locales.
…older Three coordinated UX improvements so the web user can unlock the OpenPGP key when they need to, without the unlock dialog being forced at every session start: 1. Conversation header surfaces a yellow Lock icon when a peer is encryption-capable but the local private key is locked. Click opens the unlock dialog. A new `keyLocked` variant of `ConversationEncryptionState` carries this; `handleSend` short- circuits to the unlock dialog rather than uploading attachments and failing at encrypt time. 2. Messages whose decrypt failed because the key was locked render `EncryptedPlaceholder` instead of the SDK's English fallback body. Locked → clickable Lock icon that opens the dialog; unlocked-but- still-failed → a static LockOpen line. 3. `UnlockEncryptionDialog` gets `role="dialog"`, `aria-modal`, `aria-labelledby`, `aria-describedby`, `aria-busy` so screen readers announce it correctly. Plumbing: - `webPassphraseStore` is now observable (`subscribeKeyLockState`) so the `useWebKeyLocked` hook reflects live unlock/lock transitions via `useSyncExternalStore`. Tauri always reads as unlocked since it uses a different plugin. - A small `webUnlockDialogStore` (zustand) centralises dialog open/close so ChatHeader, EncryptedPlaceholder, EncryptionSettings, and App all route through one mount in App.tsx. i18n: 4 new `chat.encryption.*` keys translated into all 33 locales.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three coordinated UX improvements so the web user can unlock the OpenPGP key when they need to, without the unlock dialog being forced at every session start:
keyLockedvariant ofConversationEncryptionStatecarries this;handleSendshort-circuits to the unlock dialog rather than uploading attachments and then failing at encrypt time.EncryptedPlaceholderinstead of the SDK's English fallback body. Locked → clickable Lock icon that opens the dialog; unlocked-but-still-failed → a static LockOpen line (clicking again wouldn't help).UnlockEncryptionDialoga11y:role="dialog",aria-modal,aria-labelledby,aria-describedby,aria-busyso screen readers announce it correctly.Plumbing
webPassphraseStoreis now observable (subscribeKeyLockState) so the newuseWebKeyLockedhook reflects live unlock/lock transitions viauseSyncExternalStore. Tauri always reads as unlocked (different plugin).webUnlockDialogStore(zustand) centralises dialog open/close soChatHeader,EncryptedPlaceholder,EncryptionSettings, andAppall route through one mount inApp.tsx.i18n
4 new
chat.encryption.*keys translated into all 33 locales.