Docs menu

Build a TermSurf app

A TermSurf app is a host client that puts a real GUI in a terminal pane—patterned on ahcalc and ahhelp.

What you are building

A TermSurf app is a client of the host. It does not embed Chromium and does not invent a second window system. It asks Astrohacker TermSurf to show UI in a pane—usually by pointing a browser overlay at a loopback URL your process serves.

The host owns panes and compositing. The engine helper owns real browser tabs. Your app owns product logic and (typically) a small local HTTP UI.

Shipped examples

  • ahcalc — scientific calculator; full-pane web UI; one product binary with client and server roles.
  • ahhelp — product help cheatsheet; same client-in-pane pattern.

Process model

  ┌─ pane (inside ahterm) ─────────────────┐
  │  CLI client  ──TermSurf──►  host       │
  │       │                    engine      │
  │       └──app control UDS──► UI server  │
  │              loopback HTTP ◄── UI      │
  └────────────────────────────────────────┘
  • CLI client — runs in the pane shell context; must see TERMSURF_*; connects to the host; sends framed SetOverlay.
  • Local UI server — serves the React (or other) UI on loopback; not the TermSurf host; not a public internet server by default.
  • Host + engine — outside your app package (see Host / Engine stubs for later how-tos).

Preconditions

Run the client inside Astrohacker TermSurf so the host injects protocol environment. Outside TermSurf, a correct client fails closed (ahcalc reports missing TERMSURF_SOCKET / TERMSURF_PANE_ID).

Discovery

VariableRole
TERMSURF_SOCKETHost Unix domain socket path — connect here.
TERMSURF_PANE_IDPane id for this client — put it on SetOverlay and queries.

Full transport tables and framing bytes: Wire.

Minimal TermSurf sequence

  1. Connect a Unix domain socket to TERMSURF_SOCKET.
  2. Start (or locate) your loopback UI server and form a URL (for example http://127.0.0.1:…/).
  3. Send a framed SetOverlay with:
    • pane_id = TERMSURF_PANE_ID
    • grid geometry (col, row, width, height) for the pane
    • url = your loopback UI
    • optional profile / browser hints
  4. On resize (e.g. terminal SIGWINCH), update geometry and re-send SetOverlay.
  5. Closing the client socket clears overlays owned by that connection (host lifecycle).

Tools such as ahweb often use Hello first; ahcalc-class apps may open with SetOverlay alone. Field definitions: Messages · SetOverlay.

Framing (short)

Every TermSurf write is: 4-byte little-endian length + protobuf TermSurfMessage bytes. Do not send bare protobuf. Details and env traces: Wire.

App-private control plane

ahcalc and ahhelp also use a separate control Unix socket (for example under /tmp/…) so a second CLI invoke can talk to an already-running UI server. That protocol is yours—line-oriented JSON or similar—not TermSurf. Keep it distinct from TERMSURF_SOCKET.

Checklist

  1. Run inside Astrohacker TermSurf so TERMSURF_SOCKET and TERMSURF_PANE_ID are set
  2. Connect a Unix domain socket to TERMSURF_SOCKET
  3. Serve UI on loopback (or equivalent) and build a local URL
  4. Send a framed SetOverlay with pane_id, grid geometry, and that URL
  5. Re-send SetOverlay on pane resize when geometry changes
  6. Use a separate app-private control channel for server lifecycle if needed

Do and don’t

  • Do prefer one product binary with client/server roles when practical (ahcalc pattern).
  • Do fail closed when TermSurf env is missing.
  • Don’t invent a host-side port registry for your app.
  • Don’t treat TermSurf as a public TCP API.
  • Don’t embed a browser engine in the app to “skip” the host—the product engine path is the host helper (Chromium as shipped).

Where to look in-tree

  • bun/ahcalc/app/cli/termsurf-client.ts — connect, SetOverlay send, resize
  • bun/ahcalc/app/cli/termsurf-encode.ts — SetOverlay body + frame
  • Wire · Messages

Next

Engine helper and terminal host how-tos are still short stubs: Engine, Host. Protocol overview: TermSurf.