# v0.5.x — UX Polish, Settings Redesign & Recovery QR — Design **Date:** 2026-05-03 **Status:** Draft **Target:** v0.5.1 / next release train ## Overview Three parallel streams building on the v0.5.0 base: - **Stream A — Fullscreen + popup layout polish** — fullscreen vault tab gets a new 3-column layout (sidebar with type-category nav, full-width list, slide-in detail drawer); popup gets a polished type-picker; glyph additions; toast system; empty states. - **Stream B — Settings UX redesign** — replace the current flat settings dump with a left-nav sectioned settings page; Security section with trusted-devices and Recovery QR integration. - **Stream C — Recovery QR + setup wizard** — implement the recovery QR cryptographic feature (Rust core + WASM); integrate into the setup wizard's final step; wire into the vault-tab Security settings section. Streams A and B share no files with Stream C (Rust/WASM). A and B share only `glyphs.ts` and `styles.css`; all other files are disjoint. All three can run in parallel. --- ## Stream A — Fullscreen + Popup Layout Polish ### A1. Fullscreen vault tab — 3-column layout **Current state.** `extension/src/vault/vault.ts` renders a fixed sidebar (~220px) with brand, search, item list, and bottom nav buttons. Clicking `+ new item` navigates to the type-picker. The main pane shows the selected item in a single-column layout. **New layout.** ``` ┌─────────────┬──────────────────────────┬──────────────────────────┐ │ sidebar │ full-width list │ detail drawer (440px) │ │ (200px) │ (flex: 1) │ slides in on row click │ └─────────────┴──────────────────────────┴──────────────────────────┘ ``` **Sidebar changes:** - Replaces the current flat item list with **type-category nav**: all items are listed by section (Logins N, Secure Notes N, Cards N, Identities N, TOTP N, Keys N, Documents N) plus an "All items" entry at the top. - Search bar stays above the category list. - Bottom nav buttons remain (+ new item, ▦ trash, ⌬ devices, ⚙ settings, ⏻ lock) — the `+ new item` button triggers the bottom sheet (see A3). - `⧉` replaces the current `⤴` pop-out button in the **popup toolbar only** — it stays in the popup toolbar and is not added to the fullscreen sidebar (you're already there). **Full-width list:** - Each row: 32px type icon (rounded, gold-tinted on selection) + title (13px) + subtitle (URL or type description, 11px muted) + `last-modified` age (10px dim, right-aligned). - Clicking a row: highlights the row and slides in the detail drawer from the right. The list narrows to accommodate the 440px drawer — flex layout handles this naturally. - Active row stays highlighted while drawer is open. **Detail drawer (440px):** - Header: type pill (e.g. `LOGIN`) left, action buttons right (`edit`, `history`, `copy pwd` where applicable), `✕` close. - Body: title (18-20px bold) + subtitle (URL/description, muted), then a **2-column field grid** for sibling fields (username/password, first/last name, number/expiry, etc.). Full-width spans for URL, notes, address, and any field without a natural pair. - Close (`✕` or Esc): drawer slides out, list returns to full width. - At ≤ 720px viewport: drawer pushes full-page (list hidden), back breadcrumb `←
` navigates back. **Files affected:** - `extension/src/vault/vault.ts` — full layout rewrite (sidebar list → category nav, main pane wiring, drawer state) - `extension/src/vault/vault.css` — layout rules for 3-column, drawer, list rows, responsive breakpoint ### A2. Fullscreen vault tab — "new item" bottom sheet **Current state.** Clicking `+ new item` in the sidebar sets `state.newType = null` and calls `renderPane()` which renders the type-picker inline in the main pane. **New behaviour.** A bottom sheet slides up from the bottom edge of the **main pane** (pane-only scrim — sidebar stays interactive). - Sheet structure: drag handle, "New item — choose type" label, 7-item type grid (Login, Secure Note, TOTP, Card, Identity, SSH/API Key, Document) as cards with large glyph (28px), name (11px muted). Selected type border turns gold on hover. - Clicking a type: sheet closes, main pane renders the add form for that type. - Dismissing (Esc, click scrim, `✕`): sheet closes, main pane returns to previous state. - Scrim covers the main pane only (not the sidebar). Sidebar nav remains clickable. **Files affected:** - `extension/src/vault/vault.ts` — sheet trigger, render, dismiss logic - `extension/src/vault/vault.css` — sheet, scrim, type-card styles ### A3. Popup — polished type-picker page **Current state.** `+ new` button in the popup toolbar navigates directly to the `add` route. `renderItemForm` is called with `state.newType = null`, which presumably renders a type picker inline. **New behaviour.** Keep the current navigation model (navigate to `add` route) but upgrade the type-picker page: - Back arrow + "New item" title in the search-bar row (replacing search input). - 2-column grid of type cards: icon (glyph, 20px), name (12px bold), description (10px muted). E.g. "Login / Username + password", "TOTP / 2FA token". - Glyphs not emoji for type icons (use the per-type glyph table from A5). - `Esc` navigates back to the list. - Keyhint bar updates to show `Esc back`. **Files affected:** - `extension/src/popup/components/item-list.ts` — `+ new` button label/glyph, keyhint - `extension/src/popup/components/item-form.ts` (or wherever the type picker lives) — card layout, glyphs ### A4. Glyphs Add to `extension/src/shared/glyphs.ts`: ```ts export const GLYPH_VAULT_TAB = '⧉'; // pop-out to fullscreen vault tab (replaces ⤴) ``` Remove the inline `⤴` from `extension/src/popup/components/item-list.ts:69` and replace with `GLYPH_VAULT_TAB`. ### A5. Item row type icons The popup item list (`buildRowsHtml` in `item-list.ts`) currently renders title-only rows with no visual type anchor. Add a per-type glyph to each row using the item's `ManifestEntry.type` field: | Type | Glyph | |------|-------| | login | `◉` | | secure_note | `◫` | | totp | `⊡` | | card | `▭` | | identity | `⌬` | | key | `⊹` | | document | `≡` | Icon: 26×26px, rounded, `--bg-elevated` fill, gold-tinted border on active row. **Files affected:** `extension/src/popup/components/item-list.ts`, `extension/src/popup/styles.css` ### A6. Empty states Two surfaces: 1. **Popup item list, vault empty** — centered message: glyph `◈` (28px dim), "No items yet", "Press `+` to add your first item." 2. **Popup item list, search returns nothing** — centered message: glyph `⊘` (28px dim), "No results for "{query}"", "Try a shorter search term." 3. **Fullscreen list pane, section empty** — same treatment scaled for the wider pane. **Files affected:** `extension/src/popup/components/item-list.ts`, `extension/src/vault/vault.ts` ### A7. Toast notification system Replace the current ad-hoc `sync-status` div with a shared toast system: - `showToast(message: string, type: 'success' | 'error' | 'info', durationMs = 2500)` in `extension/src/shared/toast.ts`. - Toasts appear bottom-center of the popup / bottom-right of the vault tab, auto-dismiss. - Used for: sync success/failure, copy-to-clipboard confirmation, device registration success. **Files affected:** new `extension/src/shared/toast.ts`, `extension/src/popup/styles.css`, `extension/src/vault/vault.css`, call sites in `item-list.ts` and `vault.ts` --- ## Stream B — Settings UX Redesign ### B1. Settings page structure Replace the current flat settings dump (`settings.ts` + `settings-vault.ts`) with a unified settings page that renders within the fullscreen vault tab's main pane (and a compact equivalent in the popup). **Left-nav sections:** ``` Device ⊙ Autofill ◈ Display Vault ◉ Security ← Recovery QR + trusted devices (replaces devices.ts nav) ↻ Generator ▦ Retention ⤓ Backup ≡ Import ``` Each section renders its content in the right panel. The left nav is 148px; content area fills the remainder. **Device vs Vault distinction:** - "Device" sections read/write `chrome.storage.local` (per-browser settings). - "Vault" sections read/write encrypted `VaultSettings` (shared across devices via git). **Files affected:** - `extension/src/popup/components/settings.ts` — rewrite as sectioned layout - `extension/src/popup/components/settings-vault.ts` — content moves into new section components **Note on vault.ts:** DEV-B delivers the settings component with a stable export signature. The `⚙ settings` nav wiring in `vault.ts` is updated as part of Stream A's vault.ts rewrite. DEV-A and DEV-B must agree on the component's export signature before either lands. ### B2. Autofill section (Device) Content replaces the current flat settings dump: - **Capture** group: "Auto-detect logins" toggle (was checkbox); "Prompt style" select (bar / toast). - **Blocked sites** group: list of blacklisted hostnames, each with a remove button. Add-hostname input at bottom. All options use the standardised `setting-row` pattern: left (title + description), right (control). ### B3. Display section (Device) Moves the existing password-coloring UI (digit color picker, symbol color picker, live swatch, reset) from its current location into a proper Display section card. ### B4. Security section (Vault) **Recovery QR card** (three states, see Stream C for implementation): - **State 1 — no QR:** amber warning ("▲ No recovery QR generated — losing your reference image would make this vault unrecoverable"), single "Generate recovery QR…" button. - **State 2 — QR exists, at rest:** green status ("◉ Recovery QR is set up"), last-generated date. Buttons: "Show / print QR…" and "Regenerate…". **No QR is visible in this state.** - **State 3 — explicit view:** modal overlay (scrim over main pane only). QR rendered at ~140×140px. Warning: "▲ Close this window before stepping away. This QR is only displayed, never saved." Actions: "⎙ Print" (triggers `window.print()` scoped to modal) and "Done" (dismisses). **Trusted devices** group: subsumes the current `⌬ devices` sidebar nav entry. Each registered device shows name, registration date, fingerprint, and a revoke button. "Register this device" entry for unregistered browsers. Once Stream B lands, the `⌬ devices` button is removed from the vault sidebar nav (settings → Security replaces it). ### B5. Generator section (Vault) Pulls the existing generator-defaults content from `settings-vault.ts` into the new section layout. No functional changes — just consistent styling. ### B6. Retention section (Vault) Pulls the existing retention content (trash retention, field history retention). No functional changes. ### B7. Backup section (Vault) Pulls the existing backup & restore section. No functional changes. ### B8. Import section (Vault) Pulls the existing import section. No functional changes. --- ## Stream C — Recovery QR ### C1. Rust core — `relicario-core/src/recovery_qr.rs` Per the existing spec at `docs/superpowers/specs/2026-05-01-recovery-qr-design.md`. Key implementation points: **KDF input:** ``` b"relicario-recovery-v1\0" || u64_be(len(nfc(passphrase))) || nfc(passphrase) ``` Fed to Argon2id with production params (`m=64MiB, t=3, p=4`), fresh 32-byte salt per generation. **Wrap:** `XChaCha20-Poly1305(wrap_key, nonce=OsRng(24), image_secret)` — 32+16=48 bytes ciphertext. **Binary payload (109 bytes):** ``` [magic "RREC" 4B][version 0x01 1B][salt 32B][nonce 24B][ciphertext 48B] ``` **QR encoding:** byte mode, error-correction M, version 6 (41×41 modules). Library: `qrcode` crate (already in workspace or add it). **API surface:** ```rust pub struct RecoveryQrPayload { /* opaque */ } pub fn generate_recovery_qr( passphrase: &str, image_secret: &[u8; 32], ) -> Result; pub fn recovery_qr_to_svg(payload: &RecoveryQrPayload) -> String; pub fn unwrap_recovery_qr( payload_bytes: &[u8], passphrase: &str, ) -> Result, RelicarioError>; ``` The payload bytes are never written to disk by this module — callers are responsible for rendering only. **Passphrase entropy floor:** enforce `zxcvbn score ≥ 3` at vault init in the CLI and the setup wizard (already gated in the extension by 1C-α; confirm CLI `create` command applies the same gate). **Files affected:** - `crates/relicario-core/src/recovery_qr.rs` — new module - `crates/relicario-core/src/lib.rs` — pub mod recovery_qr - `crates/relicario-core/src/error.rs` — add `RecoveryQr` error variants if needed - `crates/relicario-core/Cargo.toml` — add `qrcode` crate - `crates/relicario-core/tests/` — new `recovery_qr.rs` test file ### C2. CLI — `relicario recovery-qr` subcommand group ``` relicario recovery-qr generate # prompts passphrase, renders QR to terminal (kitty/iTerm2 inline protocol or ASCII fallback) relicario recovery-qr unwrap # prompts passphrase, prints image_secret as hex ``` `generate` never writes a file. It renders the QR inline in the terminal using the Kitty graphics protocol if `$TERM` indicates support, falling back to ASCII art via the `qrcode` crate's built-in ASCII renderer. **Files affected:** `crates/relicario-cli/src/main.rs` ### C3. WASM bindings ```ts // relicario-wasm/src/lib.rs generate_recovery_qr(passphrase: &str, image_secret: &[u8]) -> Result // returns SVG string unwrap_recovery_qr(payload_b64: &str, passphrase: &str) -> Result, JsValue> // returns image_secret bytes ``` **Files affected:** `crates/relicario-wasm/src/lib.rs`, `crates/relicario-wasm/Cargo.toml` ### C4. Extension — Recovery QR in Security settings Implement the three-state Security section card described in B4: - State determined by `chrome.storage.local.recovery_qr_generated_at` (timestamp or null). - "Generate recovery QR…" button: calls WASM `generate_recovery_qr(passphrase, image_secret)` → stores `recovery_qr_generated_at = Date.now()` in local storage → transitions to State 3 (show modal with SVG). - "Show / print QR…" button: re-derives QR (requires vault to be unlocked, master key in session) → shows State 3 modal. - "Regenerate…" button: same as generate, with a confirmation step first. - Print: injects SVG into a `