Desktop Account & Vault Settings (accounts, devices, notifications, vault management, email change)#847
Merged
Merged
Conversation
9dd7206 to
a5fbf49
Compare
Add an "Account Settings" entry to the logged-in account dropdown that opens a new desktop page for managing accounts. The page has an account sidebar (with Add account / Import key) and a tabbed detail view: - Account: edit profile, view public profile, copy ID, export key (password dialog), and delete account (removes the key from the cloud vault + all signed-in devices, with confirmation). - Notifications: per-account email notification settings, ported from the web vault's AccountNotificationsSection, reusing the desktop notification-config hooks. - Devices: list of the account's delegated keys via ListCapabilities. The active account and tab are persisted in the route so a refresh restores the same place, using the shared PageTabs component. Share an ImportKeyDialog component in @shm/ui between desktop and the web vault (refactor the vault's ImportAccountDialog to consume it); make two latent type-only imports in @shm/ui input/label correct so the vault's verbatimModuleSyntax build passes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lt settings - Add Account / Notifications / Devices tabs to the per-account view, using the shared PageTabs component; the active account and tab are persisted in the route so a refresh restores the same place. - Notifications tab: per-account email notification settings, ported from the web vault, reusing the desktop notification-config hooks. - Devices tab: lists the account's delegated keys via ListCapabilities. - Add a Vault Settings entry above the account list (route view: 'vault') for vault-wide settings: switch between local and remote storage, connect to a remote vault via the standard login/register dialog (custom server URL supported), read-only URL while connected, force sync, non-destructive switch-to-local (keeps keys), and log out (clears local keys -> zero accounts). Remote connect relies on the daemon's automatic local->remote identity merge in finishConnection. - Remove the vault settings from the App Settings window (VaultBackendSettings and its now-obsolete test); vault management now lives in Vault Settings. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the web vault's "change email address" flow to the desktop Vault Settings page, sharing the UI with the vault and authenticating the operation via the daemon's existing remote-vault credential. Vault server (vault/src): - changeEmailStart/changeEmailVerify now accept the daemon secret-credential bearer in addition to a browser cookie session. The browser path is unchanged; the bearer path carries the anti-phishing binding in the request/response instead of an httpOnly cookie. Add a bearer-authed GET /vault/api/vault-email to read the current vault user email. Proto + Go daemon: - Add GetVaultEmail / ChangeVaultEmailStart / ChangeVaultEmailVerify RPCs (regenerated). The daemon proxies to the vault server using the active remote connection's bearer credential. Shared UI (@shm/ui): - Extract CodeInput and a new two-step ChangeEmailDialog into @shm/ui, used by both the desktop dialog and the web vault. The vault's routed change-email pages are replaced with the shared dialog opened from SettingsView; obsolete views/routes/store actions are removed. Fix the code-input border to use border-border (border-input was invisible against the background) and autofocus the first cell. Desktop: - Vault Settings shows the current vault email and a Change button (when connected to a remote vault) that opens the shared dialog, backed by the new daemon RPC hooks. Browser email-change flow and all vault tests are preserved (98 vault tests pass); Go builds; desktop + vault typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bring the web vault's "set master password" workflow to the desktop Vault Settings page, sharing UI with the vault, and add a master-password fallback to the vault sign-in flow. Crypto / daemon: - The daemon derives the password credential entirely in-process: Argon2id (golang.org/x/crypto/argon2) -> HKDF-SHA256 split -> XChaCha20-Poly1305 wrap of the in-daemon DEK. Verified byte-for-byte identical to the vault's hash-wasm + WebCrypto derivation (password_test.go: cross-platform vector + wrap round-trip), so a password set on desktop unlocks the vault on the web. The plaintext password and the DEK never leave the daemon. - New RPCs GetVaultPasswordStatus + SetVaultMasterPassword (proxy to the vault using the active remote credential; daemon picks add vs change). Vault server: - addPassword/changePassword accept the daemon secret-credential bearer in addition to a browser cookie (requireVaultAccess). Browser flow unchanged; updated the browser-only-routes test to use addSecretCredential. Shared UI (@shm/ui): - New PasswordInput (with strength meter + checkPasswordStrength) and SetMasterPasswordDialog, used by both the desktop app and the web vault. Desktop: - Vault Settings shows master-password status + a Set/Change button backed by the new daemon RPC hooks. Web vault: - SettingsView opens the shared dialog (one non-navigating store action); removed the obsolete AddPasswordView/ChangePasswordView + routes. - LoginView: passkey stays primary, with the master password behind a fallback button that also auto-reveals when a passkey attempt fails (when a password is set), authenticating via the existing password-login path. Verified: go build + vault Go tests pass; vault typecheck + 196 vault tests pass; desktop typecheck clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…web vault polling
Bring the web vault's "notification server URL" setting to the desktop Vault
Settings page, sharing the UI, and make the value sync between desktop and the
web vault. Also poll the web vault so it picks up changes made elsewhere.
Shared UI (@shm/ui):
- New NotificationServerDialog, used by both the desktop app and the web vault
(matches the vault UX: current URL, input, server-default hint, save/cancel).
Notification server now lives in the synced vault state (not a device-local
setting):
- The daemon vault State already carried NotificationServerURL with merge-on-
sync; expose it via GetVaultNotificationServer / SetVaultNotificationServer
RPCs. Set writes the state via applyMutation and schedules a remote sync, so
it propagates to the web vault and other devices.
- Desktop: useVaultNotificationServer / useSetVaultNotificationServer hooks;
Vault Settings reads/writes the synced value. useNotifyServiceHost now prefers
the vault-state value (falling back to the local app setting, then default),
so per-account notifications use the synced server. Removed the now-redundant
Notify service host row from App Settings.
- Web vault: SettingsView opens the shared dialog (backed by the existing
saveNotificationServerUrl); removed the obsolete ChangeNotifyServerUrlView +
route + test.
Web vault sync polling:
- RootLayout polls getVault({knownVersion}) every 15s while unlocked; on a
version change it re-hydrates the vault from fresh server data. Skipped while a
load/save is in flight to avoid clobbering local state.
Verified: go build + vault Go tests pass; desktop typecheck clean; vault
typecheck + tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the web vault's security/settings rows into a shared @shm/ui
VaultSecuritySettings component (built on the shared SettingsSection/SettingsRow
and the already-shared change dialogs) so the web vault and desktop look and
behave the same.
- Web vault SettingsView becomes a thin wrapper around VaultSecuritySettings.
- Desktop Vault Settings is rewritten to the same SettingsSection/SettingsRow
design: a STORAGE section (local/remote + sync), the shared security settings
(passkey, master password, email, notify server) when connected to a remote
vault, the notify server alone when local, and a REMOTE VAULT log-out section.
- Desktop can't do WebAuthn, so the Passkeys row redirects to the browser
("Manage in browser" -> {remoteVaultUrl}/settings) to manage passkeys.
- Remove the now-orphaned desktop change-vault-email-dialog wrapper and the
unused InfoRow helper.
Verified: desktop + vault typecheck clean; 194 vault tests pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ts them Phase 1 (foundation) of unifying the account-settings UI across desktop and the web vault. Add two routing-agnostic shared components to @shm/ui: - AccountSettingsLayout: the sidebar (Vault Settings entry + selectable account list + Add account / Import key) and the detail pane. Platforms inject the account list + selection state and wire onSelect* to their own router. - AccountSettingsTabs: the per-account Account / Notifications / Devices tab bar (activeTab + onTabChange), routable per platform. The desktop account-settings page now renders these instead of its inline sidebar and PageTabs, so the layout is shared. Behavior/URLs unchanged. Web vault routing + adoption comes in the following commits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the old VaultView shell with the shared @shm/ui AccountSettingsLayout + AccountSettingsTabs on the web vault, matching the desktop account-settings experience. Every state now has a URL: / -> redirect to first account (or /settings) /accounts/:accountId -> account tab /accounts/:id/:tab -> account / notifications / devices tab /settings -> Vault Settings (security) The new AccountSettingsView reads selection + tab from react-router and drives the Valtio store (selectAccount, ensureProfileLoaded, setCreatingAccount, importAccount). Notifications reuse the existing AccountNotificationsSection; devices render the account delegations; the account tab shows the account ID (profile/export/delete land in Phase 2). VaultView/AccountsView/NotificationsView remain for now and are removed in a later phase. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the Account-tab content (identity card, view/copy ID, export key, edit profile, and a delete "danger zone" with confirmation) into a shared @shm/ui AccountProfilePanel. Both platforms now render identical UI and inject only their backend-specific bits: - desktop: HMIcon avatar, native file-picker export via the daemon, useDeleteKey, edit-profile dialog, view-public-profile navigation. - web vault: <img>/initial avatar, keyfile.create + browser blob download, store.deleteAccount, AccountProfileDialog. The web Account tab now has full parity (it previously only showed the ID). The desktop delete-confirmation AlertDialog and password export dialog move into the shared component, removing the duplicated markup. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…p + web Extract the per-account email-notification UI into a shared @shm/ui NotificationEmailSettings component so both platforms render identical UI. Each keeps its own data layer (web signs requests to the notification server with the account key; desktop talks to the gateway notify-service over the daemon), mapping its state into normalized props. Platform-only capabilities are optional props: web's "Register account" step and desktop's "Resend verification". The web vault gains a free email input (prefilled with the session email) and an Edit Email action for consistency. This is the first step toward full parity; the remaining behavioral unification (desktop adopting the web's seed-signed registration model) needs a daemon API to sign notification requests with the account key, noted as a TODO in the shared component. Tests updated to the new shared UX labels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Extract the delegated-keys ("devices") list into a shared @shm/ui
DelegatedKeysList that renders a normalized DelegatedKeyItem[]. Each platform
maps its own source: the desktop app from the daemon's ListCapabilities
(agent/writer grants), the web vault from the account's stored delegated
sessions (with per-device icons). Removes the duplicated row markup and the
desktop's stray "Coming soon." empty-state copy now that Devices is real.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The web vault's unlocked UI is now fully served by the shared AccountSettingsView (sidebar + per-account tabs + Vault Settings) wired to real routes, so the old tabbed VaultView shell and its AccountsView / NotificationsView panels are dead code. Remove them and their tests. The one VaultView responsibility not already covered — the "Desktop app connected" success banner (vaultConnectionSuccessMessage) — moves into AccountSettingsView. A new AccountSettingsView.test.tsx covers routed account selection, sidebar clicks, and the connection banner. The obsolete hash-based account-selection tests are dropped in favor of real path routes. Note: the legacy drag-to-reorder accounts affordance is not carried over to the new sidebar; revisit if account ordering is needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Account and tab selection moved to real router paths (/accounts/:id/:tab),
which the server sees on reload and would require an SPA fallback for
/vault/accounts/*. Restore the prior behavior: selection lives in the URL
hash (/vault#/a/<principal>/<tab>), which browsers never send to the server
and which pushState keeps client-side, so a reload only needs /vault to
resolve.
Vault Settings stays the one server-visible path (/settings) since the
desktop app deep-links to {remoteVaultUrl}/settings for passkey management.
AccountSettingsView now parses location.hash for the selection and navigates
with {pathname: '/', hash} instead of path params; the router drops the
/accounts/* routes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
After a refresh + passkey unlock, the user was bounced to the default account instead of the screen they left. During unlock the view mounts (decryptedDEK is set) one tick before loadVaultData populates the accounts, so the "no valid account selected" redirect fired against an empty account list and navigated to /settings — clobbering the restored #/a/<principal>/<tab> hash before the accounts existed to match it. Guard the redirect on vaultData being loaded: while loading, leave the hash untouched; once the accounts arrive, the hash resolves to the selected account and no redirect happens. Adds a regression test for the load gap. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The shared @shm/ui account-settings sidebar styles the selected item with bg-sidebar-accent / text-sidebar-accent-foreground, but the vault theme only defined --accent, not the --color-sidebar* tokens. Those utilities resolved to no color, so the active selection was invisible on the web vault. Map the sidebar tokens to the vault palette in @theme inline (sidebar-accent → accent), making the selection visible and mode-aware, consistent with the desktop app. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Rather than patch missing color tokens one at a time as shared @shm/ui components surface them, define the full set the desktop app exposes. Adds the remaining tokens (chart-1..5, selection, selection-strong, and the sidebar-primary/-foreground/-ring variants); sidebar and selection map to the vault palette so they stay on-brand and adapt to dark mode automatically. Adds theme-tokens.test.ts, which fails if the vault is ever missing a --color-* token the desktop app defines — turning the recurring invisible-UI bug into a build-time error. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… web The web vault's create-account form has a "Notify me at <email>" checkbox that registers the new account for notifications on creation; the desktop dialog had no such option and was titled "Create Profile". Add an optional notificationOption checkbox slot to the shared @shm/ui EditProfileForm, and wire the desktop create-account dialog to show it when a vault email is available (logged into a remote vault), defaulting on. On submit it registers the new account's notification config with that email via the gateway notify-service. Retitle the dialog "Create Account" to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dialogs The create-account dialogs were two separate implementations: the web vault's AccountProfileDialog (plain inputs) and the desktop's EditProfileForm (react-hook-form). Adding the notification checkbox to both was parallel code, not shared code. Extract a lean, backend-agnostic @shm/ui AccountProfileForm (avatar + name + optional description + optional email-notification opt-in) built only from @shm/ui primitives — no app-context, router, or form-library deps — so the decoupled vault can consume it. Both platforms now render this one component: - the vault's AccountProfileDialog becomes a thin dialog wrapper around it (covering both create and edit), resolving the existing avatar URL via its own store and mapping the picked file back; - the desktop create dialog renders it directly, mapping to its daemon-backed account creation, and gains the description field so the two forms match. Reverts the one-off notificationOption added to EditProfileForm; the desktop edit flow and web-app onboarding keep EditProfileForm unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a hover-revealed 3-dots options menu to each account row in the shared AccountSettingsLayout sidebar (both platforms), using the toolkit's DropdownMenu. The menu has Copy account ID, Export key, and Delete account. Export key and Delete account reuse the same dialogs as the Account tab: the export password dialog and delete confirmation are extracted into shared @shm/ui ExportKeyDialog / DeleteAccountDialog, which AccountProfilePanel now also consumes. Each platform wires the per-account actions to its own backend (web: keyfile blob download + store.deleteAccount; desktop: daemon export via the native file picker + deleteKey), targeting the hovered account independently of the one open in the detail pane. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…button Replace the static "Account Settings" title in the detail pane (above the tabs) with a shared AccountSettingsHeader showing the account's icon and name on the left and an "Open Profile" button (external-link icon) on the right. Both platforms use the shared component and wire navigation their own way: desktop opens the in-app profile route; the web vault opens the public profile (createWebHMUrl on the backend host) in a new tab. Removes the now-redundant "View public profile" action from AccountProfilePanel (the header supersedes it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Importing createWebHMUrl pulled @shm/shared/constants into the vault's
browser bundle, whose DEFAULT_GATEWAY_URL reads `process.env.VITE_GATEWAY_URL`
unguarded at module load — throwing "process is not defined" in the Bun
browser runtime and breaking the vault app.
The Open Profile link only needs `${host}/hm/<principal>`, exactly what
createWebHMUrl produces, so build it inline and drop the import. No other
@shm/shared/constants usage remains in the bundle (the only process.env left
is protobuf-es's, which is typeof-guarded).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Render "Add account" as the last item in the account list — styled like an account row with a dashed plus-in-avatar — instead of a footer action. The footer now holds only "Import key". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ivider Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ifications Per-account tabs are now just Devices (default/first) and Notifications. The old Account tab's profile panel is gone — its actions already live elsewhere: the identity card is the header, and Copy account ID / Export key / Delete account are in the row's 3-dots menu. "Edit profile" moves into that menu too (both platforms), so nothing is lost. - Shared: AccountSettingsTabs drops 'account' and reorders Devices→Notifications; accountSettingsTabSchema narrows to ['devices','notifications']; the sidebar options menu gains Edit profile. - Web + desktop: default tab is 'devices'; the Account tab branch and its AccountTabContent/AccountTab are removed; edit-profile is wired at the view/page level off the menu. Old #/a/<id>/account hashes fall back to devices. - Removed the now-unused shared AccountProfilePanel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@shm/ui and @shm/shared are file: deps that Bun's isolated linker hard-links into the vault's store; an editor's atomic write breaks that link, so source edits don't reach the running app until `bun install` re-links AND the --hot server restarts. Make `bun dev` run `bun install` first (new `sync-shared` script) so editing a shared component only requires restarting `bun dev`, not remembering a separate install step. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…show email Rename the sidebar entry and detail-pane heading from "Vault Settings" to "Identity Settings". The sidebar entry now shows the signed-in email beneath the label (a new optional sublabel on the shared SidebarItem): the web vault passes its session email; the desktop passes the remote vault email (empty, so hidden, on a local vault). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te vault useVaultEmail kept its last value after switching to a local vault, so the email lingered under the Identity Settings sidebar entry. Gate it on the remote connection status (useVaultStatus, which is invalidated on disconnect), so it disappears immediately when switching to local. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The web vault rendered a gray initial-in-circle when an account had no profile image, unlike desktop/web-app which show the jdenticon identicon (via HMIcon → UIAvatar). Render the shared UIAvatar in the vault's AccountAvatar, keyed on the same hypermedia id (`hm://<principal>`) so the generated avatar is identical across platforms. Also guard Identicon's jdenticon.update() (browser-only) so UIAvatar no longer throws in SSR/test environments. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Background/scheduled notification syncs resolved the host from the local default (appStore / NOTIFY_SERVICE_HOST) and ignored the vault's synced notification server URL — so even when the UI showed the right server (e.g. the web vault's), the sync hit a different host, set lastSyncError, and the account read as "not connected to the notification server". resolveNotifyHost now prefers the connected vault's notification server URL (grpc getVaultNotificationServer) — matching the web vault and the UI — and falls back to the local default only when the vault has none. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The daemon's SignData looks up the key by NAME, but buildDesktopSigner passed the accountUid (public key) as the signing key name. Accounts whose key name differs from the public key (e.g. imported keys keep their original name) failed to sign, so their notification sync errored and they showed "not connected to the notification server" — while an account whose name happened to equal its public key worked. Resolve the real key name via daemon listKeys (matching publicKey, like the export flow) before signing, with a small cache. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The main process has no notify host: NOTIFY_SERVICE_HOST is only injected into the renderer's Vite build (where the UI shows it), the synced vault URL was empty, and appStore was unset — so resolveNotifyHost returned null and every account's background sync failed with "not connected", regardless of account. The renderer already passes the resolved host to getConfig on each tab view; persist it into appStore there (and schedule a re-sync if it changed) so the main process's background syncs resolve the same host the UI uses. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ncs in On first load an account may still be syncing in from the remote vault, so getAccount 404s transiently — ensureProfileLoaded immediately concluded "not_found" and the name rendered "Profile not found". Add a 'loading' state (shown as "Loading…") set while fetching, and retry 404s a few times with backoff before settling on not_found, so a freshly-synced account resolves to its real name instead of flashing not-found. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a dedicated, greppable logger (seed/vault-merge, prefix "🔑 VAULT-MERGE") that dumps the local, remote, and merged identity sets during a local→remote vault sign-in, so a lost signing key can be traced to its source. Suppress all other logging via SEED_LOG_ONLY (Go subsystems + the desktop winston console), wired into the mprocs desktop pane so `./dev up` shows only the merge. Also make GetKey resolve by principal when the name lookup misses: a key's name is not guaranteed to equal its public key (imported/renamed keys), and callers routinely pass a principal as the identifier, which otherwise fails with "named key not found". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
New users arriving from the desktop app already chose to sign in there, so skip the extra /connect confirmation screen: CreateProfileView now calls completeVaultConnection directly after createAccount succeeds (falling back to the confirm screen if the connection fails). Because completeVaultConnection now runs from /profile/create instead of /connect, getCurrentVaultBaseURL can no longer derive the vault URL from the current location path (the daemon rejects the payload on any URL mismatch, e.g. ".../vault/profile" vs ".../vault"). Derive it from the origin plus the fixed VAULT_BASENAME, shared with the router basename. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The desktop Vault Connect flow was one-shot and silent: the connect token and payload poll expired after 2 minutes (shorter than a real new-user registration with email verification), a single transient fetch error or remote write conflict killed the flow permanently, and the failure reason never surfaced anywhere. - Extend connectionTokenTTL and the poll window to 15 minutes (matching the email-code expiry), and the vault server's stored payload TTL to match. - Keep polling through transient payload-fetch errors instead of aborting; report the last error if the deadline is reached. - Retry finishConnection on remote write conflicts (the web app writes the vault concurrently right when it posts the payload). - Log every step of the flow under the "VAULT-CONNECT" prefix on the forwarded seed/vault-merge logger. - Record why the last connect attempt died and expose it as last_connect_error on GetVaultStatusResponse so UIs can show it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Instead of waiting on a spinner forever when the daemon's connect attempt dies, the sign-in dialog now watches lastConnectError from GetVaultStatus and shows what went wrong with recovery guidance, a Try Again button that restarts the flow, and Cancel re-enabled. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
After the desktop app is linked, navigate to /connect/success (narrow layout) instead of landing on the main vault page with a dismissible banner. The page tells the user to return to the desktop app, with an "Open desktop app" button (a new hm://open deep link that just brings the app to the front) and a "Manage my account" button to the vault home. ConnectRouteView only redirects to "/" when it never had a pending request: completing the flow clears the request while the route is still mounted, and redirecting then would win the race against the in-flight navigation to /connect/success. Covered by a full-router regression test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The account dropdown header already shows the current account, so listing it again as a switch option visually repeated it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vault registration step (Create your profile) had its own hand-rolled form that drifted from the shared Create Account dialogs; it now renders the shared AccountProfileForm. The shared form adopts the registration step's better layout: name first with the avatar picker below it (the clickable photo square with inline preview, replacing the file-input row). All create-account forms hide the bio/description field; the Edit Profile dialog keeps it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vault and agents workspaces consume frontend/packages/* as file: dependencies that bun install copies into node_modules, so editing the shared packages while `./dev up` was running left the copies stale until a manual reinstall. Their dev scripts now run through scripts/watch-file-deps.ts, which installs deps, starts the server, and watches every file: dependency's source — re-running bun install and restarting the server on change. A quiet window after each install stops the watcher from re-triggering on bun's own writes into the shared packages' node_modules. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vault's theme tokens were a hand-copied snapshot of the desktop palette and had drifted (--input, --secondary, --highlight, dark selection/sidebar, fonts), making shared @shm/ui components render differently — e.g. grey input fields and grey outline buttons in the vault where desktop shows near-white. Move the palette into @shm/ui/theme.css and the shared variants/base styles (dark variant, base layer, ScrollArea fixes) into @shm/ui/base.css, imported by all three apps. Drop the vault's global a/::selection/reset rules so it matches desktop, and align web's one divergent token (light --secondary) to desktop's value. theme-tokens.test.ts now asserts every app imports the shared files, redefines none of the tokens the theme owns, and that the vault's file:-dep copies are in sync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o /settings The desktop app's "Manage Passkeys" deep-links to /vault/settings, but a locked or signed-out browser session lost that destination and landed on the root, which auto-selects the first account. Record a returnToPath when auth interrupts a protected route and consume it after unlock. The redirect target is frozen on the first unlocked render: the async store notification for clearing returnToPath could re-fire the still-mounted <Navigate> toward '/' and override the redirect (reproduced in the new settings-deeplink tests). Password-only sessions also had a broken unlock path: - /login rendered no password form when arriving with a remembered session, because userHasPassword/userHasPasskey were only set by preLogin; checkSession now syncs them from the session credentials. - The locked screen showed an "or" divider with no passkey option above it; it now renders the divider only when both options exist — and when the password is the only option, skips the screen entirely, forwarding straight to the password form. - PasswordInput declared an autoFocus prop but never forwarded it to the input, so the password field never autofocused. - "Use different email" on /login now logs out an authenticated locked session instead of bouncing straight back to /login. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rename all user-facing "master password" copy to just "password" across the vault web app, the shared security settings UI, and the desktop toasts, and update the tests that assert on the old wording. Internal identifiers (SetMasterPasswordDialog, the setVaultMasterPassword RPC) are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LoginView depends on in-memory flow state (email + credential flags from preLogin or a remembered session). Reloading /vault/login while logged out had none of it, rendering an empty card with no sign-in form. Wait for the session check and restart at pre-login when the email is missing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nish "master" rename The lock screen now renders the password form inline instead of detouring through /login, titles itself "Unlock your vault", and shows which account is signed in. Also removes the remaining "master" identifiers: rootKey in crypto code, SetPasswordDialog in @shm/ui, and setPasswordFromDialog in the vault store. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UNjCZ6cyJ9uod4mjnns8mc
…file button Move the settings tabs (Connected Devices / Notifications) into the shared AccountSettingsHeader alongside a green "My Profile" button, drop the separate icon+name header, and render the active tab label as an h2. Reorder tabs to lead with Connected Devices and expose ACCOUNT_SETTINGS_TAB_LABELS. Applied across the desktop app and the web vault. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the connect-success card with a welcome screen: a personalized
"You're all set, {name}" title from the account profile, a link to account
settings, and a single vibrant-green "Open Seed" button matching the Publish
button's green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
AlertDialogContent's inner wrapper had no flex/gap (unlike DialogContent), so confirmation dialogs rendered vertically cramped; AlertDialogAction/ Cancel also applied buttonVariants even when asChild-wrapping a styled Button, concatenating two variants' classes and letting stylesheet order pick the winner (red destructive buttons with dark text). Fix both in the shared component, convert the identity-settings dialogs to the canonical Header/Footer + variant-prop structure, and replace the two divergent logout confirmations (account dropdown vs Identity Settings) with a shared LogoutVaultDialog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UNjCZ6cyJ9uod4mjnns8mc
The success page was restyled to the "You're all set" welcome (button "Open Seed", link "account settings.") but this test still asserted the old "Open desktop app"/"Manage my account" text. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012PcXpTnVq45xwRBUMgczVp
05609c1 to
c36a218
Compare
- vault.go: use %w to wrap lastFetchErr (errorlint) - password.go: annotate HKDF info-label constants as //nolint:gosec (G101 false positive; they are domain-separation strings, not secrets) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012PcXpTnVq45xwRBUMgczVp
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
Unifies the account & vault settings experience across the desktop app and the web vault, sharing UI through
@shm/uiso the two stay consistent. Adds a desktop Account Settings page and Vault Settings view, then rebuilds the web vault's unlocked UI on the same shared components.Shared account-settings UI (
@shm/ui)One set of components, consumed by both platforms (each injects its own data/actions):
AccountSettingsLayout(account sidebar + Vault Settings entry + Add account / Import key) andAccountSettingsTabs(Account / Notifications / Devices).AccountProfilePanel— Account tab: identity card, view/copy ID, password-protected export key, delete account (confirmation).NotificationEmailSettings— per-account email notifications.DelegatedKeysList— Devices tab.AccountProfileForm— the create/edit profile form (avatar + name + description + optional email-notification opt-in), backend-agnostic and built only from@shm/uiprimitives so the lean vault can use it. Both create-account dialogs now render this one component.VaultSecuritySettings,ImportKeyDialog,CodeInput,ChangeEmailDialog,SetMasterPasswordDialog,NotificationServerDialog.Desktop
account-settingsroute + page: sidebar of accounts, per-account tabs (Account / Notifications / Devices), and a Vault Settings view. Active account and tab persist in the route.VaultSecuritySettings): set/change master password, change email, notification server host; passkey changes redirect to the browser.Web vault
VaultView/AccountsView/NotificationsViewshell with the sharedAccountSettingsView(sidebar + tabs + Vault Settings), wired to the Valtio store./vault#/a/<principal>/<tab>) so the server never sees it and reloads need only/vault;/settingsstays a real path (the desktop deep-links to it for passkey management). Passkey unlock restores the previous account/tab.Theme tokens
--color-*contract in the vault to match the desktop (sidebar / selection / chart tokens), mapped to the vault palette so selected-sidebar state is visible and dark-mode-aware.theme-tokens.test.ts, which fails the build if the vault is ever missing a--color-*token the desktop defines — turning a recurring invisible-UI bug into a compile-time error.Change vault email (full stack)
changeEmailStart/changeEmailVerifyaccept the daemon secret-credential bearer in addition to the browser cookie (browser path unchanged; bearer path carries the anti-phishing binding in request/response). New bearer-authedGET /vault/api/vault-email.GetVaultEmail/ChangeVaultEmailStart/ChangeVaultEmailVerifyRPCs proxying to the vault via the active remote connection.Verification
Review focus / not runtime-tested
TODOinNotificationEmailSettings).🤖 Generated with Claude Code