Previous Messages page assumed you knew what a "handle" was and showed
truncated ed25519 hex everywhere. Reframed it so a newcomer can figure
out what to do without having read the spec.
Server:
• GET /v1/by-primary/:primary — reverse lookup, ed25519:<hex> →
handle record. Used by the SPA to render @alice instead of the
truncated hex when an inbound envelope arrives from a peer we
haven't chatted with yet. 3 new integration tests cover round-trip,
NotFound, BadRequest-on-garbage.
Web — sidebar:
• "Your KEZ" panel at top — handle@server with a copy button. The
whole point: someone needs your KEZ to message you, so make
sharing it one click.
• "Start a chat" input accepts `alice` or `alice@kez.lat`. Resolves
via /v1/u/:handle before adding — explicit error if unregistered,
friendly "that's you" guard for self.
• Conversation rows show resolved handles, not hex blobs.
Web — empty state:
• 🔒 + "End-to-end encrypted chat" headline + plain-English paragraph
explaining that even the server can't read messages.
• Concrete starter hint: "open kez.lat in a second browser, create
another account, message yourself between the two."
Conversation cache redesign:
• Now keyed by peer_primary (canonical KEZ identity) with peer_handle
as display metadata. Resolves the same-person-as-two-threads bug
you'd hit when you sent to "alice" then alice replied (her primary
didn't match the "alice" key).
• IDB key bumped to :v2 — old shape abandoned (was placeholder data).
• On inbound, ensureConversation refreshes the cached handle if we
just resolved a fresher one.
Followups still queued: cross-server lookups, NATS push, group chats,
"find someone by their published claim" (paste their gist / dns proof
to discover their handle).
Live at https://kez.lat.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>