- Login and secure_note types stay in popup without attachment UI
- All other types (identity, card, key, totp, document) auto-redirect
to full tab when selected
- Attachments only shown for login/secure_note when opened in tab
Co-Authored-By: Claude <noreply@anthropic.com>
Forms can now be opened in a full browser tab via the ⤴ button,
solving Chrome's popup closure on file picker interaction. Deep
linking via URL params preserves view, item type, and item ID.
Also removes the unused dropdown picker code from item-list.ts.
Co-Authored-By: Claude <noreply@anthropic.com>
Clicking "+ new" now navigates to a type selection view instead of
showing a dropdown that gets clipped by popup bounds. The selection
view displays all item types as buttons in a scrollable list.
Co-Authored-By: Claude <noreply@anthropic.com>
Adds View variants, render cases, teardown calls, and entry points
in settings menu for trash and devices.
Co-Authored-By: Claude <noreply@anthropic.com>
Shows button when item.field_history is non-empty. Navigates to
field-history screen with historyItemId set.
Co-Authored-By: Claude <noreply@anthropic.com>
Shows current + historical values for tracked fields (password/concealed).
Click to reveal, copy button per entry (plaintext stored in a module-level
Map, never embedded in the DOM). Grouped by field name if multiple tracked
fields exist. Adds historyItemId to PopupState and 'field-history' to View.
Co-Authored-By: Claude <noreply@anthropic.com>
Shows registered devices with "← you" indicator on current device.
Revoke button on other devices. Unregistered banner if current
device not in list.
Co-Authored-By: Claude <noreply@anthropic.com>
Shows trashed items sorted newest-first with restore buttons.
Empty trash button purges all items + orphan blobs. Header shows
count and days until oldest auto-purges.
Co-Authored-By: Claude <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>
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>
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>
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>
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).
- 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>
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>
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>
Clears the final four transitional @ts-nocheck shields:
- popup.ts (already mostly updated in Slice 6 prior tasks; nocheck just
removed and the init fallback switched to list_items / ItemId typing)
- unlock.ts (list_entries → list_items; ManifestEntry typing)
- settings.ts (RelicarioSettings → DeviceSettings; pure type rename, UX
unchanged)
Also drops the stale `idfoto-extension` name in bun.lock (workspace was
renamed; lock file still carried the old name).
Verification:
git grep -n '@ts-nocheck' extension/src/ → 0 hits
bun run build + build:firefox → both green
bun run test → 52/52 passing
cargo test --workspace → green
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrites item-form.ts for the typed-item Item shape. Login is the only
editable type in Slice 6; other types fall through to coming-soon.
Form fields: title (required) + url + username + password (with gen
button backed by DEFAULT_PASSWORD_REQUEST) + totp (base32) + group +
notes. TOTP base32 is decoded via shared/base32 and wrapped as a
number[] into FieldValue-shape TotpConfig { secret, algorithm: sha1,
digits: 6, period_seconds: 30, kind: 'totp' }. Decode failure sets
state.error and aborts.
Save constructs a full Item envelope (id, title, type, tags, favorite,
group, notes, created, modified, trashed_at, core, sections,
attachments, field_history). On edit we preserve the existing item's
metadata but EXPLICITLY set trashed_at: undefined — carry-forward
from Slice 5 review M3, so an edit cannot accidentally preserve stale
trash state.
@ts-nocheck removed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrites item-detail.ts to dispatch on item.type: login gets the full
detail view (url, username, masked password + copy, TOTP with 30s
countdown, notes, group, autofill/edit/trash/back buttons). Non-login
types get a coming-soon placeholder; those grow full UIs in later slices.
Fixes Slice 4 review I1: the old autofill path sent a malformed
fill_credentials payload ({ username, password } — no id/capturedTab).
The new handler uses the (capturedTabId, capturedUrl) pair snapshotted
at popup-open and calls fill_credentials with { id, capturedTabId,
capturedUrl }, matching the SW's handler signature that enforces the
M5 + TOCTOU checks.
TOTP poll now calls get_totp on a 1s timer and renders the 30s countdown
bar against expires_at. @ts-nocheck removed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Rewrites item-list.ts to render the typed-item ManifestEntry v2
surface: title + type-icon emoji (🔑/📝/🪪/💳/🗝/📄/⏱) + icon_hint
as the meta line. Toolbar now has +new, sync, settings, lock. Keyboard
nav unchanged (/, +, arrows, Enter).
Clicking a row fires list_items → get_item (the new typed-item
messages) and stores the full Item in state.selectedItem before
navigating to 'detail'.
Also updates popup.ts PopupState:
- entries now typed Array<[ItemId, ManifestEntry]>
- selectedEntry → selectedItem (Item)
- init() uses list_items not list_entries
Trashed items (trashed_at set) are filtered out of the visible list.
@ts-nocheck removed from item-list.ts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Git-moves the three popup components so history survives the content
rewrite that follows in Tasks 22–24:
- entry-list.ts → item-list.ts
- entry-detail.ts → item-detail.ts
- entry-form.ts → item-form.ts
Also renames the exported render functions (renderEntryList →
renderItemList, etc.) and updates popup.ts imports + render switch.
The files still wear @ts-nocheck and reference the old Entry type;
content rewriting happens in Tasks 22–24.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>