From d0aa56761a99c237b524d064c48e275dbdf19f5e Mon Sep 17 00:00:00 2001 From: Pitrick3141 Date: Wed, 12 Aug 2026 16:53:54 +0800 Subject: [PATCH] feat(wallet): Enable local deletion for unusable identities --- apps/wallet/src/App.tsx | 18 +++++ apps/wallet/src/components/IdentityCard.tsx | 31 +++++--- apps/wallet/src/components/IdentityDetail.tsx | 52 ++++++++++--- apps/wallet/src/components/flows.tsx | 73 +++++++++++++++++++ .../src/lib/identity-capabilities.test.ts | 14 ++++ apps/wallet/src/lib/identity-capabilities.ts | 15 ++++ apps/wallet/src/lib/wallet-adapter.ts | 4 + packages/wallet-core/src/types.ts | 2 + packages/wallet-core/src/wallet-core.ts | 55 ++++++++++++++ packages/wallet-core/test/device-v2.test.ts | 33 +++++++++ packages/wallet-core/test/wallet-core.test.ts | 26 +++++++ tests/browser-e2e/security.spec.ts | 9 +++ 12 files changed, 311 insertions(+), 21 deletions(-) diff --git a/apps/wallet/src/App.tsx b/apps/wallet/src/App.tsx index 45e054b..bac4f76 100644 --- a/apps/wallet/src/App.tsx +++ b/apps/wallet/src/App.tsx @@ -31,6 +31,7 @@ import { ContinuityLinkModal, CreateIdentityModal, DisposeIdentityModal, + RemoveLocalIdentityModal, RotateIdentityModal, } from './components/flows'; import { @@ -64,6 +65,7 @@ type Flow = device: LocalIdentitySummary['issuedDevices'][number]; } | { type: 'dispose'; identity: LocalIdentitySummary } + | { type: 'remove-local'; identity: LocalIdentitySummary } | { type: 'rotate'; identity: LocalIdentitySummary } | { type: 'continuity'; identity: LocalIdentitySummary }; @@ -169,6 +171,14 @@ function App() { }); }; + const removeLocalIdentity = async (identity: LocalIdentitySummary) => { + await walletAdapter.removeLocalIdentity(identity.localId); + setSelectedId(undefined); + await refresh(); + setFlow(undefined); + setNotice({ kind: 'success', message: 'Identity removed from this wallet.' }); + }; + const rotateIdentity = async (identity: LocalIdentitySummary, input: RotateIdentityInput) => { const created = await walletAdapter.rotate(identity.localId, input); await refresh(); @@ -555,6 +565,7 @@ function App() { onClose={() => setSelectedId(undefined)} onRotate={() => setFlow({ type: 'rotate', identity: selectedIdentity })} onDispose={() => setFlow({ type: 'dispose', identity: selectedIdentity })} + onRemoveLocal={() => setFlow({ type: 'remove-local', identity: selectedIdentity })} onContinuity={() => setFlow({ type: 'continuity', identity: selectedIdentity })} retryingRegistration={retryingRegistrationId === selectedIdentity.localId} onRetryRegistration={() => void retryRegistration(selectedIdentity)} @@ -632,6 +643,13 @@ function App() { onDispose={() => disposeIdentity(flow.identity)} /> )} + {flow?.type === 'remove-local' && ( + setFlow(undefined)} + onRemove={() => removeLocalIdentity(flow.identity)} + /> + )} {flow?.type === 'rotate' && ( = identity.device.expiresAt); + const terminal = !active || deviceRevoked; + const statusLabel = deviceRevoked + ? 'Revoked device' + : deviceExpired + ? 'Expired device' + : deviceState === 'pending-activation' + ? 'Pending activation' + : deviceState === 'active' + ? 'Active device' : active ? identity.registered ? 'Active' @@ -57,7 +64,7 @@ export function IdentityCard({ layout initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }} - {...(active ? { whileHover: { y: -2 } } : {})} + {...(!terminal ? { whileHover: { y: -2 } } : {})} className={`overflow-hidden rounded-2xl border bg-white transition-all ${ selected ? 'border-indigo-300 ring-2 ring-indigo-100' @@ -68,10 +75,10 @@ export function IdentityCard({
- {active ? ( + {!terminal ? (