96284c293604

list all six Pro features in the nudge from a catalog shared with Settings (v5.3.2)

time
2026-10-06 13:41 UTC · 2 weeks ago
machine
Junes-MacBook-Pro.local
agent
claude-opus-5-5
session
peach pro nudge screen
parent
5157b6df8f7d
8 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/popup/pro-features.ts+15 −0src/popup/styles/popup.css+1 −13src/popup/views/ProNudge.test.tsx+9 −0src/popup/views/ProNudge.tsx+9 −18src/popup/views/Settings.tsx+1 −20
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.3.1",4+  "version": "5.3.2",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -2,7 +2,7 @@22   "manifest_version": 3,33   "minimum_chrome_version": "116",44   "name": "Peach Password Manager",5-  "version": "5.3.1",5+  "version": "5.3.2",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "5.3.1",3+  "version": "5.3.2",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/popup/pro-features.ts+15 −0
@@ -0,0 +1,15 @@1+import type { ProFeature } from "../shared/types";2+3+type ProFeatureListing = { feature: ProFeature; name: string; desc: string };4+5+export const PRO_FEATURES: ProFeatureListing[] = [6+  { feature: "sync", name: "Peach Sync", desc: "Encrypted · peer-to-peer" },7+  { feature: "codex", name: "Peach Codex", desc: "Restore from paper" },8+  { feature: "passkeys", name: "Passkeys", desc: "Zero-knowledge WebAuthn" },9+  { feature: "advanced_health", name: "Advanced Vault Health", desc: "Reuse · age · exposure" },10+];11+12+export const PRO_SECURITY_FEATURES: ProFeatureListing[] = [13+  { feature: "canary_entry", name: "Canary entries", desc: "Detects vault compromise" },14+  { feature: "duress_vault", name: "Duress vault", desc: "Decoy under coercion" },15+];

view file

src/popup/styles/popup.css+1 −13
@@ -8072,23 +8072,11 @@80728072   margin-bottom: 2px;80738073 }80748074 8075-.peach-pro-nudge-list {8075+.peach-pro-nudge-grid {80768076   margin: 8px 0 0;80778077   padding: 0;80788078   list-style: none;8079-  border-top: 1px solid var(--peach-border);80808079 }8081-8082-.peach-pro-nudge-item {8083-  display: grid;8084-  grid-template-columns: 24px 1fr;8085-  column-gap: 8px;8086-  row-gap: 2px;8087-  padding: 7px 0;8088-  border-bottom: 1px solid var(--peach-border);8089-}8090-8091-.peach-pro-nudge-item .peach-pro-grid-num { grid-row: span 2; padding-top: 2px; }80928080 80938081 .peach-pro-nudge-actions {80948082   display: flex;

view file

src/popup/views/ProNudge.test.tsx+9 −0
@@ -3,6 +3,7 @@33 import { afterEach, describe, expect, it, vi } from "vitest";44 55 import { flushView, renderView } from "./view-test-utils";6+import { PRO_FEATURES, PRO_SECURITY_FEATURES } from "../pro-features";67 78 const snoozeMock = vi.fn(async () => {});89 const silenceMock = vi.fn(async () => {});@@ -63,6 +64,14 @@6364     await flushView();6465 6566     expect(onDismiss).toHaveBeenCalledOnce();67+  });68+69+  it("lists every Pro feature from the shared catalog", () => {70+    const view = renderView(() => <ProNudgeView onSeePro={() => {}} onDismiss={() => {}} />);71+    dispose = view.dispose;72+73+    const names = [...view.container.querySelectorAll(".peach-pro-grid-name")].map((n) => n.textContent);74+    expect(names).toEqual([...PRO_FEATURES, ...PRO_SECURITY_FEATURES].map((f) => f.name));6675   });6776 6877   it("opens Pro without recording a dismissal", () => {

view file

src/popup/views/ProNudge.tsx+9 −18
@@ -1,14 +1,8 @@11 import { For } from "solid-js";22 import { PeachMark } from "../components/PeachMark";33 import { silenceProNudge, snoozeProNudge } from "../../shared/pro-nudge";4+import { PRO_FEATURES, PRO_SECURITY_FEATURES } from "../pro-features";45 5-const PRO_HIGHLIGHTS = [6-  ["Peach Sync", "Encrypted, peer-to-peer, across every device"],7-  ["Peach Codex", "Your whole vault, restorable from paper"],8-  ["Passkeys", "Zero-knowledge WebAuthn, built in"],9-  ["Duress vault", "A convincing decoy under coercion"],10-] as const;11-126 export function ProNudgeView(props: { onSeePro: () => void; onDismiss: () => void }) {137   async function dismiss(record: () => Promise<void>) {148     await record().catch(() => {@@ -25,22 +19,19 @@2519         <h1 id="pro-nudge-title" class="peach-pro-headline">2620           Peach stays free.<br />Pro keeps it <em>growing.</em>2721         </h1>28-        <p class="peach-pro-subtitle">29-          No subscription, no account. One purchase supports an independent30-          password manager and unlocks the extras, forever.31-        </p>22+        <p class="peach-pro-subtitle">No subscription. No account. Yours forever.</p>3223 33-        <ol class="peach-pro-nudge-list">34-          <For each={PRO_HIGHLIGHTS}>35-            {([name, description], index) => (36-              <li class="peach-pro-nudge-item">24+        <ul class="peach-pro-grid peach-pro-nudge-grid">25+          <For each={[...PRO_FEATURES, ...PRO_SECURITY_FEATURES]}>26+            {(item, index) => (27+              <li class="peach-pro-grid-item">3728                 <span class="peach-pro-grid-num">{String(index() + 1).padStart(2, "0")}</span>38-                <span class="peach-pro-grid-name">{name}</span>39-                <span class="peach-pro-grid-desc">{description}</span>29+                <span class="peach-pro-grid-name">{item.name}</span>30+                <span class="peach-pro-grid-desc">{item.desc}</span>4031               </li>4132             )}4233           </For>43-        </ol>34+        </ul>4435 4536         <div class="peach-pro-nudge-actions">4637           <button type="button" class="peach-pro-upgrade-cta" onClick={props.onSeePro}>

view file

src/popup/views/Settings.tsx+1 −20
@@ -33,6 +33,7 @@3333   type ShortcutAction,3434 } from "../../shared/shortcuts";3535 import { WEB3FORMS_KEY } from "../../shared/config";36+import { PRO_FEATURES, PRO_SECURITY_FEATURES } from "../pro-features";3637 3738 interface LicenseInfo {3839   valid: boolean;@@ -2268,26 +2269,6 @@22682269       );22692270     }22702271   }2271-2272-  const PRO_FEATURES: Array<{2273-    feature: ProFeature;2274-    name: string;2275-    desc: string;2276-  }> = [2277-    { feature: "sync", name: "Peach Sync", desc: "Encrypted · peer-to-peer" },2278-    { feature: "codex", name: "Peach Codex", desc: "Restore from paper" },2279-    { feature: "passkeys", name: "Passkeys", desc: "Zero-knowledge WebAuthn" },2280-    { feature: "advanced_health", name: "Advanced Vault Health", desc: "Reuse · age · exposure" },2281-  ];2282-2283-  const PRO_SECURITY_FEATURES: Array<{2284-    feature: ProFeature;2285-    name: string;2286-    desc: string;2287-  }> = [2288-    { feature: "canary_entry", name: "Canary entries", desc: "Detects vault compromise" },2289-    { feature: "duress_vault", name: "Duress vault", desc: "Decoy under coercion" },2290-  ];22912272 22922273   const [licenseKey, setLicenseKey] = createSignal<string | null>(null);22932274   const [showKey, setShowKey] = createSignal(false);

view file