The verified checkmark only appeared on the profile, never in chat, even
for clearly-verified peers. Three gaps fixed:
- Chat verified only the open conversation, so list items never showed a
badge. Verify all conversations on load (24h per-peer cache).
- A peer's proofs were only published to the server on a manual "reverify
all", so verified users were invisible to peers. Auto-publish verified
subjects when the Identity page loads.
- Unify the threshold: a badge now requires >=2 independently-verified
proofs, in both chat (VERIFY_MIN_PROOFS) and the profile (isVerified),
so "verified" means the same thing everywhere.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
A green check next to any KEZ that controls a proven account. Unlike
Twitter's "we say so," the badge means YOUR browser independently
verified ≥1 of the peer's published proofs against the channel.
Server:
• handles.proofs column (JSON array of claim subjects) + ALTER for
existing DBs. Returned in /v1/u/:handle and /v1/by-primary as
`proofs` — pure discovery; peers verify each themselves.
• PUT /v1/profile/:handle/proofs (authed X-KEZ-Auth, signed over
"PUT\n/v1/profile/<h>/proofs\n<ts>", distinct line from inbox/stream
so sigs can't cross-replay; 60s skew; max 64 subjects).
• All 20 existing http tests still pass.
Client:
• api.ts: HandleResponse.proofs + setProofs() (signs + PUTs).
• verify.ts: verifySubject(subject, primary) — runs the real channel
verifier given just subject+primary (no local envelope needed).
• conversations-store: cache verified + verified_checked_at per peer.
• Messages: on conversation open, fetch the peer's proof subjects and
verify them in the background (24h cache → snappy, rate-limit
friendly). VerifiedBadge in the conversation row + thread header.
• Identity: reverify now publishes your verified subjects to your
profile (so peers can discover them) + shows the badge on your own
card.
• VerifiedBadge.svelte: scalloped-seal check in verified-green
(distinct from the cyan brand accent).
Flow: you reverify your proofs on Identity → they publish to your
profile → when someone opens a chat with you, their client fetches +
verifies them → you get the check on their screen.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Kills the dashboard-as-home. Logged-in users now land on Chats like a
real messenger. Implements the design-team's IA recommendation.
Navigation:
• Desktop: slim left icon rail (Chats / Identity / Settings) with the
cyan key-cursor logo, active-state accent bar, unread badge.
• Mobile: fixed bottom tab bar, same 3 destinations, safe-area inset.
• Unauthenticated flow renders full-bleed with a wordmark header.
• Legacy /dashboard + /messages redirect to /identity + /chats.
Chats (restyled Messages):
• Two-pane on desktop; on mobile the list is full-screen and the
thread pushes over it with a back chevron.
• Conversation rows get identicon avatars, active accent bar, truncated
previews. Thread header shows avatar + handle + key.
• Bubbles: sent = cyan accent fill / near-black text / tail; received =
dark bubble-recv + hairline border. Emoji-only boost retained.
• Compose + "start a chat" inputs use the dark token styling; live/
reconnecting status moved into the list header.
• All functionality preserved: SSE, emoji picker, auto-scroll,
notifications, unread badge.
Identity (new — was Dashboard's identity + claims):
• Identity card: identicon avatar (ring), copyable handle@server chip,
key fingerprint, registration date.
• Proofs grouped verified / failed / pending with verified-green
badges; Add proof + Manage links.
Settings (new — was Dashboard's remainder):
• Security (app lock / biometric, reveal seed), Notifications (perm +
test), Account (lock + build sha + source).
Dashboard.svelte is now unused (left in tree, removed from routes;
cleanup later). Claims/AddClaim + auth pages (Landing/Create/Restore/
Unlock) still use the old light classes — restyle is the next phase.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>