# Settings Redesign + Feedback System — Implementation Spec > Read `AGENTS.md`, `.agent/Projects.md`, and `.agent/CONTINUITY.md` before > touching any file. Follow every invariant in Projects.md without exception. > All Koh rules in AGENTS.md apply. Use the `dev` lane. Start with a > checkpoint. --- ## 0. Pre-flight ```sh koh status --json # confirm dev lane, clean working tree koh checkpoint "before settings redesign and feedback feature" ``` Note: Projects.md §8 says "no new features until P0/P1 findings are closed." This task has explicit user authorisation to proceed despite that rule. Record the override in `.agent/CONTINUITY.md` before starting. --- ## 1. Version bump This is a new feature (MINOR). Bump all three files from `2.0.3` → `2.1.0`. All three must agree — this is a hard invariant (Projects.md §2.1 / §4.8). - `manifest.json` → `"version": "2.1.0"` - `manifest.firefox.json` → `"version": "2.1.0"` - `package.json` → `"version": "2.1.0"` --- ## 2. `src/shared/types.ts` — add `SUBMIT_FEEDBACK` to the MessageType union Add `"SUBMIT_FEEDBACK"` to the background-bound section of `MessageType` (before the content-script-bound comment block, after `"IS_CARD_DECLINED"`): ```ts | "IS_CARD_DECLINED" | "SUBMIT_FEEDBACK" // Content-script-bound message types ... ``` No other changes to this file. --- ## 3. `src/background/messages.ts` — SUBMIT_FEEDBACK handler ### 3a. Add the feedback endpoint constant Directly below the existing `PRIVATE_IPV4_RANGES` constant at the top of the file, add: ```ts // Feedback endpoint — replace with your ingestion URL before shipping. // Leaving this empty disables network submission while keeping the UI functional. // When set, this URL is validated by isAllowedCanaryWebhookUrl before use. const FEEDBACK_ENDPOINT = ""; ``` ### 3b. Add the case to the switch Place this case immediately before the `default:` case at the bottom of the `switch (msg.type)` block: ```ts case "SUBMIT_FEEDBACK": { const { category, message, version, browser } = msg.payload as { category: string; message: string; version: string; browser: string; }; if (!message.trim()) return { ok: false, error: "message is empty" }; if (!FEEDBACK_ENDPOINT) return { ok: true }; if (!isAllowedCanaryWebhookUrl(FEEDBACK_ENDPOINT)) { return { ok: false, error: "invalid feedback endpoint" }; } try { const res = await fetch(FEEDBACK_ENDPOINT, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ category, message, version, browser, source: "peach-extension" }), }); return { ok: res.ok, error: res.ok ? undefined : `HTTP ${res.status}` }; } catch (err) { return { ok: false, error: err instanceof Error ? err.message : "network error" }; } } ``` ### 3c. Update `shouldRefreshAutoLock` `SUBMIT_FEEDBACK` must not refresh the auto-lock timer. Add it to the exclusion list in `shouldRefreshAutoLock`: ```ts function shouldRefreshAutoLock(type: MessageType): boolean { return ( type !== "HAS_VAULT" && type !== "IS_UNLOCKED" && type !== "GET_VAULT_STATUS" && type !== "LOCK" && type !== "UPDATE_SETTINGS" && type !== "GET_PENDING_PASSKEY" && type !== "GET_PASSKEY_PROVIDER_STATE" && type !== "DECLINE_CARD_SAVE" && type !== "IS_CARD_DECLINED" && type !== "SUBMIT_FEEDBACK" // ← add this line ); } ``` ### 3d. Network surface — update Projects.md §3.7 Add a third permitted fetch entry to `§3.7 Network surface` in `.agent/Projects.md`: ``` 3. `FEEDBACK_ENDPOINT` constant in `src/background/messages.ts` — only when non-empty, only on `SUBMIT_FEEDBACK` messages initiated by the user. URL validated by `isAllowedCanaryWebhookUrl`. Defaults to empty string (disabled). Set by the developer before shipping. ``` Also verify the network grep still passes: ```sh grep -r "fetch(" src/ | grep -v "pwnedpasswords" | grep -v "webhookUrl" | grep -v "FEEDBACK_ENDPOINT" | grep -v "\.test\." ``` Must return zero results. ### 3e. Tests — `src/background/messages.test.ts` (or equivalent test file) Per Projects.md §2.2, every new `messages.ts` handler requires tests. Add these three cases to the existing test file: ```ts describe("SUBMIT_FEEDBACK", () => { it("returns ok:true when FEEDBACK_ENDPOINT is empty (no-op path)", async () => { // FEEDBACK_ENDPOINT defaults to "" in test environment const res = await handleMessageDirect({ type: "SUBMIT_FEEDBACK", payload: { category: "Bug report", message: "test", version: "2.1.0", browser: "Chrome" }, }); expect(res.ok).toBe(true); }); it("returns ok:false when message is empty", async () => { const res = await handleMessageDirect({ type: "SUBMIT_FEEDBACK", payload: { category: "Bug report", message: " ", version: "2.1.0", browser: "Chrome" }, }); expect(res.ok).toBe(false); expect(res.error).toBe("message is empty"); }); it("does not refresh auto-lock timer", () => { expect(shouldRefreshAutoLockPublic("SUBMIT_FEEDBACK")).toBe(false); }); }); ``` Adapt to the existing test harness conventions in the project. If `handleMessageDirect` or `shouldRefreshAutoLockPublic` are not already exported for testing, export them or use whatever test helper pattern exists. --- ## 4. `src/popup/views/Settings.tsx` — full rewrite This is the largest change. The file goes from a single 987-line flat scroll to a two-level navigation system: a home screen of category rows, each navigating to a focused sub-view. ### 4a. Overall structure The outer component keeps the same `SettingsProps` interface (no changes to `App.tsx`). It gains a new `activeSection` signal that drives navigation. ```ts type SectionName = | "security" | "vault" | "sync" | "appearance" | "shortcuts" | "license" | "feedback"; ``` The component returns a single `peach-overlay-view` div. The back button navigates: sub-view → home → `props.onBack()`. The title reflects the current section or reads `"settings"` on home. Use SolidJS `/` (not early returns — see Projects.md §4.1): ```tsx {renderSecurity()} {renderVault()} {renderSync()} {renderAppearance()} {renderShortcuts()} {renderLicense()} {renderFeedback()} ``` All state signals from the current implementation are preserved at the top of `SettingsView` exactly as they are today. Nothing is split into child components — this is one file, render-function style, closing over shared signals. Follow "co-locate related logic" from AGENTS.md. Add the feedback signals alongside the existing ones: ```ts const [fbCategory, setFbCategory] = createSignal("Bug report"); const [fbMessage, setFbMessage] = createSignal(""); const [fbSubmitting, setFbSubmitting] = createSignal(false); const [fbResult, setFbResult] = createSignal<"success" | "error" | null>(null); ``` Add the feedback submit handler: ```ts async function handleSubmitFeedback() { if (!fbMessage().trim() || fbSubmitting()) return; setFbSubmitting(true); setFbResult(null); const res = await sendMessage({ type: "SUBMIT_FEEDBACK", payload: { category: fbCategory(), message: fbMessage().trim(), version, browser: navigator.userAgent, }, }) as { ok: boolean; error?: string }; setFbSubmitting(false); if (res.ok) { setFbResult("success"); setFbMessage(""); setTimeout(() => setFbResult(null), 4000); } else { setFbResult("error"); } } ``` ### 4b. `renderHome()` — the category list Returns a `
` containing two section groups and a footer. Each row uses the `.peach-settings-cat-row` class (new, defined in §5). ``` Group 1 label: "PROTECTION" Row: Security "Locking, breach monitoring, passkeys" → "security" Row: Account "Plan & license key" → "license" Group 2 label: "VAULT" Row: Vault "Entries, health, import & export" → "vault" Row: Sync [Pro] "Paired devices & trusted networks" → "sync" Group 3 label: "PREFERENCES" Row: Appearance "Theme & accent color" → "appearance" Row: Shortcuts "Keyboard reference" → "shortcuts" Group 4 label: "SUPPORT" Row: Feedback "Report issues, suggest features" → "feedback" Footer: "Peach {version} · Browser extension" (same as current) ``` Each row renders as: ```tsx ``` The Sync row also renders a Pro badge when `!isPro()`: ```tsx Pro ``` The `peach-sync-pro-badge` class already exists in popup.css. **Icons** — all inline SVG, `width="14" height="14"`, `fill="none"`, `stroke="currentColor"` (inherits from icon container's color), `stroke-width="1.2"`, `stroke-linecap="round"`, `stroke-linejoin="round"`: | Section | Icon path description | |------------|---------------------------------------------------------------| | Security | Shield: `` | | Account | Key: `` | | Vault | Lock body+shackle: `` | | Sync | Two arrows in circle: `` | | Appearance | Sun: `` | | Shortcuts | Two keys: `` | | Feedback | Comment bubble: `` | **Section labels** use a new class `.peach-settings-nav-label` (defined in §5). ### 4c. `renderSecurity()` — Security sub-view Content is exactly the existing "security" + "duress password" + "passkeys" groups from the current Settings.tsx, reorganised into three labelled sub-sections within a single `peach-settings-body`. No logic changes — copy the JSX and signals verbatim, just remove the outer `peach-settings-group` wrapper divs and replace with `peach-settings-sub-label` divs (new class, defined in §5). Sub-section labels: - `"LOCKING"` — Lock on close, Auto-lock after - `"BREACH PROTECTION"` — Breach monitoring toggle, Check all passwords button, last check result - `"MASTER PASSWORD"` — Change master password (inline expand form, same as current) - `"DURESS PASSWORD"` — The full duress setup/disable flow (same as current) - `"PASSKEYS"` — Intercept toggle, status, browser, conflict notice The `peach-info-notice` div for the passkey conflict note is kept verbatim. ### 4d. `renderVault()` — Vault sub-view Takes the existing "vault" group and splits it into labelled sub-sections: - `"ENTRIES"` — Entries count (read-only), Duplicates (scan/remove flow, same as current) - `"MAINTENANCE"` — Trash auto-delete dropdown, Vault Health button - `"DATA"` — Import passwords, Export backup, Replace vault from backup (CJK + file buttons) No logic changes. ### 4e. `renderSync()` — Sync sub-view This is the existing `renderSyncSection()` body verbatim. Remove the outer `peach-settings-group` wrapper; the body is now the sub-view itself. The Pro gate (blurred rows + overlay) works exactly as before. ### 4f. `renderAppearance()` — Appearance sub-view Contains the two rows currently buried in the "vault" group: - Accent color picker (`.peach-accent-picker` grid — same as current) - Theme toggle button Add a sub-section label `"THEME"` above the theme row and `"ACCENT"` above the accent picker. ### 4g. `renderShortcuts()` — Shortcuts sub-view Merges the two existing keyboard shortcut groups (currently duplicated): - One row: "Inline popup shortcut" with the `Ctrl+Shift+Space — customize ↗` link - The full `.peach-shortcuts-grid` (C, U, /, ↑↓, Esc, ⌘⇧P) No duplication. The customize link opens `chrome://extensions/shortcuts` exactly as before. ### 4h. `renderLicense()` — License/Account sub-view The existing "license" group body verbatim. Label at top: `"PLAN"`. ### 4i. `renderFeedback()` — Feedback sub-view ```tsx function renderFeedback() { const categories = ["Bug report", "Feature request", "Question", "Other"]; return (

Help us improve Peach. Every report goes directly to our inbox.

TYPE

{(cat) => ( )}

MESSAGE