A controller for the
Vestaboard.

BestaBoard is a personal project — a Go backend and React frontend that drives a physical flip-dot display with rotating, data-driven content.

See the virtual board ↓
Virtual Board — Example Modes
What's Supported
Display Modes

Clock

Renders the current time and date in the Vestaboard's native character set, with per-user timezone configuration.

Weather

Pulls today's forecast from Open-Meteo for a configured lat/lng and renders high/low temperatures, conditions, and precipitation chance.

Notes

User-submitted messages up to 135 characters with a configurable display duration. Notes expire and are dismissed automatically.

Static Text

A fixed text slot in the rotation — useful for persistent announcements or any content that doesn't change between cycles.

More Modes Coming

Planned additions include calendar events, sports scores, transit alerts, and a webhook endpoint for pushing arbitrary content from external services.

Controls & Integrations

Playback Controls

Pause, resume, or skip the rotation cycle. Force-pin any mode to hold it indefinitely, then release to hand control back to the scheduler.

Preferences

Rotation interval, default note duration, weather location, temperature units, and timezone are all runtime-configurable without redeploying.

REST API

All state mutations are exposed as Bearer-authenticated HTTP endpoints via a Chi router — cleanly separated from UI concerns and independently scriptable.

Server-Sent Events

A central hub fans out scheduler state changes to all connected clients over SSE — no polling, no websocket overhead.

User Accounts

Session-based authentication with hashed credentials. Multiple users can be provisioned, each with their own bearer token for API access.

How It Works
1

Go backend with a Chi router

The server is written in Go and uses Chi for routing. Handlers are thin — they validate input and delegate to an internal scheduler and store, keeping HTTP concerns separate from business logic.

2

Pluggable display modes

Each widget (clock, weather, notes, static) implements a common Mode interface. The scheduler holds a prioritized list and calls Render() on the active mode each tick, producing a 22×6 character matrix that gets pushed to the Vestaboard Subscription API.

3

React frontend with TanStack

The UI is built with TanStack Start (SSR), TanStack Router for file-based routing, and TanStack Query for server state. Tailwind CSS 4 and shadcn/ui handle styling.

4

SSE hub for real-time state

A central event hub fans out scheduler state changes to all connected clients over Server-Sent Events. The frontend subscribes once on mount — no polling, no websocket overhead.

5

Deployed on Fly.io with Neon Postgres

The app runs in a Docker container on Fly.io with a GitHub Actions workflow that deploys on every push to main. Persistent state (notes, preferences, users) lives in a Neon serverless Postgres database.

About

BestaBoard is a personal controller for a Vestaboard — a split-flap display updated via a character-matrix API (layout depends on model). It rotates modes on a schedule and runs self-hosted instead of using Vestaboard’s first-party app for orchestration.

Backend: Go. A scheduler calls Render() on each pluggable mode; modes share one interface. Output goes to the Vestaboard API. Weather uses Open-Meteo.

Frontend: React with TanStack Start, Router, and Query. Board state streams over SSE from the Go server. Sessions and hashed credentials live in Neon Postgres.

Infra: Docker on Fly.io. GitHub Actions deploys production and demo on every push to main.