Fix web UI messaging, alias resolution, and onboarding

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>
This commit is contained in:
Fredrik Johansson
2026-06-22 23:22:32 +02:00
parent 2bc1dbbedf
commit 7fe02e9463
10 changed files with 265 additions and 34 deletions

View File

@@ -8,6 +8,8 @@ interface WasteState {
daemonStatus: 'disconnected' | 'connecting' | 'connected'
// identity
masterAlias: string | null
masterId: string | null
localPeer: PeerInfo | null
// networks
@@ -24,6 +26,9 @@ interface WasteState {
// file listings — keyed by peer id
fileLists: Record<string, FileEntry[]>
// identity backup export result (cleared when consumed)
exportedBackup: string | null
// actions
connect: (url: string) => void
disconnect: () => void
@@ -36,6 +41,8 @@ interface WasteState {
export const useWaste = create<WasteState>((set, get) => ({
adapter: null,
daemonStatus: 'disconnected',
masterAlias: null,
masterId: null,
localPeer: null,
networks: [],
activeNetworkId: null,
@@ -43,6 +50,7 @@ export const useWaste = create<WasteState>((set, get) => ({
messages: {},
activeRoom: 'general',
fileLists: {},
exportedBackup: null,
connect(url: string) {
const adapter = new DaemonAdapter(url)
@@ -74,6 +82,8 @@ export const useWaste = create<WasteState>((set, get) => ({
case 'state_snapshot': {
const networks = msg.networks ?? []
set({
masterAlias: msg.master_alias ?? null,
masterId: msg.master_id ?? null,
localPeer: msg.local_peer ?? null,
networks,
connectedPeers: msg.connected_peers ?? [],
@@ -113,6 +123,16 @@ export const useWaste = create<WasteState>((set, get) => ({
}
break
}
case 'session_ready': {
if (msg.peer_id && msg.nick) {
set(s => ({
connectedPeers: s.connectedPeers.map(p =>
p.id === msg.peer_id ? { ...p, alias: msg.nick! } : p
),
}))
}
break
}
case 'peer_disconnected': {
set(s => ({
connectedPeers: s.connectedPeers.filter(p => p.id !== msg.peer_id),
@@ -121,16 +141,20 @@ export const useWaste = create<WasteState>((set, get) => ({
}
case 'message_received': {
if (msg.message) {
const room = msg.message.room
set(s => ({
messages: {
...s.messages,
[room]: [...(s.messages[room] ?? []), msg.message!],
},
}))
const m = msg.message
const room = m.room
set(s => {
const existing = s.messages[room] ?? []
if (m.mid && existing.some(e => e.mid === m.mid)) return s
return { messages: { ...s.messages, [room]: [...existing, m] } }
})
}
break
}
case 'identity_exported': {
if (msg.backup) set({ exportedBackup: msg.backup })
break
}
case 'file_list': {
if (msg.peer_id && msg.files) {
set(s => ({