fix(crypto): suppress approval banner for devices with existing wraps

After 0.16.2 some users saw the approval banner stack up to 6+ entries
on first launch — every old device they ever registered (Tauri-era,
test installs, dev builds) showed up because the only "already legit"
filter was `created_at <= ownDevice.created_at`. That fails when own
device is restored from Backup (older than every other entry) or when
the user accumulated installs around the migration window.

Add a semantic check: if a device already has at least one row in
`conversation_keys` (recipient_device_id), it has been wrapped before
and is by definition not awaiting approval. Treat as approved silently.
Bulk query against the candidate IDs, no N+1.

Plus UX: when more than one request is pending, render a sticky header
with a count and "Alle ablehnen" button so users with stale piles can
clear them in one click.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
byGalax
2026-05-07 17:24:17 +02:00
parent e16b248366
commit f1c7501807
2 changed files with 76 additions and 11 deletions
@@ -3,6 +3,7 @@ import { useTranslation } from 'react-i18next';
import {
approveDevice,
denyAllPending,
denyDevice,
type PendingApproval,
subscribePendingApprovals,
@@ -53,6 +54,24 @@ export function DeviceApprovalBanner() {
return (
<div className="pointer-events-none fixed bottom-6 right-6 z-40 flex w-[min(92vw,420px)] flex-col gap-3">
{pending.length > 1 && (
<div className="pointer-events-auto flex items-center justify-between gap-3 rounded-xl border border-line bg-surface-3/80 px-4 py-2 text-xs text-fg-muted backdrop-blur-md">
<span>
{t('app:device_approval.bulk_count', {
count: pending.length,
defaultValue: '{{count}} Geräte warten auf Bestätigung',
})}
</span>
<button
type="button"
onClick={() => denyAllPending()}
disabled={busyId !== null}
className="cursor-pointer rounded-md border border-line bg-transparent px-3 py-1 text-xs font-medium text-fg-muted transition hover:bg-surface-2 hover:text-fg disabled:opacity-50 focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40"
>
{t('app:device_approval.deny_all', { defaultValue: 'Alle ablehnen' })}
</button>
</div>
)}
{pending.map((req) => {
const busy = busyId === req.deviceId;
const errored = errorId === req.deviceId;