Write on the page with a pen. After a pause, the diary reads the handwriting, considers the conversation, and writes an answer back onto the paper. There is no conventional chat timeline: the page itself is the interface.
Important
This repository is a fork and continuation of Maxime Rivest's original Riddle, the reMarkable Paper Pro diary shown in the original demo. The concept and original reMarkable implementation come from upstream. This fork preserves that work while adding a Pencil-first Web/PWA client, Qwen vision integration, configurable paper behavior, and persistent multi-conversation memory.
The hosted build supports drawing, local drafts, conversation management, offline installation, and local memory. AI replies on GitHub Pages require a separately deployed, authenticated oracle proxy; provider credentials are never embedded in the static site.
On iPad, open the link in Safari and choose Share → Add to Home Screen. An Apple Pencil is recommended, but other styluses, touch, and mouse input also work.
- Pencil-first Web/PWA client. Pointer Events, pressure and tilt data, coalesced samples, palm-touch suppression, erasing, undo, and automatic submission after 2.8 seconds of rest.
- Reliable iPad interaction. The writing surface suppresses Safari text selection, callouts, and page gestures that would otherwise capture Apple Pencil input.
- A more believable paper experience. Keep or dissolve the user's ink, place answers directly on the paper or in a reply card, and switch among Script, Comic Neue, book, and typewriter faces.
- Vision-first Qwen oracle. The local proxy sends the rendered handwritten
page to
qwen3.8-maxthrough DashScope's OpenAI-compatible API. DeepSeek is retained as a text-only fallback. - Real conversation continuity. Create, switch, resume, and delete local conversations; restore earlier strokes and replies; and choose a 4, 8, 16, or 32-turn context window.
- Local-first persistence. Drafts, pages, replies, settings, and up to 400 remembered entries are stored in IndexedDB. The PWA shell remains available offline.
- Static deployment. A GitHub Actions workflow builds and publishes the PWA to GitHub Pages without committing or exposing an API key.
| Client | Status | Best for |
|---|---|---|
web/ |
Primary development target | iPad + Apple Pencil, Android tablets, and desktop browsers |
riddle/ + quill/ |
Preserved and supported | Native reMarkable Paper Pro e-ink takeover |
| Native iPad prototype | Experimental, not the current mainline | Exploring PencilKit/Vision-specific behavior |
The Web client is the most accessible MVP: it needs no App Store review, can be installed like an app, and keeps the original paper-first interaction. The reMarkable client remains the deepest hardware implementation and the origin of the project.
Requirements: Node.js 20.19+ (or 22.12+) and a modern browser.
cd web
npm install
cp .env.example .env
npm run devOpen http://127.0.0.1:5173. For iPad testing on a trusted local network, run
npm run dev:lan, open the computer's LAN address in Safari, and stop the
server when testing is finished.
The preferred oracle configuration is:
QWEN_API_URL_PAY=https://dashscope.aliyuncs.com/compatible-mode/v1
QWEN_AI_KEY_PAY=your-key
QWEN_API_MODEL=qwen3.8-maxThe Vite development server and npm run start expose a same-origin
/api/oracle route, so the key stays outside browser code. The legacy
QWEN_API_URL / QWEN_AI_KEY Token Plan variables and the
DEEPSEEK_API_URL / DEEPSEEK_API_KEY / DEEPSEEK_API_MODEL text fallback
remain supported. See web/README.md for the complete runtime
and deployment notes.
GitHub Pages can host the static PWA, but it cannot safely hold a provider key
or run the local Node proxy. To enable replies in the public demo, configure the
repository variable RIDDLE_ORACLE_PROXY_URL with a CORS-enabled HTTPS proxy
that has authentication, origin checks, and rate limits. Without that proxy,
the hosted app still works as an installable local drawing and memory app.
cd web
npm run build:pagesApple Pencil / stylus / pen
│
├── Web Pointer Events ──► Canvas page ──► local oracle proxy
│ │ │
│ │ └──► qwen3.8-max vision
│ └──► IndexedDB conversations + memory
│
└── reMarkable evdev ──► Rust ink surface ──► HTTP or resident pi oracle
│
├── qtfb window
└── quill e-ink takeover
The sections below document the original hardware-oriented client retained in this fork.
You need a reMarkable Paper Pro in developer mode with a launcher installed. If that sounds like a lot, it isn't — remagic walks you through turning on developer mode and sets up everything with one command. Come back here, drop riddle in, and start writing to Tom.
Already have xovi + AppLoad? Install from the remagic catalog, grab the prebuilt bundle, or build from source.
remagic install riddle # checksum-verified download → AppLoad
remagic config riddle # settings form in your browser (+ QR for phone)Then in AppLoad: tap Reload, then The Diary. Write, and rest your pen. (Or install it from the Store app right on the tablet.)
- Grab
riddle-<version>.zipfrom the latest release and unzip it into a folder:unzip riddle-*.zip -d riddle - Copy the folder to your tablet:
scp -O -r riddle root@10.11.99.1:/home/root/xovi/exthome/appload/ - Add an API key:
cp oracle.env.example oracle.envin that folder and put yourRIDDLE_OPENAI_KEYin it (any OpenAI-compatible key). Or skip it to use pi. - In AppLoad: tap Reload, then The Diary. Write, and rest your pen.
⚠️ This modifies your device. The prebuilt bundle and the catalog build run in takeover mode: tapping The Diary stops the whole reMarkable UI and takes the screen. Leave with a 5-finger tap — xochitl restarts automatically. It runs as root and drives the e-ink engine directly. It has only been tested on a reMarkable Paper Pro (ferrari, aarch64, OS 3.26–3.27). It may not work on other models or OS versions, and you use it entirely at your own risk. Not affiliated with reMarkable AS. Keep SSH access working before you install anything — if anything ever wedges:ssh root@10.11.99.1 'systemctl start xochitl'.
pen (raw evdev, full 4096-level pressure, hardware event rate)
│ strokes
▼
riddle ── idle 2.8s → commit page → PNG ──► oracle (resident LLM process,
│ streams reply sentence-by-sentence)
▼ strokes (Dancing Script → skeletonized to single-pixel pen paths)
display backend
├── qtfb — windowed, inside xochitl (build-from-source flavour)
└── quill — full takeover: xochitl stopped, vendor e-ink engine
driven directly for instant ink (lowest latency there
is; what the prebuilt bundle runs)
riddle/— the app (Rust). Pen input, ink surface, handwriting synthesis (rasterize → Zhang-Suen thinning → stroke tracing → animated replay), the oracle process manager, and both display backends.quill/— the takeover display host (C/C++). An epfb-re-style QImage-constructor interposition shim over the vendorlibqsgepaper.sowaveform engine, exposed as a small C ABI (quill_init/quill_buffer/quill_swap) that riddle links against with--features takeover. Also carries a small family of demos (scribble, a pen-to-glass latency test, plus map, image, and GIF renderers).
| Do this | And |
|---|---|
| Write, then rest the pen | The diary drinks your ink and Tom replies |
| Write "show me what I wrote about…" | The remembered page rises through the paper: the date, your own handwriting rewriting itself stroke by stroke, Tom's old reply — all in faded ink. Touch the pen anywhere and today's page returns |
| Write "what do you remember?" | Tom answers with a handwritten list of remembered moments |
| Flip the marker | Erase |
| Draw a large ? | Summon the built-in guide |
| Tap five fingers at once | Leave the diary (takeover mode) |
| Power button | The page turns to "The diary sleeps.", then the tablet suspends; press again to wake exactly where you were (takeover mode) |
In the windowed (qtfb) flavour, xochitl keeps the touchscreen and the power button: close the diary from AppLoad instead.
Every finished page is kept — your actual pen strokes, a transcription, and Tom's reply — so the diary can do three things:
- Follow the conversation. Recent pages ride along with each request, so Tom remembers what you wrote yesterday (both backends, same behavior).
- Conjure the past. Ask in ink — "show me the page about the garden", "find what I wrote on Tuesday" — and the diary rewrites that page in front of you, in your own hand, dated, in faded ink. No buttons, no lists, no chrome: the pen is the only interface.
- Answer from memory. "What do you remember?" gets a handwritten index.
Memories live only on the tablet, in plain files under
/home/root/riddle-data/memories (delete the folder and the diary forgets;
the last ~400 pages are kept). RIDDLE_MEMORY=off in oracle.env turns all
of it off — no storage, and nothing extra sent with requests. Set
RIDDLE_TZ_OFFSET (hours from UTC) so memory dates read right.
The diary's replies come from a vision LLM that reads your handwriting from the committed page (sent as an inline PNG). There are two backends, chosen at startup — pick whichever you have:
Set an API key and riddle talks straight to an OpenAI-compatible
/chat/completions endpoint. Works with OpenAI, OpenRouter, Groq, a local
server — anything that speaks the format. No extra software on the tablet.
export RIDDLE_OPENAI_KEY="sk-..." # required
export RIDDLE_OPENAI_BASE="https://api.openai.com/v1" # optional (default)
export RIDDLE_OPENAI_MODEL="gpt-4o-mini" # optional; must see images
export RIDDLE_OPENAI_REASONING="low" # thinking models only
export RIDDLE_OPENAI_MAX_TOKENS="2000" # runaway guardAny vision-capable model works. On the tablet these live in oracle.env
next to the binary (see oracle.env.example, or just run
remagic config riddle — it has one-tap presets for OpenAI, OpenRouter,
and Gemini). Example with OpenRouter:
export RIDDLE_OPENAI_KEY="$OPENROUTER_API_KEY"
export RIDDLE_OPENAI_BASE="https://openrouter.ai/api/v1"
export RIDDLE_OPENAI_MODEL="openai/gpt-4o-mini"Two gotchas with thinking models (Gemini 3.x, o-series): set
RIDDLE_OPENAI_REASONING=low for faster first ink (some providers reject
the field on non-thinking models — leave it unset there), and keep
RIDDLE_OPENAI_MAX_TOKENS roomy — hidden reasoning tokens count against it,
and a tight cap starves the visible reply.
Verify your setup before launching the diary:
riddle --oracle-test path/to/handwriting.png # prints the streamed replyMeasured ~0.9–1.1 s to first ink on-device. The HTTPS is built into riddle (pure-Rust, no extra libraries).
If you already run pi, riddle will use
a resident pi --mode rpc process kept warm (Node + your subscription auth
loaded once), so each turn pays only model latency. Used automatically when
RIDDLE_OPENAI_KEY is not set. Defaults (override in oracle.env):
pi at /home/root/node/bin (RIDDLE_PI_BIN_DIR), provider openai-codex
(RIDDLE_PI_PROVIDER), model gpt-5.4-mini (RIDDLE_PI_MODEL).
Both stream the reply sentence-by-sentence, so the quill starts writing seconds
before the model finishes. The persona prompt lives in riddle/src/oracle.rs.
A note on Tom's memory: with the HTTP backend every page is a fresh conversation — Tom does not remember your previous page. With pi, the warm session remembers everything since the diary was opened (and pi persists that session in its own data dir on the tablet).
If the oracle can't answer — missing key, refused key, no Wi-Fi — Tom writes
the reason on the page instead of a reply, and the full error goes to the
journal (journalctl -u riddle-takeover).
Cross-compiled from x86_64. Two flavours:
The bundles above are the takeover flavour; the windowed flavour must be built. Requires xovi + AppLoad on the device.
cd riddle
cargo build --release --target aarch64-unknown-linux-gnuInstall the binary to /home/root/xovi/exthome/appload/riddle/ with an
external.manifest.json that sets "qtfb": true and points "application"
at the binary itself (the manifest in this repo is the takeover one — AppLoad
only hands riddle a window, via QTFB_KEY, when qtfb is true).
Requires the reMarkable SDK toolchain (~/rm-sdk-3.26) because the linked
vendor Qt libs need its glibc, and libqsgepaper.so pulled from your own
device (it is proprietary and not distributed here):
cd quill && ./build.sh # pulls libqsgepaper.so from the device over
# ssh, builds libquill.so + the demos
cd ../riddle && ./build-takeover.sh
./scripts/make-bundle.sh # stages the AppLoad bundle in dist/riddle/The staged dist/riddle/ is self-contained (binary, libquill.so, launch
scripts, manifest) — copy it to
/home/root/xovi/exthome/appload/riddle/, or publish it to the catalog with
remagic publish dist/riddle. Launching via AppLoad (appload-launch.sh)
detaches into a transient systemd unit, stops xochitl, runs the diary, and
always restores xochitl on exit — leave with a 5-finger tap or SIGTERM
(systemctl stop riddle-takeover); the power button sleeps and wakes the
diary without leaving it. The unit's stop hook restarts xochitl even if
riddle dies uncleanly. If anything wedges:
ssh root@10.11.99.1 'systemctl start xochitl'.
- Each committed page is rasterized to a small grayscale PNG and sent to the oracle you configured — nothing else ever leaves the tablet, and there is no telemetry.
- The PNG (
/tmp/riddle-page.png) is deleted as soon as the oracle has read it; setRIDDLE_KEEP_PAGE=1to keep the last page around for debugging. - riddle never writes replies to disk. The pi backend, however, keeps its own session history in its data dir — the HTTP backend keeps nothing.
- Tom stays in character by design: the persona prompt (see
riddle/src/oracle.rs) tells the model it is the diary and nothing else.
The reply hand is Dancing Script
(SIL OFL 1.1 — see riddle/fonts/OFL.txt).
Riddle was created by Maxime Rivest. This fork is maintained by Mappedinfo and builds on the upstream concept, reMarkable implementation, handwriting animation, and memory design. Please retain that attribution when redistributing derived work.
The repository is licensed under MIT (see LICENSE). The vendor
libraries used by the reMarkable takeover client (libqsgepaper.so, Qt) are
not included and must come from your own device or SDK. Bundled fonts retain
their respective licenses.