# Plan 1C-α — Manual Test Matrix Walkthrough for validating the extension on both Chrome and Firefox after the six-slice implementation. Branch: `feature/typed-items-1c-alpha` @ `3238ef4` (tag candidate: `plan-1c-alpha-complete`) Worktree: `/home/alee/Sources/relicario/.worktrees/typed-items-1c-alpha` --- ## Pre-flight - [ ] **P1.** Bundles built: ```bash cd /home/alee/Sources/relicario/.worktrees/typed-items-1c-alpha/extension bun run build:all ``` Expected: "compiled with 2 warnings" (WASM size only) for each bundle. `dist/` and `dist-firefox/` populated. - [ ] **P2.** Fresh-profile browsers ready (or existing profile's `chrome.storage.local` for this extension cleared). Stale `vaultConfig`/`imageBase64` from the pre-rename `idfoto` era must not persist. - [ ] **P3.** Test git repo for the vault is reachable (SSH key / HTTPS PAT working). Use a throwaway repo to avoid polluting your real vault history. - [ ] **P4.** Reference image ready (any JPEG; DCT-steg secret is embedded at init time). --- ## Loading ### Chrome - [ ] **L1.** `chrome://extensions` → Developer mode ON → "Load unpacked" → select `extension/dist/`. - [ ] **L2.** Toolbar icon visible (pin if needed). - [ ] **L3.** Click icon → first open triggers setup tab (not a popup-embedded wizard). ### Firefox - [ ] **L4.** `about:debugging#/runtime/this-firefox` → "Load Temporary Add-on…" → select `extension/dist-firefox/manifest.json`. - [ ] **L5.** Toolbar icon visible. - [ ] **L6.** Click icon → setup tab opens. --- ## 11-step core matrix — Chrome **Notes column: write what you saw. Check box only when matching expected.** ### 1. Setup tab opens from popup (audit C1) - [ ] **Do:** Fresh install, click toolbar icon. - [ ] **Expected:** `setup.html` opens in a new tab; popup closes immediately; WAR is empty so this MUST work via extension-origin tab, not WAR. - [ ] **Notes:** ___ ### 2. zxcvbn gate in setup (audit H3) - [ ] **Do:** Type weak passphrase like `password`. - [ ] **Expected:** Submit disabled, bar shows red/orange segments, feedback "Too weak…". - [ ] **Do:** Type stronger phrase until bar fills. - [ ] **Expected:** At score ≥ 3, submit enables, feedback "Strong enough." - [ ] **Notes:** ___ ### 3. Setup completes → unlock → list renders - [ ] **Do:** Upload reference JPEG, fill vault config (git host/URL/repo/token), submit. Then open popup, enter passphrase, unlock. - [ ] **Expected:** Manifest decrypts client-side. Empty list view appears with toolbar (search, + New, sync, lock, ⚙). - [ ] **Notes:** ___ ### 4. Add Login with TOTP (typed-item wire format) - [ ] **Do:** "+ New" → Login form. Fill: - title: `GitHub` - url: `https://github.com` - username: your handle - password: click "gen" (uses `DEFAULT_PASSWORD_REQUEST` — 20 chars, safe symbols) - totp: `JBSWY3DPEHPK3PXP` (well-known base32 test vector) - Save. - [ ] **Expected:** Row appears with 🔑 icon + title + favorite star position. - [ ] **Expected (CLI cross-check, optional):** From main worktree: ```bash relicario list relicario get "GitHub" --show ``` Should show the same item. TOTP secret should decode identically. - [ ] **Notes:** ___ ### 5. TOFU origin-ack prompt (audit C4 first half) - [ ] **Do:** Navigate to `https://github.com/login`. Click the blue `id` icon next to the password field. - [ ] **Expected:** Closed Shadow DOM hint appears ("First autofill on github.com / Open relicario to confirm"). In DevTools, verify `document.querySelector('[data-rel]')` finds the host but `.shadowRoot` is `null` (closed mode). - [ ] **Expected:** No credentials fill on this click. - [ ] **Notes:** ___ ### 6. Confirm origin + autofill fills correctly - [ ] **Do:** Open popup (on the github.com tab). Look for a pending-ack prompt OR (α behavior) just confirm manually: any `get_credentials` call after the hostname is acked in `VaultSettings.autofill_origin_acks` will return credentials. - Simplest α path: click the item in the popup list, click "autofill" button. This uses the popup-captured tab state path (audit M5). - [ ] **Expected:** Username + password fields fill. On React/Vue sites, the native-setter trick fires input+change events. - [ ] **Notes:** ___ ### 7. Multiple candidates → picker - [ ] **Do:** Add a second Login for github.com with a different username. Back on `github.com/login`, click the `id` icon. - [ ] **Expected:** Picker shows both titles. Click one → fills that set. - [ ] **Notes:** ___ ### 8. Capture prompt → `capture_save_login` flow (Slice 5 critical-fix) - [ ] **Do:** Go to a site not in your vault. Fill signup form (or real trial). Submit. - [ ] **Expected:** Capture prompt appears inside closed Shadow DOM. No stable element IDs — running `document.querySelector('#relicario-save-btn')` in the page console returns `null`. - [ ] **Do:** Click "Save" in the prompt. - [ ] **Expected:** ✓ Saved confirmation; prompt dismisses. Open popup → item present in list with the new hostname as title. - [ ] **CRITICAL:** If "Save" silently fails, the `capture_save_login` content-callable handler is broken — file a bug before proceeding. - [ ] **Notes:** ___ ### 9. Edit Login → password rotates; field history captured - [ ] **Do:** Select the GitHub item → edit → change password → save. - [ ] **Expected:** Detail view shows new password on reveal. List's "modified" time updates. - [ ] **Expected (CLI cross-check):** ```bash relicario get "GitHub" --show # confirm field_history now has entry for the old password ``` - [ ] **Notes:** ___ ### 10. Delete Login → soft-delete - [ ] **Do:** Select an item → "trash" → confirm. - [ ] **Expected:** Row disappears from list immediately. Popup list filters `trashed_at !== undefined`. - [ ] **Expected (CLI cross-check):** `relicario list --trashed` shows the item. - [ ] **Notes:** ___ ### 11. Lock → re-unlock - [ ] **Do:** Click "lock" in the toolbar. Try to open the popup again. - [ ] **Expected:** Unlock screen. Session handle was cleared in WASM (not just JS). - [ ] **Do:** Re-unlock. - [ ] **Expected:** Same list (including the item from step 10 still in trash, invisible). - [ ] **Notes:** ___ --- ## 11-step core matrix — Firefox Re-run 1–11 on Firefox. Critical Firefox-only check: the background script runs as a **persistent script** (not MV3 service worker); WASM loads via `initDefault(wasmUrl)` not `initSync`. Anything broken here that works in Chrome indicates WASM-loading drift. - [ ] **FF1–FF11.** Re-run the 11 steps above. Summarize anomalies: - **Notes:** ___ --- ## Security probes (bonus) Open DevTools on any page (not extension origin) and try to defeat the router: ### SP1. Content-script-originated popup-only message - [ ] **Do:** In a page console (not popup DevTools): ```js chrome.runtime.sendMessage({ type: 'unlock', passphrase: 'guess' }, console.log) ``` - [ ] **Expected:** `{ ok: false, error: 'unauthorized_sender' }` (audit C2). - [ ] **Notes:** ___ ### SP2. Cross-origin `get_credentials` attempt - [ ] **Do:** Pick an item id from the popup (e.g., via popup DevTools: `copy(currentState.selectedId)`). Go to a **different-origin** page's console: ```js chrome.runtime.sendMessage({ type: 'get_credentials', id: '' }, console.log) ``` - [ ] **Expected:** `{ ok: false, error: 'origin_mismatch' }` (audit C4). No item data leaks. - [ ] **Notes:** ___ ### SP3. Closed Shadow DOM verification - [ ] **Do:** Trigger the capture prompt (step 8). In the page console: ```js const hosts = document.querySelectorAll('[data-rel]'); for (const h of hosts) console.log(h, h.shadowRoot); // shadowRoot should be null console.log(document.querySelector('#relicario-save-btn')); // should be null console.log(document.querySelector('.relicario-capture')); // should be null ``` - [ ] **Expected:** All `shadowRoot` values are `null`; no stable selectors match (audit C3). - [ ] **Notes:** ___ ### SP4. Captured-tab navigation during fill (audit M5) - [ ] **Do:** Open popup on `https://github.com/login`. Select a github item, click "autofill", but BEFORE the fill lands, rapidly navigate the github tab to `https://example.com`. - [ ] **Expected:** No credentials typed on example.com. SW rejects with `tab_navigated`; if somehow the message reaches the content script, `fill.ts` re-checks `expectedHost` and rejects with `origin_changed`. - [ ] **Notes:** ___ (this one's hard to time; skip if not easily reproducible) ### SP5. WAR probe - [ ] **Do:** In a page console on any site: ```js fetch('chrome-extension:///setup.html').catch(e => console.log('blocked:', e)) ``` - [ ] **Expected:** Blocked (either CORS error or net::ERR). WAR is empty, so no resource is web-accessible. `` pages cannot reach setup.html. - [ ] **Notes:** ___ --- ## Final acceptance - [ ] **A1.** `cargo test --workspace` green (should still be 151+ Rust tests). - [ ] **A2.** `cd extension && bun run test` green (should be 52 passing — 11 base32 + 41 router). - [ ] **A3.** `cd extension && bun run build` green (Chrome bundle). - [ ] **A4.** `cd extension && bun run build:firefox` green (Firefox bundle). - [ ] **A5.** Lint greps clean: ```bash git grep -n 'innerHTML\|insertAdjacentHTML' extension/src/content/ # zero hits git grep -n 'idfoto' extension/ # zero hits git grep -n '@ts-nocheck' extension/src/ # zero hits ``` - [ ] **A6.** WAR empty: ```bash grep -A2 web_accessible_resources extension/manifest.json # [] grep -A2 web_accessible_resources extension/manifest.firefox.json # [] ``` --- ## Sign-off - [ ] **All 11 core-matrix steps pass on Chrome** - [ ] **All 11 core-matrix steps pass on Firefox** - [ ] **All 5 security probes pass (or SP4 skipped, others pass)** - [ ] **All 6 final acceptance checks pass** - [ ] **Ready to tag `plan-1c-alpha-complete` and decide on merge path** ### Findings / issues Use this space to log anything weird: ``` (fill in as you go) ``` ### Decision - [ ] Merge straight to `main` - [ ] Open a PR first for review - [ ] Need rework on: ___ --- *Generated 2026-04-20 — source: spec `2026-04-20-relicario-extension-1c-alpha-design.md` §5.4, plan `2026-04-20-relicario-extension-1c-alpha.md` Task 27.*