Commit Graph

156 Commits

Author SHA1 Message Date
adlee-was-taken
5217d04034 feat(ext/sw): upload_attachment + download_attachment router handlers
Both popup-only. upload_attachment encrypts via WASM, putBlobs via
GitHost (Git Data API fallback for >900 KB), persists the AttachmentRef
on the item + manifest summaries. Duplicate uploads (same content =
same id from sha256) return the existing ref without a re-upload.
download_attachment reads + decrypts and returns plaintext bytes for
the popup to wrap in a Blob. 4 new router tests (accept × 2, reject × 2).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 16:04:06 -04:00
adlee-was-taken
559c881dca feat(ext/sw): vault helpers for attachment add/remove
addAttachmentToItem appends an AttachmentRef + re-syncs the manifest
entry's attachment_summaries. removeAttachmentsFromItem returns the
removed refs so the caller can deleteBlob() the underlying bytes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 15:57:14 -04:00
adlee-was-taken
27ca91234f feat(ext/sw): GiteaHost.putBlob with Git Data API fallback
Same shape as GitHubHost (commit dc660c4) — Gitea v1 has /api/v1/
prefix, otherwise the endpoint shapes are identical. 2 new tests;
total 5 git-host tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 15:46:02 -04:00
adlee-was-taken
dc660c4ce8 fix(ext/sw): consistent error detail across all 6 putBlob throw paths
The two GET steps (get-ref, get-commit) used resp.statusText, which is
often empty on HTTP/2. Now they read resp.text() like the other 4 throw
paths so every error message includes GitHub's response body for
debugging.

Plus a test assertion for calls[2] in the Git Data API path so a
transposition of GET ref / GET commit would be caught.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 15:42:19 -04:00
adlee-was-taken
63fcfae72c feat(ext/sw): GitHubHost.putBlob with Git Data API fallback
Blobs ≤ BLOB_THRESHOLD_BYTES (900 KB) take the Contents API path
(same as writeFile). Larger blobs use the Git Data API: POST blob,
GET ref + commit, POST tree (with base_tree), POST commit, PATCH ref.
Tests cover both paths plus error propagation.

getBlob/deleteBlob are thin wrappers over readFile/deleteFile.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 15:36:10 -04:00
adlee-was-taken
511d533de0 feat(ext/sw): extend GitHost interface with putBlob/getBlob/deleteBlob
Adds the three blob ops to the interface and a BLOB_THRESHOLD_BYTES
constant. Both GitHubHost and GiteaHost ship temporary stubs so the
build stays green until tasks 3-4 fill in real implementations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 09:46:24 -04:00
adlee-was-taken
71c182af9a fix(ext/shared): correct AttachmentCaps field names to match Rust core
The previous commit (f963ae3) used per_item_max_bytes and per_vault_*_max_bytes
which don't match the Rust core's struct (per_item_max_count and
per_vault_*_cap_bytes). Also fixes the per-item semantics: it's a COUNT of
attachments per item, not a byte sum.

Spec and plan docs updated in-place so future Task 7 cap-enforcement
implementation uses the correct names + semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 09:42:51 -04:00
adlee-was-taken
f963ae33af feat(ext/shared): tighten VaultSettings.attachment_caps to AttachmentCaps
All four cap fields optional; undefined means uncapped. γ₁ enforces;
γ₂ adds the configuration UI.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 01:54:40 -04:00
adlee-was-taken
6904f729dc fix(ext/popup): update stale generator-popover mock names in settings-vault test
The mock in settings-vault.test.ts referenced the old function names
openGeneratorPopover and closeGeneratorPopover, which were renamed to
openGeneratorPanel and closeGeneratorPanel during the refactor. Update
the mock to use the current function names.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 01:18:38 -04:00
adlee-was-taken
010c4263ba fix(ext/popup): stop Escape from leaking past the generator panel
Two related bugs from the gen-panel rewrite (ac15f06):

1. Escape key was bubbling to view-level keydown handlers in login.ts
   and settings-vault.ts, causing the press that closed the panel to
   also navigate the user away from the form/settings. Fix: call
   e.stopPropagation() in the panel's escHandler before closing.

2. settings-vault.teardown() didn't close any open generator panel,
   leaving the panel's escHandler registered and activePanel state
   stale across view transitions. Fix: call closeGeneratorPanel()
   first in teardown.

Plus a configure-defaults context test for the action-row composition
(no use/cancel buttons in that context).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 00:36:10 -04:00
adlee-was-taken
ac15f060e9 feat(ext/popup): rewrite generator as inline panel with trigger
The popover (which clipped off the popup edge) becomes an inline panel
that mounts inside the form (login.ts) or settings section
(settings-vault.ts). Trigger button is  with aria-expanded toggling.
Action row varies by context: fill-field has cancel+use; configure-
defaults has only the save-default link. Escape key closes the panel.
Tests adapted to new API; 3 new tests for aria-expanded, auto-generate,
and Escape behavior.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:30:55 -04:00
adlee-was-taken
b03058abd9 refactor(ext/popup): update import paths after generator-popover → generator-panel rename
Update all import statements to reference the new generator-panel module name.
- generator-panel.test.ts: update internal import
- settings-vault.test.ts: update mock import
- settings-vault.ts: update import
- types/login.ts: update import

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:21:00 -04:00
adlee-was-taken
c9cd3696ae refactor(ext/popup): rename generator-popover module to generator-panel
Pure rename via git-mv (preserves history). Function names and behavior
unchanged. Sets up the API rewrite in the next commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:20:50 -04:00
adlee-was-taken
083b01aa91 feat(ext/popup): lowercase form labels + gold required marker
.label drops text-transform: uppercase and tightens letter-spacing.
The `*` required marker gets wrapped in <span class="req"> so it
picks up the gold accent color (matches palette refresh).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 23:15:44 -04:00
adlee-was-taken
f32fe93202 feat(ext/setup): sweep inline colors for palette refresh
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:23:34 -04:00
adlee-was-taken
bbafe7fb7e feat(ext): sweep inline blue/red colors to gold/theca-red
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:20:16 -04:00
adlee-was-taken
5bc75c9f8a feat(ext/popup): rename sig-block--blue to --gold for accuracy
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:15:46 -04:00
adlee-was-taken
976db85a45 feat(ext/popup): swap blue accent palette for burnished gold
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:10:03 -04:00
adlee-was-taken
61b16779ab fix(icons): cap PNG bit depth at 8 per channel
ImageMagick defaults to 16-bit/channel; web/extension icons should be
8-bit/channel. Cuts ~30-40% off each icon's file size with zero visual
difference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:05:20 -04:00
adlee-was-taken
5e04fcf1ca feat(icons): regenerate PNGs from refreshed SVG masters
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 22:01:36 -04:00
adlee-was-taken
ae6b025435 feat(icons): replace 16px logo with bare medallion variant
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 21:54:19 -04:00
adlee-was-taken
a3f13fd2af feat(icons): replace master logo with reliquary theca + fleur
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 21:50:18 -04:00
adlee-was-taken
fba50b89e8 feat(ext/popup): ⚙ picker → device/vault settings
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:32:07 -04:00
adlee-was-taken
15fcaf9797 feat(ext/popup): vault-settings screen (retention + generator + origin-ack revoke)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 19:31:17 -04:00
adlee-was-taken
531af03ff1 feat(ext/popup): login gen-btn opens generator popover; teardown closes it 2026-04-24 19:25:52 -04:00
adlee-was-taken
8a16482b9c feat(ext/popup): generator-popover component (Random + BIP39) 2026-04-24 19:24:19 -04:00
adlee-was-taken
af432de320 feat(ext/popup): fetch vault_settings on unlock; add to PopupState 2026-04-24 19:18:53 -04:00
adlee-was-taken
025629cacf feat(ext/sw): generate_passphrase popup-only message 2026-04-24 18:57:11 -04:00
adlee-was-taken
e47945d86a feat(ext/sw): get_vault_settings + update_vault_settings popup-only messages 2026-04-24 18:56:17 -04:00
adlee-was-taken
b52e49a51e feat(ext/shared): tighten VaultSettings types for retention + generator_defaults 2026-04-24 18:54:21 -04:00
adlee-was-taken
6ba9ccfa4c fix(ext/popup): preserve unsupported-kind fields + totp expanded state
Two fixes from the T3+T4 code review:

C1 (Critical): renderSectionBlock previously rendered all fields
regardless of kind. For fields with kind url/date/month_year/totp/etc.
(from CLI-created items), the editor showed a blank value input; if
the user typed anything, the input handler cast the kind to the
wrong thing and silently overwrote the structured value with a
string — destroying data. Fix: filter editor to supported kinds
(text/password/concealed); key data-* attributes by field.id (not
by index) so handlers look up the correct field regardless of what
the render loop emitted. Unsupported-kind fields survive save
untouched. A small muted note "N fields of unsupported kind (edit
via CLI)" flags preserved entries. +2 tests.

I1 (Important): totp.ts's kind-toggle reRender read the module-
scope sectionsExpanded flag which was only updated on structural
mutations — so toggling the disclosure open without adding/removing
anything left the flag stale, and clicking Random/BIP39 collapsed
the disclosure. Fix: read data-expanded from the live DOM before
innerHTML swap.
2026-04-24 18:51:23 -04:00
adlee-was-taken
e1d32b0379 feat(ext/popup): wire custom-field editor into all 6 type forms
Each typed-item form now mounts the collapsible sections editor before
the form-actions. Save functions accept sectionsDraft and persist it
via Item.sections so custom fields round-trip correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:17:22 -04:00
adlee-was-taken
3264cccb60 feat(ext/popup): renderSectionsEditor + wireSectionsEditor helpers
Adds the collapsible custom-fields editor (disclosure toggle, add/remove
sections + fields, in-place label/value mutation). Module-level helpers
only: caller owns the sectionsDraft and triggers rerender on structural
changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-24 18:10:09 -04:00
adlee-was-taken
553d9d7ca9 feat(ext/popup): render custom sections in all 6 type detail views 2026-04-24 10:35:46 -04:00
adlee-was-taken
3f12543c81 feat(ext/popup): renderSections helper for custom-field detail rendering 2026-04-24 10:28:10 -04:00
adlee-was-taken
706051530e fix(ext/popup): bind form escHandlers to teardown to stop listener leak 2026-04-23 23:09:52 -04:00
adlee-was-taken
23759dc163 feat(ext/popup): + New picker with all 7 item types (Document disabled) 2026-04-23 23:07:33 -04:00
adlee-was-taken
3c0b4c1589 fix(ext): get_totp handles Totp items, not just Login
Critical bug caught in T8 code review: the SW's get_totp handler
gated on core.type === 'login' and referenced core.totp, so the
standalone Totp item type (which lands in T8 with core.type === 'totp'
and core.config) had its detail-view ticker silently rejected with
'no_totp' every second. Ticker swallowed the error; rotating code
display stayed at placeholder forever.

Fix: extend the handler to resolve TotpConfig from either carrier:
- Login items: item.core.totp (optional subfield)
- Totp items:  item.core.config (required)

Also:
- Add 3 router tests covering both paths + the empty case
- Remove stale '……' placeholder check in types/totp.ts's \`t\`
  keyboard shortcut (dead code — the placeholder is '·····' or
  '······', never horizontal ellipses)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:04:27 -04:00
adlee-was-taken
673981379e feat(ext/popup): Totp view + form (countdown ring, Steam toggle)
Detail view renders a signature block with a large monospace rotating code and
a thin SVG countdown ring that sweeps via CSS transition. The ticker polls
get_totp every second and is stopped on teardown (back/edit/trash/Escape/e/d/t).

Form has a two-button kind toggle (TOTP / Steam Guard) that re-renders in place
while preserving entered values. TOTP uses digits=6 kind='totp'; Steam uses
digits=5 kind='steam'. Both default to algorithm='sha1' period_seconds=30.

Keyboard shortcuts on detail: Escape=back, e=edit, d=trash, t=copy-code.
Guarded against stealing keystrokes from editable targets.

Wires totp.renderDetail / totp.renderForm into both dispatchers and calls
totp.teardown() alongside the other types so tickers can't leak across views.

Closes T8 of the extension 1C-β1 plan (5/5 typed-item modules in place;
only T9 picker and T10 acceptance remain).
2026-04-23 22:54:49 -04:00
adlee-was-taken
e084790756 feat(ext/popup): Key view + form (concealed monospace signature block) 2026-04-23 22:42:48 -04:00
adlee-was-taken
560a3c63c4 feat(ext/popup): Card view + form (card-silhouette signature, MM/YY selects) 2026-04-23 22:39:21 -04:00
adlee-was-taken
113b0b690a feat(ext/popup): Identity view + form (profile-card signature block) 2026-04-23 22:29:04 -04:00
adlee-was-taken
99d689b9b0 feat(ext/popup): SecureNote view + form on shared helpers 2026-04-23 22:26:49 -04:00
adlee-was-taken
23d4f736e1 fix(ext/popup): close 3 critical regressions from slice-2 code review
- C1: escapeHtml now escapes " and ' so values stored in data-field-value
  attributes (concealed rows, copyable rows) round-trip correctly. Prior
  impl silently truncated passwords containing quotes. +3 regression tests.
- C2: centralize view-teardown. login.ts exports teardown() that stops
  the TOTP ticker and removes the active keydown handler; item-detail.ts
  and item-form.ts dispatchers call it before rendering the next view;
  each button handler also calls teardown() locally for belt-and-suspenders.
- C3: restore alpha's keyboard shortcuts on login detail view: c
  (copy username), p (copy password), t (copy TOTP), f (autofill), e
  (edit), d (trash), plus Escape (back). All gated by the
  is-editable-target guard so they don't eat keystrokes inside form fields.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 22:21:40 -04:00
adlee-was-taken
11c274053b refactor(ext/popup): extract Login to types/login.ts on shared helpers 2026-04-23 21:57:53 -04:00
adlee-was-taken
24a99ba07a feat(ext/popup): field-row + concealed-row + signature-block helpers 2026-04-23 21:55:36 -04:00
adlee-was-taken
da3c3893bb feat(ext/icons): replace idfoto ID-card icon with reliquary design
The prior icon was a holdover from the pre-rename idfoto project — a
stylized ID card with a portrait silhouette. Replaced with a proper
reliquary: an arched vessel with a horizontal seal band, small rivets,
standing on a blue pedestal, with a faceted gem at center representing
the protected relic.

- relicario-logo.svg: full 128-px-native design used by the setup
  wizard header and rasterized to icon-48.png and icon-128.png.
- relicario-logo-16.svg: 16-px-optimized variant (bolder strokes, no
  rivets, single-facet gem) for crisp toolbar rendering.
- Palette matches the gh-dark aesthetic used across the extension
  (#0d1117 / #161b22 background, #58a6ff / #79c0ff / #1f6feb accents).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:50:02 -04:00
adlee-was-taken
9139dd78a0 fix(ext/popup): normalize url field + humanize cryptic error messages
Bug: typing "Test" into an add-login form's URL field produced
"item json: relative URL without a base: "Test" at line 1 column 227"
in the UI banner — a serde-internal error message that no user should
ever see.

Two fixes:

1. Client-side URL normalization in the add/edit Login form
   (item-form.ts:normalizeUrl):
   - Empty string stays empty (URL is optional).
   - Scheme-less inputs get "https://" prepended so "github.com"
     becomes "https://github.com".
   - The result is run through the JS URL constructor. If that rejects
     OR if the result has no host, show a targeted message like
     "URL must include a host (e.g. https://example.com)".
   - Prevents the Rust-side url::Url::parse failure from ever firing
     for a form-shaped error.

2. Popup-side error humanizer (popup.ts:humanizeError):
   - Applied inside sendMessage so every UI-visible error passes
     through it before the state banner gets the string.
   - Translates: "relative URL without a base" → "URL must start with
     https://...", generic "item json:" / "settings json:" → form-
     field or corruption messages, and the sender/origin gates
     (vault_locked, origin_mismatch, unauthorized_sender,
     tab_navigated, captured_tab_gone) to user-action prompts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:45:55 -04:00
adlee-was-taken
357455d979 fix(ext/popup): don't eat '/' and other keystrokes while typing in inputs
Bug: item-list's global "/" shortcut (focus search) and "+" shortcut
(new item) fired even when focus was inside any input/textarea other
than the list's own search field. This ate forward-slashes typed into
the setup wizard's host-url field and the add/edit form's notes area,
and would have done the same for any printable shortcut in a future
text field.

Root cause: the handler was attached to `document`, stays attached
when the user opens an item (and its click-handler navigated without
removing the listener), and only excluded the search field by id.

Fix:
- Add isEditableTarget() helper — returns true for
  INPUT/TEXTAREA/SELECT and contenteditable elements. Global shortcut
  handlers bail early when this fires, passing the keystroke through
  to the field.
- Apply the same guard in item-detail.ts (previously only guarded
  against INPUT, missing TEXTAREA + contenteditable).
- Remove handleListKeydown on row-click so it doesn't linger on
  detail/edit views even before the route-transition keydown
  listeners install.
- Escape in the list view still works from inside an editable
  field — only the printable-character interceptions are gated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:43:43 -04:00
adlee-was-taken
69bb58c977 feat(ext/setup): polished passphrase entry UX
Setup wizard step 3 now has self-explanatory passphrase feedback:

- Strength meter: 5 segments with smooth color transitions
  (very-weak/weak/fair/good/strong). Tier 4 gets a subtle glow.
- Nuanced label (lowercase, tracked): "very weak" / "weak" / "fair" /
  "good" / "strong" — color-matched to each tier.
- Entropy readout line: "~10^N guesses — <time to crack>" with
  tiered shorthand (trivial / minutes-on-GPU / hours-to-days /
  years-on-consumer / beyond consumer / uncrackable).
- Live char counter in the strength row.
- Eye toggle buttons on both passphrase fields. Flip type="password"
  <-> type="text" without re-render, preserving focus + cursor.
- Live match indicator (✓ / ✗) between the confirm field and its eye
  toggle. Updates per keystroke.
- Create button gate widened: now requires score >= 3 AND confirm
  field filled AND confirm matches. Disabled button carries a
  tooltip saying which condition failed.
- Contextual help box above the passphrase field explaining the
  "long phrase > complex password" idea + the score >= 3 threshold.

All live-update paths (counter, label, entropy, match indicator,
button gate) go through updateStrengthUi() which targets the DOM
directly — no full re-render, so focus/cursor survive every keystroke.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 19:38:50 -04:00