From 109852ed75638f32467acf0ea6ef75f98cd5d7e0 Mon Sep 17 00:00:00 2001 From: Jason Tudisco Date: Mon, 25 May 2026 15:34:52 -0600 Subject: [PATCH] feat(kez-chat/web): dashboard shows verified claims MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dashboard's Claims section used to be a "here's what claims are" teaser with a link to /claims. Now it shows the actual verified rows: • Empty state → "+ Add your first claim" CTA • Claims exist, none verified → amber callout pointing at Re-verify • Verified claims → green rows, one per verified claim: [GitHub] github:tudisco ✓ Verified via public gist proof ↗ • Trailing summary if relevant: "1 failed verification. 2 not yet checked. See the claims page for details." A "Re-verify all" button at the section header re-runs every verifier in place, so the dashboard stays fresh without a round-trip to /claims. Uses $derived to bucket claims by verification status. $state.snapshot before passing each claim to the verifier (same proxy/structuredClone gotcha as the Claims page). Co-Authored-By: Claude Opus 4.7 --- kez-chat/web/src/routes/Dashboard.svelte | 142 +++++++++++++++++++++-- 1 file changed, 132 insertions(+), 10 deletions(-) diff --git a/kez-chat/web/src/routes/Dashboard.svelte b/kez-chat/web/src/routes/Dashboard.svelte index 5b5b2c5..823ed46 100644 --- a/kez-chat/web/src/routes/Dashboard.svelte +++ b/kez-chat/web/src/routes/Dashboard.svelte @@ -4,16 +4,37 @@ import { bytesToHex } from "@noble/hashes/utils"; import { lookup, ApiError } from "../lib/api.js"; import { session } from "../lib/store.svelte.js"; + import { + listClaims, + setVerifyResult, + type StoredClaim, + } from "../lib/claims-store.js"; + import { verifyClaim } from "../lib/verify.js"; let registryRecord = $state(null); let loading = $state(true); let error = $state(null); + let claims = $state([]); + let verifyingAll = $state(false); + + // Derived buckets for the verified-claims section. + const verifiedClaims = $derived( + claims.filter((c) => c.last_verify?.status === "ok"), + ); + const failedClaims = $derived( + claims.filter((c) => c.last_verify?.status === "fail"), + ); + const unverifiedClaims = $derived( + claims.filter((c) => !c.last_verify), + ); + onMount(async () => { if (!session.unlocked) { push("/unlock"); return; } + claims = await listClaims(); try { registryRecord = await lookup(session.unlocked.handle); } catch (e) { @@ -27,6 +48,33 @@ } }); + async function reverifyAll() { + verifyingAll = true; + try { + for (const c of claims) { + const result = await verifyClaim($state.snapshot(c) as StoredClaim); + await setVerifyResult(c.id, result); + } + claims = await listClaims(); + } finally { + verifyingAll = false; + } + } + + /** Friendly label per channel — used in the verified-claims row. */ + function channelLabel(ch: string): string { + return ( + { + github: "GitHub", + dns: "DNS", + web: "Website", + nostr: "Nostr", + bluesky: "Bluesky", + ap: "ActivityPub", + } as Record + )[ch] ?? ch; + } + function showSeed() { if (!session.unlocked) return; const hex = bytesToHex(session.unlocked.seed); @@ -64,21 +112,95 @@
-
+
-

Claims

+

+ Verified claims +

- Link other accounts (GitHub, your domain, nostr, Bluesky, ActivityPub) - to your KEZ identity by signing claims and publishing the proofs. + Accounts cryptographically linked to + {session.unlocked.handle}@{session.unlocked.server}. + Anyone can re-verify these without trusting this server.

- - Manage claims → - +
+ {#if claims.length > 0} + + {/if} + + Manage → + +
+ + {#if claims.length === 0} +

+ No claims yet. Link a GitHub account, a domain, your nostr identity, + or anywhere else you'd like to prove you control. +

+ + + Add your first claim + + {:else if verifiedClaims.length === 0} +

+ You have {claims.length} claim{claims.length === 1 ? "" : "s"} but + none are verified yet. Publish the proofs on each channel and hit + Re-verify all. +

+ {:else} +
    + {#each verifiedClaims as c (c.id)} +
  • +
    +
    + + {channelLabel(c.channel)} + + + {c.envelope.payload.subject} + +
    +

    + ✓ {c.last_verify?.summary} +

    +
    + {#if c.last_verify?.evidence_url} + + proof ↗ + + {/if} +
  • + {/each} +
+ {#if failedClaims.length > 0 || unverifiedClaims.length > 0} +

+ {#if failedClaims.length > 0} + {failedClaims.length} failed verification. + {/if} + {#if unverifiedClaims.length > 0} + {unverifiedClaims.length} not yet checked. + {/if} + See the claims page for details. +

+ {/if} + {/if}