Skip to content

feat(e2ee): on-demand unlock UX — header lock icon + clickable placeholder#412

Merged
mremond merged 2 commits into
mainfrom
feat/e2ee-unlock-ux
May 20, 2026
Merged

feat(e2ee): on-demand unlock UX — header lock icon + clickable placeholder#412
mremond merged 2 commits into
mainfrom
feat/e2ee-unlock-ux

Conversation

@mremond
Copy link
Copy Markdown
Member

@mremond mremond commented May 20, 2026

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:

  1. Conversation header surfaces a yellow Lock icon when the 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 then failing at encrypt time.
  2. Messages whose decrypt failed because the key was locked render a new 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 (clicking again wouldn't help).
  3. UnlockEncryptionDialog a11y: role="dialog", aria-modal, aria-labelledby, aria-describedby, aria-busy so screen readers announce it correctly.

Plumbing

  • webPassphraseStore is now observable (subscribeKeyLockState) so the new useWebKeyLocked hook reflects live unlock/lock transitions via useSyncExternalStore. Tauri always reads as unlocked (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.

mremond added 2 commits May 20, 2026 17:12
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.
@mremond mremond merged commit 556d75f into main May 20, 2026
2 checks passed
@mremond mremond deleted the feat/e2ee-unlock-ux branch May 20, 2026 15:22
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.

1 participant