DEV-C P2: Promise.all meant one rejected RPC failed the whole render.
allSettled + per-slot fallback keeps the active-devices surface usable
when the revoked-list feed (or one bad ssh fingerprint) is down.
Two call sites converted in devices.ts:
1. list_devices + list_revoked pair — revoked failures now render an
inline "couldn't load" slot instead of failing the page.
2. sshFingerprint map — one bad public key falls back to '(unknown)'
instead of killing the whole device list.
trash.ts only has a single sendMessage in its load path on this branch,
so it has no Promise.all to migrate. Plan was written against a slightly
different snapshot; documented divergence in report.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
DEV-C P2: settings.ts:56-65 and settings-vault.ts:15-22 had near-
identical cleanup paths. Single source for closeGeneratorPanel +
activeKeyHandler removal. Helper takes the handler as a parameter and
returns null so each caller still owns its own module-scoped handler
state.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tests were written against the pre-Stream B flat settings page. After
the left-nav restructure (bd6a301) and the management-surfaces revamp,
the Display section's IDs are only in the DOM once the user navigates
there, and renderSettings makes additional sendMessage calls (is_unlocked,
per-section data) that the original mocks didn't cover.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Tests predated the 2026-05-24 management-surfaces revamp (047df6e): popup
devices pane now shows SHA-256 fingerprint + added-by + inline two-step
revoke confirm, and the SW revokeDevice signature may have shifted to
match. Mocks + assertions updated accordingly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add settings-security.ts with renderSecuritySection / teardownSecuritySection
- Three states: amber warning (no QR), green status (QR set up), modal overlay (show/print SVG)
- Device list with inline revoke; passphrase collected via prompt()
- QR payload never written to chrome.storage; only recovery_qr_generated_at timestamp stored
- Add generate_recovery_qr / unwrap_recovery_qr message types to messages.ts + POPUP_ONLY_TYPES
- Add SW handlers in popup-only.ts delegating to wasm_generate_recovery_qr / wasm_unwrap_recovery_qr
- Declare wasm_generate_recovery_qr and wasm_unwrap_recovery_qr in wasm.d.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Notes, custom-fields disclosure, attachments disclosure, and form-actions
in fullscreen logins now sit inside a .form-lower wrapper with the same
max-width: 960px; margin: 0 auto envelope as .form-grid above. Removes
the visual rhythm break at the 2-col -> full-width transition.
Popup keeps its current single-column behavior (gated on surface flag).
Add data-field-kind attribute to renderConcealedRow so wireFieldHandlers
can distinguish password fields from other concealed rows (TOTP secrets,
CVV, PIN, private keys). Apply colorizePassword() on reveal when kind is
"password"; plain textContent otherwise. Pass kind through renderSections
for custom-section password fields.
Import colorizePassword and post-process .revealed value cells after
innerHTML render, replacing escaped-HTML text with colored spans via
the valueStore plaintext lookup.
Programmatic input.value = newPassword does not fire input events, so
the strength-meter listener at shared/form-affordances/password-tools.ts:65
never re-rates the new value — meter stays stuck on the prior reading.
Extract applyGeneratedPassword(input, value) helper that sets value, type,
then dispatches new InputEvent('input', { bubbles: true }). Vitest covers
the dispatch + a sanity check that bubbling listeners fire.
The form pane gets a flex column layout: scrollable content above,
sticky save bar at bottom. Bar uses translucent fill with backdrop-blur
and a 24px gradient fade so content scrolls under it. Save / cancel
buttons reuse the form's existing handlers via externalActions flag.
renderForm() takes an optional { surface: 'popup' | 'fullscreen' }
parameter. When 'fullscreen', the Identity and Credentials field
groups render as glass cards inside a .form-grid (two columns,
stacks at <=720px). Popup keeps its single-column layout.
Restructures the unlock screen so the form sits in a glass card with
a primary 'unlock vault' button. Logo, brand, and tagline are grouped
as a lockup. Open-vault and settings are demoted to secondary buttons.
Body gets the .surface-backdrop wrapper.
- Show revoked devices in collapsible section with strikethrough styling
- Fetch revoked.json via new list_revoked message + router case
- Registration flow uses register_device WASM API (private keys internal)
- Display revoked_by and timestamp for each revoked entry
- Update setup wizard to use new register_device API
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Brand name uses capital R in user-facing text — extension UI strings,
CLI clap help / descriptions / error prose, markdown docs. Lowercase
preserved for the binary command, crate names, npm package, file
paths, env vars, and code identifiers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Whole-branch review recommendation: switch renderFormHeader's signature
from positional (titleText) to options ({ titleText }) so Phase 3 can
add 'dirty' (and any future hooks like a save-keybinding hint) without
touching all 7 call sites in lockstep with the unsaved-guard work.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code-review feedback on Task 8: the conditional empty
<div style="margin-bottom:16px;"> spacer was an inline-styled magic
number and the 6-line header pattern was duplicated across all 7 typed
forms.
Now:
- .form-header class owns the bottom margin in both stylesheets.
- :has(+ .form-subtitle) selector drops the margin when a subtitle
follows, so spacing tokens stay in CSS instead of inline styles.
- renderFormHeader(titleText) shared helper collapses the 6-line
duplication to a one-liner per form. item-form.ts (type-selection
screen) is unaffected — it uses a different header structure.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
All seven type forms plus the type-selection screen now show a small
'esc to cancel' subtitle under the heading when rendered in the
fullscreen vault tab (isInTab() === true). The subtitle is suppressed
in the popup, where esc has the more general meaning of closing the
popup. .form-subtitle class is shared between popup and vault
stylesheets so future hooks can reuse it.
Dynamic dirty-state ('unsaved · esc to cancel') wiring is deferred to
Phase 3 (unsaved-changes guard).
Plan 2026-04-30 fullscreen UX phase 1 task 8.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code-review feedback on Task 7: the same Array<[name, renderForm]> of
all 7 typed forms appeared in three test files (required-pill,
popout-button, popout-button-fullscreen). A new typed form would have
required updating all three.
Now defined once in __tests__/_typed-forms.ts. Future typed-form
additions get regression coverage automatically.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The ⤴ popout button is meaningless when the form is already in
vault.html — gate it on !isInTab(). Affects all seven type forms plus
the type-selection screen. Regression tests cover both popup (button
present) and fullscreen (button absent) contexts via it.each across
all 7 forms.
Plan 2026-04-30 fullscreen UX phase 1 task 7.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
▦ trash and ⌬ devices in the popup settings panel now match the
fullscreen sidebar's glyph language. Lowercased labels match the brand.
Plan 2026-04-30 fullscreen UX phase 1 task 6.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Code-review feedback on Task 4:
- Test expanded from login-only to it.each across all 7 type forms
(14 assertions total). A future revert to <span class="req">*</span>
in any form now fails CI.
- .label .req rule removed from popup/styles.css and vault/vault.css —
zero consumers after the REQUIRED_PILL_HTML migration.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces ten <span class="req">*</span> sites across all seven type
forms with the shared REQUIRED_PILL_HTML snippet ('required' badge).
Adds a regression test pinning the new HTML in the login form.
Plan 2026-04-30 fullscreen UX phase 1 task 4.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New vault.html#import panel with a file picker, parse-preview
("N logins, M notes, K skipped — proceed?"), confirm/cancel
buttons, inline progress, and a post-import warnings list. The
popup's settings-vault view links to it via a new
"LastPass CSV →" button next to "Backup & restore →".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Closes three audit gaps in one pass:
1. Sync now button in the popup settings view (📤). Triggers the existing
{ type: 'sync' } SW message and surfaces success / failure inline. The
SW message was already wired but had no UI entry point.
2. Device registration from the popup. The "Register this device" button
on the devices view used to error out with a "not yet implemented"
message; it now opens an inline name input (default = browser+OS), and
on confirm sends a new register_this_device SW message that generates
an ed25519 keypair via WASM, persists private_key + name to
chrome.storage.local, and writes the public key to the remote
devices.json. No setup-wizard detour.
3. Vault tab is now an authorized sender for popup-only SW messages. The
router accepts vault.html alongside popup.html, so the fullscreen tab
can drive the same flows. Test covers acceptance from the vault tab.
New SW message: register_this_device { name }. Added to PopupMessage and
POPUP_ONLY_TYPES, handled in router/popup-only.ts.
Tests: 5 new vitest cases (3 in settings.test.ts, 2 in devices.test.ts)
+ 1 router test for vault-tab acceptance. All 194 extension tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Introduce shared/state.ts as a service-locator so popup components
(item-detail, item-form, trash, devices, settings, etc.) work in both
the popup and vault tab bundles. Both entry points register themselves
as the host; components import from shared/state instead of popup.ts.
Vault.ts now delegates to the real popup components, removing ~300 lines
of placeholder renderers.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>