Back to lab

Inkreply

A notebook for Apple Pencil where nothing turns into typed text. Every note has a ruled Notebook and an infinite Board, and an agent reads what I wrote and answers on the page itself, in its own handwriting.

product in progress
Inkreply

I study by writing by hand on the iPad: algorithm exercises, architecture sketches, math. Inkreply is the notebook I made for that. Every note has two pages, a ruled Notebook and an infinite Board, and an agent reads what is written and answers right there, in ink, in a handwriting of its own.

Why I built it

Every AI notes app I tried does the same thing: it turns the writing into text and talks in a chat beside it. The page becomes a form. I wanted the opposite. My handwriting stays the way I wrote it, scrawl and crooked arrow included, and the answer lands on the page as if someone had picked up the pen. Ink stays ink, the agent’s too.

How it works

  • Portable ink. PencilKit’s drawing data only opens on Apple platforms, so every page is also saved in a versioned JSON format of my own: each stroke with its tool, color, transform and control points with time, pressure, azimuth and altitude. The iPad, the Preact web app and the server read the same file, and a test decodes a fixed sample so the format cannot drift by accident.
  • The agent’s handwriting. The agent writes with the letters of the Hershey script font, a single line font, and a simulated hand on top: a slow tremor, pressure that rises as the pen lands and fades as it lifts, a little slant and size jitter per letter, a baseline that drifts. Lowercase letters join into one stroke per word, and the i dots and t bars come after, like a real hand. On the iPad it arrives as ordinary PencilKit strokes: I can lasso, move, erase and undo them, just like my own ink.
  • Hand drawn diagrams. Boxes, ellipses, diamonds and arrows are editable objects in the Excalidraw manner. The web draws them with rough.js, and on the iPad I ported rough.js’s generator to Swift, with the same pseudorandom generator and every random number drawn in the same order. A test compares the Swift paths with the JavaScript ones down to 1e-9, so a box has the same wobble on every device.
  • Sync on Cloudflare. A Worker with D1 and R2 keeps the library, pages, text blocks and elements. Each device sends its changes through a queue that survives going offline and asks what is new; the last writer wins, per item.

What I am building now

Today the devices ask every five seconds. I am replacing that with a Durable Object that holds every client’s WebSocket and, after each write, sends only a nudge with the version number. The frame carries no data: the receiver fetches the changes the same way as always, so the WebSocket never becomes a second source of truth, and with the hibernation API the object does not even stay awake between writes.

The app runs on the iPad, in the browser and on the Linux desktop, and native apps for Android (Kotlin, Compose and Jetpack Ink) and Windows (C#, WinUI 3 and Win2D) are in progress. It is in early access: the waitlist is on the site.