240a2e43d433
wire lastSyncAt into GET_SYNC_STATE; rewrite renderSync paired devices + trusted networks; vault advanced dropdown; rename formatHistoryTime to formatSnapshotTime
- time
- 2026-04-30 21:47 UTC · 6 months ago
- machine
- koh2
- parent
- aa5fb5dedb2b
↓ peach-browser-240a2e43.tar.gzatlas at this save
files changed2 files
src/background/messages.ts+8 −3src/popup/views/Settings.tsx+119 −65src/background/messages.ts+8 −3
@@ -637,11 +637,12 @@637637 const { getPairedDevices, getTrustedNetworks } = await import('./sync-storage');638638 const { isOnTailscale } = await import('./sync-network');639639 const { isProUser } = await import('./license');640+ const { storageGet } = await import('../shared/storage');640641 const pairedDevices = await getPairedDevices();641642 const trustedNetworks = await getTrustedNetworks();642643 const tailscale = await isOnTailscale();643644 const pro = await isProUser();644- 645+645646 let status: SyncStatus = "offline";646647 if (tailscale || trustedNetworks.some((n: { isTailscale?: boolean }) => !n.isTailscale)) {647648 if (pairedDevices.length === 0) {@@ -652,8 +653,12 @@652653 } else {653654 status = "not_trusted";654655 }655- 656- return { ok: true, data: { pairedDevices, trustedNetworks, status, isPro: pro } };656+657+ const lastSyncRaw = await storageGet("peach_last_sync_at");658+ const lastSyncParsed = lastSyncRaw ? Number.parseInt(lastSyncRaw, 10) : NaN;659+ const lastSyncAt = Number.isFinite(lastSyncParsed) ? lastSyncParsed : undefined;660+661+ return { ok: true, data: { pairedDevices, trustedNetworks, status, lastSyncAt, isPro: pro } };657662 }658663 case "TRIGGER_SYNC": {659664 const gate = await requirePro("sync");src/popup/views/Settings.tsx+119 −65
@@ -60,7 +60,7 @@6060 onExport: () => void;6161 onBreachCheckDone: () => void;6262 onOpenHealth?: () => void;63- onOpenSyncPairing?: (mode: "initiate" | "accept") => void;63+ onOpenSyncPairing?: () => void;6464 onOpenFileImport?: () => void;6565 onOpenPanic?: () => void;6666 }@@ -171,7 +171,7 @@171171 }172172 }173173 174- function formatHistoryTime(savedAt: number): string {174+ function formatSnapshotTime(savedAt: number): string {175175 const diff = Date.now() - savedAt;176176 if (diff < 60_000) return "Just now";177177 if (diff < 3_600_000) return `${Math.floor(diff / 60_000)}m ago`;@@ -196,6 +196,8 @@196196 )[reason] ?? reason;197197 }198198 199+ const [showVaultAdvanced, setShowVaultAdvanced] = createSignal(false);200+199201 const [showChangePw, setShowChangePw] = createSignal(false);200202 const [changePwCurrent, setChangePwCurrent] = createSignal("");201203 const [changePwNew, setChangePwNew] = createSignal("");@@ -417,6 +419,7 @@417419 const count = data?.syncedCount ?? 0;418420 setSyncFeedback({ kind: "ok", text: count > 0 ? `Synced with ${count} ${count === 1 ? "device" : "devices"}` : "Up to date" });419421 setLastSyncAt(Date.now());422+ void refreshSyncState();420423 } else {421424 const error = (res as { error?: string }).error;422425 const map: Record<string, string> = {@@ -433,16 +436,40 @@433436 }, 3500);434437 }435438 439+ async function refreshSyncState() {440+ const res = await sendMessage({ type: "GET_SYNC_STATE" });441+ const data = (res as { ok: boolean; data?: {442+ pairedDevices?: PairedDevice[];443+ trustedNetworks?: TrustedNetwork[];444+ status?: SyncStatus;445+ lastSyncAt?: number;446+ }}).data;447+ if (!data) return;448+ setPairedDevices(data.pairedDevices ?? []);449+ setTrustedNetworks(data.trustedNetworks ?? []);450+ setSyncStatus(data.status ?? "offline");451+ setLastSyncAt(data.lastSyncAt);452+ }453+436454 async function handleRemoveDevice(deviceId: string) {455+ setPairedDevices((prev) => prev.filter((d) => d.deviceId !== deviceId));437456 await sendMessage({ type: "REMOVE_PAIRED_DEVICE", payload: { deviceId } });457+ void refreshSyncState();438458 }439459 440460 async function handleRemoveNetwork(ssid?: string, bssid?: string, isTailscale?: boolean) {441461 await sendMessage({ type: "REMOVE_TRUSTED_NETWORK", payload: { ssid, bssid, isTailscale } });462+ void refreshSyncState();442463 }443464 465+ const [markingNetwork, setMarkingNetwork] = createSignal(false);466+444467 async function handleMarkNetworkTrusted() {468+ if (markingNetwork()) return;469+ setMarkingNetwork(true);445470 await sendMessage({ type: "MARK_NETWORK_TRUSTED" });471+ await refreshSyncState();472+ setMarkingNetwork(false);446473 }447474 448475 const [resetIdentityConfirm, setResetIdentityConfirm] = createSignal(false);@@ -1129,7 +1156,7 @@11291156 <div class="peach-history-row">11301157 <div class="peach-history-meta">11311158 <span class="peach-history-time">1132- {formatHistoryTime(snap.savedAt)}1159+ {formatSnapshotTime(snap.savedAt)}11331160 </span>11341161 <span class="peach-history-sub">11351162 {[@@ -1162,40 +1189,53 @@11621189 </p>11631190 </Show>11641191 1165- <div class="peach-danger-card">1166- <div class="peach-danger-card-head">1167- <span class="peach-danger-card-glyph" aria-hidden="true">⟳</span>1168- <h3 class="peach-danger-card-title">Replace vault from backup</h3>1169- </div>1170- <p class="peach-danger-card-copy">1171- Wipes the current vault and restores from a backup file.1172- Recoverable only if your backup is good.1173- </p>1174- <button1175- class="peach-danger-card-button"1176- onClick={() => props.onOpenFileImport?.()}1177- >1178- Choose backup file1179- </button>1180- </div>1192+ <button1193+ class="peach-settings-advanced-toggle"1194+ onClick={() => setShowVaultAdvanced((v) => !v)}1195+ aria-expanded={showVaultAdvanced()}1196+ >1197+ <span class="peach-settings-sub-label" style="margin:0">ADVANCED</span>1198+ <span class="peach-settings-advanced-chevron">1199+ {showVaultAdvanced() ? "▲" : "▼"}1200+ </span>1201+ </button>11811202 1182- <Show when={props.onOpenPanic}>1183- <div class="peach-danger-card peach-danger-card--hazard">1203+ <Show when={showVaultAdvanced()}>1204+ <div class="peach-danger-card">11841205 <div class="peach-danger-card-head">1185- <span class="peach-danger-card-glyph" aria-hidden="true">⚠</span>1186- <h3 class="peach-danger-card-title">Destroy vault</h3>1206+ <span class="peach-danger-card-glyph" aria-hidden="true">⟳</span>1207+ <h3 class="peach-danger-card-title">Replace vault from backup</h3>11871208 </div>11881209 <p class="peach-danger-card-copy">1189- Permanently wipes every entry, setting, and license on this1190- device. There is no recovery.1210+ Wipes the current vault and restores from a backup file.1211+ Recoverable only if your backup is good.11911212 </p>11921213 <button11931214 class="peach-danger-card-button"1194- onClick={() => props.onOpenPanic?.()}1215+ onClick={() => props.onOpenFileImport?.()}11951216 >1196- Begin destruction1217+ Choose backup file11971218 </button>11981219 </div>1220+1221+ <Show when={props.onOpenPanic}>1222+ <div class="peach-danger-card peach-danger-card--hazard">1223+ <div class="peach-danger-card-head">1224+ <span class="peach-danger-card-glyph" aria-hidden="true">⚠</span>1225+ <h3 class="peach-danger-card-title">Destroy vault</h3>1226+ </div>1227+ <p class="peach-danger-card-copy">1228+ Permanently wipes every entry, setting, and license on this1229+ device. There is no recovery.1230+ </p>1231+ <button1232+ class="peach-danger-card-button"1233+ onClick={() => props.onOpenPanic?.()}1234+ >1235+ Begin destruction1236+ </button>1237+ </div>1238+ </Show>11991239 </Show>12001240 </div>12011241 </div>@@ -1302,64 +1342,78 @@13021342 13031343 <div class="peach-settings-row peach-settings-row--meta peach-settings-row--stack">13041344 <span class="peach-sync-section-label">Paired devices ({pairedDevices().length})</span>1305- {pairedDevices().map((device) => (1306- <div class="peach-sync-device-row">1307- <div class="peach-sync-device-info">1308- <span class="peach-sync-device-name">{device.displayName}</span>1309- <span class="peach-sync-device-meta">1310- Last seen: {formatRelativeTime(device.lastSeenAt)} · Last synced: {formatRelativeTime(device.lastSyncAt)}1311- </span>1312- </div>1313- <button1314- class="peach-btn peach-btn--ghost peach-btn--sm peach-sync-remove-btn"1315- onClick={() => void handleRemoveDevice(device.deviceId)}1316- >1317- Remove1318- </button>1345+ <Show when={pairedDevices().length === 0}>1346+ <p class="peach-settings-hint">1347+ No paired devices yet. Add a device to start syncing.1348+ </p>1349+ </Show>1350+ <Show when={pairedDevices().length > 0}>1351+ <div class="peach-history-list">1352+ <For each={pairedDevices()}>1353+ {(device) => (1354+ <div class="peach-history-row">1355+ <div class="peach-history-meta">1356+ <span class="peach-history-time">{device.displayName}</span>1357+ <span class="peach-history-sub">1358+ {device.lastSyncAt1359+ ? `Last synced: ${formatSnapshotTime(device.lastSyncAt)}`1360+ : "Never synced"}1361+ </span>1362+ </div>1363+ <button1364+ class="peach-btn peach-btn--subtle peach-btn--sm peach-history-btn"1365+ onClick={() => void handleRemoveDevice(device.deviceId)}1366+ >1367+ Remove1368+ </button>1369+ </div>1370+ )}1371+ </For>13191372 </div>1320- ))}1373+ </Show>13211374 <div class="peach-settings-actions--row">13221375 <button13231376 class="peach-btn peach-btn--subtle peach-btn--sm"1324- onClick={() => props.onOpenSyncPairing?.("initiate")}1377+ onClick={() => props.onOpenSyncPairing?.()}13251378 >1326- Pair a new device1379+ Add a device13271380 </button>1328- <button1329- class="peach-btn peach-btn--subtle peach-btn--sm"1330- onClick={() => props.onOpenSyncPairing?.("accept")}1331- >1332- Pair with another device1333- </button>13341381 </div>13351382 </div>13361383 13371384 <div class="peach-settings-row peach-settings-row--meta peach-settings-row--stack">13381385 <span class="peach-sync-section-label">Trusted networks ({trustedNetworks().length})</span>1339- {trustedNetworks().filter(n => !n.isTailscale).map((network) => (1340- <div class="peach-sync-device-row">1341- <span class="peach-sync-device-name">{network.ssid ?? "Unknown network"}</span>1342- <button1343- class="peach-btn peach-btn--ghost peach-btn--sm peach-sync-remove-btn"1344- onClick={() => void handleRemoveNetwork(network.ssid, network.bssid, network.isTailscale)}1345- >1346- Remove1347- </button>1348- </div>1349- ))}1386+ <For each={trustedNetworks()}>1387+ {(network) => (1388+ <div class="peach-sync-device-row">1389+ <span class="peach-sync-device-name">1390+ {network.isTailscale ? "Tailscale" : (network.ssid ?? "Unknown network")}1391+ </span>1392+ <Show1393+ when={!network.isTailscale}1394+ fallback={<span class="peach-sync-device-meta">Always trusted</span>}1395+ >1396+ <button1397+ class="peach-btn peach-btn--ghost peach-btn--sm peach-sync-remove-btn"1398+ onClick={() => void handleRemoveNetwork(network.ssid, network.bssid, network.isTailscale)}1399+ >1400+ Remove1401+ </button>1402+ </Show>1403+ </div>1404+ )}1405+ </For>13501406 <div class="peach-settings-actions--row">13511407 <Show when={!isCurrentNetworkTrusted()}>13521408 <button13531409 class="peach-btn peach-btn--subtle peach-btn--sm"13541410 onClick={() => void handleMarkNetworkTrusted()}1411+ disabled={markingNetwork()}13551412 >1356- Mark current network as trusted1413+ {markingNetwork() ? "Marking…" : "Mark current network as trusted"}13571414 </button>13581415 </Show>13591416 </div>1360- <span class="peach-sync-device-meta">1361- Tailscale is always trusted1362- </span>13631417 </div>13641418 13651419 <div class="peach-settings-row peach-settings-row--meta peach-settings-row--stack">