Web UI:
- Peer input Enter key now resolves ETH/@alias (like /peer command)
- ETH address stored and shown everywhere instead of raw fingerprint
- Call UI shows ETH address: "Calling 0x0021...", "In call with 0x9D70..."
- Server URL color: #444 → #666 (readable on dark background)
- Peer input placeholder: "ETH address, fingerprint, or @alias"
- peerEthAddr persisted in localStorage across sessions
Server:
- WS binary header: strip zero-padding from 64-char to 32-char fingerprint
- Call routing now works (was failing due to padded fingerprint lookup)
- startCall() resolves ETH/alias before sending CallSignal::Offer
- Audio bridge sends auth token to wzp-web as first WS message
- Deterministic room name: sorted fingerprint pair (both peers same room)
Docs updated:
- SERVER.md: WZP integration section (components, running, TLS, auth flow)
- USAGE.md: voice call usage for web and TUI
- LLM_HELP.md: call architecture, key files, environment vars
- LLM_BOT_DEV.md: note that bots cannot participate in calls
- TESTING_E2E.md: updated WZP prerequisites with correct flags
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web client:
- After call goes "active", connects to WZP web bridge WS
- Mic capture: getUserMedia → ScriptProcessor → PCM int16 frames → WS
- Playback: WS → PCM int16 → Float32 → AudioContext.createBufferSource
- Room name derived from peer fingerprint (deterministic)
- Relay address fetched from /v1/wzp/relay-config
- Audio auto-starts on accept/answer, auto-stops on hangup/reject
- startAudio()/stopAudio() manage full lifecycle
TUI:
- /call shows "Audio: use web client for voice (TUI audio coming soon)"
- Signaling works, audio requires web client for now
This completes the last critical task — voice calls work end-to-end:
User A calls → signaling via featherChat WS → User B accepts →
both connect to WZP relay → audio flows
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- **bold**, *italic*, \`code\` rendered with ratatui styles
- # headers, > blockquotes, - bullet lists
- Multi-line messages split and indented per line
- Code spans: cyan bold, headers: white bold, quotes: gray italic
- No external dependency (custom md_to_spans parser)
- tui-markdown had ratatui version mismatch, built our own
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ChatLine gains sender_fp field for tracking who sent each message
- App gains read_receipts_sent HashSet to avoid duplicate receipts
- After each draw(), visible received messages get a Read receipt sent
- Only fires once per message_id, skips system/self messages
- Sender sees blue ✓✓ (existing display logic already handles Read)
- All ChatLine literals across 6 files updated with sender_fp field
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Call state reload on restart:
- Loads Ringing/Active calls from sled into active_calls on startup
- Expires calls older than 24h automatically
TUI sender ETH cache prefill:
- prefill_eth_cache() resolves all known contacts on poll_loop start
- First message from known contacts now shows ETH address immediately
Server integration tests (10 new):
- push_to_client offline/online
- register_ws + connection cap (5 max)
- is_online + device_count
- kick_device + revoke_all_except
- deliver_or_queue offline/online
- call state lifecycle
- list_devices
155 tests passing (was 135)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FC-P2-T6: /contacts shows online status (● online, ○ offline)
FC-P6-T6: Long messages word-wrap into multiple lines with aligned indent
FC-P6-T7: Tab completion for 33 slash commands (4 new tests)
FC-P8-T6: sendDocument accepts both JSON and multipart form data
OTPK: Auto-replenish on TUI startup when supply < 3 (generates 10 new)
135 tests passing (was 127)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FC-P2-T4: TUI call state machine
- CallInfo struct + CallPhase enum (Calling/Ringing/Active)
- Header shows call indicator: yellow "Calling...", magenta "Incoming", green timer
- /call sets Calling, /accept sets Active, /reject+/hangup clears
- Incoming signals show contextual messages (Offer→prompt, Answer→connected, etc.)
FC-P2-T5: Missed call display in TUI
- WS Text frames parsed for missed_call + bot_message JSON
- Missed calls: "📞 Missed call from X at HH:MM" + terminal bell
- Bot messages rendered as @botname: text
FC-P8-T5: Inline keyboard buttons in web
- CSS styled keyboard buttons (.kbd-btn)
- Bot messages with reply_markup render clickable button rows
- Click sends callback_data back to bot as bot_message
- Works in both WS text handler and handleIncomingMessage fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web client:
- Call bar between header and messages (hidden when idle)
- Call button appears when peer is set (not group)
- Incoming call: pulsing notification + Accept/Reject buttons
- Call states: idle → calling → ringing → active
- /call, /accept, /reject, /hangup slash commands
- CallSignal sent via WS binary frames (same as messages)
- handleCallSignal processes Offer/Answer/Hangup/Reject/Ringing/Busy
- Vibration on incoming call (mobile)
- create_call_signal WASM import wired up
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per-bot numeric IDs (privacy feature) broke sendMessage because the
reverse lookup couldn't find the fingerprint from the per-bot hash.
Fix: store numid:<numeric_id> → fingerprint in tokens tree when
generating updates. resolve_chat_id checks this mapping first.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server:
- --bots-config <path> loads JSON array of system bots on startup
- Bots auto-created if missing, aliases restored on every start
- Bot list stored in DB for welcome screen (system:bot_list key)
- GET /v1/bot/list returns system bots (public, no auth)
Welcome screen:
- Web + TUI show available bots on first login
- "Available bots: @helpbot — featherChat help, @codebot — Coding..."
- Clickable in web (via address detection)
Config: bots.example.json with 10 suggested bots
Usage: warzone-server --enable-bots --bots-config bots.json
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Privacy: from.id is now Hash(bot_token + user_fp) → different bots see
different numeric IDs for the same user. Prevents cross-bot user correlation.
Removed id_str (raw hex fingerprint) from all bot API responses.
Updated LLM_BOT_DEV.md and LLM_HELP.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Built-in BotFather (Rust, server-side):
- Intercepts messages to @botfather in deliver_or_queue
- Commands: /newbot <name>, /mybots, /deletebot <name>, /token <name>
- Creates bot with fingerprint, token, alias, tracks ownership
- Replies via push_to_client or queue (works offline)
- Only active when --enable-bots is set
Standalone BotFather (Python):
- tools/botfather.py: uses bot API (getUpdates/sendMessage)
- Delegates core ops to built-in handler
- Extensible for additional features
- Reads token from BOTFATHER_TOKEN env or .botfather_token file
Flow: User messages @botfather → "/newbot MyBot" → gets token back
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TUI:
- Header shows peer ETH address (resolved on /peer set)
- Own messages show ETH format
- Resolve display shows full formatted fingerprint (xxxx:xxxx:...)
- peer_eth field stored on App for header display
Web:
- Pasting 0x address in peer input box now resolves via /v1/resolve/
- Send path resolves 0x/@ before encrypting
- Click messages area → focuses text input
- Own messages show ETH format
Version: 0.0.23 → 0.0.24, SW cache wz-v4 → wz-v5
Build script: --local, --local-ship, --local-clean commands
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Version: 0.0.22 → 0.0.23, SW cache wz-v3 → wz-v4
TUI:
- Own messages show ETH address (0x...) instead of fingerprint
- Received messages: async ETH cache lookup (resolve on first sight)
- /info shows Identity + Fingerprint
- Welcome message shows ETH address
Web:
- Header shows only ETH address (single element, click to copy)
- Own messages show ETH format
- Received messages resolve sender ETH via /v1/resolve/
- /peer 0x... resolves via /v1/resolve/ endpoint
- Click messages area → focuses text input
Client:
- register_bundle sends eth_address to server
- ETH↔fingerprint mapping stored on registration
Build:
- --local: build on current machine (auto-detect apt/dnf/pacman/brew)
- --local-ship: build locally + deploy to all servers
- --local-clean: build + clean cargo cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Version:
- Workspace + protocol: 0.0.21 → 0.0.22
- Web client VERSION: 0.0.17 → 0.0.22
- Service worker cache: wz-v2 → wz-v3
ETH identity:
- Added WasmIdentity::eth_address() export (derives from seed via secp256k1)
- Web client sends eth_address during key registration
- Identity display shows ETH address first, then fingerprint
- No more server-side resolve needed — computed client-side
CLAUDE.md:
- Added MANDATORY version bump rule (4 places to update)
- Must bump on every functional change, never skip SW cache
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced workspace dep inheritance with explicit versions in
warzone-protocol/Cargo.toml. The crate now builds both as a
workspace member AND standalone.
WZP can now import warzone-protocol directly:
warzone-protocol = { path = "../featherChat/warzone/crates/warzone-protocol" }
This means WZP can delete its mirrored identity/crypto code and use:
- warzone_protocol::identity::{Seed, IdentityKeyPair, PublicIdentity}
- warzone_protocol::crypto::{hkdf_derive, aead_encrypt, aead_decrypt}
- warzone_protocol::ethereum::{derive_eth_identity, EthAddress}
- warzone_protocol::message::{WireMessage, CallSignalType}
- warzone_protocol::types::Fingerprint
Single source of truth for identity derivation — no more HKDF mismatches.
28/28 tests pass. Zero warnings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>