- Timestamps now show "Jun 28 10:58" for older messages (dropped
"Yesterday" which overflowed the fixed-width column)
- Timestamp column widened from 52px to 72px to fit date+time
- state_snapshot now includes known_peers (historically seen, not
currently connected) so the UI can resolve aliases in history
- MessagePane aliasFor falls back to knownPeers map
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>
file_complete in daemon mode carries transfer_id but no offer field.
The old condition required msg.offer, so the progress/cancel row was
never removed. Now clears using transfer_id first, offer.xid as fallback.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Daemon scans the download directory for .tmp.meta sidecars on network join
and emits resumable_transfers IPC event. Web UI shows them in the Transfers
panel with a dimmed progress bar and "will resume on reconnect" note.
- proto: ResumableFile type, EvtResumableTransfers, resumable_files IpcMessage field
- mesh: ScanResumable() scans download dir and emits the event
- netmgr: call ScanResumable() after join (both Join and JoinByHash paths)
- web: resumableFiles store state, resumable_transfers handler, Transfers UI section
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- store: handle history_loaded event — prepend gossipped messages,
dedup by mid, sort by ts, record cutoff timestamp per room
- MessagePane: show "earlier messages" divider between history and
live messages based on the cutoff timestamp
- types: add history_loaded, room_created, create_room to IpcMsgType;
add messages field to IpcMessage
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>
- 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>
Daemon side:
- internal/shares: new package reads/writes shares.json next to identity
- proto: add ShareEntry type, add_share/remove_share/list_shares IPC commands,
shares_list event, path field on FileEntry
- netmgr: load shares.json on startup; ScanAllShares combines legacy ShareDir
with shares.json entries (recursive walk with relative paths)
- mesh: ScanFiles callback lets manager inject multi-share scanning without
mesh knowing about shares.json
- ipc: handle add_share, remove_share, list_shares commands
Browser side:
- ShareManager component replaces FolderPicker: shows list of named shares
with remove/re-pick buttons; persists share records to waste_shares in
localStorage (name, global flag, networkId)
- FolderPicker retained for internal use; Sidebar now uses ShareManager
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- FolderPicker: uses webkitRelativePath so paths like docs/report.pdf
are preserved; adds "include subfolders" checkbox (default on)
- FileBrowser: virtual directory tree with breadcrumb navigation,
sort by name/size, search filter, folders shown before files
- Protocol: browse sends path alongside name; get uses path as key
- FileEntry type gains optional path field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
coturn's use-auth-secret mode requires the TURN password to be
HMAC-SHA1(secret, username), not the raw secret. Pass ice servers
as a constructor argument so the async credential generation happens
before RTCPeerConnection is created.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Browser adapter reads turnURL + turnSecret from WASTE_CONFIG and adds a
TURN ICEServer entry automatically. Uses time-limited credentials
compatible with coturn's use-auth-secret mode.
README: new section under Hosting with coturn install, turnserver.conf,
firewall note (UDP 3478, no NPM needed), and config.js snippet.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Emit peer_connected + peer_status(connecting) as soon as a PeerConn is
created, so peers appear in the sidebar even if ICE is still negotiating
or ultimately fails. The alias updates in-place once the DataChannel hello
arrives with the real nick.
Fixes: mobile/CGNAT peers being completely invisible when STUN hole
punching fails — they now show with a connecting/failed dot instead of
not appearing at all.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Additional rooms:
- Store tracks customRooms per networkId; createRoom action slugifies and
deduplicates, switches active room on creation
- Sidebar: + button next to Rooms label opens an inline input; Escape
dismisses; Enter creates the room
Per-network share directories:
- Store tracks sharedFilesByNetwork keyed by networkId instead of a single
global map; setSharedFiles(files, networkId?) defaults to activeNetworkId
- FolderPicker reads per-network map to display accurate file count label
File transfer UX:
- Incoming file-offer now emits pending_offer instead of auto-accepting;
user sees Accept / Reject buttons in the sidebar Transfers section
- Progress events emitted per chunk during receive; Transfers panel shows
a live progress bar with received/total sizes
- file-cancel protocol message: sender or receiver can cancel an in-flight
transfer; DataChannel is closed and buffers discarded
- PeerConn: acceptOffer(), rejectOffer(), cancelRecv(), cancelSend()
- BrowserAdapter: acceptOffer(), rejectOffer(), cancelTransfer()
- Store: pendingOffers, fileProgress, acceptOffer, rejectOffer, cancelTransfer
- Transfers component renders in sidebar; auto-hides when no activity
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements both pull (browse & download from a shared folder) and push
(send a file directly to a peer) using the yaw2 file protocol over WebRTC
DataChannels.
- PeerConn: handle browse/files/get/file-offer/file-accept/file-done
control messages; stream files in 64KB chunks over f:xid binary
DataChannels; auto-accept incoming offers (push and pull)
- PeerConn.sendFilePush(): initiate an outbound file transfer without
requiring the peer to browse first
- BrowserAdapter: sharedFiles map, setSharedFiles(), requestBrowse(),
requestGet(), sendFileTo() — propagates share map to all active peers
- Store: activeFilePeer, setActiveFilePeer, setSharedFiles, browseFiles,
sendFileTo; file_complete handler triggers browser download automatically
- FileBrowser component: third-column panel listing a peer's shared files
with name, size, and a download button
- FolderPicker component: webkitdirectory input in the sidebar (browser
mode only) for selecting a local folder to share
- Sidebar: 📎 send-file button on peer rows (hover) with inline file picker
- App.tsx: use browser mode when WASTE_CONFIG.signalURL is set, even on
localhost — allows testing browser mode via npm run dev with config.js
- deploy-daemon.sh: kill existing daemon before scp to avoid upload failure
when the binary is locked; remove duplicate kill block in restart step
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace bold duplicate-looking self row with dimmed name + small accent
"you" badge. Removes the ID (already shown in identity section at top).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Switch back to flex, set explicit widths: 52px timestamp (nowrap, 24h),
90px alias (right-aligned). Eliminates wrapping and the large gap.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Messages now use a fixed grid (44px ts · 120px alias right-aligned · 1fr text)
instead of flex, so text always starts at the same column regardless of alias length.
Self shown at top of peer list with accent dot and bold name.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Peers moved from right panel into sidebar below Rooms
- Messages left-aligned IRC style: time · alias · text
- Per-peer colored dot: green=direct/NAT-punched, amber=relay/connecting, red=failed
- Hover title on peer row shows full ID, connection type, remote address, last seen
- Removed right PeerList panel
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
BrowserAdapter speaks yaw/2.1 signaling + WebRTC protocol entirely in
the browser (libsodium-wrappers for Ed25519/X25519). Identity stored in
localStorage; peers connect via any waste/yaw2 anchor.
When served from a non-localhost origin the app defaults to browser
mode. On localhost the daemon adapter is tried first with a one-click
switch to browser mode. config.js (gitignored, served by anchor) lets
the host pre-inject signalURL and other defaults.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Protocol:
- invite.go: include full 64-char `net` hash in waste: invite blob
(matches yaw2's `net` field — any client parsing the base64 JSON can
join without knowing the plaintext name). Expose NetHash() helper.
- netmgr: add JoinByHash() — join via full 64-char hex hash alone,
storing the short ID as display name. Enables joining yaw2 networks
from a URL that only carries the hash.
- anchor: expose RunByHash() so netmgr can pass a pre-computed hash
directly without a name→hash roundtrip.
- ipc/proto: add network_hash field to join_network — routes to
JoinByHash when present and network_name is absent.
Web UI:
- Parse ?net=<64hex> (yaw2 URL param) and ?a=<anchor> in addition to
existing ?n= / ?invite= params. Hash-only joins send network_hash.
- Sidebar shows yaw: contact card (yaw:<masterID>?n=<alias>) using the
master identity — compatible with yaw2 contact card format. Click to
copy to clipboard.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Messaging:
- Append \n to WS IPC sends so bufio.Scanner fires (messages from web
were silently dropped — the line scanner never saw a newline)
- Handle session_ready in web store to update peer aliases after hello
Alias resolution:
- Seed AddPeer alias from SQLite cache so returning peers resolve
immediately, before hello exchange completes
- Remove stale SavePeer call from AddPeer (was writing placeholder IDs)
- Add PeerAlias() point-lookup to store
Onboarding redesign:
- Three-state UI: disconnected (daemon instructions), connecting,
connected-no-network (join form)
- Read ?n= / ?network= / ?anchor= / ?invite=waste:... URL params to
pre-fill the join form for invite links
- Show daemon alias + peer ID before any network is joined
(master_alias + master_id now included in state_snapshot)
- Identity backup: export (passphrase-protected yaw-key-backup-1 JSON,
download button) and restore (paste + passphrase verify)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
React + TypeScript UI on port 5274. Connects to local daemon via
WebSocket IPC (DaemonAdapter). Full chat layout with sidebar, message
pane, and peer list. Mirrors the TUI feature set.
- src/types.ts — IPC types mirroring proto.go
- src/adapter/daemon — WebSocket IPC adapter with auto-reconnect
- src/store/index.ts — Zustand store; handles all daemon events
- src/pages/ — Onboarding (connect + join) and Chat
- src/components/ — Sidebar, MessagePane, PeerList
Dev: cd web && npm run dev (port 5274)
Build: cd web && npm run build
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>