From 1c9fa1e3436558ca2bc8abb36e63c1d471493632 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sat, 23 May 2026 13:19:46 -0400 Subject: [PATCH] docs: add vault-tab management surfaces revamp spec MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brainstormed design covering UX revamp of all four in-vault admin panes (Settings, Devices, Trash, History) to match the fullscreen visual language. Closes functional gaps along the way: per-device session-timeout UI, revoke button surfacing, SHA256 fingerprint + added-by display, per-item purge countdown, and a new history index pane. Item history uses option A (aggregate existing field_history per item) — no new core storage, no schema change. Ships in v0.5.x inside the current vault.ts shell; Phase 3 shell rearchitecture and Phase 4 command palette deferred to their own rounds. Roadmap entry reconciled to point at the spec. Co-Authored-By: Claude Opus 4.7 --- ROADMAP.md | 4 +- ...t-tab-management-surfaces-revamp-design.md | 466 ++++++++++++++++++ 2 files changed, 468 insertions(+), 2 deletions(-) create mode 100644 docs/superpowers/specs/2026-05-23-vault-tab-management-surfaces-revamp-design.md diff --git a/ROADMAP.md b/ROADMAP.md index 8c4d67e..fbbbf93 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -29,8 +29,8 @@ These are specced and either in progress or immediately queued: - **Phase 3: vault-tab shell** — fullscreen sidebar with nav sections, pane routing Spec: `docs/superpowers/specs/2026-04-27-relicario-vault-tab-design.md` - **Phase 4: command palette** — ⌘K global search + action dispatch across the vault tab -- **Trash & history UI** — trash view, item history viewer, field-history viewer -- **Device manager UI** — device registration + revocation in vault tab +- **Vault-tab management surfaces revamp** — UX revamp of all four in-vault admin panes (Settings, Devices, Trash, History) to match the fullscreen visual language; closes functional gaps (session-timeout UI, revoke button surfacing, fingerprint display, purge countdown, history index). Aggregates existing `field_history` per item — no new core storage. Ships in v0.5.x. + Spec: `docs/superpowers/specs/2026-05-23-vault-tab-management-surfaces-revamp-design.md` - **CLI restructure** — subcommand reorganisation, interactive TUI mode Spec: `docs/superpowers/specs/2026-05-04-cli-restructure-design.md` - **Extension restructure** — bundle / message-routing cleanup diff --git a/docs/superpowers/specs/2026-05-23-vault-tab-management-surfaces-revamp-design.md b/docs/superpowers/specs/2026-05-23-vault-tab-management-surfaces-revamp-design.md new file mode 100644 index 0000000..b2790f6 --- /dev/null +++ b/docs/superpowers/specs/2026-05-23-vault-tab-management-surfaces-revamp-design.md @@ -0,0 +1,466 @@ +# Vault-tab management surfaces revamp + +**Date:** 2026-05-23 +**Status:** Spec, awaiting review +**Surface:** Browser extension management panes — `extension/src/popup/components/` (shared between popup and vault tab) + +## Problem + +Four "management" surfaces in the extension — **Settings**, **Devices**, **Trash**, and **field history** — all shipped in the 1C-β₂ / device-auth waves but in the *pre-fullscreen-redesign* visual language. They read as popup-derived forms stretched across the vault tab, with inconsistent typography, no glyph buttons, no focus rings, and no visual section grouping. Several functional gaps remain alongside the visual debt: + +- **Settings**: per-device session-timeout config UI was specced in the vault-tab design (2026-04-27) but never built; the only way to change session behavior today is to edit `chrome.storage.local` directly. +- **Devices**: the `revoke_device` SW message handler exists but no UI surfaces it — revocation is CLI-only. Device entries don't expose the SHA256 fingerprint (used for verifying against the server-side `devices.json`) or the `added_by` field that's already in `DeviceEntry`. +- **Trash**: per-item purge countdown isn't shown — users see "trashed N days ago" but have to mentally add the retention window to figure out when it'll be gone. +- **History**: the per-item field-history viewer (`field-history.ts`) is only reachable from an item detail page; there's no entry point to discover *which* items have history. + +This spec applies the fullscreen visual-language tokens to all four panes and closes the gaps above. It deliberately stays small and ships in the v0.5.x train, in the current `vault.ts` shell — Phase 3 shell rearchitecture is out of scope. + +## Goals + +- All four management panes adopt the fullscreen visual language: glyph buttons, focus ring, uppercase section headers with 1px bottom rule, accent tokens, required-field pill where applicable. +- Close the four functional gaps above (session timeout UI, revoke button surfacing, fingerprint + added-by display, purge countdown, history index). +- Add **one new pane** — "items with history" index — reachable from a new `◷ history` slot in the sidebar bottom-nav. +- Zero core or wasm changes; zero data-model changes; zero new schema versions. + +## Non-goals + +- **Phase 3 shell rearchitecture** (three-pane layout, `shell/three-pane.ts`, `keymap.ts`) — separate effort, separate spec. +- **Phase 4 command palette** — deferred to its own brainstorm round. +- **Item-level snapshot history** — option B/C from brainstorm; this spec uses option A (aggregate existing `field_history` per item; no new core storage). +- **Settings as a hub with sub-tabs** — would introduce sub-tab pattern not used elsewhere; defer. +- **Trash polish**: hover-preview, multi-select bulk-restore — defer. +- **Devices polish**: rotate-key flow, "last seen" detail (would need new data) — defer. +- **History polish**: diff view between historical values — defer. + +## Scope summary + +| Surface | Files touched | New? | +|---|---|---| +| Settings | `popup/components/settings-vault.ts`, `vault.css`/`popup.css` | modify | +| Devices | `popup/components/devices.ts`, SW response shape | modify | +| Trash | `popup/components/trash.ts` | modify | +| History — index | `popup/components/item-history-index.ts` | **NEW** | +| History — per-item | `popup/components/field-history.ts` | polish only (no rename) | +| Glyph constants | `shared/glyphs.ts` | depends-on-or-creates | +| Time helper | `shared/relative-time.ts` | **NEW** (extracted from 3 call sites) | +| Routing | `vault/vault.ts` | add `#history` + `#history/` routes | +| Nav | sidebar bottom-nav | grows 3 → 4 (`▦ trash · ⌬ devices · ⚙ settings · ◷ history`) | + +--- + +## Architecture + +### Component map + +``` +extension/src/ +├── shared/ +│ ├── glyphs.ts ← depends on (or creates if absent): +│ │ GLYPH_TRASH ▦, GLYPH_DEVICES ⌬, +│ │ GLYPH_SETTINGS ⚙, GLYPH_LOCK ⏻, +│ │ GLYPH_HISTORY ◷, GLYPH_REVOKE ⊘, +│ │ GLYPH_RESTORE ⤺, GLYPH_REVEAL ⊙, +│ │ GLYPH_COPY ⧉ +│ └── relative-time.ts ← NEW (small util — inlined in 3 places today) +├── popup/components/ +│ ├── settings-vault.ts ← rewrite layout; add session-timeout row +│ ├── devices.ts ← add fingerprint, "added by"; surface revoke button +│ ├── trash.ts ← add per-item purge countdown +│ ├── field-history.ts ← visual polish only (filename kept) +│ └── item-history-index.ts ← NEW: "items with history" index +└── vault/ + ├── vault.ts ← add #history routes + bottom-nav slot + ├── vault.css ← four shared utility classes (below) + └── popup.css ← same classes (shared components render in both) +``` + +### SW message protocol — 99% reuse + +| Capability | Message | Status | +|---|---|---| +| Read/write vault settings | `get_vault_settings` / `update_vault_settings` | exists | +| Read/write session timeout (per-device) | `get_session_config` / `update_session_config` | exists | +| List active + revoked devices | `list_devices` / `list_revoked` | exists | +| Register / revoke device | `register_this_device` / `revoke_device` | exists | +| List trashed, restore, purge | `list_trashed` / `restore_item` / `purge_item` / `purge_all_trash` | exists | +| Per-item field history | `get_field_history` | exists (reused for index + per-item) | + +**Single shape change:** extend `ListDevicesResponse` to include `fingerprint: string` per entry — SHA256 of the device's ed25519 public key, computed via the existing `core::device::fingerprint()` function. No new message round-trip. + +### Shared CSS utility classes + +Defined in `vault.css` and `popup.css` (shared because components render in both contexts): + +```css +.section-header { + text-transform: uppercase; + font-weight: 500; + letter-spacing: 1px; + color: var(--text-muted); + border-bottom: 1px solid var(--border-subtle); + padding-bottom: 4px; + margin-bottom: 10px; +} + +.glyph-btn { + min-width: 28px; + font-family: ui-monospace, monospace; + background: transparent; + color: var(--text-muted); + border: 1px solid var(--border-subtle); + border-radius: 3px; + padding: 2px 6px; + cursor: pointer; +} +.glyph-btn:hover { color: var(--text); background: var(--bg-input); } +.glyph-btn:focus-visible { box-shadow: var(--focus-ring); outline: none; } +.glyph-btn[data-danger]:hover { color: var(--danger); border-color: var(--danger); } + +.kv-row { + display: flex; + justify-content: space-between; + align-items: baseline; + padding: 4px 0; +} +.kv-row > .k { color: var(--text-muted); } +.kv-row > .v { color: var(--text); font-variant-numeric: tabular-nums; } + +.fingerprint { + font-family: ui-monospace, monospace; + color: var(--text-muted); + font-size: 11px; + word-break: break-all; /* wraps to two lines in popup (~360px) */ +} +``` + +### Visual language reference + +All tokens come from the existing fullscreen UX redesign spec (`2026-04-30-relicario-fullscreen-ux-redesign-design.md`, "Visual language" section). No new tokens introduced. New glyph constants added to `shared/glyphs.ts` if not already present: `GLYPH_HISTORY ◷`, `GLYPH_REVOKE ⊘`, `GLYPH_RESTORE ⤺`, `GLYPH_REVEAL ⊙`, `GLYPH_COPY ⧉`. + +--- + +## A. Settings pane + +Two-tier section grouping makes the storage distinction explicit: **VAULT SETTINGS · synced** lives in the encrypted vault (replicated via git), **THIS DEVICE · local** lives in `chrome.storage.local` (per-device, not synced). **ACTIONS** is destructive/expensive operations. + +``` +◀ settings +unsaved · ⌘+S to save no changes + + +VAULT SETTINGS · synced +───────────────────────────────────────────────────────────── + +┌──────────────────────────┐ ┌──────────────────────────┐ +│ RETENTION │ │ GENERATOR │ +│ trash [30 days ▾] │ │ length 24 │ +│ history [last 5 ▾] │ │ words 4 │ +│ │ │ [ configure defaults ↻ ] │ +└──────────────────────────┘ └──────────────────────────┘ + +┌──────────────────────────┐ +│ ATTACHMENTS │ +│ max size [25 MB ▾] │ +└──────────────────────────┘ + +┌─────────────────────────────────────────────────────────┐ +│ AUTOFILL ORIGINS │ +│ github.com acknowledged 2d ago ⊘ │ +│ gitlab.adlee.work acknowledged 5d ago ⊘ │ +└─────────────────────────────────────────────────────────┘ + + +THIS DEVICE · local +───────────────────────────────────────────────────────────── + +┌──────────────────────────┐ +│ SESSION │ +│ ○ lock every time │ +│ ● after inactivity │ +│ [15 min ▾] │ +└──────────────────────────┘ + + +ACTIONS +───────────────────────────────────────────────────────────── + + [ backup & restore ] [ import from… ] +``` + +### Decisions + +- **Two-tier grouping** with `· synced` / `· local` muted suffixes makes storage scope unambiguous without being preachy. +- **Two-column where fields are small** (Retention ↔ Generator; Attachments standalone in row 2). Collapses to single-column under 720px viewport — same rule the login form uses. +- **Session timeout** wires the existing `get_session_config` / `update_session_config` SW messages to a radio (`every_time` / `inactivity`) + minutes dropdown (5/15/30/60). Already-validated config shape from the vault-tab spec. +- **Form header** reuses the "unsaved · ⌘+S to save" / "no changes" subtitle from the form-layout spec — gives Settings the same dirty-state feedback as item edits. +- **Generator section** keeps the current "configure defaults" button opening the popover from Phase 2A — no inline expansion. +- **Actions** uses text-labelled buttons (not glyph buttons) since they open dedicated panes; text is clearer than icons for navigation-style actions. + +--- + +## B. Devices pane + +Single column (this is a list, not a form). Three-line per-entry rhythm: name (+ `← you` marker or `⊘` revoke glyph), full SHA256 fingerprint, then `added X ago · by Y`. + +``` +◀ devices + + +ACTIVE · 3 +───────────────────────────────────────────────────────────── + +⌬ Aaron's laptop ← you + SHA256:8f3a:c7d2:1e44:9b08:6f55:a201:de9c:4477 + added 2 months ago · by Aaron + +⌬ Aaron's phone ⊘ + SHA256:9c11:e4f8:2a91:db32:7c0e:51bb:e8a4:1f6d + added 3 weeks ago · by Aaron's laptop + +⌬ work-laptop ⊘ + SHA256:b277:35aa:c1e0:8f44:62b9:0d3e:7c1f:5d92 + added 8 days ago · by Aaron's laptop + + +REVOKED · 1 +───────────────────────────────────────────────────────────── +▸ show 1 revoked device +``` + +### Revoke confirmation — inline two-step + +Clicking `⊘` expands a confirmation panel in place (no modal): + +``` +⌬ Aaron's phone + SHA256:9c11:e4f8:2a91:db32:… + added 3 weeks ago · by Aaron's laptop + + ┌─────────────────────────────────────────────────────┐ + │ Revoke this device? It won't be able to sign │ + │ commits or push changes after revocation. │ + │ │ + │ [ cancel ] [ revoke ] │ + └─────────────────────────────────────────────────────┘ +``` + +### Unregistered state — top banner + +``` +◀ devices + +┌─────────────────────────────────────────────────────────┐ +│ This device isn't registered. │ +│ Registering generates an ed25519 keypair and adds the │ +│ public key to .relicario/devices.json on the remote. │ +│ │ +│ [ register this device ] │ +└─────────────────────────────────────────────────────────┘ + +ACTIVE · 2 +───────────────────────────────────────────────────────────── +… +``` + +### Decisions + +- **Full fingerprint shown** (no truncation): verifiability against `.relicario/devices.json` is the whole point of displaying it. Popup-width wrapping handled by `.fingerprint { word-break: break-all }` — wraps to two lines under ~360px. +- **`by Y` semantics**: `DeviceEntry.added_by` — the name of the device that signed the registration commit. Already in the data model, just unsurfaced today. +- **Inline two-step revoke** keeps the lightness of the rest of the extension's UX; modal would feel disproportionate. The current device gets no revoke button (the CLI keeps the "revoke self" escape hatch since that needs different post-revoke handling). +- **Revoked section** collapsed by default with count; expanded entries get the same three-line rhythm minus the revoke button, plus `revoked X ago · by Y`. +- **Unregistered banner**: fleshed-out copy explaining what registration *does* (current one-liner feels mysterious per memory of past confusion). Same flow underneath — click → modal with device-name input → `register_this_device` SW message. + +--- + +## C. Trash pane + +``` +◀ trash + +3 items · oldest purges in 22 days + + +───────────────────────────────────────────────────────────── + +🔑 GitHub login ⤺ + trashed 8 days ago · purges in 22 days + +📝 Recovery note ⤺ + trashed 12 days ago · purges in 18 days + +🔑 old-aws-root ⤺ + trashed 18 days ago · purges in 12 days + +───────────────────────────────────────────────────────────── + + [ empty trash ] +``` + +### Decisions + +- **Two-line per-entry**: type icon + name + `⤺` restore glyph; muted second line `trashed X ago · purges in Y days`. +- **Per-item purge countdown** computed client-side from `trashed_at + retention_seconds - now` and formatted via `shared/relative-time.ts`. Updates on pane render (no live timer — sub-day precision unnecessary). +- **Header summary stays** ("3 items · oldest purges in 22 days") — useful at-a-glance. +- **Destructive empty button anchored bottom-right** — separated from per-row restore buttons to reduce mis-clicks. Confirmation flow unchanged from today. +- **Sort**: trashed-date descending (newest first). Defer "sort by purge urgency" toggle — not strongly requested and adds toolbar real estate. +- **Type icons** stay as today (emoji per item type) — the global glyph treatment is for *action* buttons; type icons are content-classification and read better as the existing emoji set. + +--- + +## D. History — index pane (NEW) + +Reachable from a new `◷ history` bottom-nav slot. Sorted by most-recent-change descending. + +``` +◀ history + +5 items have field history + + +───────────────────────────────────────────────────────────── + +🔑 GitHub login + 3 changes · last 2 days ago + +🔑 AWS prod + 1 change · last 2 weeks ago + +📝 Recovery note + 2 changes · last 3 weeks ago + +🔑 Cloudflare + 1 change · last 1 month ago + +🌐 personal-email + 4 changes · last 2 months ago +``` + +### Decisions + +- **Implementation**: iterate manifest entries, fetch + decrypt each item (already cached in session state where decrypted), inspect `field_history` map; emit entries that have ≥1 history-tracked field with non-empty history. Count = sum of `field_history[*].length`. Last-changed = max `replaced_at` across all history entries. +- **Click row** → routes to `#history/` (per-item view below). +- **Empty state** when no items have history: *"No field history yet. Edits to passwords, TOTP secrets, and concealed fields will appear here."* +- **No excerpts** in the index — keeping it lean; the per-item view is one click away. + +--- + +## E. History — per-item view (existing, polished) + +Existing component (`field-history.ts`, filename kept). Visual polish only — apply the section-header rule, glyph buttons, focus ring, accent tokens. **No structural changes to content layout.** + +``` +◀ history · GitHub login + + +PASSWORD · 3 entries +───────────────────────────────────────────────────────────── + + current ●●●●●●●●●●●●●●●●●●●●●●●● ⊙ ⧉ + set 2 days ago + ─── + previous ●●●●●●●●●●●●●●●●●●● ⊙ ⧉ + 3 weeks ago + ─── + previous ●●●●●●●●●●●●●●●● ⊙ ⧉ + 2 months ago + + +TOTP_SECRET · 1 entry +───────────────────────────────────────────────────────────── + + current ●●●●●●●●●●●●●●●●●●●●●●●● ⊙ ⧉ + set 1 month ago + ─── + previous ●●●●●●●●●●●●●●●●●●● ⊙ ⧉ + (created · 3 months ago) +``` + +### Decisions + +- **Filename kept** as `field-history.ts` per user feedback during brainstorm. +- **Routing**: continues to be reachable from item-detail "view history" button (`#history/`). Now also reachable by drilling into the history index pane. +- **Reveal/copy glyphs** updated to `⊙ ⧉` constants from `shared/glyphs.ts`. +- **Per-field uppercase header** + 1px rule applied — matches the new visual rhythm. +- **Values stay concealed by default** (existing behavior). Reveal toggle and copy button per entry. Values held in component-local `valueStore` map (not DOM attributes) — existing security pattern preserved. + +--- + +## Routing & sidebar nav changes + +### `vault.ts` `VaultView` union changes + +```ts +type VaultView = + | 'list' | 'detail' | 'add' | 'edit' + | 'trash' | 'devices' | 'settings' | 'settings-vault' + | 'backup' | 'import' + | 'history' // NEW — covers both index and per-item, payload distinguishes +``` + +The existing `'field-history'` view value is **removed** from the union — the hash-parse layer normalizes any `#field-history/` URL to `#history/` before view resolution, so consumers only ever see the new value. The per-item component (`field-history.ts`) is unchanged in identity; only its dispatch key changes. + +Hash routes: +- `#history` → index pane (`item-history-index.ts`) +- `#history/` → per-item view (`field-history.ts`) +- `#field-history/` → 301-style redirect to `#history/` (one release of backward compat for any bookmarked URLs) + +### Sidebar bottom-nav + +``` +▦ trash · ⌬ devices · ⚙ settings · ◷ history · ⏻ lock +``` + +Five glyph buttons in a row at ~240px sidebar width: comfortable at 1ch each + padding. Verified. + +--- + +## Testing + +UI work — verification is mostly manual smoke: + +- Each pane loads, renders, round-trips edits +- Settings round-trip: change retention/session/attachments → reload → values persist; session-timeout actually fires lock after configured minutes +- Devices: fingerprint string matches `relicario device list` CLI output; revoke happy path + cancel; unregistered banner → register flow → confirm +- Trash: per-item purge countdown updates correctly; empty trash → confirms then purges +- History: index sorted by recency; click drills in; empty state when no history; both `#history/` and the item-detail entry point reach the same view +- Cross-context: each shared component renders correctly in both popup (~360px) and vault tab (full) + +**Unit tests** — only where logic warrants: +- `shared/relative-time.ts` — table-driven test of fixture timestamps → strings +- Purge-countdown formatting + +No new test infrastructure. Extension currently has no snapshot tests per inventory; not adding any here. + +--- + +## Rollout + +- Single PR, v0.5.x train. +- No data-model migration, no schema change, no core or wasm changes. +- Purely `extension/src/`: one new shared util, one new pane file, four modified components, CSS additions, two routing additions. +- Doc updates: `STATUS.md` move-to-recent on land; `extension/ARCHITECTURE.md` note the new `◷ history` route + 4th sidebar slot. + +--- + +## Risks + +| Risk | Mitigation | +|---|---| +| Bottom-nav crowding (4 + lock = 5 items at ~240px sidebar) | Glyphs are 1ch each; ample room verified, but confirm at narrowest viewport during smoke | +| Fingerprint length in popup context (~330px monospace) | CSS `word-break: break-all` on `.fingerprint`; no truncation | +| `shared/glyphs.ts` may not exist yet | Spec creates it if absent (called out in §1) — depends-on-or-creates | +| Decrypting all items for the history index pane | Most items are already cached after a session warm-up; cost is per-pane-load not per-event; acceptable for family-vault item counts | +| Inline revoke confirmation could be missed (no modal blocker) | Two-step pattern matches other extension confirmations (delete item, empty trash); copy is explicit about consequence | + +--- + +## Out of scope (deferred to future rounds) + +- Phase 3 shell rearchitecture (three-pane layout, command palette, drag-resize panes) +- Phase 4 command palette +- Item-level snapshot history (option B/C) +- Settings-as-hub with sub-tabs +- Trash multi-select / bulk-restore / hover preview +- Devices rotate-key flow / "last seen" detail +- History diff view between adjacent values +- Whole-revamp animations or transitions