952ee75b2c6e
show fresh recovery words in Settings after password change
- time
- 2026-05-05 15:53 UTC · 5 months ago
- machine
- koh2
- parent
- 62761aef1e41
↓ peach-browser-952ee75b.tar.gzatlas at this save
files changed2 files
src/popup/views/Settings.tsx+35 −26src/popup/views/contracts.test.tsx+48 −19src/popup/views/Settings.tsx+35 −26
@@ -2,6 +2,7 @@22 import { sendMessage } from "../App";33 import { PeachMark } from "../components/PeachMark";44 import { LockRuneCanvas } from "../components/LockRuneCanvas";5+import { RecoveryWordDisplayView } from "./RecoveryWordDisplay";56 import type { Canary } from "../../shared/types";67 import { ACCENT_COLORS } from "../../shared/types";78 import type {@@ -201,6 +202,7 @@201202 const [changePwSaving, setChangePwSaving] = createSignal(false);202203 const [changePwError, setChangePwError] = createSignal<string | null>(null);203204 const [changePwSuccess, setChangePwSuccess] = createSignal(false);205+ const [showNewRecoveryWords, setShowNewRecoveryWords] = createSignal(false);204206 205207 const [duressEnabled, setDuressEnabled] = createSignal(false);206208 const [showDuressSetup, setShowDuressSetup] = createSignal(false);@@ -308,9 +310,9 @@308310 payload: { currentPassword: changePwCurrent(), newPassword: changePwNew() },309311 }) as { ok: boolean; error?: string };310312 if (res.ok) {311- setChangePwSuccess(true);312313 setChangePwCurrent(""); setChangePwNew(""); setChangePwConfirm("");313- setTimeout(() => { setChangePwSuccess(false); setShowChangePw(false); }, 2200);314+ setShowChangePw(false);315+ setShowNewRecoveryWords(true);314316 } else {315317 setChangePwError(res.error ?? "failed to change password");316318 }@@ -1905,30 +1907,37 @@19051907 }19061908 19071909 return (1908- <div class="peach-overlay-view ov-enter-right" style="display:flex">1909- <div class="peach-overlay-header">1910- <button1911- class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn"1912- onClick={handleHeaderBack}1913- aria-label="Back"1914- >1915- <svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">1916- <path d="M6 1L1 6L6 11" />1917- </svg>1918- </button>1919- <span class="peach-overlay-title">{currentTitle()}</span>1920- <div class="peach-overlay-spacer"></div>1921- </div>1910+ <Show1911+ when={showNewRecoveryWords()}1912+ fallback={1913+ <div class="peach-overlay-view ov-enter-right" style="display:flex">1914+ <div class="peach-overlay-header">1915+ <button1916+ class="peach-btn peach-btn--ghost peach-btn--sm peach-icon-btn"1917+ onClick={handleHeaderBack}1918+ aria-label="Back"1919+ >1920+ <svg width="7" height="12" viewBox="0 0 7 12" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round">1921+ <path d="M6 1L1 6L6 11" />1922+ </svg>1923+ </button>1924+ <span class="peach-overlay-title">{currentTitle()}</span>1925+ <div class="peach-overlay-spacer"></div>1926+ </div>19221927 1923- <Switch fallback={renderHome()}>1924- <Match when={activeSection() === "security"}>{renderSecurity()}</Match>1925- <Match when={activeSection() === "vault"}>{renderVault()}</Match>1926- <Match when={activeSection() === "sync"}>{renderSync()}</Match>1927- <Match when={activeSection() === "appearance"}>{renderAppearance()}</Match>1928- <Match when={activeSection() === "shortcuts"}>{renderShortcuts()}</Match>1929- <Match when={activeSection() === "license"}>{renderLicense()}</Match>1930- <Match when={activeSection() === "feedback"}>{renderFeedback()}</Match>1931- </Switch>1932- </div>1928+ <Switch fallback={renderHome()}>1929+ <Match when={activeSection() === "security"}>{renderSecurity()}</Match>1930+ <Match when={activeSection() === "vault"}>{renderVault()}</Match>1931+ <Match when={activeSection() === "sync"}>{renderSync()}</Match>1932+ <Match when={activeSection() === "appearance"}>{renderAppearance()}</Match>1933+ <Match when={activeSection() === "shortcuts"}>{renderShortcuts()}</Match>1934+ <Match when={activeSection() === "license"}>{renderLicense()}</Match>1935+ <Match when={activeSection() === "feedback"}>{renderFeedback()}</Match>1936+ </Switch>1937+ </div>1938+ }1939+ >1940+ <RecoveryWordDisplayView onDone={() => setShowNewRecoveryWords(false)} />1941+ </Show>19331942 );19341943 }src/popup/views/contracts.test.tsx+48 −19
@@ -320,7 +320,19 @@320320 });321321 322322 it("Settings sends CHANGE_PASSWORD and forwards auto-lock/theme changes through callbacks", async () => {323- sendMessageMock.mockResolvedValue({ ok: true, data: null });323+ sendMessageMock.mockImplementation(async (msg: unknown) => {324+ const m = msg as { type: string };325+ if (m.type === "GET_RECOVERY_WORDS") {326+ return {327+ ok: true,328+ data: [329+ "abandon", "ability", "able", "about", "above", "absent",330+ "absorb", "abstract", "absurd", "abuse", "access", "accident",331+ ],332+ };333+ }334+ return { ok: true, data: null };335+ });324336 const onAutoLockMinutesChange = vi.fn();325337 const onThemeChange = vi.fn();326338 const container = mount(@@ -355,18 +367,44 @@355367 await flushView();356368 sendMessageMock.mockClear();357369 358- const headerBack = container.querySelector(".peach-overlay-header button") as HTMLButtonElement;370+ function headerBack() {371+ return container.querySelector(".peach-overlay-header button") as HTMLButtonElement;372+ }359373 360- const securityRow = Array.from(container.querySelectorAll("button.peach-settings-cat-row")).find(361- (b) => b.querySelector(".peach-settings-cat-name")?.textContent?.trim() === "Security"362- ) as HTMLButtonElement;363- securityRow.click();374+ // ── Auto-lock + theme changes (before password change) ─────────────375+ const securityRow = () =>376+ Array.from(container.querySelectorAll("button.peach-settings-cat-row")).find(377+ (b) => b.querySelector(".peach-settings-cat-name")?.textContent?.trim() === "Security"378+ ) as HTMLButtonElement;379+380+ securityRow().click();364381 await flushView();365382 366383 const selects = container.querySelectorAll("select");367384 changeSelect(selects[0] as HTMLSelectElement, "15");368385 expect(onAutoLockMinutesChange).toHaveBeenCalledWith(15);369386 387+ headerBack().click();388+ await flushView();389+390+ const appearanceRow = Array.from(container.querySelectorAll("button.peach-settings-cat-row")).find(391+ (b) => b.querySelector(".peach-settings-cat-name")?.textContent?.trim() === "Appearance"392+ ) as HTMLButtonElement;393+ appearanceRow.click();394+ await flushView();395+ const themeButton = Array.from(container.querySelectorAll("button")).find(396+ (candidate) => candidate.textContent?.trim() === "Switch to light"397+ ) as HTMLButtonElement;398+ themeButton.click();399+ expect(onThemeChange).toHaveBeenCalledWith("light");400+401+ // ── Password change → recovery words display ────────────────────────402+ headerBack().click();403+ await flushView();404+405+ securityRow().click();406+ await flushView();407+370408 const changeButton = Array.from(container.querySelectorAll("button")).find(371409 (candidate) => candidate.textContent?.trim() === "change"372410 ) as HTMLButtonElement;@@ -385,19 +423,6 @@385423 saveButton.click();386424 await flushView();387425 388- headerBack.click();389- await flushView();390- const appearanceRow = Array.from(container.querySelectorAll("button.peach-settings-cat-row")).find(391- (b) => b.querySelector(".peach-settings-cat-name")?.textContent?.trim() === "Appearance"392- ) as HTMLButtonElement;393- appearanceRow.click();394- await flushView();395- const themeButton = Array.from(container.querySelectorAll("button")).find(396- (candidate) => candidate.textContent?.trim() === "Switch to light"397- ) as HTMLButtonElement;398- themeButton.click();399- expect(onThemeChange).toHaveBeenCalledWith("light");400-401426 expect(sendMessageMock).toHaveBeenCalledWith({402427 type: "CHANGE_PASSWORD",403428 payload: {@@ -405,6 +430,10 @@405430 newPassword: "new-password-123",406431 },407432 });433+434+ // After a successful password change the recovery word display replaces settings.435+ expect(container.textContent).toContain("save your recovery words");436+ expect(sendMessageMock).toHaveBeenCalledWith({ type: "GET_RECOVERY_WORDS" });408437 });409438 410439 it("Entry copy actions send COPY and canary ping messages for password and username", async () => {