Add embedded web chat client (SPA) #7

Closed
opened 2026-02-10 18:22:47 +01:00 by clawbot · 0 comments
Collaborator

Overview

Build an IRC-style web chat client embedded directly in the Go binary, served at GET /.

Architecture

Backend (C2S HTTP API)

New API endpoints under /api/v1/:

Method Path Description
GET /server Server name + MOTD
POST /register Register nick, get bearer token
GET /me Current user info
GET /channels List joined channels
GET /channels/all List all channels
POST /channels/join Join/create channel
DELETE /channels/{ch}/part Leave channel
GET /channels/{ch}/members Channel user list
GET /channels/{ch}/messages Get messages (with ?after= cursor)
POST /channels/{ch}/messages Send message
GET /dm/{nick}/messages Get DMs
POST /dm/{nick}/messages Send DM
GET /poll Poll all new messages (channels + DMs)

Auth: Bearer token in Authorization header.

Database (migration 003)

  • users — nick, token, last_seen
  • channel_members — channel↔user join table
  • messages — channel messages + DMs (unified table with is_dm flag)
  • Proper indexes on message queries

Frontend (Preact SPA)

  • Preact (3KB) instead of React — minimal bundle size
  • esbuild for bundling — fast, zero-config
  • Built output in web/dist/, embedded via embed.FS
  • Total JS bundle: ~19KB minified

UI Design (IRC-style)

  • Tab bar (top): Server tab, channel tabs, DM tabs with close buttons
  • Messages (center): Timestamped, colored nicks, <nick> format
  • User list (right): Full-height panel for current channel, click to DM
  • Input bar (bottom): Message input with send button
  • Join dialog: Inline in tab bar for joining channels

Features

  • Nick registration with token persistence (localStorage)
  • Channel join/part/create
  • Real-time polling (1.5s interval)
  • DM windows as separate tabs
  • IRC commands: /join, /part, /msg, /nick
  • Per-nick color hashing
  • Dark theme, monospace font
  • Mobile-responsive (user list hidden on small screens)

Engineering Decisions

  1. Preact over React: 3KB vs 40KB, same API, embedded binary size matters
  2. Polling over WebSocket: Simpler, works through all proxies, adequate for chat at this scale. Can upgrade later.
  3. Unified messages table: Channel msgs and DMs in one table with is_dm flag — simpler polling query
  4. Bearer tokens: Simple auth model, no sessions to manage. Token in localStorage.
  5. embed.FS: Zero external dependencies at runtime, single binary deployment
  6. esbuild: Sub-second builds, no webpack config complexity
## Overview Build an IRC-style web chat client embedded directly in the Go binary, served at `GET /`. ## Architecture ### Backend (C2S HTTP API) New API endpoints under `/api/v1/`: | Method | Path | Description | |--------|------|-------------| | GET | `/server` | Server name + MOTD | | POST | `/register` | Register nick, get bearer token | | GET | `/me` | Current user info | | GET | `/channels` | List joined channels | | GET | `/channels/all` | List all channels | | POST | `/channels/join` | Join/create channel | | DELETE | `/channels/{ch}/part` | Leave channel | | GET | `/channels/{ch}/members` | Channel user list | | GET | `/channels/{ch}/messages` | Get messages (with `?after=` cursor) | | POST | `/channels/{ch}/messages` | Send message | | GET | `/dm/{nick}/messages` | Get DMs | | POST | `/dm/{nick}/messages` | Send DM | | GET | `/poll` | Poll all new messages (channels + DMs) | Auth: Bearer token in Authorization header. ### Database (migration 003) - `users` — nick, token, last_seen - `channel_members` — channel↔user join table - `messages` — channel messages + DMs (unified table with `is_dm` flag) - Proper indexes on message queries ### Frontend (Preact SPA) - **Preact** (3KB) instead of React — minimal bundle size - **esbuild** for bundling — fast, zero-config - Built output in `web/dist/`, embedded via `embed.FS` - Total JS bundle: ~19KB minified ### UI Design (IRC-style) - **Tab bar** (top): Server tab, channel tabs, DM tabs with close buttons - **Messages** (center): Timestamped, colored nicks, `<nick>` format - **User list** (right): Full-height panel for current channel, click to DM - **Input bar** (bottom): Message input with send button - **Join dialog**: Inline in tab bar for joining channels ### Features - Nick registration with token persistence (localStorage) - Channel join/part/create - Real-time polling (1.5s interval) - DM windows as separate tabs - IRC commands: `/join`, `/part`, `/msg`, `/nick` - Per-nick color hashing - Dark theme, monospace font - Mobile-responsive (user list hidden on small screens) ### Engineering Decisions 1. **Preact over React**: 3KB vs 40KB, same API, embedded binary size matters 2. **Polling over WebSocket**: Simpler, works through all proxies, adequate for chat at this scale. Can upgrade later. 3. **Unified messages table**: Channel msgs and DMs in one table with `is_dm` flag — simpler polling query 4. **Bearer tokens**: Simple auth model, no sessions to manage. Token in localStorage. 5. **embed.FS**: Zero external dependencies at runtime, single binary deployment 6. **esbuild**: Sub-second builds, no webpack config complexity
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: sneak/chat#7
No description provided.