website logo
← Back to Projects

ScanSplit

Rust Tauri 2 TypeScript React SQLite Claude API Tailwind CSS

The Problem

Splitting a group dinner bill always ended the same way — someone squinting at a crumpled receipt, typing line items into a notes app, and guessing at how to divide the tax and tip. Existing bill-splitting apps wanted an account, uploaded my receipts to their servers, and still made me enter every item by hand.

The Solution

Built a local-first desktop app that turns receipt photos into a fair per-person split. Claude reads the receipt and extracts the line items, you toggle who had what, and ScanSplit works out what everyone owes — all in integer cents, so the shares always sum exactly to the bill. No accounts and no server — the only outbound request is the OCR call, and everything else lives in a SQLite database on your own machine.

Reading the Receipt

Claude reads the receipt photo and returns structured line items, which land in an editable table. Each row keeps the raw text it came from underneath the parsed name — so ITEM 4823 sitting under “Caesar Salad” makes it obvious where a guess came from and what to fix. The kind dropdown marks a row as an item, tax, tip, or discount, and discounts are stored as negative amounts.

ScanSplit extracted line items

Assigning Items

Every item starts shared by everyone; clicking a name toggles that person off. Here Sam and Priya skipped the wine and the dessert, so those rows drop to 2/4 and the running totals split apart immediately. The math lives in TypeScript rather than Rust specifically so this stays instant — no IPC round-trip per click.

ScanSplit per-item assignment

What Everyone Owes

The final step names whoever fronted the bill and shows what each person owes them, with a checkbox to tick off repayments as they come in. Click any name to see the calculation broken out.

Items divide evenly among the people assigned to them, with leftover cents handed to whoever is currently furthest from balance, so rounding errors don’t accumulate on one person across a long receipt. Tax and discounts are then allocated proportionally to each person’s item subtotal using largest-remainder rounding, while tip splits evenly across everyone. Because every figure is integer cents, the individual shares always add up to the receipt total exactly.

ScanSplit per-person totals

Under the Hood

The Rust/frontend boundary is deliberate. Rust owns the Anthropic API key, the SQLite pool, and the network call; it copies each receipt into the app’s data directory, downscales anything over Anthropic’s image cap, and detects the media type from the file’s magic bytes rather than trusting the extension. Database writes happen once, at Save, as a single SQL transaction rather than on every keystroke.

Receipts also love cryptic SKUs. Correct one and ScanSplit stores the mapping alongside a merchant hint, then fills the name in automatically on future scans, with store-specific entries winning over generic ones. The API key itself never touches disk — it lives in the OS keychain via Apple Keychain, Windows Credential Manager, or Secret Service.