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
↓ peach-browser-61a7b664.tar.gzatlas at this save
files changed3 files
src/popup/App.tsx+13 −0src/popup/styles/popup.css+23 −0src/popup/views/SyncPairing.tsx+14 −103src/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>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;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"}>