# Password display character-class coloring

**Status:** design
**Target release:** v0.4.0 (or earlier — bundles cleanly with active fullscreen UX work)
**Scope:** extension only — popup (`extension/src/popup/`), fullscreen vault (`extension/src/vault/`), settings UI for color customization
**Out of scope:** CLI parity (TTY color escapes for revealed passwords are a separate problem; defer until there's user demand), per-item color overrides, theming the rest of the extension, coloring inside copy-to-clipboard payloads (clipboard always carries plaintext).

## Background

When a password is revealed in 1Password's UI, characters are colored by class to make passwords easier to scan, dictate, compare, and transcribe:

- digits — distinct color (1Password uses blue)
- symbols — distinct color (1Password uses red)
- letters — default text color

Concrete benefits:

- reading a generated password aloud without confusing similar-shaped characters
- spotting transcription errors when typing a password into a non-relicario field
- visually parsing dense symbol runs in long generated passwords

relicario's password reveal currently renders a flat-colored monospace string. Today this happens in:

- `extension/src/popup/components/field-history.ts` (history viewer's revealed cells)
- whatever vault item-detail view renders the current password value (popup + fullscreen `vault/`)
- the generator preview as a candidate password is rolled

This spec adds a single shared utility that colorizes those renders, plus a settings surface for users to pick custom digit/symbol colors.

## Goals

1. Color-code revealed password characters by class — digit, symbol, letter — across all extension surfaces that display revealed passwords.
2. Default scheme: digits blue, symbols red. Letters use existing text color.
3. User-customizable digit and symbol colors via a settings page, persisted in `chrome.storage.sync` so preferences follow the user across browser profiles.
4. Single source of truth: one `colorizePassword()` helper used everywhere, so adding a new password-display surface in the future inherits the coloring automatically.

## Non-goals

- Coloring confusable-character pairs (`l`/`1`/`I`, `0`/`O`) with a third color. Possible future work; out of scope here.
- CLI parity. The CLI currently doesn't render revealed passwords inline (it shells the value to clipboard or stdout); ANSI coloring would be a separate decision.
- Coloring OTP/2FA codes. They are all digits and would gain nothing.
- Affecting the copy-to-clipboard pathway. Clipboard payloads remain plaintext.

## Design

### Character classification

Three classes via simple regex over Unicode codepoints:

- `digit` — `/^\d$/` (matches Unicode `Nd` category via JS `\d`)
- `letter` — `/^[\p{L}]$/u`
- `symbol` — anything else (punctuation, symbols, whitespace)

Each codepoint is classified once; the helper batches consecutive same-class codepoints into a single `<span>` to keep the DOM small for long passwords.

### `colorizePassword` utility

New file: `extension/src/popup/components/password-coloring.ts` (or `extension/src/shared/` if a shared module already exists; reviewer of the plan to decide).

```ts
export function colorizePassword(password: string): DocumentFragment {
  // Returns a fragment of <span class="pwd-digit|pwd-symbol|pwd-letter">…</span>
  // runs covering the full input. Empty input → empty fragment.
}
```

Pure function, no DOM mutation outside the returned fragment. Easy to unit-test with `JSDOM`.

### CSS

Defined in the existing extension stylesheet(s) — popup and vault both import a shared rule set:

```css
:root {
  --relicario-pwd-digit-color: #2563eb;   /* blue-600 */
  --relicario-pwd-symbol-color: #dc2626;  /* red-600 */
}
.pwd-digit  { color: var(--relicario-pwd-digit-color); }
.pwd-symbol { color: var(--relicario-pwd-symbol-color); }
.pwd-letter { color: inherit; }
```

User customization is implemented by a tiny `applyColorScheme()` boot step that reads `chrome.storage.sync.password_display_scheme` at popup/vault startup and writes the values onto `document.documentElement.style` as inline `--relicario-pwd-*-color` overrides. No CSS-in-JS, no runtime style injection for each render — set once, read by every subsequent `colorizePassword()` output.

### Storage shape

```jsonc
// chrome.storage.sync key: "password_display_scheme"
{
  "digit_color": "#2563eb",   // hex string, validated on read
  "symbol_color": "#dc2626"
}
```

Missing key or invalid values → fall back to defaults, no error surface. Sync (not local) so preferences propagate across the user's browser profiles. No security implication — purely cosmetic.

### Settings UI

Adds a **Display** section to the existing extension settings page (the page reachable from the gear icon — exact route to be confirmed by the plan; the existing settings surface is in `extension/src/popup/components/settings.ts` or equivalent).

- Two color pickers labelled "Digit color" and "Symbol color".
- Live preview swatch beneath the pickers showing a sample password (`Abc123!@#xyz`) rendered with the candidate colors. Updates as the user changes pickers.
- "Reset to defaults" button — clears the storage key, swatch reverts to defaults.
- Inline accessibility hint: if the chosen color falls below WCAG AA contrast (≥ 4.5 : 1) against the surface's background color, show a subtle "may be hard to read on this background" warning under the picker. Non-blocking — the user can still save.

### Surfaces to update

Each touchpoint just swaps a textContent assignment for `colorizePassword(value)` and appends the returned fragment.

- Vault item detail (popup view) — wherever the password field renders its revealed value.
- Vault item detail (fullscreen view) — same logic for `extension/src/vault/`.
- Field-history viewer (`field-history.ts:72`) — the `<div class="history-entry__value revealed">` content swap.
- Generator preview — the live candidate-password preview as the generator rolls.

The fullscreen UX redesign (Phase 1, per `docs/superpowers/plans/2026-04-30-fullscreen-ux-phase-1-visual-foundation.md` and the recent commit `9ed7e7c`) is currently in flight. This spec coordinates with that work: if any reveal-rendering code is being rewritten as part of Phase 1, the rewrite should call `colorizePassword()` instead of plain text-content. The plan author should confirm with the user (they're doing the web-UX work themselves, per their own message) whether to land this concurrently or stage it as a follow-up.

## Migration

Additive only. No data migration. Users without a stored scheme get defaults. Existing tests for password-display behavior may need updating to expect the span structure instead of plain text — those updates are part of this work.

## Testing

Unit (Vitest, matching existing extension test conventions):

1. `colorizePassword("aB3$xY")` returns spans in correct classification order: `pwd-letter "aB"`, `pwd-digit "3"`, `pwd-symbol "$"`, `pwd-letter "xY"`.
2. Empty string returns empty fragment, zero children.
3. All-letters / all-digits / all-symbols inputs produce a single span of the appropriate class.
4. Unicode letters (e.g., `áñü`) classify as `pwd-letter` via `\p{L}`.
5. Whitespace classifies as `pwd-symbol` (verified, not accidental).
6. Snapshot test on a representative password: `aB3$xY7&_!` → expected fragment structure.

Integration:

7. `applyColorScheme()` reads `chrome.storage.sync` and sets CSS variables on `document.documentElement`. Mock `chrome.storage.sync.get`, assert resulting inline style.
8. Settings UI: changing a picker writes to storage; reset clears storage; both reflected in subsequent renders via the storage event listener.

Visual regression:

9. Manual: open vault, reveal a password with mixed character classes, confirm coloring matches expectation in popup and fullscreen views.

## Open questions

- Whether to colorize concealed (non-password) fields — the existing `concealed` field type also reveals on click. Default position: yes, apply the same coloring; concealed fields are typically API tokens/keys with mixed character classes, so they benefit equally. Confirm with user during implementation.
- Whether to add a third color for "look-alike" characters (defer; small follow-up if/when the user asks).
- Exact route for the Display section in settings (popup-settings vs vault-settings vs both). Plan to resolve based on existing settings architecture.
