- 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>