Commit Graph

304 Commits

Author SHA1 Message Date
adlee-was-taken
419408bbad feat(ext): vault-tab Backup & Restore panel
Two cards — Export (passphrase + include-image checkbox → download)
and Restore (file picker + passphrase + new-remote form). Deep-linked
from settings-vault > 'Backup & restore →'.
2026-04-28 22:11:51 -04:00
adlee-was-taken
06913a0aed test(ext/sw): router accepts/rejects backup messages per sender 2026-04-28 22:03:02 -04:00
adlee-was-taken
9ec5e9b4e1 fix(ext/sw): atomic chrome.storage update in restore_backup
Single set({vaultConfig, imageBase64?}) instead of two sequential sets,
so a partial-write window can't leave vaultConfig pointing to the new
remote while imageBase64 still references the old vault.
2026-04-28 22:01:56 -04:00
adlee-was-taken
2e825a9d33 feat(ext/sw): restore_backup handler
Unpacks .relbak via WASM, writes every vault artifact to the
user-specified fresh remote via writeFileCreateOnly (refuses to
clobber), and updates chrome.storage.local so subsequent unlocks
hit the restored vault. The reference image — when bundled — is
restored to imageBase64; otherwise the user keeps using their
existing reference.jpg.
2026-04-28 21:58:14 -04:00
adlee-was-taken
5d9ea37b7f feat(ext/sw): export_backup handler
Reads vault state via GitHost, calls pack_backup_json in WASM, returns
the .relbak bytes back to the panel for chrome.downloads.download.
Reference image inclusion comes from chrome.storage.local.imageBase64.
Git history is never bundled from the extension (CLI is the source of
full backups).
2026-04-28 20:16:52 -04:00
adlee-was-taken
f32c14f939 feat(ext/sw): export_backup / restore_backup message types 2026-04-28 20:12:07 -04:00
adlee-was-taken
7407fe512f feat(wasm): pack_backup_json / unpack_backup_json
JSON bridge for the SW. Binary fields are base64 in the JSON wrapper;
core gets borrowed byte slices.
2026-04-28 19:52:36 -04:00
adlee-was-taken
6d96ca8288 test(cli): humanize_age bucket boundaries + plural transitions
Locks the singular vs plural transition (1 minute ago vs 2 minutes
ago) and each bucket boundary (59→60s minutes, 3599→3600s hours,
86400→86400×2 days, etc.) so future tweaks can't silently regress
the user-facing labels.
2026-04-28 19:48:50 -04:00
adlee-was-taken
536ef2464b test(cli): tighten last-export label assertions to exact match
Drop the dead `stdout.contains("last export:")` + `.to_lowercase()` fallback
in status_shows_last_backup_line and status_shows_recent_backup_after_export;
assert `stdout.contains("Last export:")` verbatim instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-28 19:46:03 -04:00
adlee-was-taken
a32f13b63a feat(cli): status shows last export age
Reads .relicario/last_backup (written by cmd_backup_export). Format:
'never' for fresh vaults, '4 days ago' otherwise. Closes the
'is my backup stale?' question without leaving the terminal.
2026-04-28 19:42:10 -04:00
adlee-was-taken
bd7bef7ce4 test(cli): export/restore round-trip + error paths 2026-04-28 19:32:58 -04:00
adlee-was-taken
734325a31f feat(cli): cmd_backup_restore — unpack .relbak into target dir
Refuses non-empty target, prompts for backup passphrase, writes the
full vault layout, untars .git/ when bundled or git-inits a fresh
'restore from backup <iso8601>' commit otherwise.

Also tightens error context on tar_directory's builder.finish().
2026-04-28 19:25:45 -04:00
adlee-was-taken
7ce57353f2 feat(cli): cmd_backup_export — pack vault into .relbak
Reads the vault layout from disk, prompts for backup passphrase
(zxcvbn-gated, independent of the live vault key), tars .git/
unless --no-history, optionally bundles the reference JPEG, and
atomic-writes the .relbak. Leaves .relicario/last_backup marker
for cmd_status.
2026-04-28 19:21:02 -04:00
adlee-was-taken
b8dfcd0e97 feat(cli): clap surface for backup export/restore (handlers stubbed)
Adds 'relicario backup' as a subcommand wrapping export and restore.
Stubs return 'not yet implemented' — handlers land in Tasks 8 and 9.
The existing top-level 'relicario restore <query>' (un-trash) is
untouched.
2026-04-28 19:16:05 -04:00
adlee-was-taken
e02f62f961 test(core): backup error paths
Covers bad magic, unsupported version, wrong passphrase, truncation,
and tampered ciphertext. The wrong-passphrase / tampered-tag pair both
collapse to RelicarioError::Decrypt — same opaque-failure contract as
the live vault.
2026-04-27 22:42:44 -04:00
adlee-was-taken
1ffe333697 test(core): backup round-trips git archive + size check 2026-04-27 22:39:55 -04:00
adlee-was-taken
e4949c4c06 test(core): backup round-trips reference image bytes 2026-04-27 22:37:38 -04:00
adlee-was-taken
0b59b94a0b test(core): populated-vault round-trip for backup 2026-04-27 22:34:36 -04:00
adlee-was-taken
08086b9a9e feat(core): backup module — empty-vault round-trip
pack_backup / unpack_backup ship the magic header, format version,
Argon2id KDF, XChaCha20-Poly1305 AEAD, and zstd-compressed JSON
envelope. Empty-vault round-trip is the foundation; later tasks
add items, attachments, image, and git history.
2026-04-27 22:29:10 -04:00
adlee-was-taken
57dd186bab feat(core): add backup deps + error variants
Adds zstd, tar, base64 to relicario-core; introduces
BackupBadMagic / BackupUnsupportedVersion / BackupSchemaMismatch.
Foundation for the backup module landing in Task 2.
2026-04-27 22:22:04 -04:00
adlee-was-taken
c66fd520f8 docs(arch): per-codebase ARCHITECTURE.md + cross-codebase overview
Strategic-depth architecture documentation, the kind that's hard to
recover by reading code: invariants, multi-file flows, design rationale,
gotchas. Goal is to cut the token cost for future Claude sessions.

Four new docs (2091 lines total):

- crates/relicario-core/ARCHITECTURE.md (514 lines) — bytes-in/bytes-out
  boundary, 24 verified invariants (VERSION_BYTE=0x02, length-prefixed
  KDF input, NFC normalization, content-addressed AttachmentId, history-
  tracked field kinds, 60% imgsecret confidence floor, MAX_DIMENSION=
  10000, etc.), 7 multi-module flows, 16 non-obvious gotchas (QUANT_STEP=
  50, central-70%-embed, BIP39-128bit-then-truncate, Steam alphabet
  rationale).

- crates/relicario-cli/ARCHITECTURE.md (539 lines) — module map for the
  three source files; the cmd_add/cmd_edit per-type helper pattern (post-
  2026-04-27 refactor); the hardened-git invariant (Command::new("git")
  is gated to helpers.rs:46); the five history synthetic keys; the env-
  var escape-hatch policy; cmd_generate's two-mode design (no-unlock
  outside vault, unlock-and-read-defaults inside).

- extension/ARCHITECTURE.md (831 lines) — five-bundle structure (popup,
  vault, setup, content, service-worker); SW-as-crypto-fortress model;
  capability-set-or-silent-rejection contract; vault-tab-as-popup-class
  router parity (commit a7dbf35); origin TOFU flow; setup state machine;
  test-vs-build gap.

- docs/architecture/overview.md (207 lines) — cross-codebase entry point.
  How the three codebases fit together, the four versioned wire formats
  between them (core→WASM ABI, SW chrome.runtime protocol, vault on-disk
  layout, GitHost API), per-codebase secret residency table, build
  matrix, conventions that span all three.

Specs in docs/superpowers/specs/ remain as historical decision artifacts
("why we chose this") — the new arch docs are the source of truth for
"what is" current invariants and flows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 21:41:26 -04:00
adlee-was-taken
b951741366 docs(changelog): unreleased entries for the 2026-04-27 audit pass
Catches the changelog up with the audit-driven CLI + extension work and
the cmd_add / cmd_edit / setup.ts internal refactors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 21:13:35 -04:00
adlee-was-taken
3f0f5b1b28 feat(cli): close audit gaps — TOTP edit, history, detach, status, generator defaults
One coherent CLI completeness pass driven by the 2026-04-27 state-of-the-
project audit. All TDD; 6 new integration tests (workspace 158→164).

Stubs and dead state fixed:
- TOTP edit was an explicit stub at main.rs:925 ("delete and re-add for
  now"). Now supports editing issuer, label, and rotating the secret;
  rotated secrets are pushed to field_history under core:totp_secret.
- VaultSettings.generator_defaults was stored but never read by the CLI.
  cmd_generate now consults it when invoked inside an initialized vault;
  explicit flags override. Behavior outside a vault unchanged.

New commands:
- relicario settings generator-defaults [--random|--bip39] [--length |
  --words | --symbols | --separator] — view/edit the stored generator
  defaults.
- relicario history <query> [--show] [--field <name>] — view captured
  field history. Values masked by default.
- relicario detach <query> <aid> — remove an individual attachment +
  blob. Refuses to drop a Document item's primary attachment.
- relicario status — vault summary: root path, item counts (active /
  trashed), attachment count + total bytes, registered device count,
  last commit (%h %s).

Internal refactor (pure mechanical, no behavior change):
- cmd_add: 217-line match split into one build_<type>_item helper per
  ItemCore variant + a 7-arm dispatcher.
- cmd_edit: same treatment — edit_login, edit_card, edit_totp, etc. The
  history-tracking ones take a &mut FieldHistory alias for clarity.

Existing tests cover the refactor; the new helpers are tested through
the same integration paths.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 21:13:30 -04:00
adlee-was-taken
f79a67bb15 refactor(ext/setup): extract pure helpers to setup-helpers.ts
The setup wizard was 1205 lines in a single file. Extract the
state-independent helpers (escapeHtml, ratePassphrase, scheduleRate,
entropyText, STRENGTH_LABELS, the Strength interface) into a sibling
setup-helpers.ts. updateStrengthUi stays in setup.ts since it walks the
live wizard state object and would force every caller to thread that
state through.

setup.ts: 1205 → 1137 lines. Pure mechanical extraction; no behavior
change. Existing tests are the safety net (24 vitest files, all pass).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 21:13:13 -04:00
adlee-was-taken
a7dbf35126 feat(ext): sync now button + device register from popup; vault tab parity
Closes three audit gaps in one pass:

1. Sync now button in the popup settings view (📤). Triggers the existing
   { type: 'sync' } SW message and surfaces success / failure inline. The
   SW message was already wired but had no UI entry point.

2. Device registration from the popup. The "Register this device" button
   on the devices view used to error out with a "not yet implemented"
   message; it now opens an inline name input (default = browser+OS), and
   on confirm sends a new register_this_device SW message that generates
   an ed25519 keypair via WASM, persists private_key + name to
   chrome.storage.local, and writes the public key to the remote
   devices.json. No setup-wizard detour.

3. Vault tab is now an authorized sender for popup-only SW messages. The
   router accepts vault.html alongside popup.html, so the fullscreen tab
   can drive the same flows. Test covers acceptance from the vault tab.

New SW message: register_this_device { name }. Added to PopupMessage and
POPUP_ONLY_TYPES, handled in router/popup-only.ts.

Tests: 5 new vitest cases (3 in settings.test.ts, 2 in devices.test.ts)
+ 1 router test for vault-tab acceptance. All 194 extension tests pass.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 21:13:05 -04:00
adlee-was-taken
086b73b260 docs(claude.md): pin autonomy rule for routine decisions
Add a "Working with the user" section at the top of CLAUDE.md so the
default-to-recommended autonomy rule travels with the repo, not just
with the user's local memory. Mirrors the feedback memory of the same
name: pick the recommended option without prompting on minor
multiple-choice / yes-no decisions; pause before destructive git/rm
operations; brainstorming-skill intent-discovery questions still need
user input.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 21:12:48 -04:00
adlee-was-taken
d8a06346b9 docs(spec): import/export + LastPass migration design
Brainstormed scope: backup/restore round-trippable to relicario, plus a
LastPass CSV importer. Migration out is explicitly out of scope. CLI and
fullscreen vault tab get parity; popup is untouched.

Backup format `.relbak` v1: magic header + version + Argon2id salt +
XChaCha20-Poly1305 nonce + AEAD-encrypted, zstd-compressed JSON envelope
with base64'd binary blobs. KDF params are tied to backup format
version, not the live vault's params.json.

Reference image inclusion is opt-in; .git history is opt-out. Backup
passphrase is independent of the vault passphrase. Restore refuses if
the target dir already has a vault.

Includes architecture, data flow, error handling, testing strategy,
LastPass field-mapping table, risks, and effort estimate (~5.5 dev-days
for full CLI + extension parity).

Implementation plan and code to follow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 20:57:06 -04:00
adlee-was-taken
beff092818 fix(ext/setup): lock verified handle on Step 5 error + early-return paths
Mirrors Step 3b's discipline. Previously, if save_setup failed or addDevice
threw, state.verifiedHandle (the WASM session from Step 3b) would remain
in linear memory until tab close. Now lock+null on every exit path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 19:12:22 -04:00
adlee-was-taken
aa1ad99e6e chore: bump version to 0.2.0 + add CHANGELOG
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 19:02:35 -04:00
adlee-was-taken
2756033bf9 feat(ext/setup): unified device registration in Step 5; fixes silent dropped pubkey
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:34:35 -04:00
adlee-was-taken
e79e80b000 feat(ext/setup): Step 3b attach flow with decrypt verification
Replace placeholder renderStep3Attach/attachStep3Attach with the real
attach flow: file-picker for reference JPEG, passphrase input with
visibility toggle, then fetch salt+params+manifest.enc, call
unlock()+manifest_decrypt() to AEAD-verify credentials before
advancing to Step 4. Wrong passphrase/image shows a clear error;
partial handles are locked on failure to avoid key-material leaks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:32:27 -04:00
adlee-was-taken
214f8da673 fix(ext/setup): wizard writes settings.enc to match CLI init
Add default_vault_settings_json() to the hand-written wasm.d.ts
declarations, then use it in attachStep3New to encrypt and push
settings.enc after manifest.enc during new-vault creation. Wizard-
created vaults now have all four files the SW expects (salt,
params.json, manifest.enc, settings.enc), preventing the
get_vault_settings 404 on first unlock.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:29:10 -04:00
adlee-was-taken
3aa17e6be2 feat(wasm): default_vault_settings_json() for wizard parity with CLI init
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:27:07 -04:00
adlee-was-taken
399a276fdd feat(ext/setup): refuse to overwrite existing vault files (Step 3a clobber guard)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:24:16 -04:00
adlee-was-taken
f44aedfa76 feat(ext/setup): vault-presence probe + mode-mismatch banners on Step 2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:22:45 -04:00
adlee-was-taken
a182c1ac5a feat(ext/setup): Step 0 mode picker (new vs attach) + Step 1 back button
Replace the placeholder Step 0 with two clickable mode-card buttons (create
new vault / attach this device). Picking a card highlights it and enables
the next button; the back button on Step 1 returns to Step 0 without losing
state. Add .mode-card CSS using the existing dark palette (#30363d, #58a6ff).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:20:24 -04:00
adlee-was-taken
7fa1f2990f refactor(ext/setup): wizard state shape for mode-aware flow
Expand WizardState with mode/vaultProbe/referenceImageBytesAttach/
verifiedHandle/attaching fields; start wizard at step 0; grow progress
bar to 6 segments; rename renderStep3/attachStep3 to *New variants;
add placeholder renderStep0/attachStep0/renderStep3Attach/attachStep3Attach.
No behaviour change for the existing new-vault flow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:14:42 -04:00
adlee-was-taken
8e72ed8714 feat(ext/setup): vault-presence probe helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:12:04 -04:00
adlee-was-taken
19bb5b5293 test(ext/sw): assert PUT method on GitHub writeFileCreateOnly create path
Mirrors the POST assertion already present in the Gitea "creates" test —
catches accidental method drift.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 18:10:32 -04:00
adlee-was-taken
86b5941875 feat(ext/sw): GitHost.writeFileCreateOnly() refuses to overwrite
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:06:48 -04:00
adlee-was-taken
98c962796f test(ext/sw): assert lastCommit URL structure + comment limit/per_page divergence
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 18:04:56 -04:00
adlee-was-taken
2c94dfaf90 feat(ext/sw): GitHost.lastCommit() for vault-presence metadata
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 17:48:24 -04:00
adlee-was-taken
7588a75bdc docs: implementation plan for attach-existing-vault wizard split (v0.2.0)
11 main tasks + 2 addendum tasks (Tasks 7a/7b) covering:
- GitHost.lastCommit() and GitHost.writeFileCreateOnly()
- Vault-presence probe helper
- Wizard state refactor + Step 0 mode picker
- Step 2 probe wiring with mode-mismatch banners
- Step 3a clobber guard via writeFileCreateOnly
- Step 3b attach flow with decrypt verification
- Step 5 unified device registration (fixes silent-drop pubkey bug)
- Default vault_settings_json WASM export + wizard settings.enc write
  (fixes runtime get_vault_settings 404 reported on wizard-init vaults)
- Version bump to 0.2.0 + CHANGELOG

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 17:42:00 -04:00
adlee-was-taken
44fc157f35 docs: spec for attach-existing-vault wizard split (v0.2.0)
Setup wizard currently overwrites existing vaults silently. Adds a
mode picker (create new / attach this device), a vault-presence probe
after the connection test, and a Step 3b that verifies passphrase +
reference image by decrypting the manifest before registering a new
device key. Refuses destructive overwrite from the GUI; users wanting
a clean slate must delete the repo via their host's web UI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 17:33:07 -04:00
adlee-was-taken
ce59223fc0 feat(ext): shared state host — decouple components from popup.ts
Introduce shared/state.ts as a service-locator so popup components
(item-detail, item-form, trash, devices, settings, etc.) work in both
the popup and vault tab bundles. Both entry points register themselves
as the host; components import from shared/state instead of popup.ts.
Vault.ts now delegates to the real popup components, removing ~300 lines
of placeholder renderers.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-27 16:38:06 -04:00
adlee-was-taken
6c8ebb3548 feat(ext/vault): scaffold vault.html tab with sidebar+pane layout and hash routing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-27 15:53:53 -04:00
adlee-was-taken
7e0950e364 feat(ext/popup): session expiry listener, open-vault links, Shift+F shortcut
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-27 15:46:32 -04:00
adlee-was-taken
101f0093a4 fix(ext/sw): review fixes — storage key, timer reset scope, imports
- Rename storage key sessionTimeoutConfig → session_timeout (plan spec)
- Only call resetTimer() for non-content-script message types so content
  script polling cannot keep the session alive
- Collapse two same-module imports into one line; add CONTENT_CALLABLE_TYPES

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 15:44:13 -04:00
adlee-was-taken
86621f075f feat(ext/sw): add session inactivity timer with configurable timeout
Implements a service-worker-side session timer that locks the vault
after a configurable period of inactivity (default 15 min). Supports
two modes: 'inactivity' (timer-based) and 'every_time' (no timer).
Config persists via chrome.storage.local and is exposed through
get_session_config / update_session_config popup messages.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-27 02:24:26 -04:00
adlee-was-taken
bd13854f59 docs: vault tab + session timeout implementation plan
7 tasks: session timer, popup navigation, vault scaffold,
shared state host, device settings, router fix, manual testing.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-27 02:19:31 -04:00