Skip to content

IllustrativeThis is an illustrative engagement scenario, not a named client case study.

//SaaS & Tech · Specialized Engineering

Desktop application development: what a cross-platform build actually costs you

A reference build for custom desktop apps on macOS and Windows: framework tradeoffs, code signing and notarisation, offline-first sync with explicit conflict resolution, staged auto-update, and crash reporting wired into the release pipeline.

01The challenge

Desktop software can't be hotfixed like a web app, so signing, offline correctness, auto-update and crash visibility have to be engineered as the product, not bolted on after launch.

02What we did
  • 01Blueprinted the framework choice and cost tradeoffs in writing, and started code-signing certificate paperwork on day one.
  • 02Prototyped the offline-first sync layer — outbox, change feed, conflict handling — before drawing a single screen.
  • 03Forged automated signing, notarisation and staged auto-update channels inside CI.
  • 04Hardened crash reporting with symbol upload wired into the same pipeline step as the build.

The question this engagement had to answer

*Can one team ship and maintain a single application on macOS and Windows, keep it working when the network does not, and push a fix to every installed machine within a day?*

Teams reach for desktop when the browser genuinely cannot do the job: local file access at volume, long-running background work, hardware or peripheral integration, or operators who work where connectivity is unreliable. Those are good reasons. They also mean the app runs on machines you do not control, on OS versions you did not choose, behind security software that will treat an unsigned binary as hostile.

Why this was hard

The platform owns the distribution rules. Apple requires signing and notarisation, and it changes the requirements. Windows SmartScreen judges reputation, which a new certificate does not have. Neither is negotiable and both add calendar time you cannot compress.

Offline is a correctness problem, not a caching problem. Two operators editing the same record on two disconnected laptops is a distributed-systems question. Answer it with "last write wins" and you will lose customer data quietly for months before anyone notices.

You are blind by default. A web app tells you instantly when it breaks. A desktop app fails on someone's machine, in a build from three versions ago, with no report unless you built the reporting.

Framework selection, and what each one costs

We run this as a Blueprint decision with the cost stated out loud, because every option here is a trade and none of them is free.

OptionWhat it buysWhat it costs
ElectronFastest path from an existing web codebase; the deepest hiring pool; one UI to maintainLargest install and memory footprint; per-app Chromium to keep patched; UI never quite feels native
TauriSmall binaries, low memory, system webview, strong security postureRust in the toolchain; webview differences across OS versions become your bug surface
.NET MAUI / WPFExcellent Windows integration and enterprise deployment storymacOS is the weaker target; cross-platform parity work is ongoing
Qt / C++Real native performance, mature widget set, hardware and peripheral accessSlowest to build, hardest to hire for, licensing needs a decision
Two native codebasesThe best possible app on each platformRoughly double the build and double the maintenance, forever

The honest default for most business tools is a webview-based client. We pick Tauri when binary size, memory or endpoint security posture matter, Electron when the team already owns a large web codebase and needs deterministic rendering, and native when the app touches hardware or has real-time performance requirements. What we do not do is let the choice go unstated — it changes the price, and the price is locked before code.

Architecture

flowchart LR
    subgraph Client["Desktop client — macOS + Windows"]
        UI[UI layer<br/>webview or native shell]
        CORE[Core process<br/>business logic]
        DB[(Local store<br/>SQLite, source of truth<br/>while offline)]
        Q[Outbox queue<br/>durable, ordered]
        UPD[Update agent<br/>signature-verified]
        CR[Crash + session reporter<br/>symbolicated]
        UI <--> CORE
        CORE <--> DB
        CORE --> Q
    end

    Q -->|batched, idempotent| SYNC[Sync service]
    SYNC --> RES{Conflict resolution<br/>per-field CRDT<br/>+ domain rules}
    RES --> SRV[(Server store)]
    SRV -->|change feed since cursor| CORE

    UPD -->|signed manifest poll| REL[Release service<br/>staged channels]
    CR --> OBS[Crash intake<br/>grouped by build + stack]

    subgraph Pipeline["Release pipeline"]
        BUILD[CI build<br/>macOS + Windows runners] --> SIGN[Code sign<br/>Developer ID / EV cert]
        SIGN --> NOT[Apple notarisation<br/>+ staple]
        NOT --> ART[Artifacts: .dmg / .msi]
        ART --> REL
    end

    OBS -->|crash-free rate gate| REL
Architecture diagram — Mermaid source (not rendered)

Four things in that diagram are the build.

The local store is the source of truth while offline. The app reads and writes SQLite and never blocks on the network. Sync is a background reconciliation, not a request-response path the UI waits on.

The outbox is durable and ordered. Mutations are queued locally, sent in batches with idempotency keys, and only cleared on server acknowledgement. A laptop that closes mid-sync resumes exactly where it stopped.

Conflict resolution is explicit and per-field. Independent fields merge automatically. Fields where a merge would be wrong — money, status, anything a human is accountable for — surface as a conflict for a person to resolve. Silent resolution is the failure mode we design against.

The release pipeline gates on crash-free rate. Staged rollout percentages advance only if the new build's crash-free session rate holds against the previous build. That is what makes a same-day fix safe rather than reckless.

What we did

1. Blueprint: chose the framework in writing, with the cost named. Binary size budget, memory ceiling, minimum OS versions supported, and the hiring implication. This document is what makes the fixed price honest.

2. Blueprint: got certificates in flight on day one. Apple Developer ID and a Windows EV code-signing certificate involve organisational identity checks measured in weeks. Started before architecture, because they gate the first real release.

3. Prototype: built the sync layer before the UI. Offline-first cannot be added later; it decides the data model. We prototyped the outbox, the change feed and conflict handling against a deliberately hostile network before drawing a screen.

4. Forge: automated signing and notarisation inside CI. No engineer signs a build from a laptop. Certificates live in the CI secret store; every artifact is signed, notarised, stapled and verified by an automated gate that rejects unsigned or unstapled output.

5. Forge: shipped auto-update with signature verification and staged channels. Internal, beta and stable. The update agent verifies a signed manifest before applying anything, and rollback to the previous version is a supported path, not an emergency procedure.

6. Harden: wired crash reporting with symbol upload in the same pipeline step as the build. Symbols that are uploaded manually are symbols that are eventually missing.

How we evaluated it

The eval set. Desktop quality is measured against a device matrix, not a test suite alone. Typical matrix: the two most recent major macOS versions plus one older, Windows 10 and 11 at the enterprise builds the client's fleet actually runs, on both Intel and Apple Silicon, with and without common endpoint security agents installed. Sync correctness gets its own harness — scripted scenarios that partition the network, kill the process mid-flush, skew clocks and replay duplicate mutations.

The metrics that mattered.

Crash-free session rate is the release gate. We hold a floor in the 99.5–99.9% range depending on how much native and peripheral code the app carries, and a staged rollout does not advance below it.

Sync convergence is binary and non-negotiable: after any partition scenario, every client and the server must reach identical state, with no lost mutation and no silently resolved conflict on a protected field.

Cold start is the one users judge instantly. Targets typically sit in the 1–3 second range on the median fleet machine, which is a real constraint on framework choice.

Update adoption — the share of installs on the current build within seven days of release — typically lands in the 85–95% range once staged rollout and silent update are working. Below that, you are maintaining more versions than you think.

Failure modes, and what happens when the system is wrong. A failed update must never leave a broken install, so updates are atomic and the previous version stays on disk until the new one starts cleanly. A sync conflict on a protected field stops and asks a human rather than guessing. A crash on start is the worst case — the app writes its crash report before any state-restoring work runs, so a poisoned local state cannot suppress its own report. When telemetry cannot reach the server, reports queue locally under a size cap and drain later.

What was hard

The tradeoff we took. We treated the local database as the source of truth while offline and paid for it with a full conflict-resolution layer. The cheaper design — treat the server as authoritative and cache reads — halves the sync work and gives you an app that is read-only the moment a field operator loses signal. For teams that work offline by default, that is not an app.

The alternative we rejected. A packaged web app pointed at the existing site. It ships in days. We rejected it because it inherits every network assumption of the web app, gives no offline story, and still requires the entire signing, notarisation and update apparatus. You take on all of the desktop cost and none of the desktop benefit.

The thing that broke. Notarisation, twice. First when a bundled helper binary was signed without the hardened runtime and Apple rejected the whole package late in the release. Second when an entitlement needed for a peripheral integration conflicted with the library validation flag. Both cost days, both are now caught by a pre-submission verification step in CI that inspects every nested binary before anything is uploaded.

What we would do differently

Start the certificate paperwork before the kickoff call. We have said this on every desktop build and we still under-plan it. EV certificate issuance and organisational validation are external dependencies with no engineering lever. They should be requested the week the contract is signed.

Instrument sync from the first prototype. On one build we added sync telemetry after beta and spent a week reconstructing what had been happening in the field. Queue depth, flush latency and conflict counts now ship with the first prototype build.

Ship the update mechanism in the very first internal release. If the first build users install cannot update itself, the first fix becomes a manual reinstall across the fleet, and the update path gets tested for the first time under pressure.

What is still running

Reference builds of this shape carry ongoing specialized engineering support after launch: OS-version compatibility passes each autumn when macOS and Windows ship major releases, certificate renewal, crash-triage on the grouped intake, and periodic dependency and webview patching. A desktop app is a maintained artifact, and the maintenance is predictable enough to price.

//Results

The numbers

99.5–99.9%
crash-free session rate floor — the release gate for staged rollout
85–95%
typical update adoption within 7 days of release
1–3 s
typical cold-start target on the median fleet machine

Verification —This page describes method, architecture and outcome ranges. It contains no client name, no client metric and no quote, because it is not a named engagement. The architecture, release pipeline and evaluation gates are exactly what we implement. We will walk through a live reference build under NDA on request.

//Tech stack
  • Tauri or Electron (client)
  • SQLite (offline-first local store)
  • Code signing — Developer ID / EV certificate
  • Signed-manifest auto-update
  • Crash + session telemetry

Engagement led by

Prabal Bhandari

Founder & Chief Executive Officer · MS Computer Science, Georgia Tech

//FAQ

Questions, answered straight

Yes, and if the fleet is genuinely single-platform we will say so and price a single target. Cross-platform is a cost, not a virtue. It is worth paying when the user base is genuinely split.

//Next step

Start with a Blueprint.
Lock the scope. Ship the system.

Contact the studio

Not sure you're ready? Take the Build Readiness Scorecard