KNKuba Nowacki
← Work

Murvo

Job costing for small construction crews, built for one-handed use on site. Receipts are read with Claude OCR, and the API is DDD and CQRS with 178 API tests.

Status
In development
Platforms
iOS · Android
Started
2026
Stack
Expo, NestJS, Claude OCR, DDD/CQRS

Murvo is job costing for small construction crews. You see what each job costs while it is running, from a phone, with one hand. It is in development, with a waitlist.

A domain-driven API

The backend is NestJS with domain-driven design and CQRS. Writes go through commands that enforce the rules of a job, and reads come from separate queries shaped for the screens. It has 178 API tests, so I can change the domain without guessing what broke.

Receipts read by Claude

Take a photo of a receipt and Claude reads it with OCR: supplier, total, date, line items. The result lands on the job as a draft cost that you confirm. Nothing is saved as final without a person looking at it.

Safe to retry, hard to abuse

Site phones lose signal, so the app retries. Idempotency keys make sure a retried request creates one cost, not two. Redis rate limiting protects the expensive endpoints, such as OCR.

I also hardened SMS one-time-password sign-in against abuse: limits per number and per device, and short-lived codes.

Built for gloves and one hand

The interface has large tap targets, the main actions sit within thumb reach, and the common flows take few taps. It is designed for a person standing on a site, not sitting at a desk.

Why this much structure

DDD and CQRS are heavy for a small app. I used them here because job costing has real rules: a cost belongs to one job, a closed job cannot change, and totals have to match what was approved. Putting those rules in one domain layer, and testing them through the API, means the mobile app stays simple and cannot break them.

Where it stands

Murvo is in development for iOS and Android. There is no public build yet, so the website offers a waitlist. Screenshots will be added when the UI is ready.