Every capability, side by side
Every capability in both clients — 527 of them — each one checked against the source rather than a changelog. Where they differ, the row says whether that is a limit of the platform, a deliberate choice, or simply not built yet.
- 268on both
- 127desktop only
- 40mobile only
- 129not built yet
Radio 71
Connecting to a radio
| Feature | Desktop | Mobile |
|---|---|---|
Connect over USB serial
Platform limitPlatform. Android has no serial API for the radio link; the phone's USB-C port is used only for flashing. |
yes | not applicable on this platform |
Connect over BLE
NotesNordic UART, one notify per frame — no |
yes | yes |
Enumerate all serial ports, unfiltered
NotesNo VID/PID whitelist; a port is a candidate until the handshake says otherwise. |
yes | not applicable on this platform |
Identify a radio by protocol handshake, not by USB ID
Notes
|
yes | not applicable on this platform |
Identify a radio by advertised GATT service UUID
NotesMatches on the NUS UUID, not on the device name. The desktop sorts the advertisers to the top of its picker rather than filtering down to them. |
yes | yes |
Scan for nearby radios
NotesThe same 12s LE discovery on both, from the same constant. |
yes | yes |
List devices that do not advertise the service
NotesShown anyway, so a mis-flashed node is still reachable — a peripheral only lists its service UUIDs when the advertisement has room. The desktop drops just the nameless non-advertisers, which have nothing to show and nothing to suggest they are radios. |
yes | yes |
Manual device picker
NotesThe desktop now has two, deliberately: "Choose port…" enumerates instantly and "Connect over Bluetooth…" scans for twelve seconds, and folding them into one list would make the fast case wait for the slow one. Mobile: "Radios nearby". |
yes | yes |
Remember the last device across restarts
NotesThe desktop now remembers a pair — which transport, and which device on it ( |
yes | yes |
| Auto-connect to the remembered device at startup | yes | yes |
Dial a remembered radio directly, with no scan in front of it
NotesTwelve seconds of discovery per attempt is what makes a retry ladder unaffordable; on the phone it is also the thing Android suppresses once the screen is off, which is the only place reconnect matters. Finding a radio in the first place stays the picker's job on both. |
yes | yes |
Retry auto-connect as new scan results arrive
Deliberate choiceChoice on the desktop: its only scan runs inside the picker, which is a manual act, and a countdown running underneath a list somebody is reading would connect to something they had not picked. The phone retries on every device found and again when the scan finishes. |
no | yes |
"Forget this radio"
Not built yetGap, and a smaller one than it was: |
no | yes |
Automatic reconnect with backoff after a drop
Not built yetGap closed on the phone, which previously looked for the remembered radio at startup and never again. The first five steps are the same ladder on both — 2/5/10/15/30s — deliberately, because the cases they cover are the same: a radio rebooting, a link glitching, somebody walking through a doorway. |
yes | yes |
One reconnect policy shared by every transport
Notes
|
yes | not applicable on this platform |
Stop after a capped number of attempts rather than retrying for ever
NotesA behaviour change on the desktop, and worth knowing: serial previously retried without limit. Ten attempts on both. The phone's ladder then stretches to 60/120/300s, for two reasons that are true there and not on a mains-powered laptop — a failed BLE dial to a radio that is not there occupies the radio for about thirty seconds before Android answers, and a phone has a battery. |
yes | yes |
Say so when it has given up, and offer the retry
NotesBoth settle on the same sentence. The desktop's is a status-bar |
yes | yes |
Reset the ladder when a link comes up
NotesA drop an hour later is a fresh fault, not a continuation of the last one — and treating a flapping link like an absent one would make the first look like the second within a quarter of an hour. |
yes | yes |
Manual reconnect action
Not built yetGap closed on the phone. The desktop uses it to re-handshake after an identity import; on the phone it is the give-up retry, and bringing the app to the front counts as one, because nothing else would explain the shade line vanishing when it is tapped. |
yes | yes |
Manual disconnect
NotesDistinguished from every accidental drop on the phone by exactly this call: asked-for takes the backoff and the foreground service down with it. |
yes | yes |
| Connection status text in the main chrome | yes | yes |
Coloured connection-state indicator
NotesThe phone now shows four states, not two — connected, handshaking, working on it, given up — with a slow pulse while it is trying, because "trying" and "stopped" are the difference between something to do and nothing to do. The desktop still shows state only on the tray icon. |
partial | yes |
Per-port "probing…" progress during a scan
NotesDesktop probes candidates one at a time; a BLE scan is a single operation. |
yes | not applicable on this platform |
Connect off the UI thread
NotesOpening a serial port blocks for seconds, so the desktop probes on a worker |
yes | yes |
Wait out the board reset that opening a serial port causes
Platform limitPlatform. Toggling DTR/RTS reboots the board; 2s settle. |
yes | not applicable on this platform |
Explain a serial permission failure with the fix
NotesNames the |
yes | not applicable on this platform |
Request Android runtime BLE permissions
Platform limitPlatform. Split scan/connect on 12+, fine-location below. |
not applicable on this platform | yes |
Work around Qt reporting a granted permission as undetermined
NotesQTBUG-112164. |
not applicable on this platform | yes |
Deep-link to the OS app-settings page when permission is permanently denied
Platform limitPlatform. Android's "ask once" model leaves no other route. |
not applicable on this platform | yes |
Detect that the Bluetooth adapter is switched off
Not built yetGap closed on both, and on the phone it was worse than a missing message: Qt raises neither |
yes | yes |
Offer to switch the adapter back on
NotesThe desktop's picker offers it and then re-checks rather than assuming it worked, because an rfkill block is not the app's to lift. |
yes | no |
Retry from the top when the adapter comes back on
NotesEvery attempt made while Bluetooth was off failed for a reason that has just stopped being true, so the budget resets and the next attempt is now — including from having already given up, which is the common case: somebody switches it off for a flight and on at the other end. |
not applicable on this platform | yes |
Reachable from the headless CLI
Deliberate choiceChoice, recorded in |
serial only | not applicable on this platform |
Fixed baud rate, not user-configurable
Notes115200, deliberately not a setting. |
yes | not applicable on this platform |
Identifying the radio once connected
| Feature | Desktop | Mobile |
|---|---|---|
Device query at handshake
NotesBoth issue it; the order relative to app-start differs and does not matter. |
yes | yes |
| Self query at handshake (name, key, radio params) | yes | yes |
| Reach "ready" only once both replies are in | yes | yes |
| Show the firmware version and version code | yes | yes |
Warn when the firmware is newer than the client knows
Not built yetGap. Desktop compares against |
yes | no |
Show the firmware build date
Not built yetGap. The field is decoded by the shared core and then dropped on the phone. |
yes | no |
Show the model / manufacturer
Not built yetGap. Same — decoded, never surfaced. |
yes | no |
Show your own public key
NotesThe desktop now carries the active profile's key in the rail chip's tooltip, and the full key in each switcher entry's; there is still no labelled field anywhere. |
partial | yes |
Show contact/channel capacity
NotesThe phone uses |
yes | partial |
| Show your own node name | yes | yes |
| Show your own advertised position | yes | yes |
| Read the device battery on connect | yes | yes |
Show device storage use
Not built yetGap. The same reply carries both; the desktop discards the storage half. |
no | yes |
Read the device clock and compare it before setting it
Not built yetGap. Desktop tolerates 30s of skew and refuses to move a clock backwards. |
yes | no |
Sync the clock automatically on every connect
Not built yetGap. On the phone it is a manual button. |
yes | no |
Retry in-flight commands on a timeout tick
NotesSame 250ms tick and retry discipline; the C++ core is a deliberate port. |
yes | yes |
Idle keep-alive to detect a dead link
Not built yetGap on both. |
no | no |
| Finish sends that were in flight when the app was last closed | yes | yes |
Radio settings
| Feature | Desktop | Mobile |
|---|---|---|
Setting
NotesNotes |
Desktop | Mobile |
| Node name | yes | yes |
Advertised latitude and longitude
Not built yetGap. The phone's |
yes | no |
| Frequency | yes | yes |
| Bandwidth | yes | yes |
Spreading factor
NotesDesktop clamps with a spin box; the phone parses free text. |
yes | yes |
| Coding rate | yes | yes |
TX power
NotesSpin box vs slider, both bounded by the reported maximum. |
yes | yes |
Confirmation before changing a radio parameter
NotesGetting these wrong takes the node off the air. |
yes | yes |
Frequency or region preset (e.g. "EU868")
Not built yetGap closed on the desktop, through the country (§20) rather than as a band picker of its own: the preset is a consequence of where you are, and asking twice would let the two answers disagree. The phone still takes raw numbers only. |
yes | no |
Default region / flood scope
NotesOn the desktop it lives with Channels rather than in Settings. |
yes | yes |
| Auto-add: drop the oldest contact to make room | yes | yes |
| Summary of what the radio adds by itself | yes | yes |
Client-repeat toggle
Not built yetGap; desktop hides it unless the firmware supports it. |
yes | no |
Path-hash-size mode
Not built yetGap, same version gating. |
yes | no |
Advert interval
Not built yetGap on both; only a manual "send advert". |
no | no |
Set the clock from the host
NotesAutomatic and drift-aware on the desktop, a manual button on the phone. |
yes | yes |
BLE pairing PIN
NotesThe OS pairing dialog handles it; the firmware command is not exposed. |
not applicable on this platform | no |
Send advert, flood or zero-hop
NotesA dialog on the desktop, two buttons on the phone. |
yes | yes |
Chat 69
Chat: what a message looks like
| Feature | Desktop | Mobile |
|---|---|---|
| Own messages aligned right and tinted, incoming left and neutral | yes | yes |
Squared "notch" corner on the bubble nearest the speaker
NotesCosmetic; extra paint code the QML delegate never gained. |
yes | no |
Consecutive messages from one sender drawn as a single turn
Not built yetGap. A |
yes | no |
Avatar on incoming bubbles
Not built yetGap, same cause. The phone keeps avatars in the conversation list only. |
yes | no |
Sender name as its own line above the bubble, in channels
Deliberate choiceChoice. The phone folds it inline as "sender: text". |
yes | partial |
Day or date separators in the scrollback
Not built yetGap on both. |
no | no |
Timestamp on every message, host receive time, HH:MM
NotesBoth deliberately ignore the sender's clock — |
yes | yes |
Full date and seconds available per message
NotesThe phone's long-press "Message details" dialog; the desktop meta line is always abbreviated. |
no | yes |
| SNR shown in dB with an explicit sign, incoming only | yes | yes |
Hop count, or "direct", on incoming messages
NotesSame inverted- |
yes | yes |
"Show path" link opening the route on the map
NotesNot a URL on either — the route is a local row and the map is local. |
yes | yes |
Delivery mark on outgoing messages
NotesTick when acknowledged, "sending N/3" before that, failed after. |
yes | yes |
Failure shown as a glyph vs a word
Deliberate choiceChoice. |
`✕ failed` | `failed` |
Failed bubble filled red vs red border only
NotesThe phone only recolours the border. Not deliberate, just less paint code. |
yes | partial |
| Marker bar on a message that mentions you | yes | yes |
"as <identity>" on an outgoing message sent under another identity
Not built yetGap. The saved-configuration identity of §15, not the archive profile of §16. Both clients can apply one; the phone's message row has no |
yes | no |
"via <region>" per outgoing message
Not built yetGap. The phone shows the region once in the channel subtitle, never per message. |
yes | no |
| Channel subtitle naming slot, region and "no delivery receipts" | yes | yes |
Contact's public-key prefix in the conversation header
Not built yetGap. |
yes | no |
| Sender's key prefix on an individual message | no | no |
Chat: composing and sending
| Feature | Desktop | Mobile |
|---|---|---|
Live byte counter against the real limit
NotesBytes, not characters, and both escalate colour at 85%. |
yes | yes |
Limit shrinks by the node-name prefix on a channel
NotesLiterally the same shared C++ function on the phone. |
yes | yes |
Counter accounts for a mention growing on the wire
Notes
|
yes | yes |
Enter sends, Shift+Enter newlines
Not built yetGap. On the phone only the send button sends. |
yes | no |
Composer grows with the message
Not built yetGap on the desktop, which is a fixed 58px box that scrolls internally. |
no | yes |
In-app emoji picker
Platform limitPlatform. Android's own keyboard supplies one; a desktop text field does not. The phone only fixes the font fallback so emoji render. |
yes | no |
Attachments
NotesProtocol. Text frames carry no attachment payload. |
no | no |
Message templates or quick replies
Not built yetGap on both. |
no | no |
Drafts kept per conversation
Not built yetGap on both. |
no | no |
Send blocked while disconnected, with an explanation
NotesThe phone disables the button; if the path is reached anyway it returns silently. |
yes | partial |
| Message archived optimistically before the radio replies | yes | yes |
Delivery, retries and failures
| Feature | Desktop | Mobile |
|---|---|---|
ACK wait derived from the radio's own estimate plus a grace
NotesSame 3s grace and 3 attempts on both; the C++ side is a deliberate port. |
yes | yes |
| Automatic retry on no ACK | yes | yes |
Second attempt drops the cached route and floods
NotesThe cure for a stale path that goes nowhere. |
yes | yes |
Channel sends tick immediately, no ACK wait
NotesBroadcast has no receipts; counting attempts would never finish. |
yes | yes |
Duplicate ACK is a no-op
NotesGuarded in SQL on both. |
yes | yes |
Sends resumed after an app restart, with a staleness cutoff
Notes600s on both. |
yes | yes |
Manual resend of a failed message
Not built yetGap on both — only the automatic retries exist. |
no | no |
Offer to re-add a contact the radio no longer has, and resend
Not built yetGap. A common recovery after a repeater reflash, desktop only. |
yes | no |
Mentions
| Feature | Desktop | Mobile |
|---|---|---|
@name typed, @[name] on the wire
NotesThe form every other MeshCore client writes and parses. |
yes | yes |
Longest-name match, immune to a shorter name being a prefix
NotesHand-ported; "@Bob" does not match inside "@Bobby". |
yes | yes |
| Highlight a message that mentions you | yes | yes |
| Autocomplete popup while typing | yes | yes |
Completion by arrow keys, Tab or Enter
Not built yetGap; less pressing on a touch keyboard. |
yes | no |
Name sources for autocomplete
Not built yetGap. The phone will not complete or highlight a channel name or a channel-only participant. |
contacts, channels, channel senders, self | contacts and self |
Notification escalated for a mention
Not built yetGap. The phone has the mention test and uses it for the in-thread marker, but never wired it into the notification title or urgency. |
yes | no |
Conversations, favourites, filtering and search
| Feature | Desktop | Mobile |
|---|---|---|
| Last-message preview in the row | yes | yes |
"heard X ago" fallback when there are no messages yet
Not built yetGap; the phone shows "no messages yet" instead. |
yes | no |
Relative timestamp of the last activity
NotesDesktop is relative ("3m", "2h"); the phone shows a clock, weekday or date. |
yes | yes |
| "You: " prefix on your own last message | no | yes |
| Unread badge per row, capped at 99+ | yes | yes |
| Title bolded while unread | yes | yes |
Avatar colour stable across a rename
NotesBug on mobile. The desktop hashes the public key precisely so a rename keeps the colour; the phone hashes |
yes | no |
Presence dot on the avatar
Not built yetGap. The desktop's present/quiet/stale classification was never ported. |
yes | no |
| Favourites promoted to the top | yes | yes |
Favourites under a collapsible section header
Deliberate choiceChoice. The phone keeps a flat list with a star. |
yes | no |
Pin/unpin a favourite
NotesRight-click on the desktop, long-press on the phone. |
yes | yes |
Mute a conversation
Not built yetGap on both. |
no | no |
Archive or delete a conversation
NotesThe list is derived from messages and contacts, not a deletable object. |
no | no |
Mark read without opening
Not built yetGap. |
yes | no |
Mark back to unread
Not built yetGap on both. |
no | no |
Room servers listed with people
NotesDivergence. The desktop deliberately files a room server with people — "you log in and talk to it". The phone treats anything of advert type ≥ 2 as infrastructure, so a room server appears only under Repeaters and its unread is left out of the chat badge. |
yes | no |
| Show/hide People | yes | yes |
| Show/hide Channels | yes | yes |
The hide toggle shows how much unread it is hiding
Not built yetGap. |
yes | no |
| Search by name | yes | yes |
Search also matches the public key
Not built yetGap on the desktop. |
no | yes |
Separate search boxes for chat and for repeaters
NotesBoth learned the same way: one box wired to two lists silently emptied the chat. |
yes | yes |
Filter to favourites only, or unread only
Not built yetGap on both. |
no | no |
Contacts and channels 30
Contacts, paths, channels and regions
| Feature | Desktop | Mobile |
|---|---|---|
Details view with the full public key
NotesThe phone shows the key in the Nodes action sheet, not from an open chat. |
yes | partial |
Contact position shown as text
Not built yetGap; on the phone position exists only on the map. |
yes | no |
| Advert count for a node | yes | yes |
Signal history chart (SNR over time)
Not built yetGap. A bar per reading rather than a line, because these are separate receptions hours apart and joining them would imply a value in between. Adverts and messages are combined — a repeater nobody talks to is heard only through its adverts, a person mostly through their messages — and the two sources are drawn in different colours. |
yes | no |
SNR attributed to an advert from the raw packet that carried it
Not built yetGap, and the reason the chart above was empty for repeaters: neither advert push carries a signal, so the value exists only on the |
yes | no |
Inbound path resolved to a named hop chain
Not built yetGap. |
yes | no |
| Add a contact from an advert already heard | yes | yes |
Add a contact by typing a key with nothing heard
NotesBoth only add from something already on file. |
no | no |
Remove a contact from the radio
Not built yetGap on the desktop, which has the protocol call but never wires it to anything. |
no | yes |
Rename a saved contact locally
NotesThe name follows the radio's contact record on both. |
no | no |
Share / export / import a contact
NotesThe desktop defines the three protocol calls and never uses them; they are not in the C++ core at all. |
no | no |
| Forget a contact's route so the next send floods | yes | yes |
Fetch the inbound path from the radio
Not built yetGap. |
yes | no |
Trace a route hop by hop for live SNR
Not built yetGap. |
yes | no |
Set a path manually, or force flood per contact
NotesThe firmware has no such command. |
no | no |
| List channel slots with index, name and region | yes | yes |
Channel slot count read from the radio
Not built yetGap. The phone's "add a channel" only looks at slots 0–3; channels above that still sync and edit. |
yes | no |
| Rename a channel | yes | yes |
A rename cannot silently change the key
NotesDesktop hazard. In its default hashtag mode the desktop re-derives the key from the new name, which breaks the channel for everyone already on it. The phone only derives a key when the slot has none. |
no | yes |
Choose the key kind: public, hashtag, random or manual
Not built yetGap. The phone always derives from the name. |
yes | no |
Recognise the well-known public channel by its key
Not built yetGap; the constant is not in the C++ core. |
yes | no |
Delete a channel / clear a slot
Not built yetGap. |
yes | no |
Per-channel region, independent of the radio default
NotesThe phone's |
yes | yes |
Send through the channel's own region, restoring the default after
NotesSame swap-and-restore design, written twice. |
yes | yes |
| Read and set the radio's own default region | yes | yes |
| Warn that an empty region means unscoped | yes | yes |
Warn that region names are hashed exactly as typed
Notes
|
yes | yes |
Discover repeaters (zero-hop broadcast, 12s window)
NotesSame window and de-duplication on both. |
yes | yes |
| Discover scopes (discover, then ask each what it carries) | yes | yes |
| Clear the discovery log | no | yes |
Map 30
The map
| Feature | Desktop | Mobile |
|---|---|---|
| QtLocation OSM tiles | yes | yes |
Offline tile cache
NotesCache directory set before the plugin is built, as QtLocation requires. |
yes | yes |
| Provider-repository lookup disabled so it works offline from first launch | yes | yes |
Ships a TLS backend so tiles can be fetched at all
Platform limitPlatform. Qt for Android has none; OpenSSL is bundled, arm64 only. |
not applicable on this platform | yes |
Pre-cache an area before going offline
Not built yetGap. On the phone the cache only fills with what has been looked at. |
yes | no |
| Count of cached tiles shown | yes | yes |
Every node with a fix is drawn, whatever the list filters say
NotesSame reasoning on both: a filtered map hides where people are. |
yes | yes |
Marker colour meaning
NotesDivergence, not obviously deliberate. |
contact vs stranger | node role |
Marker name labels on the pin
Not built yetGap; on the phone the name appears only when a pin is tapped. |
yes | no |
| Own position drawn distinctly | yes | yes |
Own position comes from the radio's advertised fix, never phone GPS
Deliberate choiceChoice on both. |
yes | yes |
Enter your own lat/lon by hand
Not built yetGap. The protocol call is shared and the phone already uses it when applying a profile; no settings field exposes it. |
yes | no |
Uncertainty circle around a pin
NotesDesktop draws 500m to be honest about advertised precision. |
yes | no |
Position history track per node
Not built yetGap, and structural: the shared |
yes | no |
| Reject a 0,0 fix as "no position" | yes | yes |
Show how old a fix is
Not built yetGap on both. |
no | no |
| Pan, pinch-zoom, double-tap zoom | yes | yes |
Mouse-wheel zoom
Platform limitPlatform. |
yes | not applicable on this platform |
| On-screen zoom buttons | yes | yes |
Fit-all button
Not built yetGap. The phone only auto-frames a route or a highlighted set. |
yes | no |
Opens centred on your own node
NotesThe desktop always opens on the same fixed coordinate. |
no | yes |
Remember the last centre and zoom
Not built yetGap on both. |
no | no |
Tap a marker to reach that conversation
NotesOne click on the desktop; the phone interposes a card, which suits a fat finger. |
yes | yes |
| Draw the route a message arrived over | yes | yes |
Route legs trimmed to stop at the marker rings
NotesNear-identical algorithm, kept in step deliberately. |
yes | yes |
| Say how many hops could not be placed | yes | yes |
| Long-press the route text to copy it | no | yes |
Jump to a node's pin from a contact or repeater row
Not built yetGap. |
yes | no |
Search on the map
Not built yetGap on both. |
no | no |
Degrade gracefully if QtLocation fails to load
NotesThe desktop cannot assume which Qt is installed; the phone ships its own. |
yes | no |
Nodes and telemetry 40
Nodes and repeaters lists
| Feature | Desktop | Mobile |
|---|---|---|
| "Every node ever heard" view, contact or not | yes | yes |
Name, key prefix, type, last heard, advert count
NotesDesktop uses sortable columns; the phone folds signal and hops into one subtitle. |
yes | yes |
Full key on hover
Platform limitPlatform; a touch UI has no hover. |
yes | no |
Search the nodes list
NotesBoth match the name and the public key. The desktop matches the whole 64-character key rather than the shortened form in the column. |
yes | yes |
| Filter to nodes that are not yet contacts | yes | yes |
Sort by clicking a column header
Platform limitPlatform; a |
yes | no |
| Add a discovered node as a contact | yes | yes |
| Open a conversation from the list | yes | yes |
Mark or unmark a node as "mine" from the nodes list
Not built yetGap on the desktop, which only offers this from the Repeaters view. |
no | yes |
| Forget a route from the nodes list | no | yes |
| Remove a contact from the nodes list | no | yes |
Stale styling in the nodes list
Not built yetGap on both. |
no | no |
Separate destination for repeaters and sensors
NotesDesktop has one Repeaters screen; the phone splits all-repeaters from my-repeaters. |
yes | yes |
Route / hop count in the repeaters list
Not built yetGap. |
yes | no |
Advert count in the repeaters list
Not built yetGap. |
yes | no |
| Unread badge in the repeaters list | no | yes |
| Search the repeaters list | yes | yes |
Mark as mine from the repeaters list
NotesMobile bug. The phone's sheet hardcodes |
yes | partial |
"Show on map" from the repeaters list
Not built yetGap. |
yes | no |
Banner warning that the radio's contact table is full
Not built yetGap on the desktop, which shows capacity only as a static number in Settings. |
no | yes |
| "My repeaters" list of infrastructure you run | yes | yes |
Battery gauge with colour thresholds
NotesIdentical thresholds, drawn twice. |
yes | yes |
| Battery percent, falling back to volts | yes | yes |
| Temperature beside the battery | yes | yes |
| Firmware version, from cache, never triggering a read | yes | yes |
Stale-repeater highlighting and count (over 6h unheard)
Not built yetGap — arguably the point of the screen. |
yes | no |
Search "my repeaters"
Not built yetGap. |
yes | no |
Battery sweep: log in, ask, move on
NotesSame algorithm; both skip nodes with no saved password and say so. |
yes | yes |
| Sweep progress indicator | yes | yes |
Per-row actions beyond "Manage"
Not built yetGap. Trace route, show on map, forget route and unmark are all unreachable from the phone's my-repeaters screen. |
yes | no |
Telemetry
| Feature | Desktop | Mobile |
|---|---|---|
Decode CayenneLPP telemetry replies
NotesDesktop has its own decoder; the phone uses the shared C++ core. |
yes | yes |
Battery percent estimated from voltage
NotesByte-identical curve on both. |
yes | yes |
| Percent suppressed when the voltage is not a single cell | yes | yes |
Request telemetry for one node on demand
NotesNeeds no admin login — a guest may ask. |
yes | yes |
| Store the reading with the time it was read | yes | yes |
Show how old a reading is
Not built yetGap. The phone stores the timestamp and never displays it. |
yes | no |
| Own battery voltage | yes | yes |
Own storage use
Not built yetGap on the desktop, which discards half of the same reply. |
no | yes |
| Packet counters, airtime, noise floor, uptime, queue depth | yes | yes |
Per-contact SNR history
Not built yetGap. |
yes | no |
Repeater management 41
Repeater administration
| Feature | Desktop | Mobile |
|---|---|---|
Log in to a repeater with a password
NotesRole comes back from the node; neither client chooses it. |
yes | yes |
Remember the password locally
NotesSame storage key, so a restored archive carries it to the other client. |
yes | yes |
Log in automatically when a password is saved
Not built yetGap. |
yes | no |
Forget a rejected password automatically
Not built yetGap — the phone retries a known-bad password forever. |
yes | no |
Explicit "forget password" button
NotesThe desktop ties it to the "remember" checkbox instead. |
no | yes |
| Show the logged-in role, and warn when it cannot run commands | yes | yes |
| Log out, and log out on closing the screen | yes | yes |
Administer several repeaters at once
Platform limitPlatform. Desktop opens a non-modal window each; the phone has one page. |
yes | no |
Offer to re-add the contact when the node no longer knows you
Not built yetGap. |
yes | no |
All 32 config fields present in the form
NotesGenerated from one source. |
yes | yes |
Read one group of settings at a time
NotesOne |
yes | yes |
Read every group in one action
Not built yetGap. |
yes | no |
| Show cached values with their age before any round trip | yes | yes |
Track edits and send only what changed, after one confirmation
Not built yetGap. The phone sends |
yes | no |
Send the four radio parameters as one set radio
NotesMobile bug. The generated table says all four go together and the firmware rejects a partial set; the phone sends them individually. The correct |
yes | no |
Clamp numeric fields to their range in the client
NotesThe phone shows the range as a hint and relies on the node to refuse. |
yes | no |
| Choice fields as a picker, bools as a toggle, passwords masked | yes | yes |
Read the neighbour list
NotesSame parse, same handling of an ambiguous key prefix. |
yes | yes |
Neighbours on a map
NotesDesktop embeds a second map in the admin window; the phone hands off to its one shared map. Both say how many could not be placed. |
yes | yes |
| Neighbours viewable without logging in | yes | yes |
| Read a repeater's region tree | yes | yes |
| Toggle flood allow/deny, set home, add a region | yes | yes |
Remove a region
Not built yetGap. The phone has |
yes | no |
Save the region table to flash
NotesNot implicit on either — the firmware keeps it in RAM until told. |
yes | yes |
Read and set the default/wildcard scope
Not built yetGap. |
yes | no |
Copy a whole region tree from another repeater
NotesDiff-only, depth-first, with a trailing save. Ported deliberately. |
yes | yes |
Cancel a region copy in flight
NotesBoth say plainly that what was already sent stays on the node. |
yes | yes |
| Free-text CLI console | yes | yes |
Console log with sent / reply / error colouring
NotesThe phone folds everything that is not sent-or-reply into one "note" kind. |
yes | partial |
Command history recall
Not built yetGap; less pressing without arrow keys. |
yes | no |
Quick read-only query buttons
NotesDesktop has 12, the phone 11. |
yes | yes |
Timeout and retry on *every* command, including typed ones
Not built yetGap. The phone queues group reads, region copies and logins, but a typed console command is fired and forgotten. |
yes | partial |
| Send advert, flood or zero-hop | yes | yes |
| Reboot, with confirmation | yes | yes |
| Sync the node's clock | yes | yes |
Clear stats
Not built yetGap. |
yes | no |
Factory reset as a button
NotesConsole-only on both, deliberately. |
no | no |
Remote firmware update of a repeater
NotesNot built on either. |
no | no |
Overview page: board, version, route, last heard, key
Not built yetGap. On the phone these are console queries whose output is not laid out or kept. |
yes | no |
| Cache ver for the repeater list | yes | yes |
Cache board for the repeater list
Not built yetGap, and admitted in the phone's own comment. |
yes | no |
Firmware and flashing 43
Firmware library
| Feature | Desktop | Mobile |
|---|---|---|
Board and role catalogue (114 boards, 14 roles)
Notes
|
yes | yes |
Catalogue is advisory — an unknown board is still flashable
Deliberate choiceChoice on both. |
yes | yes |
Longest-match board parsing from the filename
Notes
|
yes | yes |
Local library with a shared index.json
NotesSame format deliberately, so one library serves both after a restore. |
yes | yes |
| Rescan the directory and rebuild a missing index | yes | yes |
Import a local .bin / .uf2 / .hex / .zip, de-duplicated by hash
NotesDesktop uses a file dialog; the phone must use Android's document picker. |
yes | yes |
Download firmware from the network
Deliberate choiceChoice on both. Neither client has any network code for this; the whole design assumes no internet. |
no | no |
Show the SHA-256 digest
NotesBoth compute and store it; only the desktop shows it. |
yes | no |
| Edit guessed metadata after import | yes | yes |
| Flag an image whose metadata was guessed, not reviewed | yes | yes |
Export an image back out of the library
Platform limitPlatform. App-private storage on Android is otherwise unreachable. |
no | yes |
Verify a signature or publisher
Not built yetGap on both. Neither authenticates firmware provenance. |
no | no |
Record when an image was last flashed, and to what
NotesSame JSON keys. |
yes | yes |
| Library included in a backup | yes | yes |
Warn when the connected radio's protocol is newer than the client
Not built yetGap. |
yes | no |
"A newer version is available" for the connected radio
Not built yetGap on both. |
no | no |
Flashing
| Feature | Desktop | Mobile |
|---|---|---|
Flash an ESP32 by driving esptool as a subprocess
Platform limitPlatform. There is no Python to shell out to on a phone. |
yes | not applicable on this platform |
Flash an ESP32 by implementing the ROM bootloader protocol
NotesWritten from esptool's own source; held to its byte layout by |
no | yes |
Upload a stub loader
Platform limitPlatform. A stub means shipping a compiled blob per chip family. |
yes | no |
Flash block size
NotesConsequence of the stub: the ROM loader's own limit is smaller, so the phone is slower. |
16 kB+ | 1 kB |
Whole-chip erase
Platform limitPlatform. |
yes | no |
Guard the radio's identity before an erase, offering to save a profile
NotesNothing to guard against without erase. |
yes | not applicable on this platform |
| Identify the chip on demand | yes | yes |
Refuse to write when the image's chip does not match the board
Not built yetGap on the desktop, which relies on the operator reading the filename. |
no | yes |
Write baud rate
NotesThe phone has no stub to manage a rate change, so it syncs conservatively. |
460800 | 115200, raised after handshake |
Verify the write by MD5 readback
NotesAutomatic inside esptool on the desktop; explicit and non-fatal on the phone. |
yes | yes |
Cancel a flash in progress
Not built yetGap. The phone's write loop has no stop hook. |
yes | no |
Live progress percentage and a streamed log
NotesDesktop scrapes esptool's output; the phone counts its own bytes. |
yes | yes |
Disconnect the radio session before flashing
Platform limitPlatform. The desktop shares one serial port; the phone's BLE and USB are separate. |
yes | not applicable on this platform |
| Flash a UF2 board by copying to its bootloader drive | yes | yes |
Identify a bootloader drive by INFO_UF2.TXT, never by name
NotesSame rule, ported deliberately. |
yes | yes |
Drive access
Platform limitPlatform. Scoped storage forbids a raw path. |
mounted path | Storage Access Framework |
Check the UF2's family and board against the drive before writing
Not built yetGap on the desktop. The phone has a full verdict engine with its own tests. |
no | yes |
Re-check the drive at the moment of writing, not just when picked
NotesGuards against the cable being moved in between. |
no | yes |
Treat the drive vanishing mid-write as success
NotesCorrect UF2 semantics on both. |
yes | yes |
USB serial: CDC-ACM
NotesThe desktop uses the kernel's driver; the phone hand-rolls one. |
yes | yes |
USB serial: CP210x
NotesVendor requests implemented in Java on the phone. |
yes | yes |
USB serial: CH340, FTDI, PL2303
Platform limitPlatform. Android has no generic USB-serial driver. Each is listed with its bridge named and refused, rather than opened and left to fail at sync. |
yes | no |
List an unsupported device with a reason instead of hiding it
NotesThe desktop does not filter at all, so the question does not arise. |
not applicable on this platform | yes |
Android USB permission prompt per device
Platform limitPlatform. |
not applicable on this platform | yes |
Reset into the bootloader over DTR/RTS
Notesesptool does it on the desktop; the phone reimplements it in Java per bridge type. |
yes | yes |
| Application-only vs merged image, with the right offset | yes | yes |
Tell the user to reconnect afterwards rather than auto-reconnecting
NotesSame wording. |
yes | yes |
Profiles and sync 112
Profiles and identity
| Feature | Desktop | Mobile |
|---|---|---|
Save the radio's current state as a named profile
NotesSame JSON format, readable by either client. |
yes | yes |
| Include the identity in a profile, opt-in per save | yes | yes |
| Profile written owner-read-only on disk | yes | yes |
Apply a profile with independent choices of what to write
NotesThe phone offers three fixed bundles instead of free checkboxes. |
yes | partial |
Type "REPLACE" to confirm writing an identity
Not built yetGap. The phone has a warning and a danger-styled button, but no typed gate. |
yes | no |
Delete a profile, warning if it holds the only copy of a key
NotesThe phone deletes on one tap with no confirmation at all. |
yes | partial |
Import or export a single profile file
Not built yetGap. On the phone a profile can only travel inside a full backup. |
yes | no |
Mint a brand-new identity in the client
Not built yetGap, and deliberate for now: |
yes | no |
| Import a private key onto the radio | yes | yes |
| Export the radio's private key | yes | yes |
Show your own public key
NotesDesktop: in the profile chip and switcher tooltips only, never as a field. |
partial | yes |
QR code of your identity
Not built yetGap on both. |
no | no |
| Change your node name | yes | yes |
| Warn that replacing an identity is irreversible | yes | yes |
Profiles: which radio's world you are looking at
| Feature | Desktop | Mobile |
|---|---|---|
Every observation stamped with the radio it was heard through
NotesOne migration, written once as |
yes | yes |
Primary keys widened where one radio's view is not unique
Notes
|
yes | yes |
Dedupe index leads with the profile
Notes
|
yes | yes |
Safety copy of the archive before the schema moves
Notes
|
yes | yes |
The migration key is never hardcoded
NotesEach device stamps its rows with the key it already holds, so the migration cannot be applied to the wrong archive. |
yes | yes |
Registry of profiles: key, name, pending flag, last used
NotesKeyed by the radio's own public key rather than a local number, so two archives can be compared later without two devices' "profile 5" colliding. |
yes | yes |
Provisional pending: profile for an archive with rows but no usable key
Notes64 hex characters becomes the profile key; anything else becomes |
yes | yes |
A fresh archive with no rows and no key gets no profile at all
NotesIt acquires one when a radio first says who it is. |
yes | yes |
Bind a provisional or unscoped archive to the radio that turns up
NotesOne transaction per scoped table, and the provisional profile is deleted as its rows leave. |
yes | yes |
Binding moves settings one key at a time, not in one UPDATE
NotesThe unscoped key is |
yes | yes |
Binding onto a profile that already holds rows is refused
NotesFolding two populated scopes needs a merge rule — which |
yes | yes |
Read markers and the default region follow the profile
NotesOne classification function per client, and |
yes | yes |
An unclassified setting is treated as device-local
NotesThe safe direction to be wrong in: it stays put rather than following a switch. A test forces the decision to be made — but it walks the Python package only, so a phone-only key such as |
yes | yes |
The scope is held by the store, never passed to a query
NotesSixty-odd SQL sites on each side; one missed predicate would show one radio's contacts inside another's chat list, silently. A source-inspection test reads both |
yes | yes |
Profile switcher
NotesDesktop: a chip at the head of the rail, because the answer scopes every destination below it. Mobile: a Profile page under More. |
yes | yes |
The key shown beside each profile, not only the name
NotesA name is a label — it can be renamed and two radios can share one. Desktop puts it in the chip and menu tooltips, the phone in the row itself. |
yes | yes |
Active profile visible in the chrome without opening the switcher
NotesThe desktop chip names the profile on every page. The phone's toolbar names the attached radio, which is a different question the moment one profile is being read while another radio is connected. |
yes | partial |
| Switcher marks which profile is the radio attached now | yes | yes |
| Switcher marks a profile not yet bound to a radio | yes | yes |
Switching rebuilds every view, and empties the open conversation
NotesThe scrollback belongs to the profile that was active when it was loaded; leaving it on screen under a new name is the exact confusion scoping exists to prevent. |
yes | yes |
A radio that is not the active profile is an offer, never an action
NotesSwitching for the user moves them out of what they were reading; re-pointing the profile at the new key would relabel one radio's whole history as another's. |
yes | yes |
The offer gates the whole post-handshake sequence, not just the UI
NotesRegion read, contact sync, channel read and the message drain all write into the active scope, so all four stop until it is answered. Losing a minute of a sixteen-deep queue is a far smaller loss than sixteen messages filed under a radio that never heard them. |
yes | yes |
Answering the offer resumes the sequence that was stopped
Notes
|
yes | yes |
Declining leaves the radio connected and unsynced, and says so
NotesDesktop puts it in the status line, the phone in the header status. Quietly resuming the sync into the wrong scope is the failure both are avoiding. |
yes | yes |
The offer is put in front of the user rather than left to be found
NotesDesktop opens a modal named after what each button does. The phone brings the Profile page to the front, because a stopped sync with nothing on screen to explain it is indistinguishable from a broken app. |
yes | yes |
Add the attached radio as a profile from the switcher
NotesThe desktop menu offers it whenever the attached radio has no profile. On the phone a new radio becomes a profile only through the connect-time offer. |
yes | partial |
The composer names the identity a send would go out under
NotesThe desktop says "Sends via " whenever it can send. The phone shows the line only when it cannot, so the ordinary case is unlabelled. |
yes | partial |
Sending refused when the attached radio is not the active profile
NotesReading is always allowed — the archive is offline — but a message typed under one profile and transmitted by another radio cannot be taken back. The desktop disables the button and repeats the refusal in |
yes | partial |
Any profile readable with nothing connected
NotesThe archive is offline. Only transmitting needs a radio. |
yes | yes |
Map, nodes, repeaters and chat all drawn from the active profile
NotesThe map's own-node marker included: it takes the active profile in preference to |
yes | yes |
Profiles from a restored archive picked up without a restart
NotesBoth stores re-read the active profile as the file is replaced, and both rebuild their lists. The desktop leaves the rail chip and the composer line naming the previous profile until the next event refreshes them. |
partial | yes |
Rename a profile
NotesThe desktop store has |
no | no |
Delete a profile, and everything observed through it
Notes
|
no | no |
Merge two profiles
NotesNot what §18 does. The merge rules fold two archives together and are explicit that a row never moves between profiles; folding one radio's history into another's is a different act, needs an answer to which |
no | no |
Merged "all profiles" views
| Feature | Desktop | Mobile |
|---|---|---|
An "all profiles" switch
NotesDesktop: a checkable entry in the rail's profile menu, because that is where the question of scope already lives. Mobile: a toggle on More → Profile. Off by default on both, and stored device-locally under |
yes | yes |
Hidden or disabled while there is only one profile
NotesThe desktop disables it and says why ("Only one profile — nothing to merge"); the phone hides it. Both also refuse at the read — |
yes | yes |
| The map merges | yes | yes |
| The nodes list merges | yes | yes |
The repeaters list merges
NotesBoth filter the merged set the same way: infrastructure advert types, held as a contact by some radio. |
yes | yes |
Conversations never merge
NotesChat, the conversation list, unread counts and the nav badges stay scoped whatever the switch says. |
yes | yes |
Deduped by public key
NotesA node both radios heard is one pin and one row. |
yes | yes |
Merged name, advert type, is-contact, is-mine
NotesName and type from whichever radio heard it last, because that is the radio that heard it renamed; contact and mine are true if any radio says so. |
yes | yes |
last_heard the newest, advert_count the sum
NotesBoth are answers about your radios collectively rather than numbers attributed to one of them. |
yes | yes |
SNR, hop count and route kept per radio, never merged
NotesThey live in the sightings. Averaging two radios' SNR would invent a reading nothing took. |
yes | yes |
Per-profile provenance line
Notes"Horsens Havn, 3 hops · ADT-DK8700, direct" — a |
yes | yes |
Provenance replaces SNR and route in the merged table
Deliberate choiceChoice on the phone: it adds the third line and keeps the active profile's own SNR and hop count beside it, falling back to the newest sighting when the active radio never heard the node at all. Wider rows, and one number on screen that the column header no longer accounts for. |
yes | no |
Long-form provenance, per radio, on demand
Not built yetGap. The desktop's cell carries a tooltip giving each radio's SNR, last-heard and advert count separately; the phone has no equivalent surface. |
yes | no |
Provenance follows the column it sits under
Notes"Route" means two different things and both clients had already chosen: the repeaters list shows the outbound route from each radio's contact record, because the question that list answers is whether you can reach the thing, while nodes and the map show the inbound path of the last advert. |
yes | yes |
Provenance on the map
NotesEach client built one of the two surfaces |
partial | partial |
Merged position: the newest fix wins
NotesMobile bug. |
yes | no |
Merged position track
Not built yetGap, and structural. Two radios watching one node moving saw the same journey and each filled gaps in the other's view of it; the desktop stacks every profile's track and re-sorts by time. The phone has no track of any kind — its map draws contacts' |
yes | no |
Merged repeater rows still carry unread and a preview
NotesThe phone's repeaters list is built from the conversation list rather than from contacts, so the merge is split down the middle: the world half of each row merges, the conversation half comes from the active profile alone, and a repeater only the other radio has ever seen gets no badge and no preview. Tapping it opens a chat, an empty one, in the active profile. That is the truth rather than a bug. The desktop's list is built from contacts and has no such seam. |
not applicable on this platform | partial |
The merged view says that it is merged
NotesThe desktop appends "across all profiles" to the summary line on all three views. The phone does it on Nodes and the Map and not on Repeaters. |
yes | partial |
"My repeaters" deliberately left scoped
NotesArgued rather than overlooked ( |
yes | yes |
Battery and firmware resolved across profiles on that scoped page
Not built yetGap, and §5b.5's leftover. Two radios that have both read a repeater hold two |
yes | no |
No cross-profile SQL anywhere
NotesEvery merged read runs the already-scoped query once per profile and stacks the answers ( |
yes | yes |
The scope scanner requires a *bound* predicate, not a mention
NotesA merged query that selects |
yes | yes |
An allowlist naming every function allowed to re-scope the store
NotesBecause the SQL scanner cannot see a merged read at all, and wrapping |
yes | yes |
Sync: merging two archives, and moving one over the network
| Feature | Desktop | Mobile |
|---|---|---|
A row identity that survives leaving its file
Notes
|
yes | yes |
origin is the writing *device*, not the profile's owner
NotesConnecting the phone's radio to the desktop — to flash it, or because BLE is being awkward — makes both machines write into that radio's profile. Keyed on the profile, both count from their own highest row and a merge folds unrelated rows together in silence. Keyed on the device they are distinguishable and the union is simply correct. Single-writer survives as an optimisation, never as something correctness rests on. |
yes | yes |
A device id that never travels as a setting
NotesA lazily-minted uuid, classified device-local in both stores, because two archives sharing one would allocate colliding sequence numbers. It is announced in a sync hello — which is how two devices that turn out to be copies of one archive are refused rather than merged. |
yes | yes |
Pre-v11 rows attributed deterministically
Notes
|
yes | yes |
Content as the fallback identity
NotesThe price of that determinism is that two devices which both wrote to one profile before v11 genuinely share a legacy origin, with overlapping sequence numbers. So identity is checked first — an indexed lookup, and the whole answer in every normal merge — and only the colliding rows are then compared by payload. A row with no payload-equal counterpart here is one this archive does not have, whatever its label claims, and it is re-homed onto a free sequence number rather than dropped. |
yes | yes |
Tombstones, so a deletion is not undone by the next merge
NotesAbsence is also what "I have not heard of it yet" looks like, so without them the union puts every deleted row back and the next exchange undoes every deletion the user has ever made — silently, and repeatedly. The desktop writes them from its only two deleting paths, |
yes | partial |
A row re-created after its deletion outranks the tombstone
NotesA contact the radio has re-advertised is newer information than its removal, and |
yes | yes |
Deleting a profile deliberately leaves no tombstone
NotesIt takes a whole radio's history with it, and for received messages the archive is the only copy. A tombstone would make that destruction contagious: delete on one device, and the next merge deletes on the other. It stays a local act, and a merge will happily bring the profile back. The safe direction to be wrong in. ( |
yes | yes |
Last-writer-wins, per field and on a short list
Notes
|
yes | yes |
Everything the radio said moves as one block
NotesName, type, flags, route and gps are one observation, already stamped |
yes | yes |
Ties resolved on the value
NotesWhich makes every rule a maximum over a total order — commutative and idempotent, so the order merges run in cannot matter. It also settles the one tie that actually occurs: every row migrated from v10 carries stamp 0, and |
yes | yes |
Read markers translated, not copied
NotesA marker's value is a |
yes | yes |
The phone writes what a merge needs, even where it cannot merge
Notes
|
yes | yes |
Restore merges rather than replaces
NotesChanged on both. Restore used to overwrite the database wholesale; both dialogs now offer Merge and Replace over the same rules a sync runs. The desktop makes Merge the default button and reports what it folded in; the phone offers two equally-weighted buttons. |
yes | yes |
Restore merges from the headless CLI too
Not built yetGap. |
no | not applicable on this platform |
A merge cannot move a row between profiles
NotesThe merge is the one operation whose subject is the file rather than the profile on screen — a backup holds every profile, and restoring only the active one's half of it would be a quiet way to lose the rest — so ten of its statements are exempt from the scope scanner, named one at a time with what each does. The functional half is a test that writes a second profile's backup into a store showing the first and asserts every row lands under the |
yes | yes |
Exchange archives with a paired device over the local network
NotesTCP 45737. Two roles and not two programs: either device may dial, and the exchange is symmetric. |
yes | yes |
Pair by typing a code shown on the other device
NotesBoth clients can show a code and type one. Ten characters of Crockford's base32, normalised before use — uppercased, dashes dropped, |
yes | yes |
One attempt, three minutes, fifty bits
NotesSaid plainly rather than dressed up: this is not a PAKE, and an attacker who reaches the pairing listener and gets one confirmation out of it can attack the code offline. So the defence is arranged differently — a wrong code closes the listener, the window closes itself, and 200,000 PBKDF2 iterations make that one offline attempt cost about half a second a guess. |
yes | yes |
Both certificates pinned, compared as exact DER afterwards
NotesNo CA and no hostname check: a peer is an address the user typed and the certificate that was pinned, which is a much smaller set than "something that verifies". Load-bearing rather than belt-and-braces, because saved repeater passwords are in scope for the merge and therefore cross this socket. |
yes | yes |
Mint the certificate the client uses
NotesDesktop through |
yes | yes |
QR code instead of typing the code
Not built yetGap on both. Nothing in the format depends on how the code got across. |
no | no |
Only the delta is sent for the append-only tables
NotesThe receiver sends a high-water mark per (profile, origin, table) and the sender selects above it — the seek |
yes | yes |
The small tables sent whole, every time
NotesProfiles, contacts, channels, repeater state, settings and tombstones: a few hundred rows against forty-odd thousand positions, so a watermark for them would be complexity bought with nothing — and their merge rule is a maximum over a total order, so re-sending is idempotent by construction. |
yes | yes |
Device-local settings filtered out before they leave
NotesAt the sender and at the receiving merge, deliberately twice. A listen port that followed the archive would have every paired device fighting over one socket. |
yes | yes |
Nothing applied until the whole stream has arrived
NotesRows land in a scratch archive and are folded in with one call to the same merge a restore uses, in one transaction. A connection that dies halfway leaves the archive exactly as it was and the cursor exactly where it was, so the next attempt simply repeats. |
yes | yes |
Sync does not get a second implementation of the merge
NotesReceived rows go through |
yes | yes |
The wire format pinned by conformance vectors
Notes
|
yes | yes |
The cross-client exchange exercised end to end
Notes
|
yes | yes |
Peers and the sync identity kept outside the archive
Notes
|
yes | yes |
Manual "sync now", per peer
NotesAnd that is the whole trigger. |
yes | yes |
An automatic trigger, or discovery of a peer on the network
Not built yetGap on both, and the one being closed as this is written: another agent is building discovery and the automatic trigger now, so nothing in this section describes them. Until they land, a peer is a host and port somebody typed, and nothing re-resolves one whose address has changed. |
no | no |
Say which network the device is on
NotesBoth show the local IPv4 address rather than the network's name. |
partial | partial |
Say plainly when there is no network, rather than showing a blank
NotesThe phone's wording names Wi-Fi; the desktop's is the flatter "Not on a network." |
yes | yes |
Tell "listening" from "nothing paired" from "not accepting"
NotesThree states that are easy to conflate, said apart on both. |
yes | yes |
"Last synced" per peer, rather than "reachable"
NotesA fact rather than a guess. The phone's peer record even carries a |
yes | yes |
Unpair a peer
NotesThe desktop confirms first; the phone unpairs on one tap. |
yes | partial |
Rename a peer
Not built yetGap on both: a peer's name is whatever it called itself at pairing, for ever. |
no | no |
Name this device, as its peers will see it
Not built yetGap. |
yes | no |
Change the listen port
Not built yetGap on both. Each side has the setter and neither has a widget. |
no | no |
Your data 25
The archive, backup and restore
| Feature | Desktop | Mobile |
|---|---|---|
SQLite archive as the record of the mesh
NotesThe radio's own storage is smaller and destructive; the app is the only full copy. |
yes | yes |
| contacts, channels, messages, adverts, repeater_state, settings | yes | yes |
positions (position history)
Not built yetGap. The mobile store never references the table. |
yes | no |
node_paths (observed routes)
Not built yetGap; matches trace/path discovery not being ported to the C++ core. |
yes | no |
sent_as_key, sent_as_name, sent_region, flooded columns written
Not built yetGap. Present in the shared schema, never populated by the phone. |
yes | no |
WAL journal, synchronous=FULL
NotesSame reasoning: the radio deletes a message once you have read it. |
yes | yes |
Archive file restricted to the owner
NotesAndroid's per-app sandbox already does this. |
yes | not applicable on this platform |
Forward-only migrations tracked in schema_version
NotesThe |
yes | yes |
Migrate an existing database, not only create a fresh one
NotesVerified on both — a natural thing to assume otherwise. |
yes | yes |
| Refuse a database newer than the build understands | yes | yes |
Automatic pruning or retention of old messages
Deliberate choiceChoice on both: the archive is the only copy. |
no | no |
Backup to a single .mcbackup container
NotesSame format deliberately, so a phone backup opens on the desktop. |
yes | yes |
Consistency across the WAL when backing up
NotesDesktop uses SQLite's backup API; the phone checkpoints then copies. |
yes | yes |
Choose which parts to include
NotesThe phone always takes everything. |
yes | no |
| Merge tiles, profiles and firmware on restore | yes | yes |
Merge the database too, rather than replacing it
NotesNew on both, and the rules are §18's. Replace is still offered; it is no longer the only answer. |
yes | yes |
| Keep a safety copy of the database before restoring | yes | yes |
Restore a bare pre-container .db
Not built yetGap. |
yes | no |
| Refuse an archive newer than the build understands | yes | yes |
| Storage usage shown in the UI | yes | yes |
"Delete everything" / reset the app
Not built yetGap on both. |
no | no |
Scheduled or automatic backup
Not built yetGap on both. |
no | no |
Database encrypted at rest
Not built yetGap on both; it relies on file permissions and the app sandbox. |
no | no |
App lock (PIN or biometric)
Not built yetGap on both. |
no | no |
Android's own backup kept away from the archive
NotesWas the open decision here; it is taken. |
not applicable on this platform | yes |
The app 66
First run, where you are, and which build this is
| Feature | Desktop | Mobile |
|---|---|---|
First-run wizard
NotesFive steps: where you are, find a radio, radio settings, your name, done. Gap on the phone, which drops a new install straight into an empty Chat. |
yes | no |
The wizard owns no settings of its own
NotesEvery one of its signals lands on a path that already existed. See above. |
yes | not applicable on this platform |
Knows a fresh install from a used one
NotesA marker file beside the archive and a setting, because they fail differently: a fresh install that has merged a backup has the setting and not the marker, and should still be walked through. An archive that already holds messages, contacts or a remembered port and has neither is quietly adopted as set up rather than interrogated. |
yes | not applicable on this platform |
Skippable, and re-runnable afterwards
NotesSkip is offered on every step and closing the window counts as one. Settings › General carries "Run setup again…" and a line saying which of finished, skipped or adopted actually happened. |
yes | not applicable on this platform |
Country selection
NotesOne device-local setting. Not profile-scoped, deliberately: it is where the operator is sitting, not a property of a radio, and two radios in one country is the normal case — scoping it would ask the same question once per radio and let the two answers disagree. |
yes | no |
Country selects the band, and its four radio parameters
NotesFrequency, bandwidth, spreading factor and coding rate. |
yes | no |
The preset is offered, never applied
Notes
|
yes | not applicable on this platform |
Country gives a transmit-power ceiling, as a warning
NotesA question and not a clamp: an operator with a licence and a reason may well mean to go higher, and what they must not do is go higher without being told they have. The note that comes with it is the only place the table says its number is conducted power at the radio rather than radiated power at the antenna, so the two are one label and never two — split them and the claim becomes a lie by omission. |
yes | no |
Country gives local links
NotesAlongside two project links, which are shown whatever the country. |
yes | no |
Countries in the table
NotesDenmark, one band, EU868. The mechanism is general and the data is not, on purpose: an unverified band must not ship, and a test enforces the rule rather than trusting it. |
1 | not applicable on this platform |
The country table generated for the other client
Notes
|
yes | not applicable on this platform |
About: the app's own version
Not built yetGap closed on the desktop, which until now could not tell a user which build they were running. Taken from |
yes | yes |
About: the archive's schema version
Not built yetGap. "Which build" and "which archive format" are different questions the moment two devices can disagree, and the desktop says both — and says so more loudly when the file and the build disagree. |
yes | no |
About: the connected radio's firmware
NotesDesktop adds the build date and the manufacturer, which it already decoded and the phone still drops (§2). |
yes | yes |
About: copy the lot for a bug report
Not built yetGap. One line, one button. |
yes | no |
| Where About lives | own Settings tab | a card inside Radio settings |
Diagnostics
| Feature | Desktop | Mobile |
|---|---|---|
| Packet counters, sent and received | yes | yes |
| TX/RX airtime, flagged when nothing has been transmitted | yes | yes |
| Noise floor and last signal | yes | yes |
| Uptime and outbound queue depth | yes | yes |
"Transmit and measure" verdict
NotesThe only way to answer "is this radio actually transmitting" without a second one. |
yes | yes |
Default flood scope read from the device, flagged when scoped
Not built yetGap. |
yes | no |
Dedicated link-state card
NotesThe desktop uses its status bar. |
no | yes |
Raw frame or hex view in the GUI
Not built yetGap on both. |
no | no |
Raw frame dump anywhere
Platform limitPlatform. |
yes (CLI) | no |
| Connection statistics (frames, bytes, discards) | yes (CLI) | no |
Log file, log level, or in-app log viewer
Not built yetGap on both. The phone's only trace is |
no | no |
Headless CLI: probe, monitor, contacts, send, advert, backup, restore
Platform limitPlatform. |
yes | not applicable on this platform |
Notifications
| Feature | Desktop | Mobile |
|---|---|---|
System notification on a new message
NotesD-Bus on the desktop, the Android shade on the phone. Not mobile-only, as is easy to assume. |
yes | yes |
| One notification per conversation, replaced rather than stacked | yes | yes |
| Suppressed for the conversation you are looking at | yes | yes |
| Cleared once the conversation is read | yes | yes |
Choose when to notify (always / unfocused / never)
Not built yetGap; the phone has one fixed policy. |
yes | no |
Escalated for a mention
Not built yetGap. |
yes | no |
Notification when the app stops trying to reconnect
NotesThe phone posts it on the foreground service's own low-importance channel, so it cannot make a sound and silencing one silences both — and only when the app is not the thing on screen, because a notification for something you are looking at is noise. The desktop's give-up goes to the status bar and nowhere else. |
no | yes |
Fallback when no notification daemon exists
NotesFlashes the taskbar, and says so in Settings. Degrading to silence is fine; pretending to have notified is not. |
yes | not applicable on this platform |
Runtime notification permission
Platform limitPlatform, Android 13+. |
not applicable on this platform | yes |
Sound or vibration configurable in the app
NotesBoth defer to the OS. |
no | no |
Do-not-disturb inside the app
Not built yetGap on both. |
no | no |
Unread badge on the OS launcher or taskbar icon
Not built yetGap on both; both badge their own nav instead. |
no | no |
App shell, theme and platform integration
| Feature | Desktop | Mobile |
|---|---|---|
Navigation
Platform limitPlatform convention. |
rail | bottom bar + More |
Destinations
NotesThe phone shows three and overflows the rest. |
Chat, Repeaters, Nodes, Map, Manage repeaters, Settings | Chat, Map, Settings, then Radio, Repeaters, Nodes, My repeaters |
Unread badge on a nav destination
NotesGeneral on the desktop; the phone hardcodes the Chat slot. |
yes | yes |
Resizable panes
Platform limitPlatform. |
yes | not applicable on this platform |
Remember window geometry and splitter sizes
Not built yetGap on the desktop — re-laid out on every launch. |
no | not applicable on this platform |
Toggleable details panel
NotesOff until asked for, and withdrawn entirely on a channel: a channel has no node behind it — no key, route, signal or position — so the toggle goes with the panel rather than sitting there doing nothing. |
yes | no |
Right-click context menus
NotesLong-press is the touch equivalent, and the phone has it. |
yes | not applicable on this platform |
Keyboard shortcuts
Platform limitPlatform. |
yes | not applicable on this platform |
Menu bar
Deliberate choiceChoice: everything is reachable from the UI. |
hidden | not applicable on this platform |
Back navigation into and out of a sub-page
NotesThe phone returns to wherever admin was opened from. |
not applicable on this platform | yes |
Hardware Back button handled explicitly
Not built yetGap; it falls through to the default Activity behaviour. |
not applicable on this platform | no |
Light / dark / follow system
Not built yetGap. The phone always follows the OS. |
yes | no |
React to a system theme change without a restart
Not built yetGap on both — each reads the setting once at startup. |
no | no |
Accent colour or font size customisation
Not built yetGap on both. |
no | no |
Icons drawn in code rather than shipped as assets
NotesSame choice, twice: nothing to package, and they recolour with the palette. |
yes | yes |
System tray, minimise/close to tray, start minimised
Platform limitPlatform. |
yes | not applicable on this platform |
Autostart at login
Platform limitPlatform. |
yes | not applicable on this platform |
Single-instance guard
NotesThe phone gets it free from |
no | yes |
Foreground service to hold BLE open in the background
Platform limitPlatform — a desktop app is never suspended. Type |
not applicable on this platform | yes |
Reconnect the BLE link from the background
Not built yetGap closed, and it was the binding one. The service used to stop the moment a link dropped — deliberately, because nothing was working towards getting it back and an ongoing notification promising otherwise would have been a lie. Something is now, so it stays up, and its text says which of the two states it is in rather than claiming the better one. That the service stays is what makes the retry possible at all: the ladder's timers are Qt timers, and taking the service down between attempts takes the attempts with it. What remains impossible, and must, is the third state — foreground, no link, nothing trying — so giving up clears the intent before it stops the service. Measured on the handset for one drop-and-recover cycle; the long end of the ladder and the give-up were not. |
not applicable on this platform | yes |
| Keep the screen on while in use | no | no |
Strings wrapped for translation
NotesThe phone is translation-ready; the desktop has no i18n mechanism at all. Neither ships a translated build. |
no | yes |
Locale-aware 12/24-hour time
Not built yetGap on both; hardcoded 24-hour. |
no | no |
App version shown in the UI
Not built yetGap closed on the desktop — Settings › About, §20. |
yes | yes |
Update check
Not built yetGap on both. |
no | no |
ABIs built
NotesFine for any recent phone; the bundled OpenSSL is arm64 too. |
not applicable on this platform | arm64-v8a only |
Nothing matches that.
This table is generated from docs/FEATURES.md in
the client repository, which is where it is maintained. The document is the
source; this page is a rendering of it. If you were after the short
version, it is on What it
does.