Jason Tudisco 76fcaa1d3c feat(kez-chat/web): always-on inbox stream + unread badge + browser notifications
Previously the SSE stream only ran while the Messages component was
mounted. Navigate to Dashboard or Claims and new messages just piled
up server-side until you came back. Now the stream runs for the whole
session, drives an unread badge in the nav, and (with permission)
fires a system notification when a message lands while you're in
another tab.

inboxService (lib/inbox-service.svelte.ts):
  • Singleton Svelte 5 $state class. session.setUnlocked() starts it,
    session.lock() stops it. Holds the SSE stream + the 30s heartbeat
    poll for the entire session lifetime.
  • Reactive state read by anyone: status (off/connecting/live/
    reconnecting), unreadCount (since last visit to /messages), and
    lastError (surfaced in the Messages footer).
  • onMessage(fn) lets components subscribe to repaint when ingest
    succeeds — Messages page uses this instead of owning its own
    stream.
  • #fireSystemNotification fires Notification API on inbound when
    Notification.permission === "granted" AND document.visibilityState
    !== "visible". Silent while you're actively looking at the tab.
    Uses tag="kez-chat-inbox" so multiple notifications collapse.

Messages.svelte:
  • Stripped its own stream/poll. Now just subscribes to inboxService.
    onMount also calls markAllRead() — landing on /messages = you've
    seen the new stuff.
  • Footer status indicator reads from inboxService instead of local
    state.

App.svelte nav:
  • Messages link grows a red unread-count badge (1, 2, …, 9+) when
    inboxService.unreadCount > 0 and the user isn't already on the
    Messages route.

Dashboard:
  • New "Notifications" section between Quick unlock and Backup with
    the standard 3-state UX: granted (green confirm), denied (amber
    "fix in site settings"), default (button to request).
  • Helpers in inbox-service.ts wrap the Notification API so non-
    supporting browsers (older Safari, Firefox in some configs) get
    graceful "not supported" copy.

Caveat (for v0.3): notifications only fire while the tab is open in
SOME state (background-but-not-closed). Closing the tab kills the
SSE stream so nothing arrives at the page to notify about. True
background push (Web Push API + VAPID + server-side push) is a
separate piece of work.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 00:10:29 -06:00

KEZ

KEZ is a portable, decentralized identity graph. It lets a person say:

"These accounts, keys, domains, and identities are all me."

…without depending on any central authority. Every connection is proven by a cryptographic signature against a key the user already controls (a nostr key, an Ed25519 key, etc.), and the proofs are published in places only the claimed account itself can publish to (their gist, their DNS, their nostr relay event). Anyone can verify the graph without trusting a server.

Repository layout

.
├── SPEC.md              ← The protocol. Language-agnostic, normative.
├── rust/                ← Rust implementation (kez-core, kez-channels, kez-cli)
├── nodejs/              ← TypeScript/Node implementation (same shape, same CLI)
├── rust-sig-server/     ← Optional HTTP store for sigchains (axum + SQLite)
├── crosstest.sh         ← Interop test: artifacts move between implementations
└── README.md            ← (this file)

Two parallel implementations. Wire-compatible: a claim signed in Rust verifies in Node and vice versa. The cross-test harness proves it.

A separate rust-sig-server/ crate provides an optional HTTP storage tier for sigchains — useful when a user doesn't want to set up DNS/hosting/nostr, but never required; the protocol stays decentralized.

Documentation

Start here:

  • SPEC.md — the language-agnostic protocol spec (v0.2). Normative for every implementation.
  • rust/README.md — Rust implementation guide: crate layout (kez-core / kez-channels / kez-cli), full CLI reference, channel plugin model, library examples, and the gap list.
  • nodejs/README.md — Node/TypeScript port: same shape as Rust, npm workspaces layout, crypto stack rationale, CLI reference.
  • rust-sig-server/README.md — the optional storage server: API reference, no-auth design + threat model, deployment recipes (bare-metal, Docker, PaaS), and how channel-based publishing remains the fallback if the server is down.

Quick start

Rust

cd rust
cargo build
cargo test                                                # 99 tests
cargo install --path crates/kez-cli                       # → `kez` on PATH
kez verify id github:jason

Full guide: rust/README.md.

Node.js

cd nodejs
npm install
npm test                                                  # 91 tests
npm run cli -- verify id github:jason

Full guide: nodejs/README.md.

Sigchain storage server (optional)

cd rust-sig-server
cargo build --release
./target/release/kez-sig-server                           # listens on :7878

Full guide: rust-sig-server/README.md.

Cross-testing

./crosstest.sh

Runs 19 scenarios that swap implementations at the artifact boundary:

# Scenario
12 nostr-signed JSON claim, both directions
34 nostr-signed compact claim, both directions
56 nostr-signed markdown claim, both directions
78 nostr-signed DNS zone form, both directions
910 ed25519-signed JSON claim, both directions
1112 ed25519-signed compact claim, both directions
1314 ed25519-signed markdown claim, both directions
15 rust builds 3-event nostr sigchain → node parses + shows
16 rust-exported sigchain JSONL == node-exported JSONL (byte-identical)
17 node builds 3-event nostr sigchain → rust parses + shows
18 rust builds ed25519 sigchain → node parses + shows
19 node builds ed25519 sigchain → rust parses + shows

If all 19 pass: JCS canonicalization, both signature suites (BIP-340 Schnorr and Ed25519), the compact kez:z1: zstd+base64url encoding, the Markdown fence, the DNS TXT shape, and the sigchain JSONL bundle format are all byte-compatible across implementations.

Pass -v for verbose output (echoes intermediate commands and proofs).

What ships in v0.2

  • Five channel plugins in each implementation: dns:, github:, nostr:, bluesky:, ap: (alias mastodon:).
  • Four wire encodings: JSON, compact, Markdown fence, DNS TXT.
  • Two primary-key algorithms: nostr/secp256k1 Schnorr (BIP-340) and Ed25519 (RFC 8032).
  • JCS (RFC 8785) canonicalization for everything signed.
  • No API keys required for any channel.

What's not done yet

Tracked in rust/README.md and the spec:

  • verify id consulting the sigchain. Sigchain types, CLI commands (kez sigchain add/revoke/show/export/publish), and the storage server all exist. But proof verification doesn't yet fetch the chain to check for revocations — every verify is still a single one-shot proof check.
  • rotate and add_device sigchain ops.
  • expires_at enforcement during claim verify.
  • Typed VerificationStatus.status reflecting the five failure modes (valid / revoked / expired / unreachable / fork).
  • Auth-required publishers (GitHub gist, Bluesky, ActivityPub).

License

Dual-licensed under MIT or Apache-2.0.

Description
No description provided
Readme 1.7 MiB
Languages
TypeScript 38.9%
Rust 31.8%
Svelte 18.2%
Python 5.4%
Shell 2.8%
Other 2.9%