The anchor was sending a 16-byte nonce. The spec (yaw2.0-protocol.md §5.1
and yaw2-implementation.md §7.1) requires 32 bytes. Any spec-conformant
peer would fail join signature verification against this anchor, breaking
interop. Signature verification was already using the raw nonce bytes
(not hex), so only the allocation size needed changing.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
WASTE_CONFIG.turnSecret was shipped in plaintext config.js and used to
compute coturn HMAC credentials client-side in browser.ts. Anyone reading
the PWA's JS could read the secret and mint unlimited long-lived TURN
credentials, turning the relay into an open proxy.
The anchor now mints short-lived (1h) credentials server-side via a new
GET /turn-credentials endpoint (-turn-secret flag), mirroring what the
daemon already does. The browser fetches credentials instead of holding
the secret. Daemon mode was unaffected (already server-side).
Docs updated to drop turnSecret from config.js examples, document the
new nginx route, and instruct anyone with an old config.js to rotate
the coturn secret since it was previously exposed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TUI /react shows usage hint on bad/missing args instead of silently no-oping
- README: document /join /net /react slash commands and Ctrl+N keybinding
- README: add send_reaction IPC command and reaction IPC event to protocol reference
- FUTURE.md: mark reactions, link previews, responsive mobile layout, TUI
multi-network and TUI reactions as shipped; add prose sections for each
- EXTENSIONS.md: add EXT-008 documenting reaction wire protocol, SQLite
schema, IPC commands/events, history replay, and browser-mode implementation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Multi-network:
- Left panel now shows Networks section above Rooms
- /join <name> joins a new network at runtime; -network flag now optional
- ctrl+n cycles between joined networks; /net <n|name> switches by number or name
- All state (rooms, peers, messages) scoped per network via netData struct
- Messages keyed by "netId:room" (same as web store)
Reactions:
- /react <emoji> — react to the last message in the current room
- /react <n> <emoji> — react to message number n
- Reactions displayed as a dimmed line below each message: 👍 2 ❤️ 1
- EvtReaction handler updates in-place and refreshes viewport
History:
- EvtHistoryLoaded now handled: historical messages prepended, deduped by mid, sorted by time
UX:
- Each message prefixed with [n] line number so /react targets are unambiguous
- -network flag is now optional (start idle, /join to connect)
- Status bar hint updated with new commands
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- MessagePane: show date in timestamps (Yesterday/MMM D) for messages
not from today; divider now appears at the top of history on load
rather than waiting for a live message to create the boundary
- TUI: same date-aware timestamp formatting (Yesterday / Jan 2)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Daemon:
- Per-network download directories (-download-dir flag, set_download_dir IPC)
- File transfer resume after disconnection: .tmp.meta sidecars survive
interruption; resume_offset in file-accept lets sender seek and continue
- set_download_dir IPC command; download_dir reported in state_snapshot
Protocol:
- PeerMessage.ResumeOffset (EXT-006) for file transfer resume
- IpcMessage.ShareNetworks json tag changed from "networks" to "network_ids"
to fix duplicate json tag collision with Networks []NetworkInfo
Desktop app (cmd/app):
- Wails v2 shell embedding daemon logic directly (no subprocess)
- System tray on Linux/Windows via getlantern/systray; macOS hides to Dock
- OS notifications for message_received and file_complete via Wails events
- notray build tag for headless/CI builds without GTK tray headers
- build-app.sh: builds web frontend, copies dist, runs wails build
Web / PWA:
- manifest.json + Apple touch icon meta tags for mobile "Add to Home Screen"
- PNG icons (192px, 512px, 180px) generated from SVG
- Wails EventsOn("notify") hook in App.tsx for native OS notifications
CI:
- .gitea/workflows/build.yml: server binaries cross-compiled for 5 platforms,
desktop app for Linux amd64, release artifacts published on v* tags
Docs:
- README: download dir, file transfer resume, desktop app, PWA, CI sections
- EXTENSIONS.md: EXT-004 daemon mode marked shipped; EXT-006 resume added
- FUTURE.md: roadmap updated
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rooms that receive a message while not active show a * prefix in the
sidebar. The marker clears when you tab to that room.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
/room <name> in the TUI sends create_room to the daemon, which persists
it in the rooms SQLite table and echoes room_created back. state_snapshot
now includes persisted rooms so they survive reconnects. Tab navigation
and room rendering pick them up automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-turn-url and -turn-secret flags on the daemon; credentials generated
using coturn use-auth-secret HMAC-SHA1 scheme (same as browser mode).
ICEServers field on mesh.Mesh threads extra ICE servers through to
every PeerConnection created by the anchor client.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Signed invites: waste: URI gains inviter+sig fields (Ed25519); hello
carries the invite so receiving peers can verify against known keys
- RequireInvite per-network flag: rejects peers without valid signed invite
- Hash-based hang links: #waste:base64 fragment pre-fills join form without
server-side leakage of network name
- Multi-share: shares.json (daemon) + waste_shares localStorage (browser);
IPC add_share/remove_share/list_shares commands
- EXTENSIONS.md: addendum documenting all waste-go protocol deviations from
YAW/2; all extensions are additive and backward compatible
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously --ws-port started WS in a goroutine then called Run() which
blocked; if TCP IPC failed it took the whole process down even though WS
was up. Now both run in goroutines and the first error from either kills
the daemon.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add --ws-port flag to waste-daemon; when set, starts a WebSocket IPC
server alongside the existing TCP one. The web UI connects to this from
the browser. Uses nhooyr.io/websocket with localhost-only origin policy.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each network now carries its own share dir, set at join_network time via
optional share_dir field or updated live with set_share_dir. The global
-share-dir daemon flag becomes a fallback default.
- proto: add ShareDir/DownloadDir to NetworkInfo and IpcMessage
- netmgr: Join accepts shareDir override; SetShareDir updates live
- ipc: wire join_network share_dir and set_share_dir command
- daemon: remove -share-dir from auto-join path (pass "" for default)
- test-network.sh: per-network join with share_dir; isolation verification
section confirms alice/friends and alice/work share dirs are independent
- test-tui.sh: join_network with share_dir; peer IDs resolved after join
All tests pass: YAW/2.1 FS, share isolation, file transfer, persistence.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Five interop issues fixed against PROTOCOL.md:
- Join signature now covers nonce_raw || net_ascii (64-char UTF-8 hex
string) as specified in §5.1, not net_raw_bytes. Both anchor server
and client updated to match.
- Chat wire fields renamed to spec names: text/ts (Unix ms int64)
replacing body/sent_at (ISO timestamp). Flat layout on PeerMessage
matches §8 exactly; store and TUI updated accordingly.
- Direct messages now use the spec "pm" type (flat {type,mid,text,ts})
instead of chat+to. Receiver reconstructs a ChatMessage with
dm:<short-id> room for IPC/storage. §8 compliant.
- File transfer message types changed to spec hyphenated names:
file-offer, file-accept, file-cancel, file-done with spec field
names (name/size not filename/size_bytes). §9 compliant.
- DataChannel open-race (§14 gotcha #3) fixed with sync.Once: doOpen
fires on OnOpen callback or immediately if the channel is already
open when WireDataChannel is called (answerer race).
Also fixes two bugs found during testing:
- mid was missing from outgoing wire messages, causing all received
messages to arrive with mid="" and collide on the UNIQUE DB
constraint. mid is now included on all sent chat/pm messages; a
random mid is generated for any received message that omits it.
- Test scripts hardened: kill -9 + active lsof polling replaces blind
sleep for port cleanup; join_network sent before peer_field queries
(local_peer is now network-scoped and nil until joined).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
YAW/2 peer wire protocol is unchanged. Changes are local only.
internal/crypto:
- DeriveForNetwork(master, networkHash) — HKDF-SHA256 from master seed + network hash;
same master + same network always produces the same Ed25519 keypair (stable peer ID)
internal/proto:
- NetworkInfo type (network_id, network_name, local_peer)
- NetworkID field on IpcMessage (optional; commands default to first network when absent)
- Networks []NetworkInfo on state_snapshot (additive alongside existing local_peer)
- EvtNetworkJoined / EvtNetworkLeft events
internal/netmgr (new):
- Manager holds N independent Network contexts (derived identity, mesh, store, anchor)
- Join(name) creates context, derives identity, opens per-network DB, starts anchor client
- Leave(id) / LeaveAll() cancel contexts and close stores
- Resolve(netID) returns named network, or Default() when netID is empty (backward compat)
- Fan-out: Manager.Subscribe() receives tagged events from all networks
- Network IDs are the first 8 hex chars of SHA-256("yaw2-net:"+name) — stable and short
internal/ipc:
- Run(mgr, port) replaces Run(m *mesh.Mesh, port, anchorURL, joinFn)
- Commands without network_id route to mgr.Default() (backward compat)
- state_snapshot includes Networks array; local_peer/connected_peers still populated from first network
- generate_invite, get_file_list, send_message all respect network_id routing
cmd/daemon:
- Creates netmgr.Manager instead of mesh.Mesh directly
- --join and --share-dir pass through Config
- Auto-join via mgr.Join() before IPC starts
test-network.sh:
- Fix peer_name bash bug: $() with && inside triggers set -e; use if/elif/else instead
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- proto: FileEntry, FileListResp types; MsgFileListReq/Resp msg types;
CmdGetFileList + EvtFileList IPC types; Files field on IpcMessage
- mesh: ShareDir field + ScanShareDir(); on DataChannel open, auto-send
MsgFileListReq to new peer; handle MsgFileListReq (scan + reply) and
MsgFileListResp (emit EvtFileList to IPC subscribers)
- ipc: get_file_list command — own list returned immediately; remote peer
list requested via DataChannel (response arrives as EvtFileList event)
- daemon: -share-dir flag wired to mesh.ShareDir
- test scripts: pass -share-dir /home/frejoh/Downloads/{alice,bob,charlie};
test-network.sh verifies each peer's own file list via get_file_list
- FUTURE.md: document per-network share directories and multi-network design
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- internal/invite: Encode/Decode waste:<base64json{anchor,network}>
- proto: CmdGenerateInvite + EvtInviteGenerated + InviteString field
- ipc: track active network name; handle generate_invite (returns invite
string when joined to a network, errors if not joined or no anchor)
- daemon: --join <invite> flag — decodes anchor URL + network name,
sets anchor and auto-joins on startup
- tui: --join <invite> flag — extracts network name, skips -network
requirement; ctrl+i generates invite and shows it full-screen;
Esc dismisses the invite overlay
- FUTURE.md: document multi-network derived-identity design
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TUI: replace per-call bufio.Scanner with a goroutine+channel reader
(lineReader) so a single scanner lives for the connection lifetime;
previous pattern silently dropped messages due to scanner read-ahead
- test-tui.sh / test-network.sh: add `return 0` to wait_port — when the
port opens and the while condition becomes false, bash returned the
condition's exit code (1) to the caller, tripping set -e immediately
- .gitignore: use /tui (root-only) instead of tui to avoid ignoring cmd/tui/
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Connects to a running daemon's IPC port on startup
- Sends join_network then get_state; listens for events in real time
- Three-pane layout: room list (left), message history (centre), peers (right)
- Tab/Shift+Tab to switch rooms, Enter to send, PgUp/PgDn to scroll
- DM rooms appear automatically when a DM arrives
- test-tui.sh boots the full stack (anchor + 3 peers) and opens the TUI
as alice, with bob and charlie sending periodic messages as live noise
- README: layout diagram, key bindings, TUI section; roadmap item marked done
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each daemon writes to <data-dir>/messages.db on startup. Messages received
or sent are stored immediately; duplicate mids (INSERT OR IGNORE) are safe
to call multiple times. Peer aliases are upserted on peer_connected and
again after hello verification when the real nick is known.
Schema
- messages(mid UNIQUE, room, from_peer, body, sent_at) — mid is the YAW/2
dedup key added in the proto migration; index on (room, sent_at) for
efficient per-room queries.
- peers(peer_id PK, alias, last_seen) — cache of every peer ever seen,
used to resolve hex ids to names when peers are offline.
Wiring
- store.Open called in cmd/daemon/main.go, passed to mesh.New.
- mesh.Mesh holds *store.Store (nil-safe; persistence is optional).
- mesh.SaveMessage called in dispatchPeerMessage (incoming) and ipc
CmdSendMessage (outgoing) so the local node's own messages are stored.
- mesh.UpdatePeerAlias called after hello verification updates the alias
with the verified nick rather than the placeholder short-id.
Messages only accumulate from join time forward — no history replay to
late-joining peers; each node's view starts from when it connected.
test-network.sh: added SQLite verification block that queries each node's
DB after the test and prints message + peer counts.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>