Architecture
Plugins run unsandboxed inside the single long-lived omarchy-shell process, so nothing that samples twice a second belongs in QML. The shell side stays a thin reader; a small daemon owns every measurement, and three files are the whole contract between them.
INSIDE OMARCHY-SHELL · QML, LOADED BY THE PLUGIN REGISTRY
BarWidget.qml
kind: bar-widget
Index, latency or live sparkline. Colour carries the state. Click opens the panel, middle-click runs a peak test.
Panel.qml
five tab Loaders
Only the selected tab is alive, so a hidden tab costs nothing per sample. Charts are Canvas, same technique as the UniFi plugin.
Service.qml
kind: service
Starts at shell startup, spawns the daemon if no lock is held, restarts it if it dies, raises outage notifications.
One nexthop stream reader for live state — bounded, no-follow, regular-file-only, so the shell never opens a state path itself  ·  one Process call to nexthop query when you open a longer window
THE CONTRACT · ~/.LOCAL/STATE/NEXTHOP
live.json
rewritten 2× / sec
Both legs, lag, loss, rates, signal, the three component scores and the index. Under 1 KB, atomic rename. This is all the bar widget ever reads.
recent.json
rewritten every 5 sec
A 30-minute ring buffer, pre-downsampled to ~360 points. The panel's default graphs paint from it with no subprocess at all.
history.db
sqlite, stdlib
1-minute rows for 7 days, 1-hour rows for a year, every speed test and every event kept. QML never speaks SQL — the CLI answers in JSON.
writes only  ·  the daemon never talks to the shell, so either side can restart without the other noticing
NEXTHOPD · PYTHON 3, STANDARD LIBRARY ONLY, NO PIP
Local leg
2 Hz
One persistent ping -D -i 0.5 to the default gateway, parsed line by line. No process spawn per sample.
Wan leg
2 Hz
Same again to an anchor. Wan latency is the anchor minus the gateway, which is what separates your Wi-Fi from your ISP.
Link + counters
1 Hz / 5 sec
/sys/class/net byte counters for throughput; iw station dump for signal, bitrate, retries and roams.
Application path
1 Hz + 5 min
A TCP handshake to the anchor on 443, and one HTTPS request every five minutes. Routers answer pings from hardware and rate-limit them under load, so ICMP alone is not what applications get.
Content speed
hourly, ~14 MB
A short ranged fetch, enough to score Speed honestly without moving real data. This is what feeds the index.
Scorer
continuous
Lag from latency, jitter and loss → Responsiveness. Downtime → Reliability. Content speed → Speed. Weakest-link, 0–100: you feel the bottleneck, not the average.
Roll-up and retention
Raw 2 Hz samples never hit the disk. The daemon folds them into 1-second aggregates in memory, 1-minute rows on the way to sqlite, and 1-hour rows after a week. A month of continuous monitoring lands under 12 MB.
only when you ask, or when a schedule you turned on says so
ON DEMAND
Peak speed test
ookla → cloudflare → fast.com
Uses the official speedtest CLI when it is on the machine, for server choice and a shareable result. Falls back to a Cloudflare or fast.com run that needs nothing installed — the same method Omarchy's own speed test uses. Idle and loaded latency are captured on every run.
Notifications and report
omarchy-notification-send
One notification when a disruption starts and one when it clears, naming the leg that failed. "Copy report" renders the visible window as plain text with timestamps, both legs and loss.
WHY A DAEMON AND NOT QML TIMERS
History has to survive a shell restart, and you restart the shell every time you change a theme. Sampling twice a second from QML would also mean a subprocess per probe inside the process that draws your desktop. And the panel must open already full of data, not start collecting when you look at it.
TWO WAYS TO RUN IT
By default the shell service starts the daemon, so installing the plugin is still just a clone and an enable — the marketplace installer never runs code. Anyone who wants monitoring while the shell is down installs a systemd --user unit with one command; the service sees the lock is held and simply attaches.