Tax Reserve 0 CHF

Know what's yours.

Kanton Zürich  ·  100 % on-device  ·  iOS

A real-time tax reserve, Swiss QR-bill invoicing and receipt scanning for sole proprietors — designed, built and shipped solo.

Download on the App Store Download on the App Store Scroll
RoleProduct Designer & Developer — design, code, brand, ship
TimelineFeb – Jul 2026, concept to App Store submission
PlatformiOS · React Native + Expo. Site: React + Vite on Vercel
StatusLive on the App Store — Canton Zürich

01 — The problem

One income. Five tax authorities. Zero visibility.

Every self-employed person in Canton Zürich pays into five separate systems — each with its own rates, brackets and municipal multipliers. Most freelancers guess how much to set aside, and guess wrong.

Invoicing, expense tracking and tax estimation each live in a different tool — or a spreadsheet, or a Treuhänder. Nothing combines all three affordably, and nothing does it privately.

AHV / IV / EO sliding scale Cantonal tax progressive Municipal tax Steuerfuss 90–124% Federal tax progressive Church tax optional

02 — The solution

Four tools, one app. All on your phone.

The Solaris orb is the heart of it: a live tax reserve that recalculates with every invoice and receipt, across 25+ Zürich municipalities. It glows teal when you're fully covered — tax awareness becomes passive.

TaxMeter dashboard, dark mode, showing a CHF 720 tax reserve in the Solaris orb
TOOL·01

Tax Meter

Real-time reserve calculator covering AHV/IV/EO (incl. sliding scale), cantonal, municipal, federal and church tax for 25+ municipalities.

TOOL·02

Bill Buster

Scan your provisional Steuerrechnung, compare it against actual year-to-date income, and auto-generate a formal Einsprache in German if you've been overcharged.

TOOL·03

QR Invoicing

Swiss QR-bills to the SIX SPS v2.3 standard in one tap. Mark an invoice paid and the revenue flows straight into the tax calculation.

TOOL·04

Expense Tracking

Photograph a receipt; on-device OCR extracts merchant, amount and date. Deductions update the reserve instantly.

03 — Design identity

Deliberately anti-tax-software.

Near-black canvas, one hero accent, and a strict typographic rule: when you see monospaced text, it's money.

The orb doubles as data display and emotional indicator. Colour went through three iterations — the shift from navy to near-black came from seeing it on a real device, not from a prompt. The app ships in both light and dark mode.

TaxMeter dashboard in light mode TaxMeter expenses screen in dark mode with category filters and receipt scan button
#0C0F1ABackground
#4ECDC4Teal · safe / primary
#CD5B5BCoral · expenses
#F5A623Amber · warnings
#9B7FB8Mauve · low state
#F0F0F5Primary text
General Sans
Display & interface
IBM Plex Mono — 10'500.00
Every financial number

04 — Architecture

Zero backend. That's the product.

No servers, no cloud database, no accounts, no analytics SDKs. Everything runs on-device with SQLite — so the App Store privacy label reads “Data Not Collected.” Almost no finance app can claim that.

The tax engine is pure TypeScript with zero dependencies, validated by 29 unit tests. Privacy wasn't a compromise; it's the positioning.

React Native + Expo SDK 54 TypeScript strict SQLite expo-sqlite ML Kit on-device OCR swissqrbill Zustand Face ID local auth
TaxMeter security onboarding screen: your data stays on your phone, no cloud, no tracking

05 — The hardest bug

A QR-bill vs. a real bank.

The QR-bill passed every internal test — then Raiffeisen's validator rejected it against three spec violations. Fixing it meant restructuring the address model across settings, onboarding and invoice creation, plus a pre-flight gate that blocks a broken bill before it ever reaches a client.

This is the detail that proves the app was built against a real standard, not a mock.

TaxMeter new invoice form with structured street, building number, postal code and town fields
swiss-qr-validator · SIX SPS v2.3raiffeisen.ch
FAIL

Creditor address incomplete — combined address lines rejected; structured street / building / postal / town required.

FAIL

Debtor address incomplete — same structural requirement on the payer side.

FAIL

Invalid reference type — SCOR with a non-RF string on a normal IBAN.

FIXED

Structured address model across settings, onboarding and invoicing · QRR for QR-IBANs, NON for standard IBANs · pre-flight validation gate before export.

06 — Three bugs worth writing down

The seams, not the API.

The interesting problems in a local-first app aren't API design — they're the seams between navigation, persistence and rendering. These were the three that fought back.

RENDERING

The white layer over black

React Navigation v7 moved scene background styling to sceneStyle, and the default isn't transparent — so a correctly rendered dark background sat under an opaque white layer the navigator injected on every screen. The fix was one line per screen, sceneStyle: { backgroundColor: 'transparent' }, but finding it took real diagnosis: the failure reads exactly like a broken theme, not a navigator default.

PERSISTENCE

A blank first paint

Zustand's persist middleware rehydrates from AsyncStorage asynchronously, but components render immediately against empty initial state — so first paint showed a blank app before data popped in, and a write racing the rehydrate could clobber stored data. An explicit hasHydrated flag, set via onRehydrateStorage, gates the UI on hydration completing instead.

LIFECYCLE

Stale data after a scan

The expense tab loaded data in useEffect, which fires on mount — not on focus. Tab screens stay mounted, so returning after scanning a receipt showed stale data until useFocusEffect tied the refresh to navigation focus instead. The class of bug matters more than the fix: local-first apps don't get cache invalidation for free from refetching a server.

07 — Process

AI-assisted. Human-directed.

Claude for strategy, tax-law research and architecture. Pencil.dev for twelve wireframed screens. Claude Code for implementation. Expo and Xcode for on-device testing.

Scope was deliberately cut from four cantons to one — Zürich with verified, accurate data beats four done with guesswork. AI moved implementation at high speed, but every product decision, design judgement and quality bar came from a human reviewing it on a real screen.

TaxMeter onboarding welcome screen with business profile setup

08 — Shipping is its own discipline

Two citations, one cycle.

App Review returned two citations on the first submission — both closed in a single cycle. The fix exposed something larger: a screenshot pipeline that could hide bugs no reviewer would ever be positioned to catch.

The screenshot set was hand-composited in Affinity binaries. Auditing it for the citation surfaced two latent bugs shipping in the pixels — HTML entities leaking as literal text (— and · rendering as “mdash;” and “middot;”), and English and German captions concatenated into a single string. Rather than patch binaries that can't be diffed or reviewed, the set was rebuilt as a data-driven pipeline — typed per-locale caption objects, an HTML template, a headless-Chromium renderer. The bug class is now structurally impossible.

App Review · first submissionresolved in one cycle
5.1.1(iv)

Pre-permission screen read as consent — a “Grant Permission” button, which Apple treats as directing the user to consent. Relabelled to “Continue.”

2.3.7

“Free” appeared in a screenshot — Apple counts references to free service as price references in metadata. Removed.

Outcome

Shipped.

0
passing tax-engine tests, zero TypeScript errors
0
Zürich municipalities with verified Steuerfüsse
0
languages — bilingual App Store listing & site (DE + EN)
0
on-device — “Data Not Collected” privacy label

Live on the App Store · marketing site live at taxmeter.ch · full custom screenshot set in both languages.

Reflection

“AI writes the code. Humans make the decisions.

This project tested whether a designer could ship a production-quality native app, a marketing site and a compliant Swiss financial tool solo, using AI tools — all the way to the App Store. The answer was yes, with a caveat: the value wasn't the code generation. It was the product thinking, the design judgement, and the willingness to test against reality — a real bank, a real device, a real tax table.