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 <noreply@anthropic.com>
18 KiB
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 itembutton 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-modifiedage (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 pwdwhere 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 logicextension/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).
Escnavigates back to the list.- Keyhint bar updates to show
Esc back.
Files affected:
extension/src/popup/components/item-list.ts—+ newbutton label/glyph, keyhintextension/src/popup/components/item-form.ts(or wherever the type picker lives) — card layout, glyphs
A4. Glyphs
Add to extension/src/shared/glyphs.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:
- Popup item list, vault empty — centered message: glyph
◈(28px dim), "No items yet", "Press+to add your first item." - Popup item list, search returns nothing — centered message: glyph
⊘(28px dim), "No results for "{query}"", "Try a shorter search term." - 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)inextension/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 layoutextension/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:
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 modulecrates/relicario-core/src/lib.rs— pub mod recovery_qrcrates/relicario-core/src/error.rs— addRecoveryQrerror variants if neededcrates/relicario-core/Cargo.toml— addqrcodecratecrates/relicario-core/tests/— newrecovery_qr.rstest 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
// 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)→ storesrecovery_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, callsiframe.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,
--successfill for completed,--goldfor current,--borderfor pending. - Centered card (max-width 560px): step eyebrow label ("Step N of 5 · "), 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 testgreen.bun run testgreen.bun run build+bun run build:firefoxclean.- No raw
snake_caseerror codes in any UI surface. - No emoji in any UI surface — all icons are Unicode monochrome glyphs.
glyphs.tsis 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/writeVaultSettings.
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.