feat: MVP two-user chat via embedded SPA (#9)
All checks were successful
check / check (push) Successful in 1m51s

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
This commit is contained in:
clawbot
2026-02-27 02:21:48 -08:00
parent 2d08a8476f
commit 32419fb1f7
8 changed files with 921 additions and 880 deletions

View File

@@ -1,15 +1,28 @@
-- Chat server schema (pre-1.0 consolidated)
PRAGMA foreign_keys = ON;
-- Users: IRC-style sessions (no passwords, just nick + token)
CREATE TABLE IF NOT EXISTS users (
-- Sessions: IRC-style sessions (no passwords, nick + optional signing key)
CREATE TABLE IF NOT EXISTS sessions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
nick TEXT NOT NULL UNIQUE,
signing_key TEXT NOT NULL DEFAULT '',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_seen DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_sessions_uuid ON sessions(uuid);
-- Clients: each session can have multiple connected clients
CREATE TABLE IF NOT EXISTS clients (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid TEXT NOT NULL UNIQUE,
session_id INTEGER NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
token TEXT NOT NULL UNIQUE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_seen DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_users_token ON users(token);
CREATE INDEX IF NOT EXISTS idx_clients_token ON clients(token);
CREATE INDEX IF NOT EXISTS idx_clients_session ON clients(session_id);
-- Channels
CREATE TABLE IF NOT EXISTS channels (
@@ -24,9 +37,9 @@ CREATE TABLE IF NOT EXISTS channels (
CREATE TABLE IF NOT EXISTS channel_members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
channel_id INTEGER NOT NULL REFERENCES channels(id) ON DELETE CASCADE,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
session_id INTEGER NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
joined_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(channel_id, user_id)
UNIQUE(channel_id, session_id)
);
-- Messages: IRC envelope format
@@ -46,9 +59,9 @@ CREATE INDEX IF NOT EXISTS idx_messages_created ON messages(created_at);
-- Per-client message queues for fan-out delivery
CREATE TABLE IF NOT EXISTS client_queues (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
message_id INTEGER NOT NULL REFERENCES messages(id) ON DELETE CASCADE,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(user_id, message_id)
UNIQUE(client_id, message_id)
);
CREATE INDEX IF NOT EXISTS idx_client_queues_user ON client_queues(user_id, id);
CREATE INDEX IF NOT EXISTS idx_client_queues_client ON client_queues(client_id, id);