@habemus-papadum/aiui-paint
v0.3.0
Published
iPad-to-browser remote paint stream: wire protocol, a host-neutral coordinator backend (channel sidecar or your own server), browser host controller, and the iPad client.
Downloads
301
Readme
@habemus-papadum/aiui-paint
View and draw on a running browser app from an iPad (or any second device) over the LAN. The iPad shows a live view of a desktop browser and streams pen strokes + navigation back; the desktop applies them to its own ink layer — including, over the aiui intent overlay, straight into the intent tool.
This package is the coordination layer: the wire protocol, a host-neutral backend
(createPaintBackend — mounted by the aiui channel as a sidecar, or
by any server you own), the desktop host controller, and a self-contained iPad client page
(served by whichever host mounts the backend). It depends on
@habemus-papadum/aiui-ink for the standalone host path.
⚠️ Security: the paint surface is unauthenticated — for a personal, trusted network only. In an aiui session it rides the channel's one port; who can reach it is the channel's
channel.bindchoice (loopback= this machine only, tunnel for the iPad;host= your whole LAN, everything on the port). See the repo's Read before running.
In an aiui session
The sidecar is hosted by default; the iPad needs the channel bound to the host interface (or a tunnel you own):
aiui claude # first interactive launch asks where the channel binds ("host" for the iPad)
aiui paint url # print the URL to open on the iPadPages served with the dev overlay auto-join as paintable hosts; ink drawn on the iPad lands in the intent tool's turn.
Fastest way to try it: the standalone demo
pnpm paint:demo # from the repo — a bespoke Express backend + a demo app togetherDraw on the demo's scrollable canvas with the mouse, then open the printed URL on an iPad to draw,
scroll, and pinch-zoom remotely. Click Share screen to send video (the real getDisplayMedia
path — the iPad shows "waiting" until you do, since screen capture needs a user gesture). Switch
JPEG ⇄ WebRTC with the video: button. Source: packages/aiui-paint/demo/ — a compact example of
wiring InkSurface + startPaintHost, the capture-gesture handshake, and both transports.
Host the backend yourself
import { createServer } from "node:http";
import express from "express";
import { createPaintBackend } from "@habemus-papadum/aiui-paint/server";
const backend = createPaintBackend();
const app = express();
app.use((req, res, next) => {
if (!backend.handleHttp(req, res)) next();
});
const server = createServer(app);
server.on("upgrade", (req, socket, head) => {
if (!backend.handleUpgrade(req, socket, head)) socket.destroy();
});
server.listen(8788, "0.0.0.0"); // the LAN bind is your posture decisionMake a browser a host
Over the intent overlay (remote ink joins the intent turn):
import { startPaintHost } from "@habemus-papadum/aiui-paint";
const sink = window.__AIUI__?.remotePaint;
if (sink) {
startPaintHost({ relayUrl: "http://your-mac.local:8788", ink: sink, label: document.title });
}Standalone (a plain ink surface, no overlay):
import { InkSurface, inkSurfaceSink, startPaintHost } from "@habemus-papadum/aiui-paint";
const surface = new InkSurface({ fadeSec: () => 0 });
const host = startPaintHost({ relayUrl: "http://your-mac.local:8788", ink: inkSurfaceSink(surface) });
// Screen capture (getDisplayMedia) needs a user gesture — call from a click, not on connect.
shareButton.addEventListener("click", () => host.requestCapture());Video is WebRTC by default (smooth, low-latency, per-viewer peer connections), with JPEG
frames as the automatic backup — and video: "jpeg" to opt out of WebRTC entirely (control and
ink are identical either way). Until capture
is armed, the host reports videoStatus: needsGesture and the iPad shows "waiting to share" rather
than black. A host that renders its own content can pass a canvas.captureStream()-backed
frameSource and skip the gesture entirely.
Entry points
@habemus-papadum/aiui-paint— browser-safe: the protocol,startPaintHost,InkSurface.@habemus-papadum/aiui-paint/server— the host-neutral Node backend (createPaintBackend).@habemus-papadum/aiui-paint/sidecar— that backend packaged as an aiui channel sidecar.
Full walkthrough, gesture map, and architecture: the iPad Paint Stream guide and the implementation plan.
