Beyond Text — Chat

A minimal realtime chat app built on Val Town. Messages are stored in Val Town SQLite and streamed to every client over Server-Sent Events. A QR code of the app's own URL is shown in the sidebar so people can scan to join.

Features

  • 💬 Realtime chat via Server-Sent Events (no WebSockets needed)
  • 🗄️ Messages stored in Val Town SQLite
  • 📱 QR code of the site URL for easy joining
  • 👤 Username saved to localStorage (prompted on first visit)
  • 🧘 No auto-scroll, no auto-focus on the input

Architecture

Rendering mermaid diagram...

File structure

index.ts                        ← Hono backend: static + /api routes + SSE stream
frontend/
  index.html                    ← HTML shell (loads Twind + React)
  index.tsx                     ← React entrypoint (mounts <App />)
  favicon.svg                   ← App icon
  components/
    App.tsx                     ← Username gating (localStorage)
    UsernamePrompt.tsx          ← First-visit username form
    Chat.tsx                    ← Messages list, input, sidebar
    QRCode.tsx                  ← Renders a QR of the site URL

API

  • GET / — serves the React app
  • POST /api/messages{ username, text } → stores a message in SQLite
  • GET /api/messages?sinceId=N — fetch messages newer than N
  • GET /api/stream?sinceId=N — SSE stream of new messages (polls SQLite every 1.5s; emits hello, message, and ping events)

Notes on design choices

  • SSE instead of WebSockets: Val Town doesn't accept incoming WebSocket connections, so the server polls SQLite on a 1.5s loop and pushes new rows to each subscribed client as SSE events.
  • No auto-scroll / auto-focus: The messages pane scrolls only when the user scrolls, and the input is never programmatically focused.
  • Username in localStorage: A first-visit prompt stores the chosen name under the key beyond-text:username. A "change" button in the header clears it.