Commit Graph

251 Commits

Author SHA1 Message Date
adlee-was-taken
c67d484152 feat(extension): update devices UI for new auth model
- Show revoked devices in collapsible section with strikethrough styling
- Fetch revoked.json via new list_revoked message + router case
- Registration flow uses register_device WASM API (private keys internal)
- Display revoked_by and timestamp for each revoked entry
- Update setup wizard to use new register_device API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 12:29:31 -04:00
adlee-was-taken
520f6ec72c feat(extension): update devices.ts for revoked.json + deploy keys
- Add createDeployKey/deleteDeployKey to GiteaHost
- Add RevokedEntry interface and readRevoked() to devices.ts
- Update revokeDevice() to write revoked.json alongside devices.json
- Update router to use new register_device WASM API (private keys internal)
- Pass revokedBy device name when revoking

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 12:27:14 -04:00
adlee-was-taken
9845febb74 feat(extension): update wasm.d.ts for secure device API
New WASM bindings that keep private keys internal.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 12:26:13 -04:00
adlee-was-taken
315967f4a1 Merge feature/fullscreen-ux-phase-2a: smart-input affordances
Phase 2A of the fullscreen UX redesign — 8 form-level smart-input
affordances (URL fill-from-tab + hostname chip, group autocomplete,
password reveal + strength bar, TOTP live preview + QR decode, notes
monospace toggle), shared between popup and fullscreen vault tabs via
the new extension/src/shared/form-affordances/ module set.

CLI parity:
- relicario rate <passphrase> (zxcvbn score / guess estimate)
- relicario completions <SHELL> (bash/zsh/fish via clap_complete)
- --group <TAB> dynamic enumeration via .relicario/groups.cache
  (plaintext leak surface; opt out with RELICARIO_NO_GROUPS_CACHE=1)
- --totp-qr <path> on add login + edit (rqrr decode)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 22:37:18 -04:00
adlee-was-taken
b450ecd1cc ext(login): wire 8 smart-input affordances into renderForm()
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 22:32:14 -04:00
adlee-was-taken
e6eb698c4c ext(affordances): wireNotesMonoToggle with chrome.storage.local persistence 2026-05-01 22:23:56 -04:00
adlee-was-taken
bd8102c9ad ext(affordances): wireTotpQr (jsqr lazy-load) for QR -> otpauth:// fill
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-01 22:14:05 -04:00
adlee-was-taken
c91b31a7ca ext(affordances): wireTotpPreview live ticker 2026-05-01 19:56:55 -04:00
adlee-was-taken
bb8b86f0d5 ext(sw): add preview_totp_from_secret popup handler 2026-05-01 19:55:24 -04:00
adlee-was-taken
7bd1a9dd7d ext(affordances): wirePasswordStrength via scheduleRate
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 19:50:18 -04:00
adlee-was-taken
026b94092e ext(affordances): wirePasswordReveal toggle 2026-05-01 19:48:32 -04:00
adlee-was-taken
e452d8df02 ext(affordances): wireGroupAutocomplete via <datalist> 2026-05-01 18:09:33 -04:00
adlee-was-taken
5fbdd30a19 ext(sw): add list_groups popup handler 2026-05-01 18:08:34 -04:00
adlee-was-taken
61dbb4d3a3 ext(affordances): wireHostnameChip with debounced URL parse 2026-05-01 18:06:15 -04:00
adlee-was-taken
8eff96da9d ext(affordances): tighten FillFromTabOpts.sendMessage return type 2026-05-01 17:54:57 -04:00
adlee-was-taken
39ae2ecbf3 style: capitalize "Relicario" in prose / UI / CLI help
Brand name uses capital R in user-facing text — extension UI strings,
CLI clap help / descriptions / error prose, markdown docs. Lowercase
preserved for the binary command, crate names, npm package, file
paths, env vars, and code identifiers.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 17:29:10 -04:00
adlee-was-taken
4be0bcff83 ext(affordances): wireFillFromTab + .glyph-btn CSS 2026-05-01 17:07:01 -04:00
adlee-was-taken
918fdef519 ext(sw): expand active-tab URL filter; isolate chrome stub in tests
Expand get_active_tab_url protocol filter regex to include view-source:,
data:, devtools:, and other browser-internal/extension contexts that would
misbehave if autofilled. Add third regression test for view-source: URLs.

Wrap get_active_tab_url tests in dedicated describe block with beforeEach/
afterEach to snapshot/restore globalThis.chrome, preventing stub leakage
between tests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 17:01:36 -04:00
adlee-was-taken
f872ab5183 ext(sw): add get_active_tab_url popup handler 2026-05-01 16:57:18 -04:00
adlee-was-taken
6eeb292fd0 ext(affordances): seed shared/form-affordances/ + barrel test 2026-05-01 16:53:58 -04:00
adlee-was-taken
ef7bd5b848 refactor(ext/popup): renderFormHeader takes options object
Whole-branch review recommendation: switch renderFormHeader's signature
from positional (titleText) to options ({ titleText }) so Phase 3 can
add 'dirty' (and any future hooks like a save-keybinding hint) without
touching all 7 call sites in lockstep with the unsaved-guard work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 14:33:29 -04:00
adlee-was-taken
1454cd8165 refactor(ext/popup): extract renderFormHeader + .form-header CSS
Code-review feedback on Task 8: the conditional empty
<div style="margin-bottom:16px;"> spacer was an inline-styled magic
number and the 6-line header pattern was duplicated across all 7 typed
forms.

Now:
- .form-header class owns the bottom margin in both stylesheets.
- :has(+ .form-subtitle) selector drops the margin when a subtitle
  follows, so spacing tokens stay in CSS instead of inline styles.
- renderFormHeader(titleText) shared helper collapses the 6-line
  duplication to a one-liner per form. item-form.ts (type-selection
  screen) is unaffected — it uses a different header structure.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 14:26:16 -04:00
adlee-was-taken
381e8ed496 feat(ext): static 'esc to cancel' subtitle in fullscreen form headers
All seven type forms plus the type-selection screen now show a small
'esc to cancel' subtitle under the heading when rendered in the
fullscreen vault tab (isInTab() === true). The subtitle is suppressed
in the popup, where esc has the more general meaning of closing the
popup. .form-subtitle class is shared between popup and vault
stylesheets so future hooks can reuse it.

Dynamic dirty-state ('unsaved · esc to cancel') wiring is deferred to
Phase 3 (unsaved-changes guard).

Plan 2026-04-30 fullscreen UX phase 1 task 8.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 14:17:59 -04:00
adlee-was-taken
38ba31768a refactor(ext/test): extract TYPED_FORMS shared list for it.each tests
Code-review feedback on Task 7: the same Array<[name, renderForm]> of
all 7 typed forms appeared in three test files (required-pill,
popout-button, popout-button-fullscreen). A new typed form would have
required updating all three.

Now defined once in __tests__/_typed-forms.ts. Future typed-form
additions get regression coverage automatically.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 14:14:21 -04:00
adlee-was-taken
71ad91592d feat(ext/popup): hide popout-to-tab button in fullscreen forms
The ⤴ popout button is meaningless when the form is already in
vault.html — gate it on !isInTab(). Affects all seven type forms plus
the type-selection screen. Regression tests cover both popup (button
present) and fullscreen (button absent) contexts via it.each across
all 7 forms.

Plan 2026-04-30 fullscreen UX phase 1 task 7.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 21:01:47 -04:00
adlee-was-taken
05b1fae9f4 style(ext/popup): replace settings nav emoji with shared glyphs
▦ trash and ⌬ devices in the popup settings panel now match the
fullscreen sidebar's glyph language. Lowercased labels match the brand.

Plan 2026-04-30 fullscreen UX phase 1 task 6.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:57:00 -04:00
adlee-was-taken
e2260e9df4 style(ext/vault): replace sidebar emoji nav with monochrome glyphs
▦ trash · ⌬ devices · ⚙ settings · ⏻ lock — all imported from the new
shared/glyphs module so popup and fullscreen stay in sync. Regression
test scans the source for the old escape-coded emoji to prevent
backsliding.

Plan 2026-04-30 fullscreen UX phase 1 task 5.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:53:50 -04:00
adlee-was-taken
a634b6c745 refactor(ext): broaden required-pill test + drop dead .label .req CSS
Code-review feedback on Task 4:
- Test expanded from login-only to it.each across all 7 type forms
  (14 assertions total). A future revert to <span class="req">*</span>
  in any form now fails CI.
- .label .req rule removed from popup/styles.css and vault/vault.css —
  zero consumers after the REQUIRED_PILL_HTML migration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:52:26 -04:00
adlee-was-taken
e2381ed2ec refactor(ext/popup): migrate required-field markers to REQUIRED_PILL_HTML
Replaces ten <span class="req">*</span> sites across all seven type
forms with the shared REQUIRED_PILL_HTML snippet ('required' badge).
Adds a regression test pinning the new HTML in the login form.

Plan 2026-04-30 fullscreen UX phase 1 task 4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:46:07 -04:00
adlee-was-taken
6e720554fa style(ext/vault): migrate .btn:focus to :focus-visible + var(--focus-ring)
Code-review feedback on Task 3: vault button focus was the last
hardcoded #d2ab43 + bare :focus rule not yet migrated. Brings vault
button focus into parity with popup (which Task 2 already migrated)
and removes the last raw accent literal from the focus-related rules.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:42:24 -04:00
adlee-was-taken
f0d8758a80 style(ext/vault): mirror color tokens, focus ring, required-pill class
Same :root block and .req-pill rule as popup/styles.css so the two
stylesheets share visual tokens. Vault input focus migrated to
:focus-visible + box-shadow ring.

Plan 2026-04-30 fullscreen UX phase 1 task 3.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:39:46 -04:00
adlee-was-taken
e5875249bf style(ext/popup): add color tokens, focus ring, required-pill class
Establishes :root CSS custom properties (accent, surfaces, status, focus
ring) and applies the focus ring to inputs/buttons via :focus-visible.
Adds .req-pill class used by Task 4 to replace the bare-asterisk required
marker. Existing .label .req kept for backward compatibility during the
migration window.

Plan 2026-04-30 fullscreen UX phase 1 task 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:36:26 -04:00
adlee-was-taken
506ad9711d refactor(ext/shared): rename REQUIRED_PILL → REQUIRED_PILL_HTML
Code-review feedback on Task 1: the _HTML suffix makes the 'this is raw
HTML, do not escape' contract obvious at every call site. Cheap to do
now (zero consumers); would be 8 diffs once Tasks 4-6 wire the constant
into the type forms.

Plan updated in lockstep so Task 4 references the new name.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:29:49 -04:00
adlee-was-taken
33b3f0b019 feat(ext/shared): glyph constants module for unified icon language
Centralizes the unicode glyphs used by sidebar nav and form action buttons
so popup and fullscreen surfaces stay in sync. Includes the REQUIRED_PILL
snippet used to replace the trailing-asterisk required-field marker.

Plan 2026-04-30 fullscreen UX phase 1 task 1.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:25:12 -04:00
adlee-was-taken
31672b714d fix(ext/vault): renderPane preserves in-memory newType when hash lacks /type
In the fullscreen UX, clicking '+ new item' set the hash to '#/add'
(no type) and called renderPane. The user then clicks a type button;
its handler calls setState({ newType: type }), which in vault.ts
triggers renderPane again. renderPane was unconditionally re-deriving
state.newType from the URL hash — clobbering the just-selected type
back to null. Result: the type-selection screen kept re-rendering and
no item could be created.

Fix: prefer route.type when present (deep-link case); otherwise keep
the in-memory state.newType. Same field order, same one-line touch.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:22:06 -04:00
adlee-was-taken
f1ae5841bc fix(ext): generate_device_keypair returns object not JSON string
The wasm-bindgen binding for generate_device_keypair uses
serde-wasm-bindgen and returns a plain JsValue (object), not a JSON
string. Two consumers were calling JSON.parse on it, causing the
runtime error 'SyntaxError: "[object Object]" is not valid JSON' which
broke device registration end-to-end.

Fixes:
- wasm.d.ts: return type now { public_key_hex; private_key_base64 }
  matching the rate_passphrase pattern (also a JsValue-returning
  binding).
- popup-only.ts (register_this_device handler) and setup.ts (initial
  device wire-up): drop JSON.parse, use the object directly.
- router.test.ts: pin the contract — mock generate_device_keypair as a
  function returning an object (matching real binding behavior) and
  assert register_this_device returns ok and forwards the public key.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:21:47 -04:00
adlee-was-taken
ab5a885f10 test(ext/vault): vitest for the Import panel
Mocks sendMessage. Covers: file-picker fires
parse_lastpass_csv, preview text matches the parsed counts,
confirm fires import_lastpass_commit with the parsed items,
warnings render after import, cancel clears the preview.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 18:45:23 -04:00
adlee-was-taken
66981588e7 feat(ext/vault): Import panel — LastPass CSV
New vault.html#import panel with a file picker, parse-preview
("N logins, M notes, K skipped — proceed?"), confirm/cancel
buttons, inline progress, and a post-import warnings list. The
popup's settings-vault view links to it via a new
"LastPass CSV →" button next to "Backup & restore →".

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 18:43:35 -04:00
adlee-was-taken
da6f08fa35 test(ext/router): sender matrix for LastPass import messages
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 23:33:52 -04:00
adlee-was-taken
ecb137a120 test(ext/sw): unit tests for parse + commit handlers
Mocks the WASM bridge and vault helpers. Covers:
- parse_lastpass_csv pass-through + error surface
- commit happy path: 3 items → 3 encryptAndWriteItem +
  1 encryptAndWriteManifest call
- vault_locked + empty-items rejections
- IDs re-minted by SW so manifest keys match the new IDs

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 23:33:16 -04:00
adlee-was-taken
b29a138411 feat(ext/sw): parse + commit handlers for LastPass import
parse_lastpass_csv is a pure pass-through to the WASM bridge.
import_lastpass_commit re-mints each item's ID via
state.wasm.new_item_id() (same pattern as add_item), encrypts
and writes per-item via git.writeFile, then writes the manifest
last. Per-item commits + a final manifest commit — extension
GitHost has no atomic-batch API, so the single-commit semantics
the CLI provides aren't replicable here.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 23:30:26 -04:00
adlee-was-taken
fbd029e4cb feat(ext/shared): message types for LastPass import
Adds parse_lastpass_csv (preview) and import_lastpass_commit
(write) to the popup-only message set, plus typed response
helpers. SW handlers + UI follow in Tasks 12-14.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-29 23:30:18 -04:00
adlee-was-taken
218ccb8efa test(ext/sw): export/restore handler unit tests 2026-04-28 22:20:07 -04:00
adlee-was-taken
c1f48ecb71 test(ext): vault-tab Backup & Restore panel 2026-04-28 22:17:09 -04:00
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