From 4dc034d84655bd60803034820dc8c8fe48f6b67b Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sun, 3 May 2026 19:32:43 -0400 Subject: [PATCH] docs(spec): v0.5.x UX polish, settings redesign, and recovery QR design Three-stream spec for the next release train: - Stream A: fullscreen 3-col layout, popup type-picker polish, glyphs, toasts, empty states - Stream B: settings UX redesign with left-nav sections (Device/Vault split) - Stream C: recovery QR crypto (Rust/WASM), setup wizard redesign (Style C), security settings tab Co-Authored-By: Claude Sonnet 4.6 --- ...v0.5.x-ux-polish-and-recovery-qr-design.md | 357 ++++++++++++++++++ 1 file changed, 357 insertions(+) create mode 100644 docs/superpowers/specs/2026-05-03-v0.5.x-ux-polish-and-recovery-qr-design.md diff --git a/docs/superpowers/specs/2026-05-03-v0.5.x-ux-polish-and-recovery-qr-design.md b/docs/superpowers/specs/2026-05-03-v0.5.x-ux-polish-and-recovery-qr-design.md new file mode 100644 index 0000000..4dd0576 --- /dev/null +++ b/docs/superpowers/specs/2026-05-03-v0.5.x-ux-polish-and-recovery-qr-design.md @@ -0,0 +1,357 @@ +# 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 `