Live Rendering

In-place terminal updates via milo.live for scripts and one-shot commands.

2 min read 420 words

milo.livere-exports Kida's terminal live-rendering primitives for use outside a full App event loop. Reach for it when you have a straight-line script, a one-shot CLI command, or a background subroutine that wants in-place updates without a reducer.

from milo.live import LiveRenderer, Spinner, stream_to_terminal, terminal_env

When to use which

Situation Use
Keyboard input, persistent state, multi-screen flow App + TickCmd
One-shot progress for a script or command LiveRenderer(context mgr)
Emit a template in chunks as it renders stream_to_terminal
Just a spinner frame tuple Spinner.BRAILLE / .DOTS

The App harness owns the render loop, message filter, view state, and cursor lifecycle.milo.livehands you raw primitives — simpler, but you write the loop.

LiveRenderer

Context manager that overwrites its previous output on eachupdate(). Falls back to log-style appends when stdout is not a TTY.

from milo.live import LiveRenderer, terminal_env

env = terminal_env()
tpl = env.from_string("{{ spinner() }} {{ label }}", name="live")

with LiveRenderer(tpl, refresh_rate=0.08) as live:
    live.start_auto(label="Working")
    do_slow_thing()
    live.update(label="Finalizing")
    finalize()

LiveRenderer auto-injects a spinnercontext variable — call it in the template ({{ spinner() }}) to emit and advance a frame on each render.

start_auto() / stop_auto()run a background refresh thread so animations keep ticking between explicitupdate()calls.

Seeexamples/liverender/app.pyfor a runnable version.

Spinner

Animated spinner with four built-in frame sets:

  • Spinner.BRAILLE — ten-frame Braille dots (also aliased DOTS)
  • Spinner.LINE — four-frame ASCII (- \ | /)
  • Spinner.ARROW— eight-frame directional arrow

Use the class attributes when you just need the frame tuple (for example, feedingTickCmd animation inside an App):

# examples/spinner/app.py
from milo.live import Spinner

SPINNER = Spinner.BRAILLE  # ('⠋', '⠙', '⠹', ...)

Instantiate Spinner(frames)only when you want a stateful, advancing spinner outside aLiveRenderer(which already provides one).

stream_to_terminal

Render a template in chunks separated by{% flush %}boundaries, with a configurable delay between chunks:

from milo.live import stream_to_terminal, terminal_env

env = terminal_env()
tpl = env.from_string(
    "Starting...\n{% flush %}Step 1 done.\n{% flush %}Step 2 done.\n",
    name="stream",
)
stream_to_terminal(tpl, delay=0.3)

Milo's built-in pipeline defs already place {% flush %}boundaries between phases, so passing a pipeline template here will stream one phase at a time.

terminal_env

Returns a pre-configured KidaEnvironmentwith terminal autoescape — the same autoescape milo uses internally. Prefer this overmilo.templates.get_env for one-off live rendering that doesn't need milo's component loader chain.