# Dev B Kickoff Prompt — v0.5.1 Stream B (Settings UX Redesign) > **For agentic workers:** REQUIRED SUB-SKILL: Use `superpowers:subagent-driven-development` (recommended) or `superpowers:executing-plans` to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. Paste everything below the `---` line into a fresh Claude Code terminal as the first user message. --- You are a **senior developer** owning Stream B for the Relicario v0.5.1 release. Stream B replaces the current flat settings dump with a unified left-nav sectioned settings page, integrating device and vault settings into a single coherent view. **Goal:** Replace `settings.ts` (flat dump) + `settings-vault.ts` (flat dump) with a single settings component that has a left-nav sidebar (Autofill, Display, Security, Generator, Retention, Backup, Import). Wire into `vault.ts` via the agreed interface. Wire `settings-security.ts` from DEV-C into the Security section. **Architecture:** All changes are in the extension. The settings component lives in `settings.ts` (rewrite). Section content for vault-level settings moves from `settings-vault.ts` into inline render functions within the new layout. DEV-C owns `settings-security.ts`; you stub it and import it. **Tech Stack:** TypeScript, vitest, webpack/bun. --- ## Setup (do this first) ```bash cd /home/alee/Sources/relicario git fetch git checkout main git pull git worktree add ../relicario.v0.5.1-stream-b -b feature/v0.5.1-stream-b-settings cd ../relicario.v0.5.1-stream-b pwd # should print /home/alee/Sources/relicario.v0.5.1-stream-b ``` **ALL subsequent work happens in `/home/alee/Sources/relicario.v0.5.1-stream-b`**. Every subagent prompt MUST begin with `cd /home/alee/Sources/relicario.v0.5.1-stream-b`. Today: 2026-05-03. Project rules in `CLAUDE.md` apply. ## Required reading 1. `CLAUDE.md` — project rules 2. `docs/superpowers/specs/2026-05-03-v0.5.x-ux-polish-and-recovery-qr-design.md` — spec sections B1–B8 3. `extension/src/popup/components/settings.ts` — current flat settings (your rewrite target) 4. `extension/src/popup/components/settings-vault.ts` — vault settings content to decompose 5. `extension/src/popup/styles.css` — existing CSS (add to, don't break) ## Execution mode Use **subagent-driven-development**. Fresh subagent per task, two-stage review between tasks. Every subagent prompt MUST start with `cd /home/alee/Sources/relicario.v0.5.1-stream-b`. ## Interface contracts ### With DEV-A (vault.ts wiring) DEV-A imports these exact exports from `settings.ts` in vault.ts. **You must use exactly these signatures:** ```ts export async function renderSettings(container: HTMLElement): Promise; export function teardownSettings(): void; ``` DEV-A calls `renderSettings(pane)` when the settings view is active and `teardownSettings()` when navigating away. ### With DEV-C (settings-security.ts) DEV-C owns `settings-security.ts`. You import it for the Security section. The agreed interface: ```ts // extension/src/popup/components/settings-security.ts export async function renderSecuritySection( container: HTMLElement, sessionHandle: number | null, ): Promise; export function teardownSecuritySection(): void; ``` **Task 1** creates a stub `settings-security.ts` with this interface (no-op implementations). DEV-C will replace it on their branch; the real implementation lands when C merges. ## Scope and boundaries **In scope:** B1–B8 (settings skeleton, all 7 sections, cleanup of old settings-vault.ts surface). **Out of scope:** Stream A and C work. The Security section's QR and device UI is DEV-C's responsibility — you only wire the import. **Hard rules:** - `renderSettings` / `teardownSettings` must be exported with these exact names and signatures. - Device sections read/write `chrome.storage.local`. Vault sections call `sendMessage` to the service worker. - Don't merge to main. The PM owns merges. ## Relay server A message-bus MCP server is running on `localhost:7331`. You have three native tools: - `post_message(from, to, kind, body)` — push a message; your `from` is always `"dev-b"` - `read_messages(for)` — drain your inbox; call with `for="dev-b"` before each task - `list_pending(for)` — check inbox count without consuming Recipients: `pm`, `dev-a`, `dev-b`, `dev-c`. Use these instead of asking the user to copy-paste. Before starting each task: `read_messages(for="dev-b")`. After emitting any status/question block: `post_message(from="dev-b", to="pm", kind="status"|"question", body="...")`. ## Coordination protocol Before starting each task, call `read_messages(for="dev-b")` to drain your inbox. When posting a status update, call `post_message(from="dev-b", to="pm", kind="status", body="...")` with the body: ``` ## STATUS UPDATE — DEV-B Time: Task: Status: IN-PROGRESS | BLOCKED | REVIEW-READY Summary: Next: ``` --- ## Files **Create:** - `extension/src/popup/components/settings-security.ts` — stub (DEV-C replaces real implementation) - `extension/src/popup/components/__tests__/settings-nav.test.ts` — left-nav structure tests **Modify:** - `extension/src/popup/components/settings.ts` — full rewrite as sectioned layout - `extension/src/popup/components/settings-vault.ts` — decompose into section functions; may be removed at the end - `extension/src/popup/styles.css` — settings-nav CSS + section styles --- ### Task 1: Stub `settings-security.ts` This stub satisfies the import used in the Security section (Task 6). DEV-C replaces it with the real implementation. **Files:** - Create: `extension/src/popup/components/settings-security.ts` - [ ] **Step 1: Write the stub** ```ts // extension/src/popup/components/settings-security.ts // Stub — real implementation provided by Stream C (DEV-C). export async function renderSecuritySection( container: HTMLElement, _sessionHandle: number | null, ): Promise { container.innerHTML = `
Security settings — loading…
`; } export function teardownSecuritySection(): void { // no-op in stub } ``` - [ ] **Step 2: Build to confirm no TS errors** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | grep "error TS" ``` - [ ] **Step 3: Commit** ```bash git add extension/src/popup/components/settings-security.ts git commit -m "chore(ext/settings): stub settings-security.ts (DEV-C replaces implementation)" ``` --- ### Task 2: Settings left-nav skeleton **Files:** - Modify: `extension/src/popup/components/settings.ts` - Modify: `extension/src/popup/styles.css` The new settings.ts is a full rewrite. It replaces the current flat dump with a two-panel layout: a 148px left-nav sidebar + content area. - [ ] **Step 1: Write the failing test** Create `extension/src/popup/components/__tests__/settings-nav.test.ts`: ```ts import { describe, it, expect, vi, beforeEach } from 'vitest'; // Mock chrome.storage.local const storageMock: Record = {}; vi.stubGlobal('chrome', { storage: { local: { get: vi.fn((keys, cb) => cb(typeof keys === 'string' ? { [keys]: storageMock[keys] } : Object.fromEntries((keys as string[]).map(k => [k, storageMock[k]])))), set: vi.fn((data, cb) => { Object.assign(storageMock, data); cb?.(); }), }, }, }); // We can't easily test the full render (no DOM env yet), so we test the // structural contract — the exported function names exist. import * as settingsMod from '../settings'; describe('settings module contract', () => { it('exports renderSettings as an async function', () => { expect(typeof settingsMod.renderSettings).toBe('function'); }); it('exports teardownSettings as a function', () => { expect(typeof settingsMod.teardownSettings).toBe('function'); }); }); ``` - [ ] **Step 2: Run to confirm the test currently passes (contract already met)** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run test 2>&1 | grep "settings-nav" ``` The test may already pass if current settings.ts exports `renderSettings`. If so, proceed — the test ensures the contract is preserved through the rewrite. - [ ] **Step 3: Rewrite settings.ts with the new sectioned layout** ```ts // extension/src/popup/components/settings.ts import { sendMessage, escapeHtml } from '../../shared/state'; import type { DeviceSettings, VaultSettings } from '../../shared/types'; import { loadColorScheme, saveColorScheme, resetColorScheme, DEFAULT_DIGIT_COLOR, DEFAULT_SYMBOL_COLOR, } from '../../shared/color-scheme'; import { colorizePassword } from '../../shared/password-coloring'; import { openGeneratorPanel, closeGeneratorPanel } from './generator-panel'; import { renderSecuritySection, teardownSecuritySection } from './settings-security'; type SettingsSection = | 'autofill' | 'display' | 'security' | 'generator' | 'retention' | 'backup' | 'import'; const NAV_ITEMS: Array<{ id: SettingsSection; icon: string; label: string; group: 'device' | 'vault' }> = [ { id: 'autofill', icon: '⊙', label: 'Autofill', group: 'device' }, { id: 'display', icon: '◈', label: 'Display', group: 'device' }, { id: 'security', icon: '◉', label: 'Security', group: 'vault' }, { id: 'generator', icon: '↻', label: 'Generator', group: 'vault' }, { id: 'retention', icon: '▦', label: 'Retention', group: 'vault' }, { id: 'backup', icon: '⤓', label: 'Backup', group: 'vault' }, { id: 'import', icon: '≡', label: 'Import', group: 'vault' }, ]; let activeSection: SettingsSection = 'autofill'; let activeKeyHandler: ((e: KeyboardEvent) => void) | null = null; let pendingVaultSettings: VaultSettings | null = null; export async function renderSettings(container: HTMLElement): Promise { container.innerHTML = `
`; wireNav(); await renderSection(activeSection); } export function teardownSettings(): void { closeGeneratorPanel(); teardownSecuritySection(); if (activeKeyHandler) { document.removeEventListener('keydown', activeKeyHandler); activeKeyHandler = null; } pendingVaultSettings = null; } function navItemHtml(item: (typeof NAV_ITEMS)[0]): string { const active = item.id === activeSection ? ' settings-nav__item--active' : ''; return ` `; } function wireNav(): void { document.getElementById('settings-nav')?.querySelectorAll('[data-section]') .forEach((btn) => { btn.addEventListener('click', async () => { teardownSecuritySection(); closeGeneratorPanel(); activeSection = btn.dataset.section as SettingsSection; // Update active state document.querySelectorAll('.settings-nav__item').forEach(b => b.classList.remove('settings-nav__item--active')); btn.classList.add('settings-nav__item--active'); await renderSection(activeSection); }); }); } async function renderSection(section: SettingsSection): Promise { const content = document.getElementById('settings-content'); if (!content) return; switch (section) { case 'autofill': return renderAutofillSection(content); case 'display': return renderDisplaySection(content); case 'security': return renderSecuritySection(content, null); // sessionHandle: null until wired from vault.ts case 'generator': return renderGeneratorSection(content); case 'retention': return renderRetentionSection(content); case 'backup': return renderBackupSection(content); case 'import': return renderImportSection(content); } } ``` - [ ] **Step 4: Add settings layout CSS** In `extension/src/popup/styles.css`: ```css /* === Settings layout === */ .settings-layout { display: flex; height: 100%; overflow: hidden; } .settings-nav { width: 148px; min-width: 148px; border-right: 1px solid var(--border, #30363d); padding: 12px 0; overflow-y: auto; flex-shrink: 0; } .settings-nav__group-label { font-size: 10px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--text-muted, #8b949e); padding: 8px 12px 4px; } .settings-nav__item { display: flex; align-items: center; gap: 8px; width: 100%; padding: 7px 12px; background: transparent; border: none; cursor: pointer; font-size: 13px; color: inherit; text-align: left; } .settings-nav__item:hover { background: var(--bg-hover, #161b22); } .settings-nav__item--active { background: var(--bg-selected, #1c2d41); } .settings-nav__icon { font-size: 14px; flex-shrink: 0; } .settings-content { flex: 1; overflow-y: auto; padding: 20px 24px; min-width: 0; } /* Setting row (label + description + control) */ .setting-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--border-subtle, #21262d); } .setting-row:last-child { border-bottom: none; } .setting-row__info { flex: 1; } .setting-row__title { font-size: 13px; font-weight: 500; } .setting-row__desc { font-size: 11px; color: var(--text-muted, #8b949e); margin-top: 2px; } .setting-row__control { flex-shrink: 0; } /* Section headings */ .settings-section-title { font-size: 12px; text-transform: uppercase; letter-spacing: 0.05em; color: var(--text-muted, #8b949e); margin: 0 0 12px; padding-bottom: 6px; border-bottom: 1px solid var(--border, #30363d); } /* Setting card (used by Security section) */ .setting-card { padding: 12px 16px; border-radius: 6px; border: 1px solid var(--border, #30363d); margin-bottom: 12px; } .setting-card--ok { border-color: var(--success, #238636); background: rgba(35, 134, 54, 0.06); } .setting-card--warn { border-color: var(--gold, #b8860b); background: rgba(184, 134, 11, 0.06); } .setting-card__status { font-size: 13px; margin-bottom: 8px; } .setting-card__actions { display: flex; gap: 8px; } ``` - [ ] **Step 5: Build and run tests** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | grep "error TS" bun run test 2>&1 | grep "settings-nav" ``` - [ ] **Step 6: Commit** ```bash git add extension/src/popup/components/settings.ts extension/src/popup/styles.css extension/src/popup/components/__tests__/settings-nav.test.ts git commit -m "feat(ext/settings): settings left-nav skeleton with section routing" ``` --- ### Task 3: Autofill section (Device) **Files:** - Modify: `extension/src/popup/components/settings.ts` - [ ] **Step 1: Implement `renderAutofillSection()`** Add this function to settings.ts: ```ts async function renderAutofillSection(content: HTMLElement): Promise { const [settingsResp, blacklistResp] = await Promise.all([ sendMessage({ type: 'get_settings' }), sendMessage({ type: 'get_blacklist' }), ]); const settings: DeviceSettings = settingsResp.ok ? (settingsResp.data as { settings: DeviceSettings }).settings : { captureEnabled: false, captureStyle: 'bar' }; const blacklist: string[] = blacklistResp.ok ? (blacklistResp.data as { blacklist: string[] }).blacklist : []; content.innerHTML = `

Capture

Auto-detect logins
Show a prompt when a login form is detected.
Prompt style
How to prompt when a login is detected.

Blocked sites

${blacklist.length > 0 ? blacklist.map((h) => `
${escapeHtml(h)}
`).join('') : '

No blocked sites.

'}
`; document.getElementById('capture-enabled')?.addEventListener('change', async (e) => { const enabled = (e.target as HTMLInputElement).checked; await sendMessage({ type: 'save_settings', settings: { ...settings, captureEnabled: enabled } }); }); document.getElementById('style-bar')?.addEventListener('click', async () => { await sendMessage({ type: 'save_settings', settings: { ...settings, captureStyle: 'bar' } }); renderAutofillSection(content); }); document.getElementById('style-toast')?.addEventListener('click', async () => { await sendMessage({ type: 'save_settings', settings: { ...settings, captureStyle: 'toast' } }); renderAutofillSection(content); }); content.querySelectorAll('.remove-bl').forEach((btn) => { btn.addEventListener('click', async () => { const host = btn.dataset.hostname!; await sendMessage({ type: 'remove_blacklist_entry', hostname: host }); renderAutofillSection(content); }); }); document.getElementById('bl-add-btn')?.addEventListener('click', async () => { const input = document.getElementById('bl-add-input') as HTMLInputElement; const val = input.value.trim(); if (!val) return; await sendMessage({ type: 'add_blacklist_entry', hostname: val }); input.value = ''; renderAutofillSection(content); }); } ``` - [ ] **Step 2: Build** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | grep "error TS" ``` - [ ] **Step 3: Commit** ```bash git add extension/src/popup/components/settings.ts git commit -m "feat(ext/settings): autofill section (capture toggle + blacklist)" ``` --- ### Task 4: Display section (Device) **Files:** - Modify: `extension/src/popup/components/settings.ts` - [ ] **Step 1: Implement `renderDisplaySection()`** Extract the existing password-coloring UI from the old settings.ts and move it into the Display section: ```ts function renderDisplaySection(content: HTMLElement): void { const scheme = loadColorScheme(); content.innerHTML = `

Password coloring

Digit color
Symbol color
Preview
${colorizePassword('Aa1!Bb2@', scheme)}
`; function updatePreview(): void { const scheme2 = loadColorScheme(); const preview = document.getElementById('color-preview'); if (preview) preview.innerHTML = colorizePassword('Aa1!Bb2@', scheme2); } document.getElementById('digit-color')?.addEventListener('input', (e) => { const color = (e.target as HTMLInputElement).value; saveColorScheme({ ...loadColorScheme(), digitColor: color }); updatePreview(); }); document.getElementById('symbol-color')?.addEventListener('input', (e) => { const color = (e.target as HTMLInputElement).value; saveColorScheme({ ...loadColorScheme(), symbolColor: color }); updatePreview(); }); document.getElementById('reset-colors')?.addEventListener('click', () => { resetColorScheme(); renderDisplaySection(content); }); } ``` - [ ] **Step 2: Build** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | grep "error TS" ``` - [ ] **Step 3: Commit** ```bash git add extension/src/popup/components/settings.ts git commit -m "feat(ext/settings): display section (password coloring)" ``` --- ### Task 5: Security section stub wire-up **Files:** - Modify: `extension/src/popup/components/settings.ts` The Security section calls `renderSecuritySection()` from `settings-security.ts` (stub from Task 1; DEV-C's real implementation replaces it). The stub's signature is `renderSecuritySection(container, sessionHandle)`. For now, pass `null` as the session handle — the vault.ts wiring from DEV-A will eventually thread the real handle through. This is acceptable for the stub phase. - [ ] **Step 1: Verify `renderSection('security')` calls `renderSecuritySection`** The `renderSection()` function already has: ```ts case 'security': return renderSecuritySection(content, null); ``` This is already wired in Task 2's settings.ts rewrite. Confirm it builds cleanly. - [ ] **Step 2: Build and confirm** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | grep "error TS" ``` - [ ] **Step 3: Commit (only if additional wiring was needed)** If you had to add anything, commit: ```bash git add extension/src/popup/components/settings.ts git commit -m "feat(ext/settings): wire security section to settings-security.ts stub" ``` --- ### Task 6: Generator section (Vault) **Files:** - Modify: `extension/src/popup/components/settings.ts` Extract the generator defaults content from `settings-vault.ts`. No functional changes — just consistent styling using `setting-row` pattern. - [ ] **Step 1: Read the current generator content in settings-vault.ts** ```bash grep -n "generator\|generate\|Generator" extension/src/popup/components/settings-vault.ts | head -20 ``` - [ ] **Step 2: Implement `renderGeneratorSection()`** ```ts async function renderGeneratorSection(content: HTMLElement): Promise { content.innerHTML = '

Loading…

'; const resp = await sendMessage({ type: 'get_vault_settings' }); if (!resp.ok) { content.innerHTML = `

Failed to load: ${escapeHtml(resp.error ?? 'unknown')}

`; return; } const settings = (resp.data as { settings: VaultSettings }).settings; content.innerHTML = `

Generator defaults

Configure generator
Password length, character classes, passphrase word count.
`; document.getElementById('open-generator-panel')?.addEventListener('click', (e) => { openGeneratorPanel(e.currentTarget as HTMLElement, settings.generator_defaults, async (req) => { await sendMessage({ type: 'save_vault_settings', settings: { ...settings, generator_defaults: req } }); }); }); } ``` - [ ] **Step 3: Build** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | grep "error TS" ``` - [ ] **Step 4: Commit** ```bash git add extension/src/popup/components/settings.ts git commit -m "feat(ext/settings): generator section (vault defaults)" ``` --- ### Task 7: Retention section (Vault) **Files:** - Modify: `extension/src/popup/components/settings.ts` Extract retention content from `settings-vault.ts`. - [ ] **Step 1: Read current retention helpers in settings-vault.ts** ```bash grep -n "retention\|Retention\|trash\|history" extension/src/popup/components/settings-vault.ts | head -30 ``` Note the helper functions: `trashRetentionToValue`, `valueToTrashRetention`, `historyRetentionToValue`, `valueToHistoryRetention`. Copy these into settings.ts or import from settings-vault.ts if it stays as a helper module. - [ ] **Step 2: Implement `renderRetentionSection()`** ```ts async function renderRetentionSection(content: HTMLElement): Promise { content.innerHTML = '

Loading…

'; const resp = await sendMessage({ type: 'get_vault_settings' }); if (!resp.ok) { content.innerHTML = `

Failed to load: ${escapeHtml(resp.error ?? 'unknown')}

`; return; } const settings = (resp.data as { settings: VaultSettings }).settings; pendingVaultSettings = { ...settings }; content.innerHTML = `

Trash retention

Keep deleted items for
Items in trash older than this are permanently deleted on the next sync.

Field history retention

Keep password history for
History entries older than this are pruned on save.
`; document.getElementById('trash-retention')?.addEventListener('change', (e) => { if (pendingVaultSettings) { pendingVaultSettings.trash_retention = valueToTrashRetention((e.target as HTMLSelectElement).value); } }); document.getElementById('history-retention')?.addEventListener('change', (e) => { if (pendingVaultSettings) { pendingVaultSettings.history_retention = valueToHistoryRetention((e.target as HTMLSelectElement).value); } }); document.getElementById('save-retention')?.addEventListener('click', async () => { if (!pendingVaultSettings) return; const r = await sendMessage({ type: 'save_vault_settings', settings: pendingVaultSettings }); if (!r.ok) alert(`Save failed: ${r.error}`); }); } // Copy retention helpers from settings-vault.ts: function trashRetentionToValue(r: import('../../shared/types').TrashRetention): string { if (r.kind === 'forever') return 'forever'; return `days:${r.value}`; } function valueToTrashRetention(v: string): import('../../shared/types').TrashRetention { if (v === 'forever') return { kind: 'forever' }; const m = /^days:(\d+)$/.exec(v); if (m) return { kind: 'days', value: Number(m[1]) }; return { kind: 'forever' }; } function historyRetentionToValue(r: import('../../shared/types').HistoryRetention): string { if (r.kind === 'forever') return 'forever'; if (r.kind === 'last_n') return `last_n:${r.value}`; return `days:${r.value}`; } function valueToHistoryRetention(v: string): import('../../shared/types').HistoryRetention { if (v === 'forever') return { kind: 'forever' }; const mLast = /^last_n:(\d+)$/.exec(v); if (mLast) return { kind: 'last_n', value: Number(mLast[1]) }; const mDays = /^days:(\d+)$/.exec(v); if (mDays) return { kind: 'days', value: Number(mDays[1]) }; return { kind: 'forever' }; } ``` - [ ] **Step 3: Build** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | grep "error TS" ``` - [ ] **Step 4: Commit** ```bash git add extension/src/popup/components/settings.ts git commit -m "feat(ext/settings): retention section (trash + field history)" ``` --- ### Task 8: Backup section (Vault) **Files:** - Modify: `extension/src/popup/components/settings.ts` - [ ] **Step 1: Read current backup content in settings-vault.ts** ```bash grep -n "backup\|Backup\|restore\|Restore" extension/src/popup/components/settings-vault.ts | head -20 ``` - [ ] **Step 2: Implement `renderBackupSection()`** Extract the backup/restore content from settings-vault.ts. Keep the same functionality, just restyled with `setting-row` pattern: ```ts function renderBackupSection(content: HTMLElement): void { content.innerHTML = `

Backup

Export backup
Download an encrypted backup of your entire vault.
Restore backup
Restore from a previously exported backup file.
`; // Wire the backup export button — same logic as settings-vault.ts document.getElementById('backup-export-btn')?.addEventListener('click', async () => { const btn = document.getElementById('backup-export-btn') as HTMLButtonElement; btn.disabled = true; btn.textContent = 'Exporting…'; const resp = await sendMessage({ type: 'export_backup' }); btn.disabled = false; btn.textContent = 'Download backup'; if (!resp.ok) { alert(`Export failed: ${resp.error}`); return; } const data = resp.data as { backup_b64: string; filename: string }; const bytes = Uint8Array.from(atob(data.backup_b64), c => c.charCodeAt(0)); const blob = new Blob([bytes], { type: 'application/octet-stream' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = data.filename; a.click(); URL.revokeObjectURL(url); }); document.getElementById('backup-restore-btn')?.addEventListener('click', () => { document.getElementById('backup-file-input')?.click(); }); document.getElementById('backup-file-input')?.addEventListener('change', async (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (!file) return; const arrayBuf = await file.arrayBuffer(); const b64 = btoa(String.fromCharCode(...new Uint8Array(arrayBuf))); const resp = await sendMessage({ type: 'import_backup', backup_b64: b64 }); if (resp.ok) { alert('Backup restored. The extension will reload.'); window.location.reload(); } else { alert(`Restore failed: ${resp.error}`); } }); } ``` - [ ] **Step 3: Build** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | grep "error TS" ``` - [ ] **Step 4: Commit** ```bash git add extension/src/popup/components/settings.ts git commit -m "feat(ext/settings): backup section (export + restore)" ``` --- ### Task 9: Import section (Vault) **Files:** - Modify: `extension/src/popup/components/settings.ts` - [ ] **Step 1: Read current import content in settings-vault.ts** ```bash grep -n "import\|Import\|lastpass\|LastPass" extension/src/popup/components/settings-vault.ts | head -20 ``` - [ ] **Step 2: Implement `renderImportSection()`** ```ts function renderImportSection(content: HTMLElement): void { content.innerHTML = `

Import

Import from LastPass
Import a LastPass CSV export file.
`; document.getElementById('import-lp-btn')?.addEventListener('click', () => { document.getElementById('import-lp-input')?.click(); }); document.getElementById('import-lp-input')?.addEventListener('change', async (e) => { const file = (e.target as HTMLInputElement).files?.[0]; if (!file) return; const text = await file.text(); const resultDiv = document.getElementById('import-result')!; resultDiv.textContent = 'Importing…'; const resp = await sendMessage({ type: 'import_lastpass', csv: text }); if (resp.ok) { const data = resp.data as { imported: number; warnings: string[] }; resultDiv.innerHTML = `

Imported ${data.imported} items.

${data.warnings.length > 0 ? `

${data.warnings.length} warning(s): ${escapeHtml(data.warnings.slice(0,3).join('; '))}${data.warnings.length > 3 ? '…' : ''}

` : ''} `; } else { resultDiv.innerHTML = `

Import failed: ${escapeHtml(resp.error ?? 'unknown')}

`; } }); } ``` - [ ] **Step 3: Build and run all tests** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | grep "error TS" bun run test 2>&1 | tail -15 ``` Expected: all tests pass. - [ ] **Step 4: Commit** ```bash git add extension/src/popup/components/settings.ts git commit -m "feat(ext/settings): import section (LastPass CSV)" ``` --- ### Task 10: Cleanup + full build pass **Files:** - Modify: `extension/src/popup/components/settings-vault.ts` The old `renderVaultSettings()` exported function was called from popup.ts and vault.ts. Check all call sites. - [ ] **Step 1: Find all callers of settings-vault.ts exports** ```bash grep -rn "renderVaultSettings\|settings-vault\|settingsVault" extension/src/ | grep -v ".test.ts" | grep -v "__tests__" ``` - [ ] **Step 2: Assess whether settings-vault.ts can be deleted** If all callers have been migrated to the new settings.ts sections, and no other code imports from it, it can be deleted. If it's still imported somewhere, keep it as a thin stub or migrate the remaining callers first. If safe to delete: ```bash git rm extension/src/popup/components/settings-vault.ts git commit -m "refactor(ext/settings): remove settings-vault.ts (content merged into sectioned settings)" ``` If still needed (some callers remain), leave it and note the remaining callers in your status update to PM. - [ ] **Step 3: Final build check — both targets** ```bash cd /home/alee/Sources/relicario.v0.5.1-stream-b/extension && bun run build 2>&1 | tail -10 bun run build:firefox 2>&1 | tail -10 ``` Expected: both build clean. - [ ] **Step 4: Run all tests** ```bash bun run test 2>&1 | tail -15 ``` Expected: all pass. - [ ] **Step 5: Open PR** ```bash gh pr create --title "feat: settings UX redesign — left-nav sectioned layout (Stream B)" --base main ``` - [ ] **Step 6: Post status to PM** Call `post_message(from="dev-b", to="pm", kind="status", body="...")` with: ``` ## STATUS UPDATE — DEV-B Time: Task: 10 of 10 Status: REVIEW-READY Summary: All 10 tasks complete. PR open. All sections implemented. Build clean. Tests pass. Next: waiting for PM ```