Welcome to Chat! This is the private messaging corner of the site — the surface Steve, Claude, and a handful of family and friends actually use every day. Log in at lynrummy.com/chat and you get:
- 1:1 DMs and group channels (the live one is
#General), with messages written in markdown, updating live as people type-and-send. - Topics — each conversation is split into named threads (a "topic" is just a named session), so a chat can hold many parallel threads without losing the plot.
- Emoji by shortcode — type
:and pick 🎉 from the autocomplete (a fixed, client-side whitelist in chat-client's chat_emoji.js; the server only ever sees the glyph). - Reactions —
:picks an emoji for the selected message,+is a thumbs-up, clicking a chip joins or retracts. Stored beside the transcript as an append-only<sid>.reactions.jsonlthe client folds (chat_reactions.js). - Images & screencasts you can drag in, a world-clock you get by clicking any
timestamp, cross-message references (
MSG_…links that jump straight to the quoted message), and a keyboard-first feel — arrows to move,/to search,q/rto quote or refer,tfor the raw transcript. - Sibling surfaces on the same machinery: Docs (
/chat/docs, markdown authoring), Recent (/chat/recent, your activity feed), and Images (/chat/images, every picture you can see, in one stream).
It's live and dogfooded daily; the architecture has settled. The code is the authority for how anything works — this file is the human orientation, and the deeper "why" of one of its best ideas lives in the essay linked below.
The one principle that organizes everything: keep the backend thin. The server (written in Zig) authenticates you, stores transcripts as plain files, renders markdown to HTML, and echoes the on-disk structure back over the wire. The URL space mirrors the filesystem 1:1, and the behavior lives in the JavaScript — navigation, the three-pane layout, the keyboard nav, the bubble rendering. New behavior almost always belongs in the client, not the server.
A few load-bearing ideas worth knowing before you dig in:
Convis the unifying abstraction. A DM is aConvwith two members; a channel is aConvwith N. Every storage path, SSE keyspace, fan-out loop, and sidebar payload takes aConvand iterates its members — so DMs and channels share one set of machinery and only diverge at the URL and the notify text.- Storage is plain files on disk — no database. Each topic is a markdown file;
uploads sit beside it.
appendMessageis the single write chokepoint every message is born through, and it fans out the live events (message, notify, recent, sidebar, images) to every member. - Live updates ride Server-Sent Events. Several per-user and per-conversation SSE streams push new messages and activity; the client replays backlog then follows live.
- MPA at the core, SPA-feel from a user-attention layer. One transcript = one topic = one URL, and switching topics is a real page load (bookmarkable, refreshable, browser-native back/forward). What makes it feel live is a separate per-user layer that survives page loads: cross-topic notifications, the violet-favicon alert, pinned sessions, and the Recent feed. The unit of identity is the URL; the unit of attention is the user.
- Markdown is our own dialect. Rather than pull in a parser, the server hand-rolls a small, deliberate markdown engine — that choice (and why it's the right one for a project like this) is the subject of the essay "Afford Your Own Markdown Dialect" (unpublished since the blog was retired).
The front end is its own repository, chat-client (a sibling checkout): hand-written JavaScript, no build step, embedded into the
zig binary through its chat_client module and served as-is. Its README
says how it is organized, and its tools/check runs every check of its own
(the JS syntax, its lint, the resize decision, the contract). This server
pins it: chat-client-rev names the commit it expects, and ops/test_chat
(in ops/check_chat) fails, naming both commits, when ../chat-client is
at another, then runs the client's tools/check there.
The markdown dialect is its own repository too, chat-markdown (a sibling
checkout, module chat_markdown).
The server side lives over in zig-server/src/ — chat.zig and its focused
neighbors (chat_page.zig, chat_sse.zig, chat_conv.zig, chat_chrome.zig, recent.zig,
images.zig, docs*.zig, …). chat/chat_client.py is a small Python reference
client for the HTTP API, so other clients (and our backups) have a worked example.
ops/start, then open /chat. The subsystem gate is ops/check_chat (JS syntax +
the lint pass + the Python client's syntax); the full pre-commit gate is ops/check.
A change reaches the site in a zig-metal-unikernel release (its DEPLOY.md).
For the project-level picture (toolchain, the responsive/mobile design, deploy), see
the repo's top-level README.md.
Built by Steve and Claude, and used daily with apoorva and Debbie. It's live, but the code is tidy and the seams are intentional — if you (or an agent you're working with) want to build on top of what we wrote, start here, then read the server modules.