Showcase

Real apps launched with community components. Yours could be next.

Mission Control

PAD T−8.0 s
Altitude
How it works

The dashboard is built only from community components: sb-starfield, sb-voxel, sb-gauge, sb-sparkline and sb-meter. It contains no custom JavaScript. The server pushes data, signals carry it, and each component watches its attributes.

1. The server streams signals

A hidden element in the section opens one long-lived request with @get('/demo/telemetry'). Four times a second the server sends a datastar-patch-signals event that merges fresh numbers into a single signal, $_tm. The data is a pure function of the clock over a 90-second mission, so the server keeps no state and every viewer watches the same flight. In CQRS terms this is a query stream: nothing is written, and hidden tabs close it and reopen it when they come back. When the server restarts for a deploy, its last event replaces that element by a copy, which opens the request again, and retry: 'error' keeps asking until the new process answers.

// internal/web/demo.go: a query stream, no state, no database.
func (s *Server) demoTelemetry(w http.ResponseWriter, r *http.Request) {
	stream, err := sse.Open(w, r, sse.Options{})
	if err != nil {
		return
	}
	var last [][]byte
	defer func() { stream.CloseWith(last...) }()
	tick := time.NewTicker(250 * time.Millisecond)
	for {
		// A pure function of the clock: every viewer sees the same flight.
		tm, _ := json.Marshal(map[string]any{"_tm": TelemetryAt(time.Now())})
		stream.Write(sse.PatchSignals(tm, false))
		select {
		case <-tick.C:
		case <-r.Context().Done():
			return
		case <-s.ctx.Done(): // a deploy: the page opens the stream again
			last = append(last, sse.Reconnect("telemetry"))
			return
		}
	}
}

2. Signals drive attributes

Each component is wired with data-attr. When $_tm changes, Datastar updates exactly the attributes that depend on it. Expressions can reshape the data on the way: the starfield's speed is derived from velocity, and warp switches on above 5 km/s.

<section data-ignore-morph
  data-signals="{_tm: {alt: 0, vel: 0, fuel: 100, temp: 18, pitch: 90}}">
  <div id="telemetry" hidden
       data-init="@get('/demo/telemetry', {retry: 'error', retryMaxCount: Infinity, retryInterval: 400, retryMaxWait: 4000})"></div>

  <sb-starfield data-attr:speed="Math.round(3 + $_tm.vel * 11)"
                data-attr:warp="$_tm.vel > 5"></sb-starfield>
  <sb-gauge label="Velocity" unit=" km/s" max="8" decimals="2"
            data-attr:value="$_tm.vel"></sb-gauge>
  <sb-sparkline label="Altitude" data-attr:value="$_tm.alt" length="80" show-value></sb-sparkline>
  <sb-meter label="Fuel" warn="30" danger="15"
            data-attr:value="Math.round($_tm.fuel)"></sb-meter>
</section>

The section is a data-ignore-morph island. The page's own render stream may re-render everything around it, but it never touches these attributes, because signals own them.

3. Components react to their attributes

  • Gauge: the new value becomes a spring target, and the needle eases toward it with a small overshoot, like hardware.
  • Sparkline: in push mode every change of value appends a point. The line fills its width, then scrolls.
  • Starfield and voxel: canvases that repaint only while something changes, pause offscreen, and respect prefers-reduced-motion.
  • Meter: re-renders its segments and turns warn or danger at its thresholds (fuel counts down, so low is bad).

Pixel Board

1 watching · 27240 pixels painted

Paint together. Everyone on this page shares one 48×48 board. Pick a colour and drag.

Faded pixels are still in flight. About 20 pixels per second each, so everybody gets a turn.

How it works

This is the "one billion checkboxes" idea from Anders Murphy's hyperlith, done the way this whole site works: CQRS over one SQLite database, and the server rendering again whatever a change touched.

1. The board is one attribute

The state of all 2,304 pixels is one string on the element: cells, a hex digit (palette index) per pixel, row by row. The server renders it like any other markup. When a new frame morphs in, the attribute changes and the component repaints its canvas. The component never owns the board. It only draws what the server says, plus your own pixels in flight, drawn faded.

<!-- rendered by the board's view, one frame for every viewer -->
<sb-pixel-board id="board" size="48" cells="0000…5ff5…0d00"
  data-on:sb-paint="@post('/cmd/paint', {
    payload: {color: evt.detail.color, cells: evt.detail.cells},
    requestCancellation: 'disabled'
  })"></sb-pixel-board>

2. Painting is a command

A stroke emits sb-paint every 80 ms, with the cells it crossed; fast drags are interpolated so there are no gaps. Each batch is a short POST that answers 204 No Content and returns no HTML. requestCancellation: 'disabled' matters here: by default Datastar cancels an in-flight request when a new one goes to the same URL, which would drop pixels mid-stroke. A per-session token bucket (20 pixels per second, bursts up to 60) keeps any one painter from flooding the board.

// internal/commands/board.go: runs inside the single writer's batch.
func (c PaintPixels) Apply(tx *writer.Tx) error {
	tx.Touch(TopicBoard) // the board's view renders again once the batch commits
	for _, i := range c.Cells {
		tx.Exec(`INSERT INTO board_cells (board, idx, color, painted_at)
			VALUES (?, ?, ?, ?) ON CONFLICT (board, idx) DO UPDATE
			SET color = excluded.color, painted_at = excluded.painted_at`,
			c.Board, i, c.Color, now)
	}
	// bump the board's version, which its encoded cells are cached by
	...
}

A single writer goroutine drains the command queue. Everyone's strokes that arrive together are applied in one SQLite transaction, each in its own savepoint, and committed once.

3. One render for every viewer

After the commit, the writer tells the render loop what the batch touched, and a paint touches only the board. The board (the header above and the canvas) is a view of its own that everyone on this page shares. In its next tick (at most one every 50 ms, however many strokes came in) the loop renders it once, from a fresh read snapshot, and sends the same frame to every viewer. The encoded board is cached per version, so the cells are read once after each paint, however many people load the page. No one's page renders. The rest of this page is your tab's own view, which renders when something of yours changes, such as the flight plan below. Its frames carry the board as the board's view last sent it, so the morph leaves the board alone.

Sending the whole board after every paint sounds wasteful until you look at the wire. The stream is compressed with zstd (Brotli in browsers without it), and the compressor keeps its window across frames. The board's frame is about 3 KB, and after a paint it costs about 40 bytes on the wire (about 20 with Brotli). There is no diffing code anywhere: Datastar morphs the DOM, and compression takes care of the network.

4. Presence

"Watching" counts the open render streams on this page. The number is part of the board's view: when someone joins or leaves, the render loop renders the board again, once, and sends it to every viewer.

Commands and components

Every control here sends a command. The server validates it, stores it and re-renders the page; until the new value comes back, the control is pending (dashed). The server upper-cases call signs, and it rejects thrust above 90%: the slider goes back.

Thrust
40%
Shields
on
Call sign
STARBASE-1

Your project here

If you've shipped something with these components, open a pull request that adds it to content/showcase.md, with a sentence about what you built, a link and optionally a screenshot.

Tip: the best entries show a component doing something the docs don't: a clever data-bind, a server-driven modal, a theme of your own.