Docs menu

Tutorial

Quickstart

Write a slowapp that draws one card, check it, and see it as it would look on the screen. About fifteen minutes, no Slowboard needed.

You will write a slowapp of three small files, build it, and render it the way a hub would. At the end you have a PNG of your card and a preview of the whole screen with your card in it.

A white card with "Hello" in black and "09:00" in red.

The card you will make.

What you need

  • Docker, or Podman.
  • Python 3.
  • A terminal. Everything below runs on Linux and macOS.

You don’t need a Slowboard or a hub. The tools check your app the same way a hub does.

1. Write the app

Make a folder called slowapp-hello with these files.

hello.py draws the card:

from datetime import timedelta

from PIL import ImageDraw

import slowapp_kit as kit
from slowapp_kit import BLACK, RED, Card


@kit.job("render")
def render(ctx):
    surface = ctx.feed("hello")
    if surface is None:          # the hub didn't ask for this feed on this run
        return
    img = ctx.canvas(surface)    # a white image exactly the size of the card
    d = ImageDraw.Draw(img)
    d.fontmode = "1"             # no antialiasing: every pixel must be one of the four inks
    cx, cy = surface.w // 2, surface.h // 2
    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")
    ctx.write_feed("hello", [Card("home", img, "Hello")],
                   valid_from=ctx.for_, valid_until=ctx.for_ + timedelta(hours=1))


kit.main()

slowapp.toml is the manifest. It names the app, says what it may use, and lists one test render (an example):

[app]
id = "hello"
title = "Hello"
contract = 3
image = "ghcr.io/you/slowapp-hello"
memory = "48m"
tmp = "8m"
timeout_s = 30
network = "none"

[[job]]
name = "render"
expected_s = 2

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

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

Dockerfile packages the app on the Python base image:

FROM ghcr.io/mike-anderson/slowapp-python:1.2.0@sha256:5cc196d93d9960643d51eac7a49f46fe4eb9a1c4f68deba0f60579ea9559a532
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"]

Finally, the example needs a folder of test data. This app reads no data, so the folder is empty:

mkdir -p fixtures/empty && touch fixtures/empty/.keep

2. Build it

docker build -t slowapp-hello:dev .

3. Get the checking tools

The tools that check an app, the SDK, ship inside the base image. Copy them out once, and give them a Python with Pillow:

docker create --name sdk ghcr.io/mike-anderson/slowapp-python:1 && docker cp sdk:/opt/slowapp /tmp/slowapp && docker rm sdk
python3 -m venv ~/.slowapp-venv && ~/.slowapp-venv/bin/pip install pillow

4. Check the manifest

PYTHONPATH=/tmp/slowapp ~/.slowapp-venv/bin/python -m slowapp_sdk validate
ok      slowapp-hello

5. Render it

PYTHONPATH=/tmp/slowapp ~/.slowapp-venv/bin/python -m slowapp_sdk fixture --image slowapp-hello:dev --repeat
ok    default             0.2 s  hello: 1 cards
        hello/home: aa190c154af92fa00463fd585e9200394df40f1bcdc36c8a418a270f00a1dbb9
1/1 examples passed; previews in out/previews

This ran your container the way a hub would, with no network, at the example’s fixed time (09:00), and checked everything it wrote. --repeat ran it twice and compared the results: the same input must always give the same picture. The long number is the card’s id, a hash of its pixels.

6. Look at it

Open out/previews/:

  • hello-default-hello-home.png is your card.
  • hello-default-frame.png is the whole screen, with your card in the first slot of the Slowboard 1085’s default layout.

The full 1360 × 480 screen, mostly white, with the Hello card on the left.

When it goes wrong

Delete the d.fontmode = "1" line, rebuild, and render again:

FAIL  default          exit 64 (contract error)
hello/render: exit 64: hello/home: 300 colours are not inks, e.g. [(1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4)]

Antialiased text has grey edges, and the screen has only four inks: black, white, yellow and red. The library refused to write the card, so the mistake shows up on your computer instead of on a screen.

A typo in the manifest is caught by validate:

error   slowapp-hello: $.app: unknown key 'netwrok'
FAIL    slowapp-hello

Next

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.