# 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 `<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_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 `<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 `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.
