feat(kez-chat/web): dashboard shows verified claims
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 <noreply@anthropic.com>
This commit is contained in:
parent
a8036cc392
commit
109852ed75
@ -4,16 +4,37 @@
|
|||||||
import { bytesToHex } from "@noble/hashes/utils";
|
import { bytesToHex } from "@noble/hashes/utils";
|
||||||
import { lookup, ApiError } from "../lib/api.js";
|
import { lookup, ApiError } from "../lib/api.js";
|
||||||
import { session } from "../lib/store.svelte.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<any | null>(null);
|
let registryRecord = $state<any | null>(null);
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
let error = $state<string | null>(null);
|
let error = $state<string | null>(null);
|
||||||
|
|
||||||
|
let claims = $state<StoredClaim[]>([]);
|
||||||
|
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 () => {
|
onMount(async () => {
|
||||||
if (!session.unlocked) {
|
if (!session.unlocked) {
|
||||||
push("/unlock");
|
push("/unlock");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
claims = await listClaims();
|
||||||
try {
|
try {
|
||||||
registryRecord = await lookup(session.unlocked.handle);
|
registryRecord = await lookup(session.unlocked.handle);
|
||||||
} catch (e) {
|
} 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<string, string>
|
||||||
|
)[ch] ?? ch;
|
||||||
|
}
|
||||||
|
|
||||||
function showSeed() {
|
function showSeed() {
|
||||||
if (!session.unlocked) return;
|
if (!session.unlocked) return;
|
||||||
const hex = bytesToHex(session.unlocked.seed);
|
const hex = bytesToHex(session.unlocked.seed);
|
||||||
@ -64,21 +112,95 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="border border-gray-200 rounded-lg p-6 bg-white">
|
<section class="border border-gray-200 rounded-lg p-6 bg-white">
|
||||||
<div class="flex items-start justify-between">
|
<div class="flex items-start justify-between gap-4 mb-4">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-xs text-gray-500 uppercase tracking-wide">Claims</p>
|
<p class="text-xs text-gray-500 uppercase tracking-wide">
|
||||||
|
Verified claims
|
||||||
|
</p>
|
||||||
<p class="text-sm text-gray-700 mt-1">
|
<p class="text-sm text-gray-700 mt-1">
|
||||||
Link other accounts (GitHub, your domain, nostr, Bluesky, ActivityPub)
|
Accounts cryptographically linked to
|
||||||
to your KEZ identity by signing claims and publishing the proofs.
|
<span class="font-mono">{session.unlocked.handle}@{session.unlocked.server}</span>.
|
||||||
|
Anyone can re-verify these without trusting this server.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<a
|
<div class="flex flex-col gap-2 shrink-0">
|
||||||
href="#/claims"
|
{#if claims.length > 0}
|
||||||
class="px-3 py-2 text-sm bg-gray-900 text-white rounded-md hover:bg-gray-700 no-underline"
|
<button
|
||||||
>
|
class="px-3 py-1.5 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 disabled:opacity-50"
|
||||||
Manage claims →
|
onclick={reverifyAll}
|
||||||
</a>
|
disabled={verifyingAll}
|
||||||
|
>
|
||||||
|
{verifyingAll ? "Verifying…" : "Re-verify all"}
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<a
|
||||||
|
href="#/claims"
|
||||||
|
class="px-3 py-1.5 text-sm bg-gray-900 text-white rounded-md hover:bg-gray-700 no-underline text-center"
|
||||||
|
>
|
||||||
|
Manage →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if claims.length === 0}
|
||||||
|
<p class="text-sm text-gray-500 italic">
|
||||||
|
No claims yet. Link a GitHub account, a domain, your nostr identity,
|
||||||
|
or anywhere else you'd like to prove you control.
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="#/claims/add"
|
||||||
|
class="mt-3 inline-block px-3 py-2 text-sm border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 no-underline"
|
||||||
|
>
|
||||||
|
+ Add your first claim
|
||||||
|
</a>
|
||||||
|
{:else if verifiedClaims.length === 0}
|
||||||
|
<p class="text-sm text-amber-800 bg-amber-50 border border-amber-200 rounded p-3">
|
||||||
|
You have {claims.length} claim{claims.length === 1 ? "" : "s"} but
|
||||||
|
none are verified yet. Publish the proofs on each channel and hit
|
||||||
|
<strong>Re-verify all</strong>.
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<ul class="space-y-2">
|
||||||
|
{#each verifiedClaims as c (c.id)}
|
||||||
|
<li class="flex items-center justify-between gap-3 p-3 bg-green-50 border border-green-200 rounded">
|
||||||
|
<div class="min-w-0 flex-1">
|
||||||
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
|
<span class="text-xs font-semibold text-green-800 bg-green-200 px-1.5 py-0.5 rounded">
|
||||||
|
{channelLabel(c.channel)}
|
||||||
|
</span>
|
||||||
|
<span class="font-mono text-sm text-gray-900 truncate">
|
||||||
|
{c.envelope.payload.subject}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p class="mt-1 text-xs text-green-900">
|
||||||
|
✓ {c.last_verify?.summary}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
{#if c.last_verify?.evidence_url}
|
||||||
|
<a
|
||||||
|
href={c.last_verify.evidence_url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="text-xs text-green-700 hover:text-green-900 underline shrink-0"
|
||||||
|
>
|
||||||
|
proof ↗
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{#if failedClaims.length > 0 || unverifiedClaims.length > 0}
|
||||||
|
<p class="mt-3 text-xs text-gray-500">
|
||||||
|
{#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.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="border border-gray-200 rounded-lg p-6 bg-white">
|
<section class="border border-gray-200 rounded-lg p-6 bg-white">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user