Approach
Fewer surprises is the entire method.
Software projects rarely fail because of a hard algorithm. They fail because nobody said the uncomfortable thing early enough. Everything about how we run an engagement is designed to surface problems while they are still cheap.
We interview the people who will use the software, map the systems it has to talk to, and find the risks early. It is a small, contained way to judge how we work before you commit to more.
You receive Written scope, architecture recommendation, estimate, release plan
Interaction design and technical design happen together, so nothing gets drawn that cannot be built well. The hardest technical question is answered with a working prototype, on a real device.
You receive Clickable prototype, design system, technical specification
Every two weeks you get an installable build on your own devices, through TestFlight, internal Play tracks or MSIX. You see the real product throughout, and priorities can change as you learn.
You receive Working builds, automated tests, release notes
Store submissions, phased rollouts, crash monitoring and a rollback plan. For internal software: fleet deployment, staff training material and a support handover.
You receive Store listings or signed packages, runbook, monitoring
A maintenance plan covers OS releases, security patches and dependency upgrades. If you would rather bring it in-house, we hand over the code, documentation and accounts, and walk your team through them.
You receive OS compatibility, security updates, or a full handover
The part you never see
Good software is mostly what happens when things go wrong.
A technician saves a record in a basement with no signal, then closes the app. That record has to arrive later, once, in the right order.
This is the kind of code we write for that problem, shown here in the language of each platform. It is unglamorous, and it is what decides whether your staff trust the software.
OutboxSync.swift
actor OutboxSync {
private let store: OutboxStore
private let api: APIClient
init(store: OutboxStore, api: APIClient) {
self.store = store
self.api = api
}
/// Sends queued writes in the order they were made.
/// The idempotency key means a retry after a dropped
/// connection can never create a duplicate record.
func drain() async throws {
for entry in try await store.pending() {
try Task.checkCancellation()
let outcome = await api.send(entry.payload, key: entry.id)
switch outcome {
case .delivered:
try await store.markDelivered(entry.id)
case .retryLater(let delay):
try await store.scheduleRetry(entry.id, after: delay)
return // Keep the order. Resume from here next pass.
case .rejected(let reason):
try await store.quarantine(entry.id, reason: reason)
}
}
}
}OutboxWorker.kt
class OutboxWorker(
context: Context,
params: WorkerParameters,
private val store: OutboxStore,
private val api: ApiClient,
) : CoroutineWorker(context, params) {
// WorkManager runs this when the network returns,
// even if the user closed the app hours ago.
override suspend fun doWork(): Result {
for (entry in store.pending()) {
when (val outcome = api.send(entry.payload, entry.id)) {
is Outcome.Delivered ->
store.markDelivered(entry.id)
is Outcome.RetryLater ->
return Result.retry() // Keeps the queue in order.
is Outcome.Rejected ->
store.quarantine(entry.id, outcome.reason)
}
}
return Result.success()
}
}OutboxSync.cs
public sealed class OutboxSync(IOutboxStore store, IApiClient api)
{
/// <summary>
/// Sends queued writes in order. A rejected record is set aside
/// for review, so one bad row never blocks the rest of the day.
/// </summary>
public async Task DrainAsync(CancellationToken ct)
{
await foreach (var entry in store.PendingAsync(ct))
{
var outcome = await api.SendAsync(entry.Payload, entry.Id, ct);
switch (outcome)
{
case Delivered:
await store.MarkDeliveredAsync(entry.Id, ct);
break;
case RetryLater retry:
await store.ScheduleRetryAsync(entry.Id, retry.Delay, ct);
return; // Keep the order. Resume from here next pass.
case Rejected rejected:
await store.QuarantineAsync(entry.Id, rejected.Reason, ct);
break;
}
}
}
}Engineering standards
The floor, not the ceiling.
Code review on every change
Nothing reaches your main branch without a second engineer reading it. The history shows who approved what, and why.
Automated tests where failure is costly
Sync logic, payments, permissions and data migration are covered thoroughly. Coverage is a means, and we do not chase the number.
Security by default
Our baseline follows the OWASP mobile guidance: secrets in platform keychains, encrypted storage, certificate handling and dependency scanning on every build.
Decisions written down
Architecture decision records explain why the software is the way it is, so your next engineer does not have to guess.
In the contract
Promises are easy. These are in writing.
- You own what you pay for
- Source code and intellectual property are assigned to you in the contract, without a licence-back or lock-in.
- Your accounts, your repositories
- Code, store listings and cloud resources sit in your organization from the first day of work.
- A confidentiality agreement first
- We will sign your NDA or provide ours before you share anything sensitive.
- A Canadian contract
- Written under Ontario law and invoiced in Canadian dollars, with HST handled the ordinary way.
- Named people
- You will know who is working on your software. Any subcontractor is disclosed and approved by you beforehand.
- A plain written estimate
- Scope, assumptions, exclusions and cost in language your finance team can read without a translator.