← All posts

A lighter, quieter node: less chatter, more battery, better calls

No new headline feature this stretch — the opposite, on purpose. A phone running MeshHold was spending gigabytes of data and too much battery on background chatter, so this was a diet: make the mesh talk far less, let the radio sleep, and fix one more round of calls-in-the-wild.

A phone with a healthy battery, on a mesh that has gone quiet

No new headline feature this stretch — the opposite, on purpose. A phone running MeshHold was quietly spending gigabytes of data a day and more battery than it had any right to, almost none of it on anything you asked for. So this was a diet: make the mesh talk far less, let the radio actually sleep, and — because the wild keeps finding them — fix another round of call bugs.

A mesh that talks less

The mesh was a chatterbox. It re-published its presence on a timer whether or not anything had changed, re-announced every file a node held one at a time, ran periodic speed tests between peers, and woke every trusted device on trivial events like "a track started playing." Individually tiny; together, a phone bleeding data in your pocket.

So: presence goes out only when something actually changes — and slowly, on a metered connection. A node now restates everything it holds as a single compact digest instead of a message per file. A pair of nodes that keep agreeing with each other back off and check in less often. The catalog cycle stopped pestering peers that aren't even online. And the speed-test protocol is gone entirely — it turned out to cost more than the number it produced was worth. A node that used to hum with background traffic now mostly keeps quiet until there's a reason not to.

Let the radio sleep

The other half of the bill is battery, which mostly comes down to letting the hardware idle. When your phone goes to sleep, MeshHold now closes its delivery layer so the radio can genuinely power down instead of being poked awake. Idle shapers and tickers stopped waking the CPU for no reason. And — my favourite find — the daemon had been reading the machine's network interfaces on every single stream; on Windows that's a surprisingly expensive call, and doing it once every ten seconds instead quietly gave a chunk of idle CPU back. None of these is glamorous. All of them are the difference between a background service you forget about and one you notice in your battery graph.

Calls, again, and better

The wild is unforgiving about calls. A real one turned up a subtle, maddening bug: when someone simply stops talking, the other end read the silence as the network getting worse and over-buffered to compensate — so a natural pause in conversation made the whole call feel laggy afterwards. The fix is to treat silence as information: a sender now marks its own pauses, the receiver knows a gap is a gap and not a dropout, and speech carries a redundant copy so the first word after a pause isn't the one that goes missing.

Around that, the call's media moved into a worker, where a busy or stalled browser tab can no longer stutter it, and a single "send controller" now owns the decision of how much to send — reading the evidence through one door and climbing straight back to a good quality the way WebRTC does, instead of creeping.

Tidying up

Two quieter cleanups. The delivery-layer rework from earlier finished sweeping out what it made dead: with libp2p's relay gone, the whole idea of a "relayed connection" went with it, along with a pile of functions nothing called any more. And a couple of the daemon's overgrown internals — one had thirty-four fields where a handful would do — got put on a diet of their own, with a recorded import budget that only lets them shrink from here.

That's what the run-up to 0.8 looks like from the inside: not a new box on a feature list, but the thing getting lighter, quieter, and steadier under you.


Since the last post

  • Traffic diet: presence is published on change (slowly on a metered link) rather than on a timer; a node restates what it holds as one digest instead of a message per file; agreeing pairs check in less often; the catalog stops asking absent peers; the peer-to-peer speed-test protocol removed entirely
  • Battery: the delivery layer closes when the phone sleeps so the radio can idle; idle shapers and tickers stop waking the CPU; network interfaces are read once every ten seconds, not once per stream (a real Windows idle-CPU win); a six-minute sleep grace on Android
  • Calls hardened: silence is marked and treated as a pause, not a dropout, so a lull no longer inflates the buffer; speech carries a redundant copy; call media runs in a worker a stalled tab can't reach; a single send controller owns the rate and climbs back like WebRTC
  • Meetings: the room runs its own protocol, a guest reaches every member, a late-arriving leg learns the layout, and a node that leaves takes its layout demotion with it
  • Transport cleanup finished: node.obfs.plain became node.obfs.meshlink (old name honoured until 2026-09-28); with libp2p relay gone, "relayed connection" and its dead code were removed
  • Codebase diet: a recorded import budget so the daemon's god objects can only shrink; one overgrown service stack cut from thirty-four fields; the block-read path lifted out of the REST layer
  • Product switches: donations and the price list each sit behind a switch, both currently off