Getting started

Overview

What Perch Display is, and the one rule that shapes every widget.

Perch Display turns a spare iPhone on a stand into a programmable, glanceable display for your Mac. You author a widget once — a single self-contained HTML document — and your Mac feeds it live data. The phone renders it fullscreen and updates the instant the data changes.

The pieces

  • The Mac hub (the Perch app) runs a small local HTTPS/WebSocket server on your network. It stores your widgets, serves their HTML, and pushes their data.
  • The iPhone display (the Perch iOS app) pairs with the hub over your LAN and shows the active widget fullscreen.
  • perch-mcp is an MCP server bundled with the Mac app. It lets Claude Code (or any MCP client) publish widgets and push data in plain language.

The one rule: no code runs on the phone

This is the decision (D7 in the spec) that shapes everything: a widget is a pure function of injected data. It does no network, holds no token, and never re-runs logic on the phone.

The native iPhone app holds the authenticated session and does all network. It fetches the widget HTML, polls the data endpoint, and injects the result into the running page one-way — HTML via loadHTMLString, data as a structured JavaScript argument. A strict Content-Security-Policy (connect-src 'none') is injected before your code loads, so fetch, XMLHttpRequest, and WebSockets are simply unavailable inside a widget.

<!-- The entire data contract, from the widget's side: -->
<script>
  window.PERCH.onData((d) => render(d)); // d is your JSON, redelivered on every update
  window.PERCH.ready();                  // optional: signal "UI is hydrated"
</script>

Why it matters:

  • The session token never enters agent-authored widget JS — it stays in native code.
  • Cross-widget data isolation is automatic.
  • Polling/streaming is written once in PerchKit; widgets never think about it.

How data flows

  1. You (or Claude Code) publish a widget’s HTML to the hub.
  2. The hub serves that HTML at GET /widget/:id and holds a JSON feed for it.
  3. The phone fetches the HTML, then polls GET /api/data/:id every refreshMs (default 1500 ms) over the trusted native channel.
  4. On each poll the native app calls window.PERCH.__deliver(payload), which invokes your onData callback with the fresh JSON.

You write step 1 and the onData renderer. Perch Display does the rest.

Next: build one end-to-end in the Quickstart.