Docs menu

Tutorial

The smallest slowapp, explained

The quickstart's Hello app, file by file: what each line does and which rule it follows.

The quickstart app is about as small as a slowapp gets: one Python file, one manifest and one Dockerfile. This page explains each part. How slowapps work has the concepts behind it.

hello.py

@kit.job("render")
def render(ctx):

A slowapp does its work in jobs: short runs that start, draw, and exit. The hub runs the job named render whenever the screen needs fresh cards. @kit.job registers the function, and kit.main() at the bottom reads the command line (job render) and calls it. ctx is the run’s context: the time to draw for, the cards wanted, your settings.

    surface = ctx.feed("hello")
    if surface is None:
        return

A feed is a named stream of cards from your app. The hub tells each run which feeds it wants and at what size, the surface. ctx.feed("hello") returns that surface (surface.w, surface.h), or None if this run doesn’t need the feed.

    img = ctx.canvas(surface)
    d = ImageDraw.Draw(img)
    d.fontmode = "1"

ctx.canvas gives a white Pillow image of exactly the surface’s size. A card must be exactly that size. fontmode = "1" turns off antialiasing: every pixel must be one of the four inks, BLACK, WHITE, YELLOW or RED.

    d.text((cx, cy), "Hello", font=kit.font("DejaVuSans-Bold.ttf", 64), fill=BLACK, anchor="mm")
    d.text((cx, cy + 60), f"{ctx.for_:%H:%M}", font=kit.font("DejaVuSans.ttf", 28), fill=RED, anchor="mm")

kit.font loads a font from the base image (DejaVu Sans, DejaVu Sans Condensed and Alfa Slab One). ctx.for_ is the time the card will be shown, not the time the job runs: the hub runs apps a little ahead of time, so always draw for ctx.for_.

    ctx.write_feed("hello", [Card("home", img, "Hello")],
                   valid_from=ctx.for_, valid_until=ctx.for_ + timedelta(hours=1))

ctx.write_feed writes the feed’s cards. The first card is the home card, the one the slot normally shows; any further cards are detail cards, one key press away. "Hello" is the card’s title, shown in the strip when the card is next. valid_until says how long the card stays true. After that, plus a grace period, the screen marks it with an “as of” note.

write_feed checks size, colours and the card rules before writing. If anything is wrong it stops the job with exit code 64, which is what you saw in the quickstart’s error example.

slowapp.toml

[app]
id = "hello"
title = "Hello"
contract = 3
image = "ghcr.io/you/slowapp-hello"
memory = "48m"
tmp = "8m"
timeout_s = 30
network = "none"
  • id names the app everywhere: in the layout (hello/hello is this app’s hello feed), in file paths and on the hub.
  • contract = 3 is the version of the rules between hub and app that this app follows.
  • memory, tmp, timeout_s and network are grants: what the app may use. This app gets 48 MB, 8 MB of scratch space, 30 seconds and no network. The hub enforces them.
[[job]]
name = "render"
expected_s = 2

[[feed]]
name = "hello"
size = "slot"

One job and one feed. size = "slot" means “whatever size the slot is”. The hub passes the real size on each run.

[[example]]
name = "default"
fixture = "fixtures/empty"
now = "2026-10-07T09:00:00-07:00"
feeds = { hello = "452x444.k4" }

An example is a test render: a fixed time, a folder of test data (the fixture) and the feeds to draw. 452x444.k4 is a surface: 452 × 444 pixels in the four-ink set k4. The checking tools render every example, and CI renders them on every push.

Manifest reference lists every key.

Dockerfile

FROM ghcr.io/mike-anderson/slowapp-python:1.2.0@sha256:5cc196d9…
COPY slowapp.toml /app/slowapp.toml
COPY hello.py /opt/app/hello.py
ENV PYTHONPATH=/opt/app:/opt/slowapp
ENTRYPOINT ["python3", "/opt/app/hello.py"]
CMD ["job", "render"]
  • The base image is pinned by version and digest, so the app always builds on the same image. Base images explains the versions.
  • The manifest must be at /app/slowapp.toml. The code can go anywhere.
  • /opt/slowapp holds the Python SDK (slowapp_kit) and the fonts.
  • The hub starts the container with the command job render.

What the hub sees

The job writes two files under /out:

/out/feeds/hello/deck.json
/out/feeds/hello/cards/home.png

deck.json lists the cards with their titles and validity. home.png is a small indexed PNG that uses only the four inks. That is the whole interface: any program that writes these files and exits 0 is a slowapp. Slowapps in any language shows one written in Go.

Updates, now and then

A short note when there is a new board, a new slowapp, or kit news. Rarely more than one a month.