Device to dashboard
When your app is running on someone's phone, you want to know what it's actually doing — did the screen load, did the payment go through, how long did it take, did anything go wrong? Apps record little notes about all of this as they run. Those notes are how you tell whether things are healthy or broken.
The hard part is collecting those notes from real users. Phones go through tunnels. Browsers get closed. Laptops sleep. When a device is offline, the usual approach just loses whatever it couldn't send.
kuilt takes a different path: write the note down safely on the device first, and deliver it whenever the network comes back — minutes or hours later. Nothing is lost while offline, and — this is the part that's normally hard — nothing is counted twice when a flaky connection makes the device send the same note again.
The rest of this page walks the whole path, in the order you'll build it:
Record the notes as your app runs.
They survive being offline and sort themselves out across devices.
See them in a dashboard — deliver them to your telemetry backend.
1. Record what your app is doing
This is a normal logging/metrics setup — you record events as your app runs, and they show up later. There are three kinds of note, and each has its own page for what it is and how to record it:
Traces — one timed unit of work ("this checkout took 480 ms").
Metrics — numbers you count or sample ("requests served", "memory in use").
Logs — lines of text ("user checked out").
kuilt records all three through one combined entry point, WarpTelemetry:
The one thing to notice: every call above finishes the instant the note is written to local storage. None of them wait for a network. That's the whole idea, and the next section is why it's safe.
2. It survives being offline
The industry-standard way for apps to emit this kind of data is called OpenTelemetry — a shared vocabulary for exactly those three kinds of note (traces, metrics, logs), plus the dashboards that read them (Jaeger, Prometheus, and friends). kuilt-otel speaks that same vocabulary, so the data you record lands in the tools you already use.
What it changes is when a record is considered done. A normal setup sends each note to a collector over the network and fails if the network is down. kuilt-otel instead succeeds the moment the note is durably written to local storage. Getting it onward is a separate step that happens whenever a connection is available — possibly much later, and possibly from a different device (more on that below).
That "no double-counting" property isn't politeness — it's structural. Every kind of note is stored as a replicated data type: traces as a set keyed by id, metrics as mergeable counters, logs as an ordered append-only sequence. Re-sending something you already sent is a merge with itself, which changes nothing. The classic "my retry counted the request twice" bug simply can't happen here.
The same machinery reconciles notes between devices. Two devices that briefly meet exchange only the records the other is missing — so telemetry recorded on a phone that never once reached your servers can still arrive, carried by another device that did. A brief window of connectivity is enough.
3. See it in a dashboard
Recording is half the job; eventually you want to look at the data. This is the step that delivers it onward to your telemetry backend — Jaeger, an OpenTelemetry Collector, whatever you already run.
OpenTelemetry backends receive data over a common wire protocol, OTLP (the OpenTelemetry Protocol). You write one small adapter — an OtlpEdge — that knows how to talk OTLP to your backend, and hand it to a WarpOtlpBridge. The bridge does the careful part: on every reconnect it asks the backend what it already has, and sends only what's missing.
Because any device that reaches the backend can drain what it holds — including records that synced over from peers — you don't need every device online at once. One device with a connection can carry the room's telemetry to your dashboard.
Where it runs, and the honest limits
It works on every platform kuilt targets — phones (Android, iOS), desktop and servers (JVM, macOS), and the browser (wasm) — each with crash-safe local storage underneath.
A few things to know going in: timestamps come from each device's own clock, so a long-offline device with a skewed clock can mis-order against its peers; a trace that straddles an offline and an online device only completes once the offline half syncs; and local storage is bounded — when a cap is hit, evicted items are always logged, never silently dropped.
Going deeper
Capturing — record the logs your app already writes into this same offline buffer, then reach into an otherwise-unreachable device (a phone or a CI simulator) and pull them off from a test.
Offline-first OpenTelemetry — the design — why the replicated-data representation makes a resend safe, the local-write inversion, the digest-reconciled delivery, and the full limits.
Re-discovering which step led to which — kuilt already knows the happens-before order of events across every device, so it can reconnect traces that lost their thread across an offline gap — labelled potential, never overclaimed.
API reference — every type in
kuilt-otel, with runnable examples.