Four features completing Plan 1C: device ed25519 keypair registration
during setup wizard, device management UI, trash view with restore/purge
(including orphan blob cleanup), per-item field history view, and
per-attachment size cap setting in vault settings.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Document is no longer 'coming soon' — the type chooser unlocks it,
form dispatcher routes to documentType.renderForm, detail dispatcher
routes to documentType.renderDetail. teardown chains include documentType.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two leaks from 705b171:
1. Lazy-load thumb for image-mime primary attachments created
URL.createObjectURL but never revoked. Now tracked in a
module-level registry, revoked on teardown.
2. 🔍 preview toggle's object URL same issue. Now tracked, revoked
on teardown + on toggle-off (when user clicks the preview button
to collapse).
Download button's URL (already self-cleaning via setTimeout) left
untracked — no change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Form requires title + primary_attachment; the primary-row picker is
compact in edit mode (dashed-border when empty, filename row when
filled). Detail view promotes the primary to a gold signature block
(48×60 thumb + filename + meta + ↓ download · 🔍 preview). For image-
mime primaries, the thumb lazy-loads via decrypt + object-URL; the
preview button toggles an inline expanded view.
Supplementary attachments use the standard compact disclosure (Task 7)
when present.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each existing type form (Login, SecureNote, Identity, Card, Key, TOTP)
renders + wires the attachments-disclosure in both edit and view modes.
Form save reads from attachmentsDraft; teardown revokes any image
object URLs. Item-list rows show a 📎 glyph for items with at least
one attachment.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two follow-ups from code review of c5f0449:
1. In MV3 the SW can be killed mid-message; sendMessage then resolves
to undefined. Add `(!resp || !resp.ok)` guards at 4 call sites
(fetchThumbUrl, settings fetch, upload, download) plus optional
chaining on error accessors.
2. JSDoc on wireAttachmentsDisclosure documents the "call once per DOM
instance" contract — Task 8's re-wire pattern works because it
replaces outerHTML before re-attaching, destroying old listeners
via GC.
Module-level objectUrlRegistry concern (concurrent disclosure
instances) deferred — current popup architecture renders one item at
a time, so the issue doesn't manifest today.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Compact disclosure rendering attachment rows with an action column
(× in edit, ↓ in view). Image-mime rows lazily decrypt + show a 16×16
thumb via object URLs; teardown revokes them on disclosure close. Edit
mode adds a "+ attach file" button wired to a hidden file input that
checks vault caps client-side before sending upload_attachment to SW.
6 new tests; total ~143.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two small follow-ups from code review of 5217d04:
1. Document the cap-enforcement layering in the upload handler. SW
enforces per_attachment_max_bytes via WASM (defense-in-depth);
per_item_max_count and per-vault caps are enforced client-side
in the popup (Task 7's attachments-disclosure).
2. Use ref.id (the validated value found on the item) instead of
msg.attachmentId for blobPath construction in download_attachment.
Eliminates a theoretical path-traversal surface even though the
handler is popup-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
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>
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>
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>
Wires Rust attachment-encrypt surface into the extension. Adds GitHost
putBlob/getBlob/deleteBlob ops with Git Data API fallback for blobs
>900 KB (Contents API base64-bloats and rejects past ~1 MB). Adds the
Document item type (deferred from β₁ — needs primary_attachment).
UX: compact disclosure for attachments on every typed-item form (matches
β₂ custom-fields pattern). Image-mime rows get 16×16 thumb-icons (lazy
decrypt + object-URL lifecycle). Document detail promotes the primary
attachment to a gold "signature block" matching Totp's pattern. Item-list
gets a 📎 indicator (no count) for items with attachments.
γ₂ (later) covers trash + field-history + device + caps UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
.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>
Replaces the right-anchored popover (which clips off the popup edge)
with an inline panel that injects into the form below the password row.
Trigger becomes a ✨ icon button (gold-bg). "save default" demoted to
secondary link; single gold "use" CTA. Bundles label-casing polish
(drop CAPS LOCK, gold required marker) since .label is shared.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
Round chapel-style theca with fleur-de-lis finial replaces the arched
niche + blue gem. Extension primary accent shifts from GitHub blue to
B/C-midpoint burnished gold; danger red shifts to theca tone. Backgrounds
and text stay GH-dark to keep the CLI feel.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.
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>