Robert StraubmüllerCo-founder, LevelworkKonstanzProjectsAboutEmailLinkedInGitHub

Case · 2026

Stub

Receipts go on the scanner glass and come back as searchable PDFs, named and listed for the books. My own tool, built with Claude Code.

Scan

Role
Problem, workflow, interaction design, decisions, testing on my own receipts
Built with
Claude Code as pair programmer
Client
Own project
Status
Open source (AGPL-3.0)
Proof
Code on GitHub ↗

The itch

Every receipt had to be scanned, cropped, named and typed into a list. I wanted to lay it down and be done.

As a freelancer I collect receipts for fuel, parking, post and business meals. Each one meant a scan, a crop, a file name with date, shop and amount, and a line in a list. A business meal also needs the occasion and the guests, written by hand on the receipt. Stub does all of that from one press of the space bar.

What it does

Scan, split, read, name, list. I only check and press space for the next one.

The app talks to the network scanner directly over AirScan, with no driver or vendor software. It finds the receipts on the glass, several at once if I lay them side by side, and crops each one. macOS reads the text for a searchable PDF; Claude reads the image and fills in date, shop, kind, amount and the handwritten note. The PDF is renamed to something like 2026-10-14_Gasthaus-Seeblick_Bewirtung_48,60EUR.pdf, and a list per day collects every receipt as a row that opens in Excel. Pressing space confirms the receipt on screen and starts the next scan, so a stack of receipts is one rhythm: lay down, space, check.

Stub after a scan of three receipts: a café receipt with handwritten guests and occasion on the left; on the right the fields date, amount, merchant, kind and the note “Projektplanung Q4; Anna Berger, Jonas Weil”, the new file name, and the three receipts of the session with their total
One scan, three receipts. The handwritten occasion and guests are in the note field. Test receipts, German UI.
Crop dialog over the whole scan: three receipts on the grey glass, each in its own frame labelled Beleg 1 to 3, with buttons for the whole scan and automatic detection
The frames the app found. Each frame becomes its own file; I can drag, add or delete frames.

Decisions

Four choices, each with the option I did not take.

Claude reads the picture, not just the text.

Text recognition alone misses the handwriting on a business-meal receipt, and that note is what the tax office asks for. So Claude gets the image and the recognised text as a hint. Text recognition still runs: it makes the PDF searchable, and if Claude is unavailable, simple pattern matching for date, total and shop takes over.

Several receipts on the glass, split without a model.

A trained detector would have been one more thing to host and to trust. The app looks for empty paper instead: columns first, where 10 mm are blank, then rows, where 30 mm are. The mask keeps faded thermal print and ballpoint, ignores the shadow at the glass edge and drops dust. When it is wrong, I drag the boxes myself.

What I type wins.

Reading takes a while, so it runs in the background while I scan the next receipt. If I correct a field before the result arrives, the late result does not overwrite it, and a receipt cropped again or given a second page is read again from scratch. Deleted receipts go to the Trash, not into nothing.

How the glass is split: four receipts and a speck of dust, columns first, then rows, then a box with margin for each.

The fourth: Claude runs with a fence. It is called through the Claude Code command line with only the permission to read the image files, nothing to write, run or fetch, and the prompt says that whatever the receipt says is data, not an instruction. Scans and PDFs stay on the Mac; only the receipt image goes to Claude for reading. The local service quits itself fifteen minutes after the last browser window closes.

How I tested it

41 automated tests on every push, my own receipts by eye, and three invented ones through the real app.

The tests run on GitHub Actions with invented receipts only: receipt detection on synthetic scans, the scan protocol against a simulated scanner, file names, lists and the session. They do not check how well Claude reads.

For this page I made three test receipts, a petrol station, a restaurant with a handwritten business-meal note and a post office, and sent them through the unchanged app with a simulated scanner on 27 October 2026. Three runs, every receipt cut out correctly and every field right, including the handwriting and “Postfiliale” filed as Deutsche Post, postage. The screens above show the demo receipts that come with Stub, a café, a petrol station and an office supplier; one run with them, also on 27 October, read every field right as well. Three receipts are not a test set; see below.

15 / 15
fields right on three test receipts, in each of three runs
8 s
for Claude to read three receipts side by side; text recognition takes under a second each
4
of my own receipts cut out of one real scan on 27 October

From pressing Scannen to three named files took 12.5 and 13.7 seconds, four seconds of that for the simulated scan. The first run of the session was much slower, at 82 seconds.

Built with Claude Code

My part was the problem, the workflow, every decision above and testing on my own receipts.

The code was written with Claude Code as pair programmer. It started as one Python file and one page for the interface, opened from a small Mac app. For the public version it was split into modules, with the tests and a demo that runs without a scanner. Like Saunter, it started as an annoyance and became a tool I use.

  1. First version in use
  2. Several receipts per scan
  3. Open source on GitHub, with tests

What I'd change

I check every receipt by eye, but I have not measured how often Claude gets a field wrong. A small test set of real receipts with the right answers, run after every prompt change, would turn “seems right” into a number. And calling the command line once per receipt is simple but slow to start; a direct API call with a fixed output schema would be faster and easier to test.