61a7b6644c68

wire SyncPairingView into App.tsx (sync-pairing view); single-mode discovery; Advanced toggle CSS

time
2026-04-30 21:49 UTC · 5 months ago
machine
koh2
parent
240a2e43d433
3 files src/popup/App.tsx+13 −0src/popup/styles/popup.css+23 −0src/popup/views/SyncPairing.tsx+14 −103
src/popup/App.tsx+13 −0
@@ -39,6 +39,7 @@3939 const HealthView    = lazy(() => import("./views/Health").then(m => ({ default: m.HealthView })));4040 const TrashView     = lazy(() => import("./views/TrashView").then(m => ({ default: m.TrashView })));4141 const PanicView     = lazy(() => import("./views/Panic").then(m => ({ default: m.PanicView })));42+const SyncPairingView = lazy(() => import("./views/SyncPairing").then(m => ({ default: m.SyncPairingView })));4243 4344 const Autofill = registerPlugin<{4445   checkPendingSave(): Promise<{ pending: string | null }>;@@ -53,6 +54,7 @@5354   | "list"5455   | "add"5556   | "settings"57+  | "sync-pairing"5658   | "import"5759   | "export"5860   | "health"@@ -335,6 +337,7 @@335337         return;336338       case "health":337339       case "panic":340+      case "sync-pairing":338341         setView("settings");339342         return;340343       case "trash":@@ -630,6 +633,7 @@630633       if (e.key === "Escape") {631634         if (v === "list") { setView("entry"); return; }632635         if (v === "settings" || v === "import" || v === "export" || v === "add" || v === "health") { handleBack(); return; }636+        if (v === "sync-pairing") { setView("settings"); return; }633637       }634638 635639       // Bindings-driven dispatch. Single-key letter bindings without@@ -1315,8 +1319,17 @@13151319                   onExport={() => setView("export")}13161320                   onBreachCheckDone={loadEntries}13171321                   onOpenHealth={openHealth}1322+                  onOpenSyncPairing={() => setView("sync-pairing")}13181323                   onOpenFileImport={handleOpenFileImport}13191324                   onOpenPanic={() => setView("panic")}1325+                />1326+              </Suspense>1327+            </Show>1328+            <Show when={view() === "sync-pairing"}>1329+              <Suspense fallback={<div class="peach-loading"><PeachWordmark /></div>}>1330+                <SyncPairingView1331+                  onClose={() => setView("settings")}1332+                  onPaired={() => { /* sync state refreshes when popup returns to Settings */ }}13201333                 />13211334               </Suspense>13221335             </Show>

view file

src/popup/styles/popup.css+23 −0
@@ -1974,6 +1974,29 @@19741974   margin-top: 4px;19751975 }19761976 1977+/* ── Settings: collapsible Advanced section ─────────────────────────────── */1978+.peach-settings-advanced-toggle {1979+  display: flex;1980+  align-items: center;1981+  justify-content: space-between;1982+  width: 100%;1983+  background: none;1984+  border: none;1985+  cursor: pointer;1986+  padding: 12px 0 4px;1987+  margin: 0;1988+  color: inherit;1989+}1990+.peach-settings-advanced-toggle:hover .peach-settings-advanced-chevron {1991+  color: var(--peach-t1);1992+}1993+.peach-settings-advanced-chevron {1994+  font-size: 9px;1995+  color: var(--peach-t3);1996+  letter-spacing: 0;1997+  transition: color 120ms ease;1998+}1999+19772000 /* ── Feedback form ──────────────────────────────────────────────────────── */19782001 .peach-feedback-group {19792002   flex: 1;

view file

src/popup/views/SyncPairing.tsx+14 −103
@@ -3,7 +3,6 @@33 import type { DiscoveredPeer, PairedDevice, PairingStep } from "../../shared/sync-types";44 55 interface SyncPairingProps {6-  mode: "initiate" | "accept";76   onClose: () => void;87   onPaired: (device: PairedDevice) => void;98 }@@ -14,19 +13,13 @@1413 1514 export function SyncPairingView(props: SyncPairingProps) {1615   const [step, setStep] = createSignal<PairingStep>({ stage: "idle" });17-  const [codeInput, setCodeInput] = createSignal("");18-  const [codeError, setCodeError] = createSignal<string | null>(null);1916 20-  // Discover-and-tap state for the initiate flow. The background broadcasts21-  // SYNC_PEER_DISCOVERED / SYNC_PEER_LOST as peers come and go on mDNS;22-  // tapping a peer routes to INITIATE_PAIR_WITH_PEER.2317   const [discoveredPeers, setDiscoveredPeers] = createSignal<DiscoveredPeer[]>([]);2418   const [discoveryActive, setDiscoveryActive] = createSignal(false);2519   const [discoveryError, setDiscoveryError] = createSignal<string | null>(null);2620   const [pairingPeerId, setPairingPeerId] = createSignal<string | null>(null);2721 2822   onMount(() => {29-    if (props.mode !== "initiate") return;3023     void startDiscovery();3124   });3225 @@ -86,40 +79,6 @@8679     props.onPaired(paired);8780   }8881 89-  async function handleConnect() {90-    const code = codeInput().replace(/\D/g, "").slice(0, 6);91-    if (code.length !== 6) {92-      setCodeError("Enter a 6-digit code");93-      return;94-    }95-    setCodeError(null);96-    setStep({ stage: "connecting", peerName: "" });97-98-    const res = await sendMessage({99-      type: "ACCEPT_PAIRING",100-      payload: { code },101-    });102-    const response = res as { ok: boolean; data?: PairedDevice; error?: string };103-104-    if (!response.ok) {105-      setStep({ stage: "error", message: response.error ?? "Connection failed" });106-      return;107-    }108-    setStep({ stage: "verifying" });109-110-    if (response.data) {111-      setStep({ stage: "done", device: response.data });112-      props.onPaired(response.data);113-    }114-  }115-116-  function handleCodeInput(e: InputEvent) {117-    const target = e.currentTarget as HTMLInputElement;118-    const value = target.value.replace(/\D/g, "").slice(0, 6);119-    setCodeInput(value);120-    setCodeError(null);121-  }122-12382   return (12483     <div class="peach-overlay-view ov-enter-right">12584       <div class="peach-overlay-header">@@ -132,14 +91,14 @@13291             <path d="M6 1L1 6L6 11" />13392           </svg>13493         </button>135-        <span class="peach-overlay-title">pair devices</span>94+        <span class="peach-overlay-title">add a device</span>13695         <div class="peach-overlay-spacer" />13796       </div>13897 13998       <div class="peach-pairing-body">140-        <Show when={props.mode === "initiate" && step().stage !== "done" && step().stage !== "error" && step().stage !== "connecting" && step().stage !== "verifying"}>99+        <Show when={step().stage === "idle"}>141100           <div class="peach-pairing-content peach-pairing-discover">142-            <p class="peach-pairing-headline">Pair a device</p>101+            <p class="peach-pairing-headline">Add a device</p>143102             <Show when={discoveryError()}>144103               <p class="peach-pairing-error">145104                 {discoveryError() === "pro_required"@@ -150,7 +109,7 @@150109             <Show when={!discoveryError() && discoveredPeers().length === 0}>151110               <p class="peach-pairing-status">152111                 <Spinner />153-                Searching for Peach on your local network...112+                Searching for Peach on your local network…154113               </p>155114             </Show>156115             <For each={discoveredPeers()}>@@ -168,74 +127,26 @@168127                       onClick={() => void handlePairWithPeer(peer)}169128                       disabled={pairingPeerId() !== null}170129                     >171-                      {pairingPeerId() === peer.deviceId ? "..." : "Pair →"}130+                      {pairingPeerId() === peer.deviceId ? "…" : "Pair →"}172131                     </button>173132                   </Show>174133                 </div>175134               )}176135             </For>177136             <p class="peach-pairing-subline peach-pairing-subline--center">178-              Make sure both devices are on the same Wi-Fi network.137+              Open Peach on the other device. Both devices need to be on the138+              same WiFi network.179139             </p>180140           </div>181141         </Show>182142 183-        <Show when={props.mode === "accept"}>184-          <Show when={step().stage === "idle" || step().stage === "entering_code"}>185-            <div class="peach-pairing-content">186-              <p class="peach-pairing-headline">Pair with another device</p>187-              <p class="peach-pairing-subline">188-                Enter the 6-digit code shown on the other device.189-              </p>190-              <div class="peach-pairing-code-inputs">191-                {Array.from({ length: 6 }, (_, i) => (192-                  <input193-                    class={`peach-pairing-code-box ${codeError() ? "has-error" : ""}`}194-                    type="text"195-                    inputMode="numeric"196-                    maxLength={1}197-                    value={codeInput()[i] ?? ""}198-                    onInput={(e) => {199-                      const current = codeInput();200-                      const digit = (e.currentTarget as HTMLInputElement).value.replace(/\D/g, "").slice(-1);201-                      const newValue = current.slice(0, i) + digit + current.slice(i + 1);202-                      setCodeInput(newValue.slice(0, 6));203-                      setCodeError(null);204-                      if (digit && i < 5) {205-                        const inputs = document.querySelectorAll(".peach-pairing-code-box");206-                        (inputs[i + 1] as HTMLInputElement)?.focus();207-                      }208-                    }}209-                    onKeyDown={(e) => {210-                      if (e.key === "Backspace" && !codeInput()[i] && i > 0) {211-                        const inputs = document.querySelectorAll(".peach-pairing-code-box");212-                        (inputs[i - 1] as HTMLInputElement)?.focus();213-                      }214-                    }}215-                  />216-                ))}217-              </div>218-              <Show when={codeError()}>219-                <p class="peach-pairing-error">{codeError()}</p>220-              </Show>221-              <button222-                class="peach-btn peach-btn--accent peach-btn--md peach-lock-submit"223-                onClick={() => void handleConnect()}224-                disabled={codeInput().length !== 6}225-              >226-                Connect227-              </button>228-            </div>229-          </Show>230-231-          <Show when={step().stage === "connecting" || step().stage === "verifying"}>232-            <div class="peach-pairing-content">233-              <Spinner />234-              <p class="peach-pairing-status">235-                {step().stage === "connecting" ? "Connecting..." : "Verifying..."}236-              </p>237-            </div>238-          </Show>143+        <Show when={step().stage === "connecting" || step().stage === "verifying"}>144+          <div class="peach-pairing-content">145+            <Spinner />146+            <p class="peach-pairing-status">147+              {step().stage === "connecting" ? "Connecting…" : "Verifying…"}148+            </p>149+          </div>239150         </Show>240151 241152         <Show when={step().stage === "done"}>

view file