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>
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>
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>
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>
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>
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>
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>
Bug: setup tab's zxcvbn meter silently stayed at score=-1 because the
router's isSetup exception only allowed save_setup, so rate_passphrase
got unauthorized_sender. Result: the "create vault" button stayed
disabled forever even with a strong passphrase.
Fix: add a narrow SETUP_ALLOWED set containing save_setup,
rate_passphrase, and is_unlocked (step-4 extension detection). Reject
everything else from the setup tab. Also clean up setup.ts's unlock
call — it was passing the raw 32-byte imageSecret where JPEG bytes with
embedded secret are required; the Rust-side unlock calls imgsecret::
extract internally.
Diagnostic logging across the message path so the next silent failure
speaks up:
- [relicario setup] staged logs through vault-init; console.error
with the failure stage name in the UI banner.
- [relicario setup] rate_passphrase lastError / rejected / threw
branches each log their own warning.
- [relicario router] console.warn on unauthorized_sender (with sender
classification) and unknown_message_type.
- [relicario sw] first-message wasm init announced; per-message
non-ok result logged; thrown errors console.error'd.
Tests: +3 setup-allowlist tests (rate_passphrase accepted, is_unlocked
accepted, fill_credentials + unlock rejected). 55/55 green.
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>