github.com/JadeKharats/motivators
0.1.2 / published Aug 2, 2026 / repository
Ephemeral, real-time Moving Motivators sessions — Crystal + Kemal, in-memory, ships as an 11 MB scratch image. Part of the Happy tutorial suite.
Moving Motivators
Ephemeral, real-time Moving Motivators
sessions for a team — built with Crystal and Kemal. In-memory only, no
database, ships as a ~14 MB container built FROM scratch and running as
an unprivileged user.
A facilitator opens a session and gets a short join code. Participants join with that code and privately rank ten motivator cards from most to least important to them. When everyone is ready, the facilitator triggers a reveal and all rankings appear side by side for discussion.
Part of the Happy tutorial suite — small self-hosted team-ritual tools, each built with a framework matched to its problem size. A full build-along write-up lives in
TUTORIAL.md.
Key properties
- Ephemeral. State lives in memory and nowhere else. Sessions expire after a few hours and are swept. Nothing to back up, nothing to leak from disk.
- Private until reveal. A ranking stays hidden from the other participants until the facilitator reveals — enforced on the server, not just in the UI. The payload sent to a participant never contains another's ranking before the reveal.
- Real-time over WebSocket: joins, ready state, and the reveal itself.
Quick start
Try it now on the live instance, or run it yourself with Docker or Podman Compose:
docker compose up # or: podman-compose up
Then open http://localhost:3000, click Start a session in one tab, and Join with the code from another tab.
Or run the published image directly:
docker run -p 3000:3000 ghcr.io/jadekharats/motivators:latest
Local development
shards install
crystal spec # run the spec suite
crystal run src/motivators.cr # serve on :3000
Quality tooling used on the domain:
crystal spec— 33 examples, domain and state machine plus an end-to-end WebSocket test that proves confidentiality on the wireameba— lint, cleancrytic— 100% mutation score on the domain
How it's built
The domain — session, participant, ranking, and the two-phase state machine — is pure Crystal with zero framework dependency, so it's tested without booting an HTTP server. Kemal is only the adapter that turns HTTP and WebSocket traffic into calls on that domain.
One deliberate limit: state lives in a single process's memory, so this runs
as one replica only — a second container would have its own separate
sessions. That's the right trade for a tool a team spins up for a half-hour
retro; scaling out behind a shared pub/sub is a possible extension, not the
default. See TUTORIAL.md for the full reasoning.
License
MIT © 2026 David D. YOTEAU
API
- Hub
Keeps the live WebSocket connections for each session and pushes updates.
- Motivator
The ten Moving Motivators cards, in their canonical presentation order.
- Participant
One person in a session.
- ParticipantView
What a participant is allowed to see of a session at a given moment.
- Payload
Turns a SessionView into the exact bytes sent to a client.
- Ranking
A participant's complete ordering of the ten cards, most important first.
- Session
An ephemeral ranking session.
- SessionStore
In-memory registry of live sessions.
- SessionView