Backend:
- Session/client UUID model: sessions table (uuid, nick, signing_key),
clients table (uuid, session_id, token) with per-client message queues
- MOTD delivery as IRC numeric messages (375/372/376) on connect
- EnqueueToSession fans out to all clients of a session
- EnqueueToClient for targeted delivery (MOTD)
- All queries updated for session/client model
SPA client:
- Long-poll loop (15s timeout) instead of setInterval
- IRC message envelope parsing (command/from/to/body)
- Display JOIN/PART/NICK/TOPIC/QUIT system messages
- Nick change via /nick command
- Topic display in header bar
- Unread count badges on inactive tabs
- Auto-rejoin channels on reconnect (localStorage)
- Connection status indicator
- Message deduplication by UUID
- Channel history loaded on join
- /topic command support
Closes#9
- SessionResponse: use 'id' (int64) not 'session_id'/'client_id'
- StateResponse: match actual server response shape
- GetMembers: strip '#' from channel name for URL path
- These bugs prevented the CLI from working correctly with the server
The poll loop was storing msg.ID (UUID string) as afterID, but the server
expects the integer queue cursor from last_id. This caused the CLI to
re-fetch ALL messages on every poll cycle.
- Change PollMessages to accept int64 afterID and return PollResult with LastID
- Track lastQID (queue cursor) instead of lastMsgID (UUID)
- Parse the wrapped MessagesResponse properly
- Integration tests for all API endpoints (session, state, channels, messages)
- Tests for all commands: PRIVMSG, JOIN, PART, NICK, TOPIC, QUIT, PING
- Edge cases: duplicate nick, empty/invalid inputs, malformed JSON, bad auth
- Long-poll tests: delivery on notify and timeout behavior
- DM tests: delivery to recipient, echo to sender, nonexistent user
- Ephemeral channel cleanup test
- Concurrent session creation test
- Nick broadcast to channel members test
- DB unit tests: all CRUD operations, message queue, history
- Broker unit tests: wait/notify, remove, concurrent access
- Merge fanOut/fanOutDirect into single fanOut method
- Move channel lookup to db.GetChannelByName
- Add regex validation for nicks and channel names
- Split HandleSendCommand into per-command helper methods
- Add charset to Content-Type header
- Add sentinel error for unauthorized
- Cap history limit to 500
- Skip NICK change if new == old
- Add empty command check
Major changes:
- Consolidated schema into single migration with IRC envelope format
- Messages table stores command/from/to/body(JSON)/meta(JSON) per spec
- Per-client delivery queues (client_queues table) with fan-out
- In-memory broker for long-poll notifications (no busy polling)
- GET /messages supports ?after=<queue_id>&timeout=15 long-polling
- All commands (JOIN/PART/NICK/TOPIC/QUIT/PING) broadcast events
- Channels are ephemeral (deleted when last member leaves)
- PRIVMSG to nicks (DMs) fan out to both sender and recipient
- SPA rewritten in vanilla JS (no build step needed):
- Long-poll via recursive fetch (not setInterval)
- IRC envelope parsing with system message display
- /nick, /join, /part, /msg, /quit commands
- Unread indicators on inactive tabs
- DM tabs from user list clicks
- Removed unused models package (was for UUID-based schema)
- Removed conflicting UUID-based db methods
- Increased HTTP write timeout to 60s for long-poll support