# 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 `#7c5719` background, `#fff3cf` text — 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-row` pattern next to the password input; replaces the current `` with ``. - **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_defaults` as the initial knob state. Panel composition (top to bottom): 1. **Kind toggle** — pill-style two-button switch: `random` / `passphrase`. Active button: gold-bg. 2. **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). 3. **Preview row** — generated value in monospace gold (`#f1cf6e`), with a `↻` regenerate button. 4. **`more ▾` disclosure** — when expanded, shows the rarely-used knobs: - For `random`: symbol charset (`safe` / `full` toggle). - 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). 5. **Action row:** - **`↑ save these as default`** — small underlined link, left-aligned, `#8b949e` color → `#d2ab43` on hover. Writes current knobs to `VaultSettings.generator_defaults` via the existing `update_vault_settings` message; 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`, `#fff3cf` text. 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:** `.label` class drops `text-transform: uppercase` and reduces `letter-spacing` from `0.5px` to `0.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 `` 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 `cancel` and `use` buttons since there's no password input to fill — instead, the panel is purely for inspecting/configuring defaults. The `↑ save these as default` link 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 to `generator-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`** — replace `gen-btn` text 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`** — replace `configure-gen` button content with ✨; update click handler; render the inline panel in place rather than calling the popover open. - **`extension/src/popup/styles.css`** — add `.gen-trigger` rule (button styling); add `.gen-panel` and child rules (replacing `.generator-popover` rules). Modify `.label` rule to drop uppercase and tighten letter-spacing/weight; modify `.label .req` (or equivalent for the `*`) to gold. Remove the `.generator-popover` rules 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 in `login.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 on `document.body` children. 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-row` pattern 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. - [ ] `use` button commits preview into password input and closes panel. - [ ] `cancel` button closes panel without committing. - [ ] Escape key closes panel without committing. - [ ] Clicking ✨ again while panel open closes it. - [ ] `↑ save these as default` link writes to `VaultSettings.generator_defaults`; toast appears; panel stays open. - [ ] Vault settings ✨ button opens the same panel inline (no popover); `↑ save these as default` is 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 test` passes (existing 7 generator tests survive the rename + 2-3 new tests added → ~9–10 generator-panel tests; total still around 124–127). - [ ] `bunx tsc --noEmit` clean. - [ ] `bun run build:all` clean (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 `.label` class 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.ts` filename 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 new `openGeneratorPanel(opts)` signature that takes `{ parent, anchor, initial, onPicked, onCancel }`. The login-form caller passes the form element as `parent`. - **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 default` toast:** 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 default` link 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_defaults` should already be initialized (it is, per β₂). Confirm and document.