Utility

ROOMFUL

If your place burned down tomorrow, could you tell your insurer what was in it? Almost nobody can.

Click to preview · 2× speed

Cataloguing a room. About eight seconds an item.

Stack/Tools

ReactTypeScriptTailwindZustandIndexedDBPWA

What it does

Walk around the house with your phone. Photograph a thing, name it, put a value on it, and the camera reopens for the next one. About eight seconds an item, because if it takes twenty nobody finishes a house.

Later, at a laptop, fix the values you guessed at, add serial numbers off the receipts, and export a dated PDF with photos and totals for your insurer.

It installs to a home screen and opens with no signal, which matters because the place you actually use it is a garage with no reception. Nothing is ever uploaded. No account, no server, no analytics.

The hard part

Fitting a whole house of photographs into a browser storage quota, with no server to offload to.

A phone photo is three to five megabytes. Forty of them is more than most browsers will store. So every image is decoded off the main thread, downscaled to 1200px on its long edge and re-encoded as WebP, landing around 200 KB. That's still enough resolution to read a serial number off a label.

Compression here isn't about bandwidth, which is the usual reason. There's nothing to upload. It's purely about fitting inside a quota that can't be raised.

A decision I'd defend

IndexedDB over localStorage. localStorage caps at roughly 5 MB and stores strings only, so photos would have needed base64 encoding, a third larger and blocking the main thread. About ten items would have exhausted it. IndexedDB stores binary directly and gets a quota measured in hundreds of megabytes.

The cost is that everything becomes asynchronous, which is why the app loads all its text records into memory once at startup and treats memory as the read path. Photos deliberately stay out and are fetched only when something renders them.

Selected screens

The phone, left to right: the house total by room, a room's items, and the capture screen whose primary button reopens the camera rather than returning to a list.
First run. It offers the room you're standing in, or a sample home, so you can see what an export looks like before cataloguing anything of your own.
Desktop three-column layout. The phone captures and the laptop reviews, so they get different arrangements of the same components.
Export and transfer. With no server to sync through, moving an inventory between devices is a backup file you carry.
The exported document. Grouped by room with photos and subtotals, generated by a print stylesheet rather than a PDF library.

Where it stands

Shipped and live. Installs to a home screen, opens in airplane mode, and produces a document you could attach to a claim.

23 tests covering the money helpers and the total calculations, including the property that the grand total always equals the sum of the room totals.

What's still missing

Devices can't see each other's data. That's the direct cost of having no backend rather than an oversight, so moving an inventory between a phone and a laptop is a file you export and carry.

Object URLs are held for the whole session, so memory grows with the number of photos viewed. Fine at a few hundred items.

And a value above JavaScript's safe integer range breaks totals. It needs an input cap and a test, and it's documented rather than fixed.

Full reasoning for all three is in the README.

I wrote up the whole build: why no backend, what that bought and what it cost, and the bug I spent two hours on when the stack trace had already named it.

Or step sideways