Estimaid app iconCase study · iOS

Estimaid: engineering an offline-first iPhone app

Estimaid is a native iPhone app for cleaning contractors. It turns a walkthrough of a property into a priced, itemized document the client can keep, and it does that whether or not there is a usable signal in the building. KUBERSTAR designed it, wrote every line of it, and runs its releases.

Role
Product design, iOS engineering, release
Platform
iPhone, iOS 18 and later
Stack
Swift, SwiftUI, SwiftData, StoreKit
Status
Live on the App Store
A finished Estimaid document on an iPhone, rendered on-device by Core Graphics from the same SwiftUI view the app draws
Estimaid's pricing input screen, where the pure pricing function recalculates a breakdown on every keystroke
Estimaid's list of saved jobs, read from the local SwiftData store the app opens at launch
Estimaid's rate-card settings, the configuration the pricing function takes as its second argument
Estimaid's summary screen, computed from the frozen totals stored on each saved job
Shipped screens · swipe to explore
The brief

Four constraints that decided the architecture.

None of these are preferences. Each one rules out an approach that would otherwise be the obvious way to build a small business app, and together they are why the app looks the way it does inside.

No desk, and no guarantee of signal

The document has to be produced where the work is - a basement, a stairwell, a half-renovated flat. Anything on the critical path that needs a server is a feature that fails exactly when it is needed.

Old numbers have to stay old

A price a client agreed to in March cannot move because the contractor edited their rate card in June. The historical record and the live configuration are different things and had to be stored as different things.

One repository, one team

The interface, the app code, the translations, the analytics and the release pipeline all live in the same repository, because there is no separate platform team to hand any of them to.

Not an English-only audience

The app ships in 34 translated locales besides English. Every layout has to survive a word three times longer than the one it was designed around, in a script it was not drawn for.

Engineering decisions

What we chose, and what it cost.

Six decisions worth writing down, either because the obvious alternative is worse in a way that is not obvious, or because we learned it the expensive way.

Local store first, cloud as a mirror

The app opens a SwiftData store on the device; CloudKit only mirrors it. If the mirrored container cannot be built it falls back to a purely local one, then to a destructive reset, then to memory - and the memory tier shows a permanent banner rather than pretending everything is fine.

Pricing as a pure function

The price breakdown is a nonisolated function over value types with no access to the store, the network or the main actor - so the test target calls it directly. Its result is frozen onto the job when it is saved, which is how a rate-card edit stops being able to rewrite a number a client already agreed to.

One schema, widened by inference

Every model change so far has been an additive property with a default, which the store resolves by inference when an older install opens it. Declaring a second versioned schema over the same live model types hashes to the same checksum and terminates the app on launch, before its first frame, for every upgrading user. A test now pins that shut.

The widget lives in another process

A widget extension cannot read the app's store, and relocating that store into a shared container would migrate every installation that already exists. The app instead derives a small equatable snapshot on the main actor and writes one JSON file into an App Group - and skips the write when the snapshot is unchanged, so a launch that changed nothing causes no file churn and no timeline reload.

Analytics that survive the swipe-away

The most valuable event to record is a completed purchase, which is also the moment a user is most likely to close the app. Events go into a bounded on-disk spool before the request is sent and are deleted only on a 2xx, then retried on the next foreground. With analytics switched off the spool is emptied rather than quietly kept.

A restore that refuses to guess

Restoring a backup deletes the store and re-inserts from the archive. The only thing that makes that safe is a decoder that throws on anything it cannot fully read, so tolerance for a missing key is granted one key at a time with a written reason - never as a blanket policy that would let a garbage payload decode into defaults and take the real data with it.

A closer look

A layout budget measured in points, not characters.

SwiftUI hands a tab title to UIKit, so the usual shrink-to-fit modifiers never apply, and iOS lets an over-long label overlap its neighbour instead of truncating it. The app shrinks the tab bar's label font down to an 8pt floor; past that, the only remaining fix is a shorter word. So the budget is written down as a rendered width at that floor, measured with the platform's own text layout, and a test measures the five real titles out of the built app bundle in every locale.

A character count cannot tell these apart. Armenian “Ժամանակացույց” and German “Einstellungen” are both exactly 13 characters long, and render 88 and 67 points wide.

By the numbers

The parts that are not the app code.

34

translated locales, plus English

482

strings in the app's catalog

485

test cases across 41 test files

39

storefronts with localized release notes

Counted from the estimaid-ios repository, August 2026
Our role

Built end to end inside the studio.

Estimaid is our own product, which is why it is the one worth showing: the team that drew the screens also wrote the persistence layer, the pricing function, the widget, the localization gate and the release scripts. Nothing was handed over a boundary, so nothing here can be described as someone else's decision.

Product design

Interface, design system, and the adaptive layout rules that keep every screen usable at accessibility text sizes and in the longest translation.

iOS engineering

The whole app: SwiftUI, SwiftData persistence and its migration policy, StoreKit purchases, a WidgetKit extension, and App Intents.

Localization

34 translated locales in the app plus localized store metadata, behind a gate that fails on a missing translation or an over-budget label.

Release engineering

Scripted archive, upload, TestFlight distribution and store metadata, plus a privacy manifest checked against the events the app actually sends.

This is how we build.

If you are weighing up a native iOS project, the decisions above are the kind of thing you would be buying. Tell us what you are building and we will tell you where it gets hard.

Estimaid's own site, estimaid.app

A Kuberstar Product