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>
13 KiB
Generator UX redesign + adjacent popup polish — design
Date: 2026-04-24 Scope: Replace the right-anchored popover that opens from the password generator trigger with an inline panel that lives inside the form. Swap the "gen" text button for a ✨ icon button. Tighten the label/affordance treatment in the touched screens (login form + vault settings) along the way. Backgrounds, palette, and other unrelated UI stay untouched.
Goal
The current popover (β₂, commit 8a16482) positions itself by anchoring its left edge to the trigger button's left edge, but the trigger sits on the right side of the password input row. Combined with the popover's min-width: 300px inside a 360 px Chrome popup, the popover always overflows the popup boundary by ~180–220 px. In manual testing it appears as a clipped card with cut-off labels and inaccessible buttons.
A surgical clamp-fix (~10 lines) would patch the symptom but leave the underlying UX awkward — even when fully visible, the popover floats over the form, hides what you were filling out, and crams two primary actions ("save default" + "use this value") next to each other. The user's feedback was explicit: "we may gotta plan some ui overhauls here, like an emoji instead of 'gen' and a cleaner UI approach for sure." This redesign replaces the popover pattern entirely instead of patching it.
Visual identity
Trigger button
- Icon: ✨ (U+2728 sparkles emoji). Reads as "auto-generate / freshly minted." Visually rhymes with the sparkle dot on the new logo's gem (commit
a3f13fd). - Color: deep gold
#7c5719background,#fff3cftext — matches primary-button styling from the palette refresh. - Hover state: background
#aa812a(mid gold). - Active state (panel open): background
#aa812a(visually distinct from idle so the user can tell at a glance whether the panel is open). - Layout: stays in the existing
.inline-rowpattern next to the password input; replaces the current<button class="btn" id="gen-btn">gen</button>with<button class="gen-trigger" id="gen-btn" aria-expanded="false">✨</button>. - Tooltip:
title="generate password"for hover. - Width: ~38 px (single emoji glyph fits without padding noise).
Inline panel (replaces popover)
When ✨ is clicked, a panel injects into the form's DOM between the password row and the next form-group (e.g., the totp-secret row). Other fields below shift down. The panel:
- Lives at the form's full available width (no positioning math, no clipping).
- Has a subtle gold border (
1px solid #aa812a) to feel attached to the trigger. - Auto-generates a preview the moment it opens, using
VaultSettings.generator_defaultsas the initial knob state.
Panel composition (top to bottom):
- Kind toggle — pill-style two-button switch:
random/passphrase. Active button: gold-bg. - Common knobs (always visible):
- For
random: length slider (8–48, default 20), four character-class checkboxes (a-z / A-Z / 0-9 / !@#). - For
passphrase(BIP39): word_count slider (3–10, default 4), separator text input (1 char), capitalization radio (lower / upper / title).
- For
- Preview row — generated value in monospace gold (
#f1cf6e), with a↻regenerate button. more ▾disclosure — when expanded, shows the rarely-used knobs:- For
random: symbol charset (safe/fulltoggle). - For
passphrase: nothing extra (separator and capitalization moved to common). - For both: an empty placeholder when no advanced knobs apply (so the disclosure always renders for consistency, even if collapsed-only).
- For
- Action row:
↑ save these as default— small underlined link, left-aligned,#8b949ecolor →#d2ab43on hover. Writes current knobs toVaultSettings.generator_defaultsvia the existingupdate_vault_settingsmessage; shows a brief "saved" toast next to the link; panel stays open. Demoted from primary button because most of the time the user just wants this password, not to change global defaults.cancel— secondary button (transparent bg, gray border).use— primary CTA: gold bg#7c5719,#fff3cftext. Commits the current preview value into the password input and closes the panel.
Adjacent polish (scope B)
Touched only in screens we're already modifying (login form + vault settings):
- Form labels:
.labelclass dropstext-transform: uppercaseand reducesletter-spacingfrom0.5pxto0.02em. Lowercase labels match the panel's knob labels and feel less shouty. Font weight goes 600 → 500 for slightly less visual weight; color stays#8b949e. - Required marker: the existing
*next to required-field labels picks up gold (#aa812a) instead of inheriting label gray, so it actually reads as a marker. - Button styles: primary form buttons (cancel/save at the bottom of the login form) already use the palette refresh; nothing to change there.
These polish changes apply to ALL form labels in the login form and vault settings (not just the password row), since the .label class is shared. Other forms that use .label (SecureNote, Identity, Card, Key, Totp, Document-coming-soon) will pick up the lowercase treatment automatically — that's a deliberate choice, not a side effect: the CAPS LOCK feel was a project-wide rough edge that's worth fixing in this slice.
Behavior
| Trigger | Action |
|---|---|
| click ✨ | toggle panel open/closed; auto-generate on first open using saved defaults |
| click ↻ | regenerate preview (no commit) |
| change a knob | debounced auto-regenerate (150 ms — same as existing) |
click use |
commit current preview into password field, close panel |
click cancel |
close panel without committing; password field unchanged |
click ↑ save these as default |
write current knobs to VaultSettings.generator_defaults; show toast; panel stays open |
| press Escape (when panel open) | close panel without committing |
| click ✨ again while panel open | close panel (no commit) |
The panel does NOT close on click-outside. The user might want to drag from the panel to verify the value or copy it before clicking use; closing on click-outside makes that fragile. Escape and explicit cancel/use are the dismissal paths.
Vault settings adaptation
The vault settings screen currently has a <button id="configure-gen">configure ▾</button> next to a generator-summary text line. After redesign:
- The "configure ▾" button becomes a ✨ button matching the login form trigger.
- When clicked, the same inline panel renders inside the vault-settings "generator" section (not as a popover).
- One difference from the login-form context: the action row drops the
cancelandusebuttons since there's no password input to fill — instead, the panel is purely for inspecting/configuring defaults. The↑ save these as defaultlink becomes the only action in this context, and ✨ closes the panel just like in the login form. - The generator preview text line (
generatorSummary(...)) stays above the panel even when expanded — it serves as a "current default" reference.
Files affected
Modified
extension/src/popup/components/generator-popover.ts— major rewrite. Probably gets renamed togenerator-panel.ts(cleaner semantics). Same module, different positioning (inline DOM injection vs absolute-positioned popover) and different action set per context.extension/src/popup/components/types/login.ts— replacegen-btntext content with ✨; update click handler to call the renamed module; drop the standalone close-on-blur logic if any.extension/src/popup/components/settings-vault.ts— replaceconfigure-genbutton content with ✨; update click handler; render the inline panel in place rather than calling the popover open.extension/src/popup/styles.css— add.gen-triggerrule (button styling); add.gen-paneland child rules (replacing.generator-popoverrules). Modify.labelrule to drop uppercase and tighten letter-spacing/weight; modify.label .req(or equivalent for the*) to gold. Remove the.generator-popoverrules entirely once the new panel works (no need to keep old popover CSS around).
Renamed
extension/src/popup/components/generator-popover.ts→extension/src/popup/components/generator-panel.ts. Test file follows:__tests__/generator-popover.test.ts→__tests__/generator-panel.test.ts. Update imports inlogin.ts,settings-vault.ts, and the test file accordingly. Sequencing decision (git-mv first vs rewrite first) noted in open questions.
Updated tests
extension/src/popup/components/__tests__/generator-popover.test.ts(renamed): existing 7 tests cover knob → message-shape behavior. Most should survive verbatim — they're DOM-level, not positioning-level. Update test setup to mount the panel inline (in a parent container) rather than asserting ondocument.bodychildren. Add 2–3 new tests:- Panel opens via aria-expanded toggling on the trigger
- Panel auto-generates on first open
- Escape key closes the panel
Markup unchanged but new selectors
- The
.inline-rowpattern in login form stays. Just the button content/styling changes.
Acceptance
- Clicking ✨ on the login form opens an inline panel below the password row.
- Panel auto-generates a preview using current
VaultSettings.generator_defaults. - Knob changes debounce-regenerate; ↻ button forces a regenerate.
usebutton commits preview into password input and closes panel.cancelbutton closes panel without committing.- Escape key closes panel without committing.
- Clicking ✨ again while panel open closes it.
↑ save these as defaultlink writes toVaultSettings.generator_defaults; toast appears; panel stays open.- Vault settings ✨ button opens the same panel inline (no popover);
↑ save these as defaultis the only action; ✨ toggles closed. - All form labels in login + vault settings are lowercase with reduced letter-spacing.
- Required-field
*marker is gold (#aa812a). - No element overflows the popup right edge in any state.
bun run testpasses (existing 7 generator tests survive the rename + 2-3 new tests added → ~9–10 generator-panel tests; total still around 124–127).bunx tsc --noEmitclean.bun run build:allclean (Chrome + Firefox).- No new automated tests for the visual polish (label casing, gold
*) — visually verified. - Manual: walk through both contexts (login form + vault settings) on Chrome and Firefox.
Out of scope
- The capture-prompt and ack-prompt content scripts (still use their own button styling — no change here).
- The setup tab's strength-bar / advice-block (touched in logo-refresh palette swap; nothing more to do).
- Other popup forms beyond their
.labelclass picking up the lowercase treatment automatically (no per-type form rework). - Generator output strength visualization (zxcvbn meter inside the panel) — could be a future polish but not now.
- Multi-preview / "show 3 candidates" pattern — keeping the single-preview + regenerate flow.
- Animation/transitions on panel open-close — purely instant for now (a fade or slide-down can be added later as polish without breaking anything).
- Click-outside-to-close — explicitly NOT included (see Behavior section reasoning).
Open questions deferred to plan
- Module rename ordering: is it cleaner to (a) rewrite in-place keeping the
generator-popover.tsfilename then rename in a follow-up, or (b) git-mv first then rewrite? Plan ships (b) — git-mv preserves history, reviewers see "rename + edits" cleanly. - Test mounting strategy: existing tests
document.body.appendChild(host)then assert. New panel mounts inside a parent. Plan: tests create a parent div, pass it as the mount target to a newopenGeneratorPanel(opts)signature that takes{ parent, anchor, initial, onPicked, onCancel }. The login-form caller passes the form element asparent. - The "more ▾" placeholder: for passphrase mode, all knobs are common and there's nothing in advanced. Plan: render the disclosure with text "(no advanced options for passphrase)" when expanded, OR hide the disclosure entirely in passphrase mode. Plan ships the hide-when-empty option — less visual noise.
save defaulttoast: existing toast infrastructure in popup? If yes, reuse. If not, the smallest toast = a 1.5s fade-in/fade-out span next to the↑ save these as defaultlink saying "✓ saved". Plan picks based on what already exists.- Vault-settings panel ✨ — when no defaults exist: the very first time a vault is created,
VaultSettings.generator_defaultsshould already be initialized (it is, per β₂). Confirm and document.