plan(kez-chat): add web app design — Svelte SPA served by chat-server
The test UI is a Svelte 5 + TypeScript + Vite + Tailwind single-page
app served as static files by kez-chat-server. The web app uses the
exact same HTTP API a native client would use, so every action in the
UI dogfoods the API contract.
Architecture changes:
- kez-chat-server now serves `/` as the SPA (tower-http ServeDir)
alongside the existing /v1 API
- Web app talks NATS over WebSocket (nats.ws + nats-server's
built-in websocket transport — same auth callout, same nkey auth,
same JetStream durable consumers)
- Web app cannot do Iroh: browsers can't open raw UDP sockets and
Iroh's WebTransport story isn't ready in 2026. Web shows manifests
and prompts "Download requires CLI" for actual file transfer.
- Key storage in browser: passphrase-encrypted IndexedDB (documented
limitation — native clients use OS keychain)
New / updated sections in document.md:
- §1: opening pitch mentions the web app + that it dogfoods the API
- §4.1: responsibilities table adds "serves the test web app"
- §4.4 NEW: full design of the web app — stack, capabilities, what
it can't do in v0, deployment model
- §4.5: endpoint list now includes / (the SPA) and /assets/*
- §4.3: nats.conf snippet enables WebSocket transport alongside the
existing native NATS port; both transports hit the same auth
callout
- §5.4: file-sharing flow notes the web app caveat (visible manifest,
CLI required for actual download)
- §6.1: folder layout adds web/ subdirectory with Svelte/Vite/Tailwind
scaffolding and an updated Dockerfile (multi-stage: build web →
build rust → ship)
- §6.3: dependencies split into Rust server vs Web app sections.
Web app pulls in svelte, typescript, vite, nats.ws, @noble/curves,
@scure/base, canonicalize, svelte-spa-router, tailwindcss,
idb-keyval.
- §7 MVP scope: full Web app checklist added; CLI section renamed
and clarified ("same Rust core powers CLI and future native GUI")
- §8 out-of-scope: "file transfer from the browser" added
- §11 sequenced plan: split into 12 steps; new phases 7-10 are the
web app build (scaffold → account/contacts → chat → manifest);
step 12 deferred native GUI
- §12 summary: rewritten to reflect "two Rust services + a Svelte
web app + a CLI"
- Decisions-locked table: added rows for test UI choice, browser
file transfer, manifest format, frontend framework, in-browser
key storage
This commit is contained in:
parent
055040423e
commit
a1d1aa6983
@ -21,6 +21,9 @@ A real-time chat + file-sharing application with verified identities.
|
|||||||
downloaded when a recipient actually wants them. No background sync.
|
downloaded when a recipient actually wants them. No background sync.
|
||||||
- Identity is portable: the underlying key + sigchain survives the home
|
- Identity is portable: the underlying key + sigchain survives the home
|
||||||
server going dark. Handles can be migrated to other servers later.
|
server going dark. Handles can be migrated to other servers later.
|
||||||
|
- A **Svelte web app** served directly by `kez-chat-server` is the test
|
||||||
|
UI. It uses the same HTTP API any native client would use, so the
|
||||||
|
web app dogfoods the API. See §4.4.
|
||||||
|
|
||||||
This is the Keybase model rebuilt on a decentralized substrate:
|
This is the Keybase model rebuilt on a decentralized substrate:
|
||||||
- **Identity layer** → KEZ (instead of Keybase's central account system)
|
- **Identity layer** → KEZ (instead of Keybase's central account system)
|
||||||
@ -198,6 +201,7 @@ microservices (NATS broker, sigchain server).
|
|||||||
| **NATS auth callout** | ✅ Yes |
|
| **NATS auth callout** | ✅ Yes |
|
||||||
| **WebFinger endpoint** | ✅ Yes |
|
| **WebFinger endpoint** | ✅ Yes |
|
||||||
| **HTTP API for clients** | ✅ Yes |
|
| **HTTP API for clients** | ✅ Yes |
|
||||||
|
| **Serves the test web app** (Svelte SPA, built into the binary) | ✅ Yes (§4.4) |
|
||||||
| **Sigchain storage** | ❌ No — defer to `kez-sig-server` (separate container) |
|
| **Sigchain storage** | ❌ No — defer to `kez-sig-server` (separate container) |
|
||||||
| **NATS broker** | ❌ No — separate `nats-server` (Go) container |
|
| **NATS broker** | ❌ No — separate `nats-server` (Go) container |
|
||||||
| **Iroh pinning** | ❌ No for v0 — files transfer P2P when both peers are online. Pinning is a future tier. |
|
| **Iroh pinning** | ❌ No for v0 — files transfer P2P when both peers are online. Pinning is a future tier. |
|
||||||
@ -301,14 +305,22 @@ NATS:
|
|||||||
2. Set `NATS_URL=nats://your-broker:4222` in the chat-server's env.
|
2. Set `NATS_URL=nats://your-broker:4222` in the chat-server's env.
|
||||||
3. Apply our reference `nats.conf` snippet to their NATS deployment.
|
3. Apply our reference `nats.conf` snippet to their NATS deployment.
|
||||||
|
|
||||||
The auth_callout config snippet:
|
The auth_callout config snippet (and WebSocket for browser clients):
|
||||||
|
|
||||||
```conf
|
```conf
|
||||||
# nats.conf — patched into whichever NATS deployment is used
|
# nats.conf — patched into whichever NATS deployment is used
|
||||||
|
|
||||||
|
# Enable WebSocket transport so the browser SPA can connect.
|
||||||
|
# Native CLI clients use the standard NATS port (4222).
|
||||||
|
websocket {
|
||||||
|
port: 8443
|
||||||
|
no_tls: false # behind a TLS terminator in prod
|
||||||
|
}
|
||||||
|
|
||||||
authorization {
|
authorization {
|
||||||
auth_callout {
|
auth_callout {
|
||||||
issuer: "<our auth-callout signing nkey public part>"
|
issuer: "<our auth-callout signing nkey public part>"
|
||||||
auth_users: ["AUTHUSER"] # placeholder identity NATS uses
|
auth_users: ["AUTHUSER"] # placeholder identity NATS uses
|
||||||
account: "DEFAULT"
|
account: "DEFAULT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -316,12 +328,88 @@ authorization {
|
|||||||
|
|
||||||
The chat-server signs auth-callout responses with a long-lived nkey
|
The chat-server signs auth-callout responses with a long-lived nkey
|
||||||
that NATS trusts. When a client connects to NATS with their KEZ
|
that NATS trusts. When a client connects to NATS with their KEZ
|
||||||
ed25519 key, NATS forwards the auth request to our chat-server,
|
ed25519 key — whether via native protocol (CLI) or WebSocket
|
||||||
which checks the handle registry and signs a yes/no response.
|
(browser) — NATS forwards the auth request to our chat-server,
|
||||||
|
which checks the handle registry and signs a yes/no response. Same
|
||||||
|
auth path for both transports.
|
||||||
|
|
||||||
### 4.4 Endpoints
|
### 4.4 The test web app
|
||||||
|
|
||||||
|
The chat-server serves a Svelte single-page app as static files under
|
||||||
|
`/`. The web app is the test UI for the project — and crucially, it
|
||||||
|
**uses the exact same HTTP API a native client would use.** No
|
||||||
|
backend-rendered pages, no server-side state for the SPA. Every action
|
||||||
|
in the web UI goes through the public `/v1/...` API, which means the
|
||||||
|
web app is also a continuous test that the API contract works end to
|
||||||
|
end.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
Browser hits https://kez.lat/ → SPA HTML+JS+CSS
|
||||||
|
SPA calls https://kez.lat/v1/u/chris → handle lookup (same as CLI)
|
||||||
|
SPA opens wss://kez.lat/nats → NATS broker over WebSocket
|
||||||
|
SPA calls https://sig.kez.lat/v1/sigchains/... → fetch sigchain (same as CLI)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Stack
|
||||||
|
|
||||||
|
| Layer | Pick |
|
||||||
|
|---|---|
|
||||||
|
| Framework | **Svelte 5 + TypeScript** |
|
||||||
|
| Build | **Vite** (output: static files served by chat-server) |
|
||||||
|
| Routing | **`svelte-spa-router`** (hash routing — works under any subpath) |
|
||||||
|
| NATS client | **`nats.ws`** — the official NATS WebSocket client. nkey auth supported. |
|
||||||
|
| Crypto | **`@noble/curves`** + **`@noble/hashes`** (same primitives our Node port uses) |
|
||||||
|
| Key storage | **passphrase-encrypted IndexedDB.** User enters a passphrase on first launch; seed is encrypted with that key. Documented limitation: browsers don't have an equivalent of OS keychain. Native clients (CLI, future Tauri) get better protection via OS keychain. |
|
||||||
|
| State | **Svelte stores** (built-in; no Redux needed) |
|
||||||
|
| Styling | **Tailwind** (default; trivially swappable) |
|
||||||
|
|
||||||
|
#### What the web app can do (v0)
|
||||||
|
|
||||||
|
- Account creation: generate ed25519 key in-browser, display mnemonic
|
||||||
|
for paper backup, register handle via `POST /v1/register`, upload
|
||||||
|
sigchain endpoint events to `kez-sig-server` via HTTP.
|
||||||
|
- Contacts: look up handles, fetch sigchains, display verified
|
||||||
|
identities (uses the same channel-verification logic via in-browser
|
||||||
|
TypeScript, sharing `@kez/core` and `@kez/channels`).
|
||||||
|
- 1:1 chat: subscribe to NATS inbox over WebSocket, decrypt incoming,
|
||||||
|
encrypt + publish outgoing. Real-time messaging works.
|
||||||
|
- Manifest browse: fetch and decrypt `@chris`'s shared-files manifest;
|
||||||
|
display the list of files; show metadata.
|
||||||
|
- Identity verification view: show the user's sigchain visually (claims,
|
||||||
|
channel proofs, rotations).
|
||||||
|
- Settings: show/re-display the mnemonic, re-verify it, log out
|
||||||
|
(clears IndexedDB).
|
||||||
|
|
||||||
|
#### What the web app can't do (v0)
|
||||||
|
|
||||||
|
- **File download / upload via Iroh.** Browsers can't open raw UDP
|
||||||
|
sockets, and Iroh's WebTransport story isn't ready in 2026. The
|
||||||
|
web app shows the manifest entries and a "Download (requires
|
||||||
|
desktop client)" button that points at the CLI. v0.5 may revisit
|
||||||
|
if Iroh-in-browser matures.
|
||||||
|
- Anything that requires the OS keychain (proper offline crypto).
|
||||||
|
|
||||||
|
#### Deployment
|
||||||
|
|
||||||
|
The web app's static build output (e.g. `kez-chat-server/web/dist/`)
|
||||||
|
is bundled into the chat-server's Docker image at build time and
|
||||||
|
served by axum's `ServeDir`. No separate static host, no separate
|
||||||
|
CDN. `docker compose up` deploys the SPA along with everything else.
|
||||||
|
|
||||||
|
The build pipeline:
|
||||||
|
1. `cd web && npm install && npm run build` → produces `web/dist/`
|
||||||
|
2. Dockerfile copies `web/dist/` into the runtime image
|
||||||
|
3. Rust binary serves it as `GET /*` (with the API mounted at `/v1/...`)
|
||||||
|
|
||||||
|
For dev: `npm run dev` runs Vite's dev server on port 5173, proxying
|
||||||
|
`/v1` requests to the locally-running chat-server. Hot module reload
|
||||||
|
works as normal Svelte dev.
|
||||||
|
|
||||||
|
### 4.5 Endpoints
|
||||||
|
|
||||||
|
```
|
||||||
|
GET / the web app (Svelte SPA)
|
||||||
|
GET /assets/* SPA static assets (CSS, JS, images)
|
||||||
GET /v1/healthz
|
GET /v1/healthz
|
||||||
GET /v1/u/:handle handle → { primary, sigchain_url, endpoints }
|
GET /v1/u/:handle handle → { primary, sigchain_url, endpoints }
|
||||||
POST /v1/register claim a handle (signed body)
|
POST /v1/register claim a handle (signed body)
|
||||||
@ -331,8 +419,8 @@ GET /.well-known/webfinger?resource=acct:tudisco@kez.lat
|
|||||||
POST /internal/nats/auth verify nkey signature, return permissions
|
POST /internal/nats/auth verify nkey signature, return permissions
|
||||||
```
|
```
|
||||||
|
|
||||||
Sigchain endpoints are **not** on this server — clients talk directly to
|
Sigchain endpoints are **not** on this server — both web and native
|
||||||
`kez-sig-server` for those.
|
clients talk directly to `kez-sig-server` for those.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -427,11 +515,19 @@ The broker sees:
|
|||||||
the unwrapped content key, verifies BLAKE3 hash. File appears.
|
the unwrapped content key, verifies BLAKE3 hash. File appears.
|
||||||
```
|
```
|
||||||
|
|
||||||
**v0 limitation:** If tudisco is offline at step 10, chris waits.
|
**v0 limitations:**
|
||||||
Iroh will retry; download starts when tudisco's node comes back.
|
|
||||||
Pinning (the server holding a copy) is **not** in v0 — we accept this
|
1. If tudisco is offline at step 10, chris waits. Iroh will retry;
|
||||||
limitation in exchange for zero server-side storage cost and the
|
download starts when tudisco's node comes back. Pinning (the
|
||||||
simplest possible architecture.
|
server holding a copy) is **not** in v0 — we accept this
|
||||||
|
limitation in exchange for zero server-side storage cost and
|
||||||
|
the simplest possible architecture.
|
||||||
|
2. **The browser SPA can do steps 1–7 (sender side) and step 8
|
||||||
|
(notification + manifest entry visible), but cannot do step 10
|
||||||
|
(fetch the blob)** — browsers can't speak native Iroh. Web users
|
||||||
|
see "File available, open in CLI to download." Native CLI does
|
||||||
|
the whole flow. v0.5 may revisit when Iroh's WebTransport story
|
||||||
|
matures.
|
||||||
|
|
||||||
### 5.5 Browsing someone's files (Keybase-style)
|
### 5.5 Browsing someone's files (Keybase-style)
|
||||||
|
|
||||||
@ -473,15 +569,27 @@ fetching is per-file deliberate. **Recipient never auto-syncs.**
|
|||||||
├── kez-chat/ ← THIS PROJECT
|
├── kez-chat/ ← THIS PROJECT
|
||||||
│ ├── document.md (this file)
|
│ ├── document.md (this file)
|
||||||
│ ├── Cargo.toml
|
│ ├── Cargo.toml
|
||||||
│ ├── src/
|
│ ├── src/ Rust server
|
||||||
│ │ ├── main.rs binary entry
|
│ │ ├── main.rs binary entry
|
||||||
│ │ ├── handles.rs handle registry (sqlite-backed)
|
│ │ ├── handles.rs handle registry (sqlite-backed)
|
||||||
│ │ ├── nats_auth.rs NATS auth callout endpoint
|
│ │ ├── nats_auth.rs NATS auth callout endpoint
|
||||||
│ │ ├── webfinger.rs WebFinger discovery endpoint
|
│ │ ├── webfinger.rs WebFinger discovery endpoint
|
||||||
|
│ │ ├── static_files.rs serves the built web app (axum::ServeDir)
|
||||||
│ │ └── api.rs axum routes + state
|
│ │ └── api.rs axum routes + state
|
||||||
|
│ ├── web/ Svelte web app (the test UI)
|
||||||
|
│ │ ├── package.json
|
||||||
|
│ │ ├── vite.config.ts
|
||||||
|
│ │ ├── svelte.config.ts
|
||||||
|
│ │ ├── tailwind.config.ts
|
||||||
|
│ │ ├── src/
|
||||||
|
│ │ │ ├── routes/ pages (login, contacts, chat, profile, settings)
|
||||||
|
│ │ │ ├── lib/ crypto, nats client, sigchain helpers
|
||||||
|
│ │ │ └── app.svelte
|
||||||
|
│ │ └── dist/ build output (copied into Docker image)
|
||||||
│ ├── deploy/
|
│ ├── deploy/
|
||||||
│ │ ├── docker-compose.yml chat-server + nats + sig-server
|
│ │ ├── docker-compose.yml chat-server + nats + sig-server
|
||||||
│ │ ├── nats.conf with auth_callout config
|
│ │ ├── nats.conf with auth_callout + websocket config
|
||||||
|
│ │ ├── Dockerfile multi-stage: build web → build rust → ship
|
||||||
│ │ └── systemd/ alternative deployment
|
│ │ └── systemd/ alternative deployment
|
||||||
│ └── tests/
|
│ └── tests/
|
||||||
│ └── http.rs integration tests
|
│ └── http.rs integration tests
|
||||||
@ -522,6 +630,8 @@ import cleanly from the start.
|
|||||||
|
|
||||||
### 6.3 Dependencies (planned)
|
### 6.3 Dependencies (planned)
|
||||||
|
|
||||||
|
**Rust server (`kez-chat-server`):**
|
||||||
|
|
||||||
| Crate | Why |
|
| Crate | Why |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `kez-core` (path) | Identity types, ed25519, signing |
|
| `kez-core` (path) | Identity types, ed25519, signing |
|
||||||
@ -533,13 +643,28 @@ import cleanly from the start.
|
|||||||
| `serde` / `serde_json` | Standard |
|
| `serde` / `serde_json` | Standard |
|
||||||
| `thiserror` / `anyhow` | Standard |
|
| `thiserror` / `anyhow` | Standard |
|
||||||
| `tracing` / `tracing-subscriber` | Logging |
|
| `tracing` / `tracing-subscriber` | Logging |
|
||||||
| `tower-http` | CORS, request tracing |
|
| `tower-http` | CORS, request tracing, **`fs` feature for serving the SPA static files** |
|
||||||
| `clap` | CLI args |
|
| `clap` | CLI args |
|
||||||
|
|
||||||
**Not** depended on by the chat-server:
|
**Not** depended on by the chat-server:
|
||||||
- `iroh` — server doesn't run an Iroh node in v0 (no pinning)
|
- `iroh` — server doesn't run an Iroh node in v0 (no pinning)
|
||||||
- nats-server (Go) — separate container, not a Rust dep
|
- nats-server (Go) — separate container, not a Rust dep
|
||||||
|
|
||||||
|
**Web app (`web/`):**
|
||||||
|
|
||||||
|
| Package | Why |
|
||||||
|
|---|---|
|
||||||
|
| `svelte` 5.x | Framework |
|
||||||
|
| `typescript` | Types |
|
||||||
|
| `vite` | Build + dev server |
|
||||||
|
| `nats.ws` | NATS client over WebSocket (browser-native NATS protocol) |
|
||||||
|
| `@noble/curves`, `@noble/hashes` | Same crypto primitives used in our Node port |
|
||||||
|
| `@scure/base` | bech32 (nsec/npub if needed), base64url |
|
||||||
|
| `canonicalize` | RFC 8785 JCS — for signature interop with native clients |
|
||||||
|
| `svelte-spa-router` | Hash-based routing |
|
||||||
|
| `tailwindcss` | Styling (default; trivially swappable) |
|
||||||
|
| `idb-keyval` | Tiny IndexedDB wrapper for the encrypted seed + cache |
|
||||||
|
|
||||||
### 6.4 NATS broker — bundled in compose, not in code
|
### 6.4 NATS broker — bundled in compose, not in code
|
||||||
|
|
||||||
NATS is **not embedded in the Rust binary** — it's the official Go
|
NATS is **not embedded in the Rust binary** — it's the official Go
|
||||||
@ -604,34 +729,51 @@ fallback storage) is a future addition (§8).
|
|||||||
- [ ] Registration signature validation (uses kez-core)
|
- [ ] Registration signature validation (uses kez-core)
|
||||||
- [ ] WebFinger endpoint
|
- [ ] WebFinger endpoint
|
||||||
- [ ] NATS auth callout (POST /internal/nats/auth)
|
- [ ] NATS auth callout (POST /internal/nats/auth)
|
||||||
|
- [ ] Static-file serving for the SPA (`tower-http` `ServeDir`)
|
||||||
- [ ] Healthz / metrics
|
- [ ] Healthz / metrics
|
||||||
- [ ] Integration tests against real nats-server + sig-server in a
|
- [ ] Integration tests against real nats-server + sig-server in a
|
||||||
test docker-compose
|
test docker-compose
|
||||||
|
|
||||||
### Deployment
|
### Web app (`web/`)
|
||||||
|
|
||||||
- [ ] docker-compose.yml (chat + nats + sig-server)
|
- [ ] Project scaffold (Svelte 5 + Vite + TypeScript + Tailwind)
|
||||||
- [ ] nats.conf with auth_callout configured
|
- [ ] Account creation flow (key gen in-browser, mnemonic prompt,
|
||||||
- [ ] systemd alternative deployment recipe
|
registration POST, sigchain upload)
|
||||||
- [ ] README with TLS / reverse proxy guidance
|
- [ ] Login flow (mnemonic in → derive key → unlock IndexedDB)
|
||||||
|
- [ ] Contacts list (handle lookup, sigchain fetch + display)
|
||||||
|
- [ ] 1:1 chat (NATS-over-WebSocket subscribe/publish, E2E encrypt/decrypt)
|
||||||
|
- [ ] Manifest browse (fetch from sigchain → list entries)
|
||||||
|
- [ ] "Download requires CLI" affordance on manifest entries
|
||||||
|
- [ ] Identity verification view (visualize the sigchain)
|
||||||
|
- [ ] Settings (re-show mnemonic, verify it, log out)
|
||||||
|
- [ ] Build script integrated into the chat-server Docker image
|
||||||
|
|
||||||
### Client (`kez-chat-cli` — separate project later)
|
### CLI client (`kez-chat-cli`)
|
||||||
|
|
||||||
Out of scope for the server work, but the **server isn't usable without**
|
Same Rust core powers both the CLI and (later) a native GUI. CLI gets
|
||||||
at least a CLI client that does:
|
the **file** capabilities the web app can't have:
|
||||||
- [ ] Account creation (key gen + mnemonic backup + handle registration)
|
- [ ] Account creation (key gen + mnemonic backup + handle registration)
|
||||||
- [ ] Contact lookup + verification
|
- [ ] Contact lookup + verification
|
||||||
- [ ] Send / receive 1:1 chat messages (E2E via NATS)
|
- [ ] Send / receive 1:1 chat messages (E2E via NATS native)
|
||||||
- [ ] Send / receive files (E2E via Iroh)
|
- [ ] Send / receive files (E2E via Iroh)
|
||||||
- [ ] Browse @user shared-files manifest
|
- [ ] Browse `<handle>` shared-files manifest + download files
|
||||||
|
|
||||||
UI app comes after CLI proves the flow works.
|
### Deployment
|
||||||
|
|
||||||
|
- [ ] docker-compose.yml (chat-server [includes SPA] + nats + sig-server)
|
||||||
|
- [ ] nats.conf with auth_callout + websocket configured
|
||||||
|
- [ ] Multi-stage Dockerfile (build web → build rust → final image)
|
||||||
|
- [ ] systemd alternative deployment recipe
|
||||||
|
- [ ] README with TLS / reverse proxy guidance (Caddy recommended)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. Out of scope (v0)
|
## 8. Out of scope (v0)
|
||||||
|
|
||||||
- **Iroh pinning** (sender must be online for receiver to fetch)
|
- **Iroh pinning** (sender must be online for receiver to fetch)
|
||||||
|
- **File transfer from the browser** (the web app can browse manifests
|
||||||
|
but file download/upload needs the CLI; browsers can't speak Iroh
|
||||||
|
natively in 2026)
|
||||||
- **Group chat** (only 1:1 for v0)
|
- **Group chat** (only 1:1 for v0)
|
||||||
- **Forward secrecy / ratcheting** (Double Ratchet, MLS) — chat is
|
- **Forward secrecy / ratcheting** (Double Ratchet, MLS) — chat is
|
||||||
encrypted but each message uses the same X25519-derived key per pair
|
encrypted but each message uses the same X25519-derived key per pair
|
||||||
@ -675,6 +817,11 @@ Settle yes/no on this and the design is locked.
|
|||||||
| Handle scope: federation or global? | **Global for v0**, federation-ready design (see §3.5). |
|
| Handle scope: federation or global? | **Global for v0**, federation-ready design (see §3.5). |
|
||||||
| Recovery if key lost? | **Paper backup (24-word mnemonic), Keybase-style.** No server-side recovery. |
|
| Recovery if key lost? | **Paper backup (24-word mnemonic), Keybase-style.** No server-side recovery. |
|
||||||
| Iroh pinning in v0? | **No.** Sender must be online for receiver to fetch. Pinning is a future tier. |
|
| Iroh pinning in v0? | **No.** Sender must be online for receiver to fetch. Pinning is a future tier. |
|
||||||
|
| Test UI: TUI / web / native GUI? | **Web app, served by `kez-chat-server` as static files.** Built in Svelte 5 + TypeScript + Vite + Tailwind. Uses the same HTTP API native clients use, so it dogfoods the contract. Talks NATS over WebSocket (`nats.ws`). |
|
||||||
|
| Browser file transfer? | **Not in v0.** Browsers can't speak Iroh natively in 2026. The web app shows manifests and prompts "Download requires CLI" for actual files. v0.5 revisit if Iroh's WebTransport story matures. |
|
||||||
|
| Manifest format? | **Option A** — signed JSON blob, hash committed via a new `set_shared_files` sigchain op. Simpler, reuses sigchain primitives. |
|
||||||
|
| Frontend framework? | **Svelte 5 + TypeScript + Vite**. Tailwind for styling (trivially swappable). |
|
||||||
|
| In-browser key storage? | **Passphrase-encrypted IndexedDB.** Documented limitation: browsers lack a Keychain equivalent. Native clients (CLI, future GUI) use OS keychain. |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -739,18 +886,39 @@ When we start building:
|
|||||||
No UI. Enough to prove the chat flow works end-to-end against
|
No UI. Enough to prove the chat flow works end-to-end against
|
||||||
the server.
|
the server.
|
||||||
|
|
||||||
5. **Iroh integration in the client** (not the server).
|
5. **Iroh integration in the CLI** (not the server).
|
||||||
- Client runs a local Iroh node
|
- CLI runs a local Iroh node
|
||||||
- `kez-chat share @chris ./file.pdf`
|
- `kez-chat share @chris ./file.pdf`
|
||||||
- `kez-chat fetch <ticket>`
|
- `kez-chat fetch <ticket>`
|
||||||
|
|
||||||
6. **Shared-files manifest.** New `set_shared_files` sigchain op.
|
6. **Shared-files manifest.** New `set_shared_files` sigchain op.
|
||||||
`kez-chat browse @tudisco` lists his shared files.
|
`kez-chat browse @tudisco` lists his shared files.
|
||||||
|
|
||||||
7. **Deployment recipe.** docker-compose, systemd, deployment doc.
|
7. **Web app scaffold** (Svelte 5 + Vite + Tailwind). Set up
|
||||||
|
`kez-chat/web/`, wire Vite dev proxy to the running chat-server,
|
||||||
|
"hello world" SPA served by axum's `ServeDir`. Multi-stage
|
||||||
|
Dockerfile builds `web/dist/` then bakes it into the runtime image.
|
||||||
|
|
||||||
8. **Then** start the GUI app. Could be Tauri (Rust + web frontend),
|
8. **Web app: account + contacts + identity.** Account creation in
|
||||||
Iced (pure Rust UI), or something else.
|
the browser (key gen, mnemonic backup, registration, sigchain
|
||||||
|
upload). Contacts list with sigchain-based verification. Identity
|
||||||
|
view (visualize the sigchain). No chat yet.
|
||||||
|
|
||||||
|
9. **Web app: chat.** `nats.ws` connection, nkey auth, subscribe to
|
||||||
|
inbox subject, encrypt/decrypt with `@noble/curves`. Real-time
|
||||||
|
chat in the browser. End-to-end: messages sent from CLI arrive
|
||||||
|
in the web app and vice versa.
|
||||||
|
|
||||||
|
10. **Web app: manifest browse.** Fetch and decrypt the
|
||||||
|
`set_shared_files` manifest of any contact; display the entries;
|
||||||
|
show "Download requires CLI" affordances on each.
|
||||||
|
|
||||||
|
11. **Deployment recipe finalized.** Production-ready docker-compose
|
||||||
|
(chat-server with embedded SPA + nats + sig-server), Caddy config
|
||||||
|
for TLS, systemd alternative.
|
||||||
|
|
||||||
|
12. **Then** native GUI (Tauri, etc.) — if web app + CLI isn't
|
||||||
|
enough. Likely v1 stretch, not MVP.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -763,19 +931,23 @@ the handle) backed by an ed25519 primary key. The same key authenticates to a NA
|
|||||||
(chat, presence, file tickets — broker is dumb, clients do E2E with
|
(chat, presence, file tickets — broker is dumb, clients do E2E with
|
||||||
ChaCha20-Poly1305 over X25519-derived keys) and identifies an Iroh
|
ChaCha20-Poly1305 over X25519-derived keys) and identifies an Iroh
|
||||||
node (P2P bulk transfer, content-addressed blobs, on-demand fetch).
|
node (P2P bulk transfer, content-addressed blobs, on-demand fetch).
|
||||||
**Our project ships two Rust services** (`kez-chat-server` for handle
|
**Our project ships two Rust services + a Svelte web app + a CLI:**
|
||||||
registry + NATS auth callout + HTTP API, and the existing
|
`kez-chat-server` (handle registry + NATS auth callout + HTTP API +
|
||||||
`kez-sig-server` for sigchain storage) **plus a docker-compose recipe
|
serves the SPA), the existing `kez-sig-server` (sigchain storage),
|
||||||
that includes `nats-server`** for turn-key deployment. NATS isn't in
|
the `web/` Svelte app (the test UI, served as static files by the
|
||||||
our Rust code — it's the official Go binary running as its own
|
chat-server, uses the same HTTP API any native client would —
|
||||||
container — but it's wired up in our compose so operators can
|
dogfoods the contract), and `kez-chat-cli` (Rust binary that's
|
||||||
`docker compose up` and have everything working. Operators with
|
also the scripted-test surface). NATS isn't in our Rust code — it's
|
||||||
existing NATS deployments can disable the bundled service and point
|
the official Go binary running as its own container — but it's
|
||||||
us elsewhere. The chat-server does not run an Iroh node
|
wired up in our docker-compose so operators can `docker compose up`
|
||||||
and does not pin files in v0; file transfer is pure P2P between
|
and have everything working. Operators with existing NATS deployments
|
||||||
online peers. Account recovery is via a 24-word paper-backup
|
can disable the bundled service. The chat-server does not run an
|
||||||
mnemonic. Federation across home servers is deferred but the design
|
Iroh node and does not pin files in v0; file transfer is pure P2P
|
||||||
keeps it as a flip-the-switch future change.
|
between online peers, and **the browser can't speak Iroh natively —
|
||||||
|
so the web app shows manifests but file download requires the CLI**.
|
||||||
|
Account recovery is via a 24-word paper-backup mnemonic. Federation
|
||||||
|
across home servers is deferred but the design keeps it as a
|
||||||
|
flip-the-switch future change.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user