# 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 `&#x2934;` 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 `← <Section>` 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 &#x2934;)
```

Remove the inline `&#x2934;` 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<RecoveryQrPayload, RelicarioError>;

pub fn recovery_qr_to_svg(payload: &RecoveryQrPayload) -> String;

pub fn unwrap_recovery_qr(
    payload_bytes: &[u8],
    passphrase: &str,
) -> Result<Zeroizing<[u8; 32]>, 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<String, JsValue>  // returns SVG string
unwrap_recovery_qr(payload_b64: &str, passphrase: &str) -> Result<Vec<u8>, 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 `<iframe>` styled for print, calls `iframe.contentWindow.print()`.

**Files affected:**
- New `extension/src/popup/components/settings-security.ts`
- `extension/src/popup/components/settings.ts` — wire Security section

### C5. Extension — Recovery QR in setup wizard (Step 5 "Done")

The wizard's final step adds a **skippable banner** above the "Download reference image" button:

```
◫  Generate a recovery QR before you go
   If you lose your reference image, this QR lets you recover your vault.
   [Generate now]  [Skip — I'll do this in Settings]
```

- "Generate now": calls WASM → shows QR modal inline on the wizard page. After dismissing, banner becomes green "◉ Recovery QR generated".
- "Skip": dismisses banner permanently for this session; user can generate later from Settings → Security.
- The banner is informational, not a blocker. Vault is fully usable without a recovery QR.

**Files affected:** `extension/src/setup/setup.ts`

### C6. Setup wizard redesign (Style C)

Redesign the setup wizard from the current single-column glass-card layout to **Style C (centered hero card)**:

- Full-page dark background (`--bg-page`).
- Relicario logo glyph + wordmark centered at top.
- **Colored progress track**: 5 segments, `--success` fill for completed, `--gold` for current, `--border` for pending.
- Centered card (max-width 560px): step eyebrow label ("Step N of 5 · <step name>"), h2 heading, hint text, form content, action row.
- **Glyphs not emoji** throughout. Mode cards use `◈` (create new) and `⌥` (attach). Mode-card glyphs at 28px. All other icons from the existing glyph set.
- Probe-banner success state uses `◉` (filled circle, matches ⊙/⊘ family).
- Action row: "◂ back" text button (left), "Continue ▸" primary button (right).

This is a pure CSS/markup change — no logic changes.

**Files affected:** `extension/src/setup/setup.ts`, setup CSS (inline or extracted)

---

## Responsive behaviour

| Viewport | Fullscreen behaviour |
|---|---|
| ≥ 960px | 3-column: sidebar + list + drawer |
| 720–960px | 2-column: sidebar + list; drawer pushes full-pane on click |
| ≤ 720px | Sidebar collapses (hamburger/icon strip); list full-width; detail is full-page push |

The popup is always narrow (~340px) — popup-specific components are unaffected by the fullscreen responsive rules.

---

## Acceptance criteria (shared)

- `cargo test` green. `bun run test` green. `bun run build` + `bun run build:firefox` clean.
- No raw `snake_case` error codes in any UI surface.
- No emoji in any UI surface — all icons are Unicode monochrome glyphs.
- `glyphs.ts` is the single source of truth for all icon constants; no inline Unicode literals at call sites.
- QR code is never written to any file, `chrome.storage`, or git. `recovery_qr_generated_at` (timestamp only) is the only persisted artifact.
- Settings left-nav sections all render without console errors. Device sections read/write `chrome.storage.local`. Vault sections read/write `VaultSettings`.

---

## Stream split summary (for multi-agent kickoff)

| Stream | Owner | Core files | Dependency |
|---|---|---|---|
| A — Fullscreen + popup layout | DEV-A | `vault.ts`, `vault.css`, `item-list.ts`, `glyphs.ts` | none |
| B — Settings UX | DEV-B | `settings.ts`, `settings-vault.ts`, new `settings-security.ts` | waits for C4 interface (can stub) |
| C — Recovery QR | DEV-C | `recovery_qr.rs`, `relicario-wasm/src/lib.rs`, `setup.ts`, `settings-security.ts` | none |

B and C share `settings-security.ts` — DEV-C owns the file, DEV-B wires it into the nav. Coordinate on interface (component export signature) before DEV-B proceeds with B4.
