Cursor party 1 person here, 0 moves a second

Go · SQLite · Datastar

lith is the runtime under the Go apps of zwei und eins: one SQLite file, one writer that applies every change in order, views rendered once per tick for everyone who watches them, and HTML streamed to each browser over server-sent events.

The four ideas

The name is the design: each letter is one rule the runtime keeps, and each rule has a package behind it.

LLinear writes

Every change is a command, and one writer applies them in the order they arrive. It takes all the commands waiting into one transaction, with a savepoint each: a command that fails rolls back alone, and the rest commit together. The more people write at once, the more commands share each commit.

How Linear writes works

The writer race, live

66,688 commands a second

lith's writer benchmark, 200 producers on an Intel i5-13500. No race on this server yet: start the first.

All the numbers

IImmediate mode

A region of a page is a function of the database: view = f(state). The server renders it whole, in one read transaction, and Datastar morphs the new HTML into the page. No state in the browser guesses what the server will say, so there is nothing to reconcile.

How Immediate mode works

The guestbook, live

No entries yet. Be the first.

    Sign it

    TTicks

    A commit names the topics it touched, and every view that listens to one of them turns dirty. Once per tick, 50 ms by default, each dirty view renders once and its frame goes to every stream that watches it: two hundred tabs on one board cost one render.

    How Ticks works

    The shared board, live

    The last tick with a move

    None since the server started.

    Frames a second

    0.0

    Nobody on the board yet

      Move a card

      HHypermedia

      Frames reach the browser as Datastar events over server-sent events. Each stream compresses with a window of its own, so a frame that repeats most of the last one costs a few hundred bytes, and a memory budget shrinks the windows as more streams open.

      How Hypermedia works

      This page's stream, live

      …

      waiting from the memory budget

      Frames
      0
      Ratio so far
      1:1
      Last frame
      waiting

      The first frame has no frame before it to refer back to. The frames after it do, and cost far less.

      Inspect every number

      Measured

      In process, on an Intel i5-13500 under Linux, in October 2026. With synchronous=NORMALSQLite syncs the log only at checkpoints: a power loss can undo up to about 35 s of commits, a crash of the process none. FULL syncs every batch. unless it says otherwise.

      commands a second through one writer200 producers, 109 commands to a transaction, synchronous=NORMAL
      66,688
      from a command's arrival to its commit, at p50beside 200 open streams and 800 commands a second
      0.54 ms
      render per tick for 200 streams of one boarda 139 KB view changing 800 times a second, no frame dropped
      1
      bytes on the wire for each frame of that boardzstd, every stream compressed on its own
      2,205
      of heap for an open streamzstd with a 2^19 window and low memory; brotli at its default window holds 9.9 MiB
      1.0 MiB
      moves a second in kanban, with 200 peoplea move answered in 13.1 ms at p95
      14,928

      How each number was taken, with the commands to run it again: bench/RESULTS.md. The kanban numbers come from kanban's own load tool.

      Start a project

      1. lith-new copies the template into a new module: a guestbook with tests, CI, a systemd unit, a Caddy block and a deploy script with rollback.
      2. Run it and open two tabs. Sign in one; the other shows the entry in the next tick.
      3. Replace the guestbook with your first feature and keep the wiring. Getting started walks through it.

      A new module from lith's template, its guestbook running.

      shell
      go run github.com/zweiundeins/lith/cmd/lith-new@latest \
      	~/guestbook example.com/you/guestbook
      cd ~/guestbook && go mod tidy
      go tool task dev   # http://127.0.0.1:8080

      lith in a module of your own: then wire the database, the writer and the hub in main.

      shell
      go get github.com/zweiundeins/lith@latest

      A project on a lith checkout: its go.mod gets a replace directive for the checkout, for work on both.

      shell
      git clone https://github.com/zweiundeins/lith ~/src/lith
      cd ~/src/lith && go run ./cmd/lith-new -lith . ~/guestbook example.com/you/guestbook

      Demos

      Each demo runs on this server, on lith, with the code that does it beside it.

      • Guestbook

        Sign the book: the entry reaches every open tab in the next tick, and only your tab hears if it was refused.

      • Writer race

        Up to 200 producers send commands to one writer for three seconds, and everyone watching sees the batches grow, tick by tick.

      • Stream inspector

        Your own stream, live: what the hub sends this tab, what zstd makes of it, and what the memory budget gave its encoder.

      • Canvas

        Paint on one pixel canvas with everyone here: each stroke is a command, and one render a tick carries the board to every painter.

      • Shared board

        Drag a card to another lane: the board renders once in the next tick, and every open tab sees it glide there.

      • Cursor party

        Every visitor's pointer, live: each tab sends its position 15 times a second, and one view draws them all once per tick.

      • A million checkboxes

        One grid of a million boxes for everyone: tick one, and every tab that has it in view sees it flip within a tick.

      • Game of Life

        One board for everyone: the server computes ten generations a second while anyone watches, and every tab gets the same frame.

      Credits

      hyperlith

      The Clojure framework on Datastar that lith is inspired by.

      northstar

      The Go starter for Datastar apps that lith is inspired by.

      Datastar

      The hypermedia framework in the browser: signals, morphs and the events every lith stream speaks.