Skip to content

Add autofill for desktop browser login forms#12

Merged
dbro merged 99 commits into
mainfrom
feature-autofill
May 27, 2026
Merged

Add autofill for desktop browser login forms#12
dbro merged 99 commits into
mainfrom
feature-autofill

Conversation

@dbro

@dbro dbro commented May 27, 2026

Copy link
Copy Markdown
Owner

Adds a bookmarklet-based autofill feature for use in desktop browsers (mobile not supported). Autofill simplifies the process to get data from the vault to the webpage form by filling login forms directly without using the clipboard. Each bookmarklet is a cryptographic delegate (an ECDSA P-256 key pair generated at install time) so credentials are authenticated and encrypted in transit.

Supports two modes:

  • Same-profile: bookmarklet communicates with Portpass via BroadcastChannel (no extra software)
  • Cross-profile: bookmarklet communicates via switchboard (https://github.com/dbro/switchboard) WebSocket broker, enabling Portpass to run in a separate hardened browser profile with no extensions

What's included

  • autofill.html: standalone popup UI: URL matching, record picker, chip display of autotype sequence, "Show in Portpass" button; $transport abstraction covers both BC and WS modes
  • bookmarklet.js: generates javascript: URL with embedded private key; bridges field-click events
  • delegates.js: IDB module for delegate public keys; verifyAndUpdate verifies ECDSA and tracks per-delegate use counts
  • RecordEdit: Visual/Raw toggle for the Autotype field; chip builder with palette, drag-to-reorder, inline mini-forms; blocking error and warning banners
  • RecordRead: read-only chip display of autotype sequence
  • VaultSheet: delegate list (name, created, uses, last used, Revoke) and "+ New bookmarklet" modal with draggable chip
  • Dashboard: BC message handler and switchboard WebSocket client; lazy credential fetch; verifyAndUpdate gating
  • README: full autofill documentation including setup, autotype code reference, best practices, same/cross-profile explanation
  • SECURITY.md: delegate model threat analysis, cross-profile channel security, supply-chain risk

Autotype sequence
Sequences describe what to type into login forms: \u\t\p\n (fill username → Tab → fill password → Enter) is the default. Extended codes: \m email, \2 TOTP, \fN custom field N, \wNNN/\WNNN wait, \s Shift-Tab, \ literal backslash. Unknown codes produce a warning (amber banner) but don't block save; structural errors block save.

Tests

  • autofill.spec.ts 16 tests: autotype field validation, chip builder, error/warning banners, round-trip
    persistence, read view
  • autofill_popup.spec.ts 9 tests: popup protocol (hello/query)
  • bookmarklet.spec.ts 9 tests: autofill phases, field fill, submit, dismiss, error states

Known issue
Bookmarklet shows a globe icon in Chrome's bookmarks bar (root cause: App.svelte replaces the favicon with a data: URL, which overrides the bookmark icon). Workaround: bookmark Portpass normally first, then edit the URL to the javascript: code.

dbro and others added 30 commits May 19, 2026 21:21
Adds a bookmarklet-based autofill flow: a javascript: URL (installable
from VaultSheet) opens Portpass as a popup, performs an ECDH key exchange,
and returns AES-GCM-encrypted credentials to a page overlay that types
them into the focused form field per a per-record autotype sequence.

- pwsafe: read/write Autotype field (UUID 0x0025) alongside existing fields
- RecordEdit/RecordRead: autotype sequence input with validation and read view
- Dashboard: postMessage handler (hello/query protocol, gated on isPopup)
- App: popup mode detection, locked-vault error handler, window.name for reuse
- StartPage: "Unlock to use Autofill" hint when opened as popup
- VaultSheet: AUTOFILL section with bookmarklet chip and copy-link button
- bookmarklet.js: self-contained IIFE; sendRetry covers WASM load time
- Tests: autofill.spec.ts, autofill_popup.spec.ts, bookmarklet.spec.ts,
  vault_sheet.spec.ts (bookmarklet UI tests)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
window.open() with a named target cannot cross browsing-context-group
boundaries in modern browsers, so every bookmarklet click opened a fresh
locked popup requiring re-unlock.

Fix: when a popup detects an already-open unlocked Portpass tab via a
BroadcastChannel ping, it enters relay mode — skipping WASM and the
vault UI entirely, and instead bridging the bookmarklet's postMessage
protocol to the main tab over BroadcastChannel. The main tab's Dashboard
listens for relay-ping/relay-hello/relay-query and responds in kind.
The relay popup auto-closes after forwarding the record or an error.

Falls back to the original unlock-in-popup flow when no main tab responds
within 300 ms (e.g. Portpass not yet open).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…flashes

The bookmarklet previously opened relay.html twice as a full tab (once to
fetch matching records, once to fetch credentials), causing two disruptive
tab switches per autofill. Now relay.html opens once as a small popup window
(popup=yes) and handles the entire flow itself: ECDH key exchange with
Dashboard, URL search, picker UI, credential decryption, and fill delivery.
The bookmarklet is reduced to: open popup → wait for ready → send init →
wait for fill → execute autotype.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Reset __ppRunning immediately when user closes the relay popup (was stuck
  for 60s, making re-click a no-op)
- Extend fill-wait timeout to 1 hour (pp.closed polling handles dismissal)
- Save full origin+pathname URL when replacing record URL, not canonical form
- Show existing URL verbatim in Replace URL label, not canonicalized

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dling

New codes: \m email, \2 TOTP, \s Shift-Tab, \fN custom field (N=1–9),
\wNNN wait NNN ms, \WNNN wait NNN s, \\ literal backslash.
Literal (non-backslash) text between codes is typed into the current field.

Unknown codes no longer block save — they show a warning in both the edit
form and read view instead. Portpass silently skips them at fill time so
sequences written for the official Password Safe app still save correctly.

Fixed: adjacent literal segments separated by \\ or an unknown code were
incorrectly emitted as separate fillField calls, causing each to overwrite
the previous. They now accumulate into one token.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…marklet chip

- relay.html: Pkey favicon + brand bar (logo + "Portpass" heading)
- bookmarklet fill overlay: brand bar with 16px logo above record title
- VaultSheet bookmarklet chip: replace bookmark SVG with Pkey icon

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dashboard now computes sensitiveCodes (p, 2, sensitive custom fields)
alongside encrypted credentials. relay.html checks the autotype sequence
against sensitiveCodes using a proper parser — literal \\ no longer
causes false positives — and errors if any sensitive field would be
filled on a non-HTTPS page.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ile autofill)

- delegates.js: new IDB module — per-vault ECDSA P-256 delegate storage with
  getDelegates/addDelegate/revokeDelegate/verifyAndUpdate
- bookmarklet.js: replace insecure v1 bookmarklet with delegate variant;
  makeDelegateBookmarkletUrl embeds ECDSA private key in javascript: URL;
  relay.html receives privKey via targetOrigin postMessage
- VaultSheet: replace static bookmarklet chip with delegate list (name/created/
  uses/last-used/Revoke) and "New bookmarklet" modal; key generation happens at
  create time, private key shown once in draggable chip, never stored

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…op (steps 2–4)

relay.html:
- Mode detection now based on BC ping result, not privKey presence; after init
  arrives, waits 200ms for a BC pong — same-profile if pong comes, cross-profile
  if it times out (privKey always present for delegate bookmarklets)
- Cross-profile path: import ECDSA privKey, derive pubkey SPKI from JWK, sign
  {url,nonce,ecdh,ts}, fire web+portpass:// via anchor click, poll
  127.0.0.1:7677/pick/{nonce}, ECDH-decrypt blob, show picker
- Same-profile path: unchanged BC flow; onHelloResponse sends query with
  currentUrl directly (pendingSameProfileUrl buffer removed)
- processRelayBlob handles {error} blobs from Portpass

App.svelte (handleIntent):
- Parses web+portpass://autofill?url=&sig=&pub=&ecdh=&nonce=&ts= params
- Rejects if vault locked, params missing, or ts outside ±60s/5s window
- Verifies ECDSA signature via verifyAndUpdate (increments useCount/lastUsed)
- On success sets pendingIntent state, passed as prop to Dashboard

Dashboard.svelte:
- New intent/onclearintent props
- buildRecordFields: extracts plain credential fields for cross-profile blob
- processAutofillIntent: finds matching records, ECDH-encrypts array for
  relay.html's pubkey, POSTs {ephPub,iv,ciphertext} blob to relay server;
  POSTs {error} blob on failure so relay.html shows error immediately

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… verification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace web+portpass:// protocol handler approach with relay server as two-way
channel. Chrome routes web+portpass:// to the active profile rather than the
profile where the PWA is installed, making the LaunchQueue approach unreliable
for cross-profile use on Linux.

New cross-profile flow:
- relay.html signs the request and POSTs {url,nonce,ecdh,ts,sig,pub} to
  /drop/{delegateId} on portpass-relay, then polls /pick/{nonce} for credentials
- Dashboard checks /pick/{delegateId} for each registered delegate on window focus;
  verifies ECDSA signature, increments useCount/lastUsed, encrypts credentials,
  POSTs blob to /drop/{nonce} — no protocol handler or LaunchQueue needed
- delegateId embedded in bookmarklet URL alongside privKeyJwk
- index.html CSP: add http://127.0.0.1:7677 to connect-src

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…timeouts

relay.html: relay.c makes a single recv() call and memcpy's from that buffer
using Content-Length — when the POST body hasn't fully arrived, the stored
blob is zeroed/garbage. Fix: pollRelayServer detects bad blobs (empty,
unparseable, or missing ephPub/error keys) and calls an onBadBlob callback;
startCrossProfile passes a callback that re-posts the signed request so
Portpass re-processes and re-drops the correct credentials. Retry adds ~2s
on the bad-blob path, which occurs intermittently on loopback.

index.html: add http://127.0.0.1:7677 to connect-src CSP directive so
Dashboard can fetch the relay server.

relay.html: error close extended to 30s, fill close to 10s for debugging;
detailed processRelayBlob and pollRelayServer logging retained.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
relay.html doHello() now signs {relayNonce, ecdhSpki} with the ECDSA
private key (already in scope from the bookmarklet URL) and includes
sig/pub/ecdhSpki in the relay-hello BC message.

Dashboard relay-hello handler verifies the signature via verifyAndUpdate
before completing the ECDH exchange. Rejects unsigned or unverified
hellos with relay-error. This closes the masquerade attack on the
same-profile path: a content script at the Portpass origin can observe
the BC channel but cannot forge the signature.

Side effect: useCount/lastUsed now increment for same-profile requests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
bookmarklet.spec.ts:
- Replace makeBookmarkletUrl with makeDelegateBookmarkletUrl
- Add createDelegateBookmarklet() helper that opens VaultSheet, creates a
  delegate via the UI, and captures the bookmarklet href — no production
  code exposure needed; also exercises the delegate creation flow itself
- setupAutofillTest() now creates a delegate and returns bookmarkletUrl
- activateBookmarklet() takes bookmarkletUrl as a parameter
- All 9 tests updated to pass the bookmarklet URL
- Timeout bumped to 30s to accommodate delegate creation UI steps

autofill_popup.spec.ts:
- Remove the 4 "Autofill relay mode" tests: they drove App.svelte's
  tryRelay() bridge (Portpass as popup), which is no longer the production
  autofill path and is now broken because Dashboard requires ECDSA on
  relay-hello. The same-profile relay flow is covered by bookmarklet.spec.ts.
- Query protocol and UI tests (9 tests) unchanged — they test direct
  postMessage to Portpass popup, which is unaffected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add login page's #__pp to the Promise.race so the test proceeds as soon as
the bookmarklet renders the error overlay (~2s), rather than waiting for the
popup to close. With the extended debug close timeout (30s), the popup-close
path never wins within 10s, causing the overlay to be auto-dismissed before
the assertion ran.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use localhost:7677 instead of 127.0.0.1:7677 for portpass-relay URLs in
  relay.html, Dashboard.svelte, and index.html CSP — localhost has a stronger
  loopback exemption in Firefox's mixed-content rules; relay.c binds to
  INADDR_LOOPBACK which handles both

- relay.html postRequest(): catch TypeError and surface the clear
  "portpass-relay is not running — start it first" message instead of
  the raw "NetworkError when attempting to fetch resource." that was
  previously shown when the relay server was not running

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove all [portpass] and [portpass relay] console.log statements added
during cross-platform debugging. Revert relay.html close delays: fill
popup back to 100ms, sendError back to 3000ms.

Fix syntax error in delegates.js verifyAndUpdate introduced by log removal
(missing try/catch closing braces).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The cleanup agent removed console.log lines but also accidentally:
- Stripped the closing }) and .catch of postError in processAutofillIntent,
  leaving it as an unclosed arrow function (Vite compile error)
- Removed the timestamp expiry check (age > 60s) in checkPendingAutofillRequests
  along with its log line — requests would never expire
- Dropped try/catch closing braces in delegates.js verifyAndUpdate

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Update SECURITY.md:
- Autofill section under dedicated-profile mitigation: describe both
  same-profile and cross-profile modes; explain delegate model + portpass-relay
  allows cross-profile without sacrificing extension isolation
- Add "Autofill security" section: trusted islands, ECDSA authentication,
  ECDH+AES-GCM credential encryption, relay server as dumb pipe, limitations
  (credential in DOM, bookmarklet theft, install-time boundary)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…extensions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The clean profile must be free of extensions to ensure the bookmarklet
dragged to the destination browser is genuine. Extensions in the
destination browsing profile cannot intercept it because the key arrives
already stored in the bookmark store.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
README.md:
- Add Autofill to feature list
- Update Password Safe comparison: autofill into native apps is still
  missing, but web form autofill via bookmarklet is now a Portpass strength
- New Autofill section: how it works, same-profile vs cross-profile,
  setup steps, autotype codes, best practices; references SECURITY.md
  for delegate model and threat details

SECURITY.md:
- Already committed in prior commits this session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Firefox doesn't implement the File System Access API, so the app
previously showed a dead-end error. Now it falls back to <input
type="file">, opens the vault with readonly:true, and skips features
that need a persistent handle (recent vaults, secondary auto-unlock,
biometric offer).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ict)

Updates all hardcoded port references in Dashboard.svelte, relay.html,
and the CSP connect-src in index.html.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
delegates.js:
- Replace useCount/lastUsed with bcCount/bcLastUsed and relayCount/relayLastUsed
- verifyAndUpdate() takes a channel arg ('bc' or 'relay') to increment the right pair
- Migration zeros counters on old delegate records
- getRelayUrl/setRelayUrl: per-vault relay URL stored in IDB (default http://localhost:7577)

store.js: add relayUrl writable store

Dashboard.svelte:
- Init relayUrl store from IDB on vault open
- Use relayUrl store for all relay server fetch URLs (replaces hardcoded localhost:7577)
- Pass 'bc' or 'relay' channel to verifyAndUpdate at each call site

VaultSheet.svelte:
- Delegate rows show total uses (bc + relay) and max last-used timestamp
- Advanced section: relay URL input with Save/Cancel when dirty, 3-state status
  indicator (blank/ok/error) probed on open and every 5s, count of cross-profile
  autofill uses across all delegates
- Probe uses AbortSignal.timeout(500ms); status blank until first probe completes

relay.c: add GET /status → 200 "ok" for VaultSheet probe

vault_sheet.spec.ts: fix 5 pre-existing failures — bookmarklet chip tests now go
through the + New bookmarklet flow before asserting chip visibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Relay now returns 204 No Content instead of 404, suppressing Chrome's
automatic console error logging on every poll cycle.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chrome logs connection errors at the network layer before JS sees them —
they can't be suppressed. Backoff reduces the noise from once per 2s to
once per 30s when portpass-relay is not running.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
dbro and others added 28 commits May 24, 2026 19:34
concurrent_save: c3a13c3 shortened the conflict dialog message but didn't
update the test assertion — "modified by another Portpass instance" → "modified
since it was loaded".

clipboard: the intermediate readText() assertion at line 99 raced with the
200ms mock timer; the timer could clear the clipboard between waitForFunction
resolving and the next page.evaluate call. The assertion was redundant
(copyPassword already verified content via waitForFunction); removed it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…, remove dead base32 code

- pseudoRandomBytes: replace silent zero-padding fallback with panic; crypto/rand
  failure is an environment invariant violation, not a recoverable condition
- refreshEncryptedKeys: derive new EncryptionKey/HMACKey into locals and write back
  to db only after all operations succeed, preventing partial struct mutation on error
- RecordEdit: remove dead base32Encode/base64ToBase32 helpers; TwoFactorKey is always
  withheld (null) in the record view, so totpSecret initialises directly to ''

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…version

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tab interception

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Use `npm ci` instead of `npm install` in `make setup` to enforce lock
  file and prevent silent dependency drift in CI and local builds
- Run `npm audit fix`: upgrade Svelte 5.55.5→5.55.9 and devalue 5.8.0→5.8.1
  to clear two reported vulnerabilities (both SSR-only, not exploitable here)
- Reject opening a secondary vault whose UUID matches an already-open
  secondary vault, preventing silent eviction of a legitimate vault from
  WASM memory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Per the pwsafe v3 spec, unknown property IDs within field 0x30 must be
retained when the database is saved. Adds UnknownProps storage to
CustomField, populates it in parseCustomFields, writes it back in
marshalCustomFields, and propagates it through the WASM UpdateRecordFields
path. Adds round-trip unit tests at both the parse/marshal and file level.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ss API

- Fix showOpenFilePicker detection to use typeof check (iOS has the
  property but it's not a callable function)
- Open vaults via fallback <input type="file"> on iOS; mark them
  read-only since createWritable() isn't supported
- Enable Edit button for read-only vaults (primary and secondary),
  showing a 'read-only' chip next to it
- Show 'Save as' instead of 'Save' when editing a read-only vault;
  triggers showSaveFilePicker (Firefox) or blob download (iOS)
- After Save As, update selectedFile so the vault becomes writable
  going forward
- Keep FAB and desktop New button visible regardless of read-only status
- Enable biometric/PIN unlock on fallback (iOS) path: offer after
  password unlock, show button on file re-selection when enrolled
- Support 'Unlock additional vault' on iOS via hidden file input fallback
- Handle read-only secondary vaults with the same Save As flow
- Add readonly_vault.spec.ts (10 tests); update multi_vault and unlock
  specs to reflect new read-only Edit/chip behaviour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bare URLs (e.g. yahoo.com) were treated as relative paths, routing
to /portpass/yahoo.com. Prepend https:// when no scheme is present
in all three code paths: RecordRead link, RecordList context menu,
and Dashboard Enter-key handler. Tests cover all three paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(pointer: fine) checks only the primary pointer — on Windows laptops
with touchscreens, the touchscreen is primary so the check fails even
with a mouse/trackpad connected. Switch to (any-pointer: fine) so the
autofill fields appear whenever any fine pointer is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Opening the same vault file as a secondary (or re-adding an already-open
secondary) caused closeDatabase() to remove the live vault from WASM memory,
breaking all subsequent record reads and vault detail lookups.

Fix 1 (JS): remove the closeDatabase() calls in the duplicate-detection
early-return paths in confirmSecondarySetup() — the vault is already in
the WASM map under that UUID, so closing it would destroy the live instance.

Fix 2 (Go/WASM): openDB() now skips the map store if the UUID already exists,
preserving any unsaved in-memory edits if the same file is opened again.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- autofill.html: onOpenerMessage now checks event.source === window.opener
  before processing init messages, closing a theoretical spoofing path
  (matches the guard already present in onBookmarkletMessage)
- biometric.js: remove leftover [DEBUG] console.error in unlockWithBiometric
  catch block that was leaking WebAuthn error details in production

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
autofill.html was missing the same baseline security headers present in
index.html. Adds a tight CSP (no WASM eval needed, inline styles required
since styles are embedded, ws://localhost:7577 for switchboard) and
no-referrer referrer policy to prevent leaking the current page URL.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- autofill.html: remove || '*' fallback on all outgoing postMessage calls;
  fill message (which carries credential data) and cancel messages now
  require bookmarkletOrigin to be set, matching the guard on event.source
  added previously. ready signal legitimately keeps '*' (fired before init).
- index.html: base-uri 'self' → 'none' to block <base> tag injection
  entirely, consistent with autofill.html which already had 'none'.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
script-src 'self' blocks inline scripts; autofill.html is entirely
inline JS with no build tooling to generate script hashes, so
'unsafe-inline' is required. The CSP still restricts object-src,
base-uri, connect-src, and img-src.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace regex/startsWith scheme checks with url.includes('://')
which handles any scheme (http, https, ftp, ssh, etc.). Also convert
the external-link icon in RecordRead from a bare <a href> to an
onclick handler with window.open(), so the browser never resolves the
URL as a relative path regardless of template-expression evaluation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Try keyboard events first (for SPA Enter-key listeners), then look for
[type=submit] or [default-button] within the closest form, then fall back
to the sole button in the form if there is exactly one. Page-level button
searches are intentionally excluded to avoid clicking unrelated buttons.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a Vite transform plugin that runs terser (compress + mangle) on the
DELEGATE_BOOKMARKLET_IIFE source at build time, replacing the .toString()
call with the pre-minified string literal. Bookmarklet source stays
readable; generated javascript: URLs are properly minified in both dev
and production.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Firefox lacks showOpenFilePicker and can't serialize FileSystemFileHandle
to IDB, so no handle was ever stored and the unlock screen always showed
blank. Three changes:

- recentHandles: always store `name` alongside handle; if IDB write fails
  (handle not serializable), retry storing name only
- StartPage: load remembered name from IDB even when supportsFilePicker is
  false; show it on unlock screen and open file picker on Unlock click
- StartPage: guard requestPermission/queryPermission calls (Chrome-only API
  extensions) so Firefox 111+ doesn't throw on the handle path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Uppercase text, slightly heavier weight, text-soft color, and
vertical alignment fix for the chip alongside the Edit button.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(any-pointer: fine) returns false on Windows touch devices (Surface,
tablet mode) even though bookmarklets work fine on any desktop OS.
Replace with UA-based mobile detection so Windows+Edge and Windows+Firefox
always show the autofill/delegate UI.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@dbro dbro merged commit 7c2f3fd into main May 27, 2026
3 checks passed
@dbro dbro deleted the feature-autofill branch May 27, 2026 08:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant