An easy-to-host PDS on the ATProtocol, iPhone and MacOS. Maintain control of your keys and data, always.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(identity-wallet): add alert badge to identity cards

Add alert status display on identity cards in IdentityListHome:
- Fetch alert status via checkIdentityStatus() on mount
- Listen for plc_alert events from background monitoring
- Render red alert badge when alertCount > 0
- Make alert badge clickable to navigate to alert detail
- Add onalert callback prop for parent handling

Verifies: plc-key-management.AC6.4 (alert badge display)

+60 -3
+60 -3
apps/identity-wallet/src/lib/components/home/IdentityListHome.svelte
··· 1 1 <script lang="ts"> 2 - import { onMount } from 'svelte'; 3 - import { listIdentities, getStoredDidDoc, getDeviceKeyId } from '$lib/ipc'; 2 + import { onMount, onDestroy } from 'svelte'; 3 + import { listen, type UnlistenFn } from '@tauri-apps/api/event'; 4 + import { listIdentities, getStoredDidDoc, getDeviceKeyId, checkIdentityStatus, type UnauthorizedChange, type IdentityStatus } from '$lib/ipc'; 4 5 import { extractPdsFromPlcDoc, extractHandle, truncateDid } from '$lib/did-doc-utils'; 5 6 import DIDAvatar from './DIDAvatar.svelte'; 6 7 7 8 let { 8 9 onadd, 9 10 onselect, 11 + onalert, 10 12 }: { 11 13 onadd: () => void; 12 14 onselect: (did: string, didDoc: Record<string, unknown>) => void; 15 + onalert?: (did: string, changes: UnauthorizedChange[]) => void; 13 16 } = $props(); 14 17 15 18 interface IdentityCard { ··· 23 26 let didDocs = $state<Map<string, Record<string, unknown>>>(new Map()); 24 27 let loading = $state(true); 25 28 let loadError = $state<string | null>(null); 29 + let alertData = $state<Map<string, UnauthorizedChange[]>>(new Map()); 30 + let unlisten: UnlistenFn | null = null; 26 31 27 32 function isDeviceKeyRoot( 28 33 didDoc: Record<string, unknown>, ··· 83 88 } 84 89 } 85 90 86 - onMount(() => { 91 + onMount(async () => { 87 92 loadData(); 93 + 94 + // Fetch initial alert status 95 + checkIdentityStatus() 96 + .then((statuses) => { 97 + const data = new Map<string, UnauthorizedChange[]>(); 98 + for (const s of statuses) { 99 + if (s.alertCount > 0) data.set(s.did, s.unauthorizedChanges); 100 + } 101 + alertData = data; 102 + }) 103 + .catch((e) => console.warn('Alert check failed:', e)); 104 + 105 + // Listen for plc_alert events from background monitoring timer 106 + unlisten = await listen<IdentityStatus[]>('plc_alert', (event) => { 107 + const data = new Map<string, UnauthorizedChange[]>(); 108 + for (const s of event.payload) { 109 + if (s.alertCount > 0) data.set(s.did, s.unauthorizedChanges); 110 + } 111 + alertData = data; 112 + }); 113 + }); 114 + 115 + onDestroy(() => { 116 + unlisten?.(); 88 117 }); 89 118 90 119 function getBadgeLabel(deviceKeyIsRoot: boolean | null): string { ··· 137 166 </div> 138 167 </div> 139 168 <div class="card-badge"> 169 + {#if alertData.get(card.did)?.length} 170 + <div 171 + class="badge badge--alert" 172 + role="button" 173 + tabindex="0" 174 + onkeydown={(e) => { if (e.key === 'Enter') { e.stopPropagation(); onalert?.(card.did, alertData.get(card.did) ?? []); } }} 175 + onclick={(e) => { e.stopPropagation(); onalert?.(card.did, alertData.get(card.did) ?? []); }} 176 + > 177 + <span class="badge-dot"></span> 178 + {alertData.get(card.did)?.length} {alertData.get(card.did)?.length === 1 ? 'Alert' : 'Alerts'} 179 + </div> 180 + {/if} 140 181 <span 141 182 class="badge" 142 183 class:badge--root={card.deviceKeyIsRoot === true} ··· 331 372 332 373 .badge--unknown .badge-dot { 333 374 background: #9ca3af; 375 + } 376 + 377 + .badge--alert { 378 + background: #fef2f2; 379 + color: #991b1b; 380 + cursor: pointer; 381 + transition: background 0.2s, color 0.2s; 382 + } 383 + 384 + .badge--alert:active { 385 + background: #fecaca; 386 + color: #7f1d1d; 387 + } 388 + 389 + .badge--alert .badge-dot { 390 + background: #ef4444; 334 391 } 335 392 336 393 .empty-state {