01 Hello World — Technical Details

This page covers the internals of how the simplest lofigui app works: the request lifecycle, polling mechanism, and graceful shutdown.


Request lifecycle

Sequence diagram: browser polls server while model runs, server exits when model completes

Handle — single-endpoint serving

app.Handle(model) combines start and display into one endpoint. It uses the buffer as state:

Buffer Action running Behaviour
Empty No Start model goroutine, render with Refresh header
Any Yes Render current buffer with Refresh header (polling)
Non-empty No Render final output, no Refresh header (done)
Why the buffer? — After the model completes, the browser has one pending refresh from the last response's Refresh header. Without the buffer check, that stale refresh would restart the model. The non-empty buffer signals "completed" without needing a third state flag.

Auto flush and shutdown

When the model goroutine returns normally, Handle calls flush() automatically:

  1. EndAction() — stops polling (no more Refresh headers)
  2. Grace period (2s) — the browser's pending refresh arrives, gets the final page without a Refresh header
  3. signalDone() — triggers http.Server.Shutdown()

The server returns nil from app.ListenAndServe on graceful shutdown (exit code 0). Panics and bind errors return non-nil (exit code 1).

Flush is implicit in Handle, explicit elsewhere — models using HandleRoot/HandleDisplay (later examples) call EndAction() directly. The server stays alive for restart. Flush() is available as a public method for models that want to trigger shutdown explicitly.

Lazy defaults

For the simplest case, NewApp() provides sensible defaults that later examples override:

Default Value Override
Controller Built-in Bulma template (created lazily on first request) app.SetController(ctrl)
Refresh time 1 second app.SetRefreshTime(n)
Display URL /display app.SetDisplayURL(url)
Favicon Auto-registered on DefaultServeMux Register your own /favicon.ico handler first

HandleRoot / HandleDisplay

For apps that need restart support or a long-lived server, use separate endpoints:

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    app.HandleRoot(w, r, model, true)
})
http.HandleFunc("/display", func(w http.ResponseWriter, r *http.Request) {
    app.HandleDisplay(w, r)
})

HandleRoot resets the buffer, starts the model, and redirects to /display. HandleDisplay renders the current state. The model calls EndAction() when done. The server stays alive — visiting / again restarts the model.