Files
mc-webui/docs/whatsnew.md
T
MarekWoandClaude Opus 5 8f1334ee00 fix(map): reject impossible advert coordinates before they blank the map
A tester's cached-contacts map came up empty and Path Analyzer drew routes
across the globe. Two of his 2131 cached contacts carried garbage positions
from corrupted adverts - lat 1642 / lon -323 and lat 309 / lon -1768.

Leaflet clamps latitude when projecting but not longitude, so fitBounds()
spanned 1797 degrees, dropped to zoom 0 and centred on lon -870; all 2273
real markers were rendered several world-widths outside the viewport.
Reproduced against his DB: zoom 0, bounds -1431..-309. The device-only view
was fine because both bad rows are cache-only.

Four layers, so no single miss can blank a map again:

- app/geo.py: one definition of a usable coordinate, applied on write in
  Database.upsert_contact() so the cache stops storing garbage, and on API
  output so a cache poisoned by an older build heals itself with no
  migration. Corrupted advert types (61, 126, 152, 210 seen in the wild)
  are logged but kept raw - they already render as UNKNOWN.
- static/js/geo-utils.js: hasValidGps()/withValidGps()/fitMapToPoints()
  replace eight copies of the same check across six files. Path Analyzer
  had a looser variant that also accepted lon == 0, which put nodes off
  west Africa and stretched routes across the map; it now shares the rule.
- Per-marker try/catch and a bounds validity check, so an entry that still
  slips through costs its own pin rather than the whole map.

Verified against the tester's DB in a container: cached map 2273 markers,
lon span 1797 -> 19 degrees, centred on Poland; the route that drew the
world-spanning line (msg 29499, token 72AF) now renders as a real
Krakow-Kielce path with the bad hop left unresolved. Live local instance
regression-checked on all five map pages. tests/test_geo.py: 43 passed;
full suite 290 passed, 2 pre-existing failures unrelated to this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 11:35:53 +02:00

65 KiB
Raw Blame History

What's New

User-facing summary of changes since the last main release. Maintained on dev and finalized before each merge to main.

Releases are numbered MAJOR.MINOR.PATCH from 2.1.0 onward and tagged on GitHub — MAJOR when a deploy needs manual action from you, MINOR for new features, PATCH for fixes only. Sections before 2.1.0 are dated only; the project wasn't tagging releases yet. The exact build running on your server (a date and commit, like 2026.07.26+95d96ec) is still shown in the menu under the release number, for pinning down a specific deploy.

For deep technical notes, see architecture.md. For the full git history, run git log.


Unreleased

Features

  • Notifications now say who wrote what, where — and tapping one takes you there. A new message used to produce the same line every time, New: 1 channel, with the app's own name on top and nothing else: not the sender, not the text, not even which channel it landed on. Tapping it did no more than bring the app forward, wherever it happened to have been left. Everything needed was already arriving with the message and simply was not being used. A channel message now shows the channel as the title and sender: message underneath, a direct message shows the sender's name and the message itself, and each conversation keeps its own notification instead of one shared line, so a busy channel cannot bury a DM. When several messages arrive in the same place while you are away they collapse into one notification carrying the newest text, with a count beside the name — #krakow (4) — rather than replacing each other silently. Tapping opens the conversation it is about: the channel, or the direct-message panel on that contact. That works whether the app was already running or started from cold, in the Android app and in the browser alike, and it reaches the right place without reloading the interface when the app was merely in the background.

Fixes

  • One corrupted advert no longer empties the contacts map. Switching Cache on over the contacts map could leave you looking at a blank map of the world with not one of your contacts on it, and the Path Analyzer could draw a route as a single line stretching across the globe. Nothing was wrong with the contacts you could not see. Adverts arrive over the radio unchecked, and a packet mangled in flight can carry a position that is not a position at all — a latitude of 1642 degrees, a longitude of -1768. One such entry is enough, because the map fits itself around everything it is given: it zooms out until that impossible point is on screen, and every real contact ends up several world-widths away, outside the window. The tester who reported this had exactly two of them among 2131 cached contacts. Positions outside the range the Earth actually has are now thrown away rather than stored, thrown away again on the way out to the browser so a cache that already holds some heals itself the moment you update, and refused once more by the map itself. The contact is still kept and still listed — it simply no longer claims to be anywhere. The same check now guards every map in the app: contacts, repeaters, a repeater's neighbours and the Path Analyzer, which had its own looser version of the rule and would place a node reporting a longitude of exactly zero off the coast of Africa.

  • New messages on the channel you had open no longer arrive in silence, and old unread ones stop announcing themselves as new. Two separate faults made notifications look unreliable even while the app was awake and connected. The first: a message on whichever channel you last had on screen was quietly excluded from notifications, on the assumption that you were looking at it — which is exactly wrong once the app is in the background, and meant that anyone who left mc-webui on their busiest channel was told about every channel except that one. A channel now counts as open only while somebody is actually looking at it. The second: the first time the app compared its unread counts against the server it treated the entire backlog as having just arrived, so a page that loaded in the background could announce New: 492 channels about messages that were days old. That first comparison is now only a measurement, and anything already unread when the app starts is treated as history rather than news.

  • Still unresolved: notifications going quiet after a while on Android. The changes above fix what a notification says and where it takes you, but not the separate problem of the Android app falling silent some minutes after you switch away from it. That has a different cause — Android freezes apps that have nothing running in the foreground, which stops the connection the notifications depend on — and no setting on the phone can prevent it. Fixing it properly needs a background service in the app itself, which is planned for a later release.


2.12.0 — 2026-08-27

Features

  • An instance you can hand to strangers: demo mode. mc-webui has never had a login, which is the right trade on a home network and the wrong one the moment you share the address — to show the project off, to collect feedback, or because a store wants a dozen testers who use the app for a fortnight. What happens next is predictable: visitors assume the node is theirs and rename it, and the console is right there for anyone curious enough to type into it. Putting MC_DEMO=true in .env turns the instance read-only for everybody except you. Everything that reconfigures the radio is refused — the name, the coordinates, the frequency and the rest of the radio settings, reboot, advert and flood advert — along with everything that deletes data, everything that reaches other people's repeaters, the update trigger that rebuilds the containers, the settings that publish packets to an external broker, and the handful of things that hand out secrets such as stored repeater passwords, database backups and the server log. The console stays open for the commands that only read, so it is still worth demonstrating, and refuses the rest with a list of what it will accept. What does keep working is the part that makes a demo worth visiting: reading everything, sending messages in channels and as DMs, and changing the theme and layout, which live in your browser and affect nobody else. The refusal happens on the server, in front of every endpoint and on the console's own connection, so a stale page or a crafted request gets the same answer as a click. In the interface the locked controls are greyed out rather than hidden — the point is to show mc-webui off, and a feature nobody can see impresses nobody — with a yellow DEMO badge next to the version, a short explanation at the top of Settings and above the console, and a message rather than a silent shrug when something is clicked. Full guide in demo-mode.md.

  • Two ways to keep full access to your own demo. Locking the instance is only half of it; you still need to administer the thing. MC_DEMO_TRUSTED_NETS takes a list of networks — 192.168.0.0/16, say — whose visitors are never restricted, so from your own LAN the app behaves exactly as it always has, with nothing to type. MC_DEMO_UNLOCK_CODE sets a code you enter once under Settings → Operator access; the browser is then remembered for thirty days from any network, which is what you want when the instance is published through a tunnel and every visitor looks like it is coming from the same place. A Lock again button sits in the same spot for when you want to see the instance the way a visitor does. The cookie holds a derived token rather than the code itself, so changing the code in .env logs every unlocked browser out — which is also how you take access back. One warning worth repeating from the guide: if mc-webui is published through a tunnel or a reverse proxy, every visitor arrives from that one address, and if the tunnel runs on your LAN then listing your LAN as trusted would unlock the instance for the whole internet. mc-webui declines to make that mistake — an address that arrives with a proxy header is not matched against the trusted list unless MC_TRUST_PROXY is on — and says so in the log at startup, but the safe habit is to use the unlock code whenever the app is not reached directly.

Fixes

  • Syncing a repeater's clock now uses the server's time, and your own node's clock is kept right by itself. Pressing Sync clock under My Repeaters → Actions could set a repeater to a time that was wrong by an hour or more, and the time it reported back rarely matched what the same panel showed a minute later. Two separate things were behind it. The command mc-webui sent asked the repeater to copy the timestamp out of the packet that carried the command — and that timestamp comes from your own node, not from the server, because the firmware deliberately stamps command packets with its own clock. Your node's clock, meanwhile, is only ever set by hand and then left to run free, and on boards without a dedicated timekeeping crystal it drifts badly: the test device here turned out to be sixteen hours behind. So the button was faithfully copying a wrong time onto the repeater. mc-webui now puts the moment inside the command itself, taken from the server's clock and untouched by anything in between, and it checks your node against the server when it connects and once an hour after that, correcting it whenever it has drifted more than half a minute. One limit is worth knowing, because nothing in the app can work around it: the firmware refuses to move any clock backwards, so a node or repeater that has run fast cannot be corrected over the air at all and needs a restart before it will accept the right time. When that applies to your own node it is now written to the log instead of being retried in silence every hour.

  • Clocks now say which timezone they are showing. The same instant was being written three different ways with nothing on screen to tell them apart: the Clock row under My Repeaters → Status is drawn in your browser's timezone, the console printed the server's, and repeaters answer their own clock command in UTC. Reading 17:16 from one and 19:16 from another, minutes apart, is enough to make a perfectly healthy setup look broken. The Status row now names the zone alongside the time (23.08.2026, 21:21:01 CEST), and the console prints both readings at once (2026-08-23 21:15:50 CEST / 19:15:50 UTC), so a console reading can be held against a repeater's own reply without doing the arithmetic first.

  • The Quick Access buttons no longer sit on top of the send button, because they are now off by default. On a phone the column of round buttons down the right edge overlapped the message composer, and the bottom one landed squarely on top of send — you could see the button, but tapping it hit the floating button instead. Enough people ran into it to make clear this was the default's fault rather than theirs, so the bar is now opt-in: Settings → Appearance → Show Quick Access buttons, unticked to begin with. With it off, everything that lived on the bar — filter, search, direct messages, contacts and settings — appears in the main menu under the ☰ icon instead, so nothing becomes unreachable. If you had already made a choice there, it is kept: only browsers that never touched the setting see the new default. For anyone who likes the bar, it is unchanged once switched on, including the size and spacing sliders and dragging it by its chevron to a spot that suits you. One gap remains: the direct-message screen has its own separate bar, which can be dragged and collapsed but does not yet follow this setting.

  • A message quoting something in angle brackets no longer breaks the row of buttons under it. A bot answering with Type 'channel <name>' for a description was enough: the buttons beneath that message came out mangled, with a stray title="Quote" printed next to them, and the first two — reply and quote — stopped working. The cause was in how those buttons were built: the message text was pasted into them without being escaped, so an apostrophe closed the button early and a < ended it altogether, spilling the rest of the markup onto the page as text. The text is now escaped properly wherever it is handed to a button, in channel chat and in direct messages alike, so quotes, angle brackets and both kinds of quotation mark are just characters again. The same fix covers sender names containing an apostrophe, whose reply, map and block buttons were quietly dead for the same reason.


2.11.0 — 2026-08-20

Features

  • The Android app has been rebuilt for Android 16, and now paints the system bars to match your theme. Google requires everything submitted to Play from September 2026 to target Android 16, and that version takes away an app's ability to opt out of drawing behind the status bar and the navigation bar. Left alone, that would have pushed the top of the interface under the clock and the message box under the gesture bar. The app now measures those bars itself — along with the display cutout and the keyboard — and keeps the interface clear of them, so the layout is unchanged from what you are used to. What is new is that the strips behind the two bars take the background colour of the page rather than staying white, and follow you when you switch between the light and dark theme, without needing the app restarted. The bar icons flip between dark and light along with them, so they stay readable either way. If you run the app from the .apk in this repository, this is version 2.0; the signing key is unchanged, so it installs straight over the previous one and keeps your saved server address.

  • The Android app now has a published privacy policy. It is a requirement for anything listed on Google Play, but it is worth having regardless, because the honest answer is short: the app collects nothing, contains no analytics or advertising, and the author operates no servers to collect anything with. What stays on the phone is the address you typed, the login for your server if it asks for one, and the browser cache — each listed, with the reason it exists, alongside every permission the app requests. It is published from this repository at marekwo.github.io/mc-webui/privacy-policy.html, in English and Polish.


2.10.0 — 2026-08-19

Features

  • Opening a busy channel now takes you to where you stopped reading, not to the bottom. Until now, every channel opened at the newest message, which is the right place to be when you are up to date and the wrong place to be when you are not: catching up on a channel that had collected forty messages while you were away meant scrolling up by hand and hunting for the last thing you recognised. A red New messages line is now drawn at the boundary between what you had already seen and what arrived since, and the view opens on that line rather than at the bottom, with the last message you had read still visible above it for context. The line stays where it was put for as long as you are in the channel — it does not creep downwards as you read — so you can always tell how much of the backlog you have worked through, and messages arriving while you are reading are added below it, where they belong. It appears only when there is genuinely something to catch up on: a channel you are up to date with, or one you are opening for the first time, behaves exactly as before and opens at the newest message. The same line, and the same behaviour, applies to direct messages. Should you want the bottom immediately, the jump-to-bottom button is already there waiting, and sending a message takes you down on its own.

  • Contacts, channels and locations shared in chat now look like something you can use. Other MeshCore clients let you drop a contact, a channel or your position into a conversation, and the mesh carries it as ordinary message text — which is why, until now, it arrived here as a wall of hex: <e56c230c…3ae729:1:MarWoj mobile>, unreadable and useless. Those messages now appear as cards with a name, a type and a button, in channel chat and in direct messages alike, including every such message already sitting in your history. The button says what will actually happen before you press it: Add Contact when the key is new to you, Update name to X when you already hold that key under a different name — the key is the identity, so this renames what you have rather than creating a duplicate — Already in contacts when there is nothing to do, and for channels a plain refusal when the name you were sent is already occupied by a different key, instead of quietly shadowing the channel you already had. A shared contact goes into your cache rather than onto the device: it arrived unsolicited, device contact memory is limited, and the card offers Push to device right there if you do want to spend a slot. Channels go straight to the device, since without the key in the firmware there is nothing to decrypt with. A shared position becomes a card with View on map, a copy button, links out to Google Maps and OpenStreetMap, and — something the official app does not show you — how far away it is and in which direction. Coordinates are only recognised when they are genuinely coordinates, so "1,5 km" in a sentence is still just text.

  • You can share those things yourself, with the new + button beside the emoji button. It offers three: a contact, a channel, or a location. Your own details are the first entry of the contact list rather than a separate menu item, so sharing yourself is one tap, and the same applies to your own position in the location picker — which otherwise lets you tap the map for any point you like. The contact list covers everyone you know, cache included, with a search box because a busy mesh gives you well over a thousand of them. Sharing a channel takes its region scope along, which matters: a recipient transmitting under the wrong scope simply would not be heard. What lands in the message box is the same plain text every other MeshCore client uses, so the card shows up properly whatever app is on the other end, and you can write a comment around it. While the text is valid you get a preview of the card the recipient will see; break the text by editing it and the preview disappears, which is your warning that it will arrive as raw text. These payloads are bulky — a shared contact eats roughly 90 of the message's bytes — so the counter under the box accounts for it, and anything that will not fit in one message is refused rather than trimmed, because half a public key is worth nothing.

  • Repeater settings now tell you when a setting simply isn't there, instead of calling it an error. Repeaters on the mesh do not all run the same firmware or the same hardware, so a setting that exists on one is genuinely absent on another. Until now the panel had only two answers — a value, or a red error — so a perfectly healthy repeater that had never heard of a setting looked broken, and the section reported a failed read. Such a field now gets a grey not supported badge with a short explanation, stays locked, and is left out of anything you Apply. The section itself no longer counts it as a failure. There is one honest caveat, and it comes from the firmware rather than from mc-webui: when a repeater rejects a setting after having already stored it — which is what newer firmware does with RX boosted gain on radios that cannot do it — "not supported" says nothing about what the device now holds. Press Refresh to see the truth.

  • Three settings added for repeaters that support them. Advertisement gains two more hop limits alongside the existing one: a separate cap for flooded adverts, and a cap for flood packets carrying no region scope. Both have existed in the firmware for a while but were never exposed here. Advanced gains channel activity detection (firmware v1.17 or newer): the radio listens before transmitting and holds off if the channel is busy. On older repeaters it appears as not supported, as described above.

  • A repeater can be powered off from the Actions tab (firmware v1.17 or newer). This is deliberately not the same kind of button as Reboot. A reboot brings the repeater back on its own in a few seconds; a power-off does not, and nothing on the mesh can undo it — the node stays off until somebody physically gets to it, which for a repeater on a mast is a trip rather than a click. So it is not confirmed with a Yes/No box: you have to type the repeater's name to unlock the button, and the server checks that name again before sending anything.

  • Repeater telemetry labels the processor temperature as what it is. Repeaters on firmware v1.17 and newer report the temperature of the MCU chip itself. It arrived as a plain "Temperature" row on channel 1, which invites reading it as the temperature where the repeater is installed — it is not, and it normally sits well above room temperature. It is now labelled MCU temperature. Boards without a usable internal sensor send nothing, and in that case the row is simply absent rather than showing zero.

Fixes

  • A direct-message conversation no longer drags you back to the bottom while you are reading it. Scrolling up through an old exchange in Direct Messages held for at most a minute: the periodic refresh redrew the conversation and threw the view back to the newest message, over and over. Channel chat had never done this. Direct messages now leave the view alone unless you were already at the bottom.

2.9.1 — 2026-08-14

Fixes

  • On an iPhone, tapping a text field no longer blows the whole page up. Tap the message box, and the page zoomed in — the send button pushed off the right edge, the layout too wide for the screen — and it stayed that way after the keyboard was dismissed, until you pinched it back down by hand. It happened in Safari and in Chrome, on a freshly unboxed phone with nothing configured, and it was not the phone's fault: iPhones deliberately zoom in on a form field whose text is smaller than 16 pixels, on the assumption that you would otherwise be squinting at it. The message box was set to 14.4 pixels on narrow screens, so every tap tripped it. Worse, the app's layout is pinned to the height of the screen and does not scroll as a page, which leaves the browser nothing to scroll back — so the zoom stuck instead of undoing itself. Every text field, dropdown and search box in the app is now at least 16 pixels on a touch screen, which is the size iPhones are content to leave alone. iPads never did this, and Android phones never did either, which is why it went unnoticed for so long. On a computer the fields are unchanged.

2.9.0 — 2026-08-14

Features

  • A path can now be set straight to Direct. The path dialog could send a node's route back to FLOOD, and it could push one of your configured paths — but the third option the firmware supports, Direct, had no button. Direct means no repeater at all: the packet goes to the node and nowhere else, which is what you want for a neighbour you can reach on your own radio, and the only way to stop your traffic from being repeated across the mesh for a node sitting on the next hill. Until now, getting there meant clearing the path through the Console. A Set Direct button now sits next to Restore FLOOD, in both places paths are managed: the contact info dialog in Direct Messages, and the path dialog under My Repeaters. Like Restore FLOOD it changes only the path on the device — your configured paths are left alone — and it asks before it does it.
  • You can restart your device from the interface. Rebooting a node used to mean either opening the Console and typing reboot, or getting to the hardware and pulling its power — awkward when the device sits on a mast or in another room, and awkward for anyone who never opens the Console. Open the menu, pick Device, and the Info tab now has a Reboot button under the table. It asks first: the device drops off the mesh for a few seconds while it restarts, and a message addressed to you in that window can be lost. The firmware sends nothing back in reply to the command, so what the app confirms is that the command went out — not that the device is back. It will be, on its own, a few seconds later, and mc-webui reconnects without any help. The same restart a remote repeater has had all along under My Repeaters → Actions, now for the node on your own desk.

Fixes

  • Switching channels on a phone no longer pops the keyboard open. The channel picker in the top bar was a text field you could type into, so every tap on it summoned the on-screen keyboard — which on a phone covers half the screen, over a list four or five items long. The search was solving a problem nobody has: the firmware caps how many channels a device can hold, so there is never enough of them to be worth searching. The picker is now a plain button that opens the same list, with the same last-message previews, unread badges and favourites, and nothing to type into. On a computer, Enter or Space still opens it, the arrow keys move through it, Enter picks and Escape closes.

2.8.0 — 2026-08-13

Features

  • You can now record what the app receives from the radio, and send that recording to whoever is helping you. Some problems are impossible to diagnose from a screenshot. The classic one: you send a message to a channel, and the badge listing the repeaters that heard it never appears — while the Analyzer plainly shows the packet travelled onward. Nothing on screen can tell you whether nobody in earshot repeated it, or whether the repeat did come back over the air and was dropped on the link between your device and mc-webui before the app ever saw it. Those two have the same appearance and completely different answers. A new Settings → Diagnostics tab records the raw material needed to tell them apart: press Start recording, reproduce the problem, press Stop and save, and you get a file. It contains every radio frame the app received while recording, what the app decided about each one, what it expected to hear back for each message you sent, and — the number that matters most — your device's own count of packets received, taken at the start and at the end. If the device says it heard two hundred packets and the app only ever saw a hundred and twenty, frames are being lost on the companion link, and that is now a measurement rather than a guess. Recording stops on its own after the time you choose (5 to 60 minutes) or once the file reaches 25 MB, whichever comes first, so a recording you forget about cannot fill your disk. While one is running, a red Recording marker sits in the status bar at the bottom of the chat, because it should never be something you leave on by accident.
  • Sending a recording. Under the same tab, each saved recording can be downloaded, deleted, or — if the person helping you has given you an upload token — sent to them directly with one button, which gives you back a link to pass on. Without a token nothing is sent anywhere: the download is always there, and you forward the file yourself however you prefer.
  • Read the privacy notice before you send one. A recording necessarily contains the text of every message that arrived while it was running, along with contact names and public keys. That is what makes it useful and also what makes it worth thinking about: the tab says so plainly, and asks for confirmation before an upload. Only send one to someone you trust.

Fixes

  • Long routing paths stay readable on a phone. Under My Repeaters, a path through several hops is written as AB→CD→EF with nothing between the hops, and a browser will not break a line in the middle of that — so on a narrow screen the path either ran off the right edge of the card or was cut short with an ellipsis, hiding exactly the hops you opened the page to read. Paths now wrap onto a second line, breaking after a hop arrow, in all four places they appear: the repeater list, the header of a repeater's management page, the picker for adding a repeater, and the current-path line in the path dialog. A repeater's coordinates also no longer get separated from their pin icon when the line wraps.

2.7.0 — 2026-08-07

Features

  • The device's Statistics tab now shows everything the device actually reports. Open the menu, pick Device, and switch to Statistics: until now it listed the battery, an error counter, the number of packets sent, and then some counts out of mc-webui's own database — while the stats command in the Console, reading exactly the same data from exactly the same device, printed a good deal more. The reason was a plain mismatch of field names: the tab asked the device for "uptime", "queue", "airtime" and "packets received" under names the firmware does not use, so those rows quietly disappeared instead of showing up empty, and nobody could tell the difference between a value the device withholds and a value the app failed to ask for. All of them are back, and with them the ones that were never displayed at all — last RSSI, last SNR and the noise floor (worth knowing: how loud the band is where your node sits determines what it can still hear), the split of sent and received packets into flood and direct, the count of RX errors, and channel utilization, which is the share of the time since the last reboot that your radio spent either transmitting or receiving. The tab is now laid out in four titled groups — System Information, Radio Statistics, Packet Statistics and Database — the same grouping, in the same order, with the same labels as the status view for a remote repeater under My Repeaters, so the two read the same way. Airtime is shown as a duration (8h 31m) rather than a number of minutes. Everything here except the Database group is counted from the last device reboot, not for all time.

Fixes

  • The Path Analyzer map no longer shows raw HTML in its pop-ups. Click a repeater on the map — either a hop on the route or one of the amber candidates the analyzer offers where the path is ambiguous — and the pop-up naming it ended the second line with the literal text <code>47</code> instead of the repeater's hash in a monospaced box. Only the display was affected; the hash itself was always correct.
  • A message you sent no longer loses the list of repeaters that heard it when something else goes out at the same moment. Your own messages find out which repeaters picked them up by listening for their own echo coming back off the mesh a few seconds later — and while waiting, the app could only keep track of one message at a time. Send a message and then press Resend on an older one, or send two in quick succession, and the second action took over that slot; when the first message's echo finally arrived it was filed as somebody else's traffic. The message kept its text but never got its signal badge, its repeater list or its Analyzer button, and nothing would bring them back. On the test server this affected roughly one message in thirty. The app now keeps every recent send in mind at once, and listens for five minutes instead of one, because repeats do sometimes arrive minutes after the send. Resend repairs the damage too: a message with no badge could not be re-armed before — re-arming needed exactly the packet identifier that was missing — so it is now recovered from the copy of the packet the app keeps at send time. Press Resend on a badge-less message, wait for a repeater to pick it up, and the badge appears. One thing this cannot fix: if no repeater in range repeated your message at all, or the repeat was lost on the way, there is no echo to hear and the badge stays empty — that is the radio, not the app.
  • Resending an old message now asks first. Resend puts the exact same packet back on the air, and repeaters normally recognise it and stay quiet — that is the whole point, it only reaches the ones that missed it the first time. But a repeater only remembers packets it saw recently, so past a certain age nothing recognises it any more and the message spreads across the channel as if it were brand new: everyone sees it again, dated today. Pressing Resend on a message older than an hour now tells you how old it is and what will happen, and lets you back out. Anything more recent than that behaves exactly as before, with no extra click.

2.6.0 — 2026-08-03

Features

  • The Android app can now sign in to a server that asks for a password. Putting mc-webui behind a reverse proxy is what makes publishing it to the internet reasonable, and the usual next step is to make that proxy demand a username and password — an Access List in Nginx Proxy Manager, for instance. The app had no answer for that: it showed the proxy's bare "401 Authorization Required" page and stopped there, which meant the phone was the one thing you could not protect. It now asks for the login the first time it connects and remembers it per server address, so later launches go straight in; get it wrong and the same prompt returns with the username already filled in. This appears only when your server actually asks for a password — an instance without one, local http:// or a proxy with no access control, behaves exactly as it always has and never shows this screen. One thing worth knowing: if you turn the password on, or change it, while the app is already open, the app cannot notice mid-session — close it and open it again. This is app version 1.3 — install the new .apk over the one you have; it is signed with the same key, so it goes in as an update and keeps your server address. See android-app.md.
  • The HTTPS guide now explains how to put a password in front of mc-webui. The guide always mentioned that Nginx Proxy Manager can do this — mc-webui has no accounts of its own — but never said how, which made the one step that actually matters for an instance published to the internet the one step you had to work out yourself. https-setup.md now walks through creating an Access List and attaching it to your proxy host, and is clearer about a distinction that is easy to miss: requiring a login keeps strangers out, while closing port 5000 (MC_BIND_ADDRESS=127.0.0.1) stops your own network from reaching the app around the proxy — and therefore around the password. Publishing to the internet wants both; a server whose testers still use http://<address>:5000 wants only the first.

2.5.0 — 2026-08-03

Features

  • You can now reach mc-webui over HTTPS. Until now the interface was served over plain HTTP only, which meant the address bar warned it was not secure, and — less obviously — that browsers quietly withheld a set of features they reserve for encrypted connections. There is now an optional HTTPS front end you switch on with a single line in .env; it starts Nginx Proxy Manager next to the app, and you pick your certificate in its web interface rather than editing configuration files. Three routes are covered: a free Let's Encrypt certificate for a public domain, a Let's Encrypt certificate via a DNS challenge — the one worth knowing about, because it gives a fully trusted certificate on a server that is not exposed to the internet at all, with no port forwarding — and a self-signed certificate for reaching the server by its IP address. Existing installations are untouched: nothing starts and nothing changes unless you ask for it, and updates keep working exactly as before. The guide is https-setup.md, including which switch to be careful about (Websockets Support — leave it off and several open tabs will crawl) and why a self-signed certificate cannot work with the Android app.
  • The interface can be translated, and Polish has started. mc-webui was written English-only, with every label and message baked into the code. There is now a translation system behind it, and a Language setting at the top of Settings → Appearance. It covers the whole interface: the main window — menu, chat, message bubbles, its dialogs and Settings — every panel that opens in its own window (System Log, Console, My Repeaters, Path Analyzer, Contacts, Direct Messages), and the running commentary too: the small toasts after every action, the confirmation dialogs, the search results, the update flow and the notifications your phone shows when the app is in the background. Error text that comes back from the server itself is still English — that is the one part left, and it is a separate job. Your choice applies to the browser you set it in and also becomes the default for anyone else opening the server without a preference of their own.
  • You can add a language yourself, without waiting for a release. A language is a single file. Copy en.json from app/translations/, translate the values, and drop it into a translations folder inside your config directory — the same place the database lives. Refresh the page and it appears in the Language list, named however you named it, with no rebuild and no restart. Anything you leave untranslated falls back to English, so a half-finished translation is perfectly usable. A file you drop in also overrides one that ships with the app, so you can correct the built-in Polish on your own server. English and Polish ship in the box; everything else is open to whoever wants to write it. See translations.md, which explains the format and — importantly — which words to leave alone: mesh terms like flood, hop, advert, RSSI and the repeater roles stay English in every language, because that is what the firmware, the CLI and the forums all use. The Console and the log lines themselves stay English for the same reason.
  • Times and dates behave the same in every language. Only the words are translated — "Yesterday" becomes "Wczoraj", "5 min ago" becomes "5 min temu". The clock and the number formatting keep following your browser's own settings, so switching the menus to English will not suddenly turn your 24-hour clock into "9:53 AM". One display of large numbers on the repeater statistics page had been hard-coded to American thousands separators; it now follows your locale like everything else.

Fixes

  • Copy buttons work over plain HTTP again. Copying a public key, a channel key or a routing path did nothing on some pages when mc-webui was open over http:// on a network address — no error, no text on the clipboard, just a button that appeared to do nothing. Browsers only offer the modern clipboard function to pages served over HTTPS (or opened on the machine itself), and several of those buttons had no fallback for anything else. They all share one now, so copying works over HTTP everywhere in the interface. Serving the interface over HTTPS removes the restriction at the source.
  • The Android app no longer renders text larger than the browser does. The app is a wrapper around the same page you open in Chrome, but Android's WebView quietly scales text by your phone's system font-size setting while Chrome ignores it and uses its own. If you had enlarged the system font even one step, everything in the app came out around 20% bigger than on the website — the title truncated to "mc-web…", longer messages wrapped earlier, and the connection status and refresh time at the bottom broke across two lines each. The app now pins text at the same size the browser uses. This is app version 1.2 — install the new .apk over the one you have to pick it up; it is signed with the same key, so it goes in as an update and keeps your server address. See android-app.md.
  • The bar at the bottom copes with text that does not fit. The connection status, the region badge and the "Updated:" time sat in a row that could not wrap, so when they ran out of room each one broke apart internally instead — a green dot with "Connected" stranded underneath it, a time split from its own label. They now drop onto a second line as whole items. This mostly showed up in translated interfaces, where the labels are longer than the English ones they were laid out for ("Aktualizacja:" against "Updated:"), and on narrow phones.

2.4.2 — 2026-07-31

Fixes

  • Unread counts stand out in the channel list on a phone. On a wide screen, a channel with unread messages shows the count in a blue badge next to its name. On a narrow screen the channel list is a drop-down from the top bar, and there the same number was drawn in plain text, the same colour and size as everything else in the row — easy to read as part of the timestamp and easy to miss entirely. It is now the same blue badge as on a wide screen.

2.4.1 — 2026-07-31

Fixes

  • You can keep mc-webui open in several tabs again. Two or three open windows used to bring the whole thing to a crawl — the message list crept, buttons took ten or twenty seconds to do anything, and the status could sit on "Connecting…". It looked exactly like an overloaded server, and it wasn't: the server was answering in a few thousandths of a second the entire time. The live connection that pushes new messages to an open page was running in a mode that keeps a browser connection permanently occupied, and a browser only allows six connections to one address shared across every tab. Three tabs took the lot, so everything else — loading messages, marking them read, sending — queued in the browser waiting for a free one. That connection now uses a proper WebSocket, which does not come out of that budget. Measured with three tabs open: a request that had been taking around 15 seconds now takes about 12 milliseconds. The advice to keep only one window open no longer applies. If you run mc-webui behind a reverse proxy that isn't set up to pass WebSocket connections through, the page quietly falls back to the old behaviour and works exactly as before.
  • Messages no longer go missing after the app has been in the background. Coming back to a minimised app — or to a phone that had been asleep — could show a chat that quietly stopped at whatever message arrived last before the screen went off, with everything since then missing until the app was force-stopped and reopened. New messages reach an open page over a live connection, and Android tears that connection down while the app sits in the background; nothing then went back to ask the server what had been missed. Now every way back from a gap re-reads the list: the connection coming back, the app returning to the foreground, and a heartbeat that notices when the page has been frozen. The same applies to direct messages, and to a browser tab that lost its network for a while.
  • A Refresh item in the menu. The browser's pull-to-refresh has no equivalent in the Android app, so there is now a Refresh entry at the top of the menu that reloads the messages from the server on demand — in the app, and everywhere else too.
  • The connection-status check is about five times cheaper. Every open page asks the server how the mesh device is doing — on load, once a minute after that, and each time you come back to a tab you had left. Answering that took roughly a quarter of a second, almost none of it spent on the device itself: the server was counting the rows of every table in the database to report two numbers, and the row it needed for the "last message" timestamp was found by reading the entire message table and sorting it. The heaviest part, counting the radio-echo records, was thrown away unused — and that table only ever grows, so the check was getting slower the longer an instance had been running. It now asks for exactly the three values it needs, in one go. This is a smaller effect than the bundling below and it does not change what you see on screen; it does take a recurring cost off the server on every open page.
  • Far less load on the server while you have messages on screen. Whenever new radio traffic came in, the page asked the server about every message on screen separately — hundreds of individual requests at a time, repeated every few seconds, and the same messages over and over. On a busy channel that was thousands of requests a minute from a single tab, which left the server little room to answer anything else; the worst of it looked like the mesh device had dropped off, with the message list stuck on "Loading messages…" and the status on "Connecting…", while the device was connected the whole time. Those requests are now bundled into one. A page that needed hundreds of requests per update now needs a single one. The remaining reason several windows were slow is fixed separately — see the first entry above.

2.4.0 — 2026-07-29

New features

  • The Android app can send notifications now. When the app shipped in 2.3.0, notifications were the one thing it couldn't do, and the advice was to keep a Chrome "Add to Home Screen" install alongside it. That's no longer necessary — turn notifications on in the mc-webui menu as usual, allow the permission Android asks for, and new messages and pending contacts arrive as ordinary Android notifications. Tapping one reopens the app. Two things are actually better here than in the browser: they work over plain http:// too (Chrome refuses notifications on an unencrypted page), and the app shows up in Android's notification settings like any other, so you can silence it or change its sound there. The same limit as the browser still applies: notifications arrive while the app is open or recently in the background, and stop once Android suspends it — reopening the app catches you up. Requires app version 1.1; see the Android App guide for how to update.

2.3.0 — 2026-07-28

New features

  • There is an Android app now. A small companion app opens your own instance full screen — no address bar, its own icon in the app drawer, and it looks like a proper Android app rather than a page in a browser. It holds no mesh logic of its own: on first run it asks for the address of your server (http://192.168.1.100:5000 on the local network, or your https:// hostname behind a reverse proxy), remembers it, and from then on launches straight into mc-webui. Everything still runs on your server and your MeshCore device. It is not on Google Play, so you download the .apk from the repository and install it yourself — the new Android App guide walks through that, including the "unknown sources" permission and the Play Protect notice Android shows for any app that didn't come from the Play Store. Scanning contact QR codes works (on https:// instances — over plain http:// the camera is blocked by the same browser rule that applies in Chrome), and database backups download to the phone's Downloads folder. One thing stays with the browser: notifications, which the app has no access to — if you rely on them, keep the Chrome "Add to Home Screen" install alongside it, the two work happily side by side. The app's source sits next to the .apk, so you can read what you are installing or build it yourself.

2.2.0 — 2026-07-27

New features

  • Quotes now use the > convention everyone else already uses. Quoting a message used to wrap it in guillemets — @[Daniel] »Hejka« Cześć! — a shape mc-webui invented back when it couldn't send multi-line messages. It now puts the quote on its own line behind a > and drops the cursor underneath it, so a reply reads as a quote in the standard MeshCore app and every other client too, not just here:

    @[Daniel] >Are we still on for tonight?
    Yes, 8pm works.
    

    Quotes you type by hand get the same italic, tinted styling, so > written manually now looks like a proper quote as well. Nothing you received earlier changes: the old »…« form is still recognised and still displayed exactly as before.


2.1.0 — 2026-07-26

New features

  • Releases now have version numbers. This is the first numbered release: the menu shows 2.1.0 with the exact build (date and commit) underneath, so "which version are you on?" has a short answer, and the build is still there when a problem needs pinning down. Each release is tagged on GitHub with these notes attached, so you can see what changed without digging through the commit history.
  • The Path Analyzer map starts on the route you asked for. The map used to plot every located repeater on top of your route, which buried the path you actually wanted to see. It now opens showing just that route, and two checkboxes in the map's top corner add the rest back when you want it: All repeaters brings back the purple dots of uninvolved repeaters, and Alternative paths draws the other copies of the same message your node overheard. Both start off, and switching them never moves or re-zooms the map, so you keep the view you panned to.
  • See where a message's routes actually differ. With Alternative paths on, each of the message's other routes gets its own light colour, matched by a coloured dot next to it in the side list, so you can tell the lines apart — tap one to see its hops and SNR. Copies of one message usually travel most of the same way, so only the stretches where an alternative really diverges are drawn, plus a dot marking where it ends. Often that's the last hop alone, which used to be invisible under the main route.
  • The Path Analyzer remembers your filters. Time range, hop and hash-size filters, the text searches, and the Routes segment length are kept between visits, so a working set like "Last 1 day + 2/3-byte" no longer has to be set up every single time. Clear resets and forgets them. Opening the analyzer from a chat route ignores your saved filters for that visit, so they can't hide the message you tapped through to. The settings are stored by your browser, not on the device — each browser or phone keeps its own.
  • Jump from a chat route straight to the map. Tapping a route under a channel message used to just copy it to the clipboard. Now it opens the Path Analyzer on its map view with that message selected and that exact route already drawn — the quickest way to see where a message physically travelled. Copying isn't gone: each route in the popup keeps a small clipboard icon for pasting into the console's change_path.

Reliability & polish

  • Your own sent channel messages no longer show a route that isn't theirs. A bug could attach a stranger's overheard packet to your just-sent message, so the delivery badge and the Path Analyzer occasionally displayed a repeater hash and a physically impossible path that were never part of your message. Sent-message echoes are now matched exactly, so the route you see is really yours.

2026-07-22

New features

  • My Repeaters — administer your repeaters from the browser. A new full-screen panel (main menu → My Repeaters) for the MeshCore repeaters you hold the password to. Add repeaters from your device's contacts, save the admin password once, and from then on logging in is one click — the app shows whether the repeater granted you ADMIN or GUEST rights. Each entry also gets the same path editor as DMs, so you can pin a direct route to make logins and commands fast. Heads-up: repeaters never answer a bad login, so a wrong password looks exactly like an unreachable repeater — the app's error messages say so instead of guessing.
  • Monitoring tools per repeater: Status, Telemetry, Neighbors. After login you land in a management panel. Status shows battery, uptime, clock, RSSI/SNR/noise floor, packet counters, and channel utilization in one compact table. Telemetry lists every Cayenne LPP channel at once, with proper units. Neighbors shows every zero-hop node the repeater hears (name, last heard, SNR) — plus a map view that draws SNR-labeled links to the neighbours it can place. Works with guest logins too.
  • Remote CLI, Settings, and Actions (admin logins). CLI is a real terminal to the repeater — quick-command chips, per-repeater history, round-trip times. Settings edits the repeater configuration in collapsible sections (Basic, Radio, Location, Features, Network health, Advertisement, Operator info, Advanced): values load live from the repeater, only the fields you change are sent, and every field reports back individually — including a "reboot required" badge for radio parameters and the firmware's own error text for rejected values. Actions covers zero-hop advert, flood advert (marked "not recommended" — high network load), clock sync, and a confirmation-guarded reboot in a Danger zone. Erasing the file system stays USB-serial-only by firmware design, and the panel says so.
  • Observer mode — feed packet analyzers straight from mc-webui. The new Settings → Observer tab turns your node into a MeshCore observer: every packet the device overhears is published to one or more MQTT brokers in the standard meshcore-packet-capture format, so analyzer services (a self-hosted Corescope, letsmesh-style maps) see your local mesh traffic without a separate capture script or a dedicated second node. Configure brokers with host/port, optional username/password and TLS; each row shows a live connected/error badge, and the tab counts packets captured vs published in real time. Capture is completely passive — chat and direct messages are unaffected — and all changes apply immediately, no restart needed. (LetsMesh token-authenticated brokers are not supported yet.)
  • Scheduled flood adverts. The Observer tab includes an optional advert interval in hours: the app sends a flood advert on that schedule so your observer stays visible on analyzer maps. The timer survives restarts, so a redeploy won't send an extra advert early. Set it to 0 to keep adverts fully manual.
  • Path Analyzer — see how your messages travel the mesh. A new full-screen tool (main menu → Path Analyzer) loads every channel message from all your channels for a chosen window (1–7 days) together with every copy your node overheard, and lets you dig in four ways. Messages: expand any message to see all its routes hop by hop with SNR — copy a repeater hash, copy the whole route, or jump straight to the map. Repeaters: per-repeater statistics (how many packets each hash relayed, over how many messages, and average SNR when it was the hop you actually heard), sortable and clickable to filter. Routes: the busiest hop sequences across all your traffic — pick a length (2–4 hops) and see which stretches of the mesh relay the most, with an "as path end" count that highlights the routes actually delivering to you. Map: pick a message and its shortest route is drawn instantly as a red line with numbered, name-labeled points — when a short 1-byte hash matches several repeaters, the candidates are shown in amber and you pick the right one instead of the app guessing (a mistaken pick can be undone per hop or reset for the whole path), and uncertain segments are drawn dashed so you always know which part of a route is confirmed.
  • Path Analyzer filters. Everything is filterable live and in combination: hop count, path-hash size (1/2/3-byte), repeater — by hash or by name, and now as a >-chained sequence to find a specific consecutive route (e.g. AFE6>6E9A) — sender, and message text. All four views follow the active filters, and clicking a Routes or Repeaters row drops you into the message list already filtered.
  • Path Analyzer works on phones. On narrow screens the whole filter bar collapses behind a Filters button (with a badge counting the filters you have set), the tables shed their secondary columns (packet hash and hash size move into the expanded row) and long routes wrap instead of scrolling sideways, and on the map the message list gets most of the height with the map fixed to a comfortable share below it.
  • Pin a repeater's location from a map. In My Repeaters → Settings → Location, a new Pick from map button lets you click a point on a map to fill in the latitude and longitude — no more looking up coordinates by hand.

Reliability & polish

  • Console login reports your role. A successful repeater login in the Interactive Console now answers "Logged into X as admin" (or guest) instead of a bare success line.
  • Failed repeater login no longer makes you retype the password. Since a wrong password and an unreachable repeater are indistinguishable — and a failure is usually just a flaky connection — the retry prompt now comes back with your saved password already filled in, so you can retry with one tap.
  • Repeater list reads better on narrow phones. A repeater's "last login" time now sits on its own line, so a long pinned path no longer squeezes the rest of the row.

Deploy notes

  • This update adds a new Python dependency (paho-mqtt) and raises the meshcore library requirement to 2.3.7, so the Docker image must be rebuilt — the standard mcupdate flow does this automatically.
  • Passwords you save in the app — MQTT broker credentials in the Observer tab and repeater admin passwords in My Repeaters — are stored in plain text in the app database. That's a deliberate trade-off for a private single-user LAN app; use dedicated credentials where you can.

2026-07-02

New features

  • Explicit Send on phones and tablets. On touch devices, pressing Enter now inserts a new line instead of sending — you tap the Send button to publish. This stops a mistapped Enter on the on-screen keyboard from firing off a half-typed message. Desktop keeps Enter-to-send. Applies to both group chat and direct messages.
  • Manage the Letsmesh analyzer like any other entry. The built-in Letsmesh Analyzer is now a normal row in Settings → Analyzer — rename it, disable it, star it as the default, or delete it, just like a service you add yourself. The chart icon under a message resolves at click time: nothing enabled shows a "No analyzer configured" hint, a starred default (or a single enabled service) opens directly, and several enabled without a default show a chooser. The row's switch now reads Enabled when it's on.

Reliability & polish

  • Multi-line direct messages keep their line breaks. A DM you sent across several lines showed correctly to the recipient but collapsed to a single line in your own copy. Your own bubble now preserves the line breaks too.

2026-06-26

New features

  • Resend a channel message (same packet). Your own group-chat messages now carry a repeat-arrow button that re-broadcasts the exact same packet. Repeaters that already forwarded it stay quiet, but nodes that never heard it can still pick it up — so a resend fills in coverage on the existing message's delivery badge instead of posting a duplicate. Requires companion firmware 1.16 or newer; the button is hidden on older devices.
  • Clearer "Edit message" button. The button that copies a message back into the composer for hand-editing used to be mislabeled "Resend." It's now a pencil Edit message button on both channel and direct messages, clearly separate from the real Resend above.
  • Custom Analyzer services. A new Settings → Analyzer tab lets you register your own MeshCore Analyzer services. Each entry has an enable/disable switch, a "star" toggle to mark it as the default, and an Edit/Delete pair. The chart icon under each group-chat message now resolves at click time: built-in Letsmesh if you haven't configured anything, the default service when one is set, or a chooser modal when several are enabled. URL templates use {packetHash} as a placeholder.
  • Apply a saved path straight from Contact Info. Each entry in the Paths list inside the DM Contact Info modal gained an upload-arrow button. Click it to push that configured path to the device as the active route — no more switching to the console to run change_path.
  • Database "Optimize now" button + live size. The Backup modal now shows the current DB size and exposes an Optimize now button that runs SQLite VACUUM on demand. Useful after a big retention pass when you want to reclaim space without waiting for the nightly job.
  • Automatic message retention is on by default. A nightly job at 03:30 (using your container's TZ) trims old data: 90 days of channel messages and direct messages, 60 days of advertisements, and 30 days of diagnostic data (echoes, paths, acks — these account for the bulk of long-term DB growth). When at least 1 000 rows are deleted, the database is VACUUM-ed automatically so file size shrinks too.
  • Smarter watchdog (host service). The host-level mc-webui-watchdog systemd service now catches a new failure mode — a "sluggish" device that briefly stalls on stats/battery commands while still receiving traffic — by counting soft-pattern hits over a 2-minute window. The app also exposes a new /health/strict endpoint that external monitors can poll.

Reliability & polish

  • System Log tab no longer floods the server. Opening the System Log could trigger a feedback loop that hammered the server with 10+ requests a second; the log noise that caused it is now filtered out, so the tab stays quiet.
  • The connection badge stops lying about device state. The status badge could flip back to "Connected" on a routine message refresh even while the device was actually disconnected. Device status is now driven only by real device connectivity, with a 60-second fallback check so a long-open tab stays accurate.
  • Automatic recovery after a failed reconnect. A reconnect that failed quietly used to leave the app stuck "disconnected" until the container was restarted by hand. The background liveness watcher now keeps retrying instead of giving up after a single failure.
  • No more 10–15 s freezes on app load. The realtime channel used a transport that the dev server couldn't upgrade; we now stay on long-polling, which keeps real-time pushes working without the reconnect loop.
  • Channel list stays complete when the device is slow. Channels are now read from the local cache rather than re-queried slot-by-slot, so a brief device stall no longer leaves you with just the Public channel after a refresh.
  • Sending on a re-used channel slot now works after a deletion. When you delete a channel, the device compacts the remaining slots — until now the app kept using the old keys for that slot. We refresh the secret from the device just before each send.
  • Region scopes work for all channels, not just slots 0–7. Channels stored in higher slots (e.g. #ubot, #swietokrzyskie) now accept scope changes.
  • Multi-byte routing paths render correctly everywhere. Contact list, DM modal, retry status, console output — all now show 2-byte and 3-byte hops with the right hop count and byte size (e.g. D103,5E34 (2 hops, 2B)), where they previously truncated the path or rendered single-byte hops.
  • Self-healing TCP connection. Long-lived TCP sessions against meshcore-proxy can degrade in ways the socket can't see — some commands silently time out while RX still trickles in. The app now detects this on a send failure and reconnects in-place, with a backup liveness watcher that triggers a reconnect when no RX event has been seen for 5 minutes.
  • Settings analyzer modals: backdrop and URL wrapping. Add/edit/chooser modals now dim the Settings backdrop correctly, and long URLs in the Analyzer list no longer push controls off-screen on narrow mobile viewports.
  • Console change_path accepts more formats and respects hop size. You can now use commas, spaces, or arrows between hex chunks (D103,5E34 / D103 5E34 / D1->90->05). For multi-byte paths, all chunks must be the same length — that length determines whether the path is sent as 1-, 2-, or 3-byte hashes.

Deploy notes

  • After deploying this release, restart the host watchdog so it picks up the new soft-pattern detection:
    sudo systemctl restart mc-webui-watchdog.service
    
  • The retention job runs the first time at 03:30 local. Your DB may shrink noticeably overnight — that's expected.

How this file is maintained

This file is updated before each merge of dev → main. Each release section starts as "Unreleased" and is renamed/dated once the merge happens, then a fresh "Unreleased" section is opened at the top.

Sections are grouped roughly as:

  • New features — things a user can find/click that didn't exist before
  • Reliability & polish — fixes for problems users actually noticed, plus quality-of-life tweaks
  • Deploy notes — anything the operator must do beyond the usual mcupdate

Internal refactors, code-level cleanups, and developer-only changes belong in git log and architecture.md, not here.