# Relicario — Credential Capture Design

Experimental feature that detects login form submissions and prompts the user to save or update credentials in the vault. Configurable prompt style (notification bar or toast). Off by default.

## Scope

- Content script: detect form submissions with password fields, capture credentials
- Prompt UI: injected notification bar or floating toast (user-configurable)
- Dedup: check manifest before prompting — skip if already saved, offer update if password changed
- Blacklist: "Never for this site" option, persisted in `chrome.storage.local`
- Settings: enable/disable capture, choose prompt style
- Popup: settings view accessible from unlock screen

## Trigger

The content script listens for two events on forms that contain a password field:

1. `submit` event on the `<form>` element
2. `click` event on submit buttons (`button[type=submit]`, `input[type=submit]`, or buttons inside the form)

When triggered:
1. Read the username value from the detected username field (same detection priority as `detector.ts`)
2. Read the password value from the password field
3. If either is empty, skip
4. Send `{ type: 'check_credential', url, username, password }` to the service worker

## Service Worker: `check_credential` Message

New message type added to the Request union:

```typescript
{ type: 'check_credential'; url: string; username: string; password: string }
```

Response:

```typescript
{ ok: true; data: { action: 'save' | 'update' | 'skip'; entryId?: string; entryName?: string } }
```

Logic:
1. If vault is locked, respond `skip`
2. Check `captureBlacklist` in `chrome.storage.local` — if the URL's hostname is blacklisted, respond `skip`
3. Check `captureEnabled` setting — if false, respond `skip`
4. Search manifest entries by hostname match (same as `findByUrl`)
5. If no match: respond `{ action: 'save' }`
6. If match with same username and same password: respond `{ action: 'skip' }` (already saved)
7. If match with same username but different password: respond `{ action: 'update', entryId, entryName }` (password changed)
8. If match with different username: respond `{ action: 'save' }` (new account on same site)

To compare passwords in step 6/7, the service worker must decrypt the matched entry to read the stored password. This is acceptable because it only happens on form submission, not on every page load.

## Prompt UI

Two styles, user-configurable:

### Bar Mode (default)

A fixed-position bar at the top of the page, injected into the DOM:

```
┌──────────────────────────────────────────────────────────────────┐
│  Relicario: Save login for github.com? (alee)   [Save] [Never] [✕] │
└──────────────────────────────────────────────────────────────────┘
```

- Background: #161b22, border-bottom: 1px solid #30363d
- Text: #c9d1d9, monospace font
- Slides down from top with CSS transition
- z-index: 2147483647 (max, above everything)
- Save button: #1f6feb, Never button: #21262d, Dismiss: ✕ icon
- For updates: "Update password for github.com? (alee)" with [Update] button

### Toast Mode

A floating element in the bottom-right corner:

```
┌─────────────────────────────────┐
│  Relicario                         │
│  Save login for github.com?     │
│  alee                           │
│  [Save] [Never] [✕]            │
└─────────────────────────────────┘
```

- Position: fixed, bottom: 16px, right: 16px
- Same color scheme as bar mode
- Border: 1px solid #30363d, border-radius: 4px
- Auto-dismiss after 15 seconds if not interacted with
- For updates: same layout with "Update password?" text

### Prompt Behavior

When user clicks:
- **Save:** Content script sends `add_entry` message to service worker with `{ name: hostname, url: full_url, username, password }`. On success, prompt shows brief "Saved" confirmation then disappears.
- **Update:** Content script sends `update_entry` message with the existing entry ID and new password. Brief "Updated" confirmation.
- **Never:** Content script sends `{ type: 'blacklist_site', hostname }` to service worker, which appends to `captureBlacklist` in `chrome.storage.local`. Prompt disappears.
- **Dismiss (✕):** Prompt disappears. No action taken. Will prompt again next time.

## Settings

Stored in `chrome.storage.local` under key `settings`:

```typescript
interface RelicarioSettings {
  captureEnabled: boolean;    // default: false
  captureStyle: 'bar' | 'toast';  // default: 'bar'
}
```

Plus a separate key `captureBlacklist: string[]` (array of hostnames).

### Settings View in Popup

Accessible from the unlock screen via a "settings" link (already exists as a button). New popup view `settings` that shows:

```
← back

SETTINGS

CREDENTIAL CAPTURE (experimental)
[toggle] Auto-detect logins
Style: [bar ▾] / [toast]

BLACKLISTED SITES
github.com  [✕]
netflix.com [✕]
```

The toggle and style selector write to `chrome.storage.local`. Blacklist entries can be removed individually.

## New Message Types

```typescript
// Request
| { type: 'check_credential'; url: string; username: string; password: string }
| { type: 'blacklist_site'; hostname: string }
| { type: 'get_settings' }
| { type: 'update_settings'; settings: Partial<RelicarioSettings> }
| { type: 'get_blacklist' }
| { type: 'remove_blacklist'; hostname: string }

// Response for check_credential
{ ok: true; data: { action: 'save' | 'update' | 'skip'; entryId?: string; entryName?: string } }
```

## File Structure

### New files

```
extension/src/content/capture.ts    # Form submission listener + prompt injection
extension/src/popup/components/settings.ts  # Settings view
```

### Modified files

```
extension/src/content/detector.ts   # Import and init capture module
extension/src/service-worker/index.ts  # Handle new message types
extension/src/shared/messages.ts    # Add new Request/Response types
extension/src/shared/types.ts       # Add RelicarioSettings interface
extension/src/popup/popup.ts        # Add 'settings' view to state machine
extension/src/popup/components/unlock.ts  # Wire up settings button
```

## Security

- Credentials are captured from the DOM only on form submission — no keylogging, no continuous monitoring
- Captured credentials are sent to the service worker via `chrome.runtime.sendMessage` (same secure channel as autofill)
- The prompt UI is injected into the page DOM but styled with inline styles and high z-index to avoid CSS conflicts
- The "Never" blacklist prevents unwanted prompting but doesn't affect manual autofill

## Non-Goals

- Detecting password change forms (change old → new password flows)
- Capturing credentials from non-standard login flows (OAuth redirects, SSO)
- Syncing settings across devices
