I purchased an iPad in 2020 for graduate school and it was absolutely wonderful for taking notes. I could search through them due to excellent handwriting recognition, add digital content, access notes on multiple devices, and fold in lecture material and slides directly. It was a game changer. There’s an existing, sprawling ecosystem for secure, open-source text-based note taking applications. I especially like Obsidian. One missing piece is the security- and privacy-focused answer to the question of handwritten notes. That space is much smaller, unfortunately. In fact, I think it’s largely non-existent. Until now.
While I tried a few, I’ve largely stuck with the same handwriting app for most of that time, and it served me well. Over the years, though, it moved away from what I care about most: control over my own data. Note passwords silently disappeared and stopped protecting anything; password-protected or not, backups were never encrypted; and my notes were moved to the company’s cloud automatically and without any consent. I don’t consider those reasonable or acceptable choices for a commercial product, and certainly aren’t the ones I want for my notes.
With the exceptional performance of LLM coding models, I wanted to poke at the space to see if I could design and implement a better solution. Something that actually protects privacy and gives people control over their own data. And something that doesn’t suck to use. Pre-LLM, that last point would be a pretty tall order for a single developer with a day job, but we don’t live in that world anymore.
Sempere is a handwriting and audio notes app for iPad, iPhone and Mac, plus a command-line tool and a web viewer. The apps need iPadOS, iOS or macOS 27 or later. Every note is encrypted on the device with keys you hold. There is no account to make and no central server: a vault is a folder of encrypted files, and the app keeps a complete copy of it on each device, so everything works offline. If you want the vault on more than one device, you choose where it syncs.
The code is GPL-3.0-or-later at github.com/anthonytw/sempere.
What it does
- Writing. Use a stylus, mouse, or finger with the system pens, marker, pencil and eraser; lasso, text boxes, images, PDFs as page backgrounds, equations in LaTeX, audio recordings and video clips.
- Search. Titles, tags, typed text, PDF text, and handwriting, optionally recognized and transcribed on the device by Apple’s Vision framework. Matches are highlighted on the page. Recordings can be transcribed on the device and searched too.
- History. Saves are kept as a note’s history: browse it and restore any earlier version. Old autosaves can be thinned to save space; checkpoints always stay.
- Storage and sync. Each device keeps a complete copy of the vault and works offline, always. To sync, add a target: a folder in iCloud Drive, in any Files provider or on a NAS, or a WebDAV server. Sync is two-way, and a vault can have several targets. Notes are only ever added as new files, so sync tools almost never see a conflict, and Sempere merges the few that change itself.
- Getting out. Export to PDF, PNG or Markdown from the app; the CLI adds SVG, HTML and JSON.
- Getting in. Import from Notability backups, or start a fresh vault.
- No app required. The
sempereCLI runs on macOS and Linux, and the web viewer decrypts a vault in the browser. With nothing but theagetool,gunzipandjq, any note can still be read.
How it is designed
A vault is a folder of write-once files. Each note is a log of saves. Every save writes a new file and never modifies an old one, so a sync service can copy files in any order without corrupting anything. When two devices edit the same note, their logs merge: each stroke and item has its own identity, and concurrent changes are resolved by fixed rules rather than by whichever file synced last.
Encryption is age. Every file is an age file, and all keys are post-quantum hybrid (ML-KEM-768 with X25519). The age format is implemented in the project, on top of Apple’s CryptoKit and swift-crypto, and checked against the reference test vectors. Each device generates its own key, which never leaves it and is kept in the Keychain, optionally behind Face ID or Touch ID. The vault has its own key, wrapped once for each device and once for a single recovery key. The recovery key is printed as a paper kit with a QR code, and kept by no device; you can also keep a passphrase-protected copy of it in the vault. A new device joins by checking short codes against the device that created the vault.
Nothing leaves the device unencrypted. Handwriting recognition and transcription run on the device. There is no telemetry, no analytics and no crash reporting service.
The open format is documented. The on-disk format is specified in docs/format.md. Strokes are stored as vectors (PencilKit’s B-spline control points) in JSON, so they can be rendered by anything that reads the spec. The CLI’s renderer is the reference.
What it does not protect. Encryption hides what you wrote, drew, recorded and attached, but not that the vault exists, how many notes it has, or when they were saved. Someone who can unlock your device can open your notes. The security page lists the details.
How it was built
The process I used to build this was similar to my work on the KidsPlay software, but on steroids. It is a much larger project, with larger scope, and meant to be secure and reliable. This is my direct replacement for my existing note taking application, so it has to work well. I also wanted to share it with a larger audience, so it had to be polished and well-documented. I was also curious to push the limits of app development with Claude Code, and this was a great opportunity to do that!
Sempere was written with Claude Code, locally and in the cloud, mostly unattended, using Opus and Sonnet 5.5 models. In under four weeks from the first commit to the App Store, it produced about 280 merged pull requests: about 175,000 lines of Swift and TypeScript, and about 130,000 lines of tests. Yowza! Agents churned for days on my device locally, running (way too many) tests automatically on the Mac app and CLI, in simulators for iPad and iPhone, and a separate Linux box for Linux CLI validation.
The process:
- One driving session, many workers. A local Claude Code session kept the plan and the roadmap, wrote a brief for each piece of work, and handed it to a worker: a cloud session for most features, or a local agent on the Mac in its own git worktree when the work needed the simulators. Three or four workers ran in parallel, each on one branch.
- Independent review before merge. Separate cloud sessions reviewed every pull request: they merged
maininto it, checked correctness against the format spec, CLI parity, untrusted-input handling, Linux portability and tests, then fixed what they found. A script merged a pull request only when CI was green and a review was newer than the last commit. Later, related branches were merged into one batch, tested together, and landed as a single pull request. - CLI first. Every feature landed in the command-line tool before the app, so everything except the UI could be tested on Linux in CI.
- A written roadmap instead of an issue tracker. Work was planned in design documents in the repository (
docs/design/), split into phases that each became a pull request, instead of in issues. - App tests on the Mac. GitHub’s hosted Mac runners needed over an hour for the app’s test suites, so the full iPad, iPhone and Mac Catalyst suites ran on my Mac instead. The result was posted back to the pull request as a required check: nothing merged without it.
- Testing on hardware in batches. Changes were tested on an iPad, an iPhone and a Mac through TestFlight, one build per batch of merged work. Feedback from my testing went back into the roadmap.
- An audit before the first release. Multi-agent reviews of the whole codebase looked for vulnerabilities, bugs, inefficiencies and inconsistencies. The findings were fixed and re-reviewed; what only a real device could confirm became a checklist for testing.
- A separate account for Claude. Claude Code ran under its own macOS user with its own GitHub account, not mine. It could open and merge pull requests on this project, but it never ran as me.
The human part was the specification, the design decisions and the testing on real devices. I brought in my software engineering background to drive the design and development and identify the right questions, the appropriate solutions, and the right trade-offs.
Try it
- Download: App store (Mac, iPad, and iPhone, macOS/iOS 27+)
- Website: sempere.anthonywertz.com, including a live demo of the web viewer with a sample vault.
- Source: github.com/anthonytw/sempere.
Sempere is free software with no warranty. Keep a backup of your keys: if you lose every copy, no one can recover your notes.