docs(changelog): drop the "!" command prefix and space out no em dashes in 1.1.0

Commands are bare by default; `command_prefix` is empty unless configured, so
`!wx` misrepresents a stock install. Shipped release sections are left alone.
This commit is contained in:
agessaman committed 2026-09-20 12:50:38 -07:00
1 parent 31e56b3766
commit 53dcc2d0e7
1 file changed
+32 -32
+32 -32
View File
@@ -30,8 +30,8 @@ semantic versioning.
region. Configure it in `[Region_Warnings]`; retention is governed by
`[Data_Retention] region_warning_retention_days`.
A channel sender is a display name, not an identity — MeshCore's channel
messages carry no public key — so DM warnings only go to a name the radio
A channel sender is a display name, not an identity—MeshCore's channel
messages carry no public key—so DM warnings only go to a name the radio
already holds a contact for, and a message with no `Name: ` prefix is counted
but never warned. Warnings dropped for want of a contact are counted and
shown on the page, so a bot that keeps no contacts reports that rather than
@@ -57,7 +57,7 @@ semantic versioning.
regional flood scopes (#283), so `[Channels] flood_scopes` and
`outgoing_flood_scope_override` no longer have to be edited by hand. It
writes `config.ini` and queues a hot config reload, then polls that reload and
reports what the bot actually did — including saying plainly when nothing
reports what the bot actually did—including saying plainly when nothing
picked the change up. Scope names are normalized on save the way the bot
normalizes them (`west` becomes `#west`), and a name containing `,`, `%` or an
inner `#` is refused before anything is written, because those are the three
@@ -102,7 +102,7 @@ semantic versioning.
alongside the built-in ones, tagged with `source: "local"`. Settings edited
there route to `local/config.ini` rather than the base `config.ini`, matching
the local overlay the bot already merges at startup via `[Bot] local_dir_path`
— a section already tracked in the base config keeps saving there. The web
—a section already tracked in the base config keeps saving there. The web
viewer previously only read/wrote against `config.ini`, so any local plugin
settings actually stored in the overlay were invisible and unsavable from the
UI (#272).
@@ -121,15 +121,15 @@ semantic versioning.
blanks the value and reports only `has_value`, matching the key-name
redaction already used elsewhere, and the field renders as a
`type="password"` input with a show/hide toggle. When a value is already
saved the field shows a `(saved — enter new value to change)` placeholder
saved the field shows a `(saved—enter new value to change)` placeholder
and leaving it untouched keeps the stored secret, so an edit elsewhere on
the form does not blank it.
- Localized proactive weather messages (daily forecasts, rain nowcasts, weather
alerts) via `services.weather_service.*` translation keys. `WeatherService` now
uses the bot's `translator` instead of hardcoded English strings, so proactive
outputs respect the configured `language` setting — the same mechanism already
used by `!wx`, `!gwx`, `!rain` and other commands.
outputs respect the configured `language` setting—the same mechanism already
used by `wx`, `gwx`, `rain` and other commands.
- Russian (`ru`) translation for the full bot UI, including all weather service
keys, command keys, categories, and common strings.
@@ -137,11 +137,11 @@ semantic versioning.
- `format_temperature_high_low()` now accepts an optional `translator` parameter.
When provided, the `H`/`L` temperature labels and compass wind directions are
locale-aware (`H`/`L` → `В`/`Н` in Russian; `WNW` → `ЗСЗ`). Passed from
`WeatherService`, `!wx`, and `!gwx` callers.
`WeatherService`, `wx`, and `gwx` callers.
- `modules/alert_format.py` holds one NWS alert formatter, shared by `!wx alerts`
- `modules/alert_format.py` holds one NWS alert formatter, shared by `wx alerts`
and the proactive `WeatherService` broadcasts. Both now localize from the same
code path, so a Russian bot no longer answers `!wx alerts` in English while its
code path, so a Russian bot no longer answers `wx alerts` in English while its
proactive alerts are Russian. It replaces four copies of the event-type
abbreviation table and two of the time compactor, which had already drifted
apart. Alert strings moved to `common.alerts.*` and wind directions to
@@ -156,7 +156,7 @@ semantic versioning.
- Shlink is now supported as a URL shortener alongside v.gd / is.gd, selected with
`short_url_website_service = shlink` under `[External_Data]`. It authenticates with
`short_url_website_api_key` in an `X-Api-Key` header and needs `short_url_website`
set to your own instance — there is no default, and the bot skips shortening rather
set to your own instance—there is no default, and the bot skips shortening rather
than sending the key to a host you did not configure.
- `shorten` and `if_nonempty` response-template filters. `shorten` runs a value
@@ -165,8 +165,8 @@ semantic versioning.
non-empty value with literal `L` and clears otherwise, which is how a whole clause
is hidden rather than labelled: `{packet_hash|if_nonempty:"https://…/{packet_hash}"|shorten}`
prints nothing at all when RF correlation fails, instead of a broken link. Both
filters also answer to their other spellings — `shorten_url` in a template,
`shorten_url` in a feed format, `if_notempty` — so a chain copied between a feed
filters also answer to their other spellings—`shorten_url` in a template,
`shorten_url` in a feed format, `if_notempty`—so a chain copied between a feed
format and a command `response_format` works unchanged either way.
- Response-template filter arguments may be double-quoted, and a quoted argument may
@@ -206,7 +206,7 @@ semantic versioning.
- Documented installing with `pipx`, which sidesteps PEP 668 on Debian 12+, Ubuntu
23.04+, Fedora and Arch (#222), including where `config.ini`, the database and
`local/` live — everything resolves relative to the config file's directory, so an
`local/` live—everything resolves relative to the config file's directory, so an
absolute `--config` is what makes a pipx install deterministic.
- Migration 23: nullable `snr` / `rssi` columns on `observed_paths` for
@@ -235,7 +235,7 @@ semantic versioning.
takes precedence over the in-place `--update-venv` path, so the two can be
combined.
- `[PacketCapture] observer_name` — an optional name reported as the `origin` of
- `[PacketCapture] observer_name`—an optional name reported as the `origin` of
MQTT packet and status payloads. It lets the observer/analyzer identity differ
from the MeshCore RF node, which is useful when one bot name is already taken
by the radio's advertised name. Unset (the default) keeps the previous
@@ -314,7 +314,7 @@ semantic versioning.
- A region-scoped channel message now restores global flood even when
`set_flood_scope` raises. The restore only ran in the `finally` around the
send, so a set that raised left the device pinned to that region and every
later send — channel replies, DMs, scheduled sends — went out under it.
later send—channel replies, DMs, scheduled sends—went out under it.
- A DM waiting for its ACK no longer holds the radio. Radio commands were
serialized per call, so a DM's retry loop kept every other command waiting
@@ -374,7 +374,7 @@ semantic versioning.
- Weather output no longer leaks translation key paths into mesh broadcasts. The
localization pass replaced several `dict.get(key, fallback)` lookups with bare
`translate()` calls, and `Translator.translate` returns the dotted key path when
a key is missing from both the locale and the English fallback — deliberate, so
a key is missing from both the locale and the English fallback—deliberate, so
missing translations are visible in development, but it reaches the air in
production. An NWS title we cannot classify (`event_type = "Unknown"`, e.g.
"Hazardous Weather Outlook") rendered as
@@ -391,7 +391,7 @@ semantic versioning.
hardcoded English month list, so any locale with translated months took the wrong
branch and then failed the regex, truncating mid-string:
`🟠Flood Warning King до июн 28 6 дн от NWS SEA`. Adding a space before AM/PM —
needed because Russian writes "6 дня", not "6дня" — broke the same regex for
needed because Russian writes "6 дня", not "6дня"—broke the same regex for
English too, spending 8 characters of a 130-byte budget on a redundant date and
pushing the shortened URL out. Times are now carried as parsed parts and rendered
through a per-locale `common.alerts.time_12h` template, so nothing re-parses
@@ -405,13 +405,13 @@ semantic versioning.
the existing `common.date_time.month_abbreviations` instead of the duplicate
`services.weather_service.months` block the pass had added.
- `!gwx` display units follow the `[Weather]` unit config instead of the response
- `gwx` display units follow the `[Weather]` unit config instead of the response
language. Visibility and pressure were switched on `base_language != 'en'`, so a
bot with `language = ru` and the default `temperature_unit = fahrenheit` printed
Fahrenheit temperatures beside kilometers, and `en-GB` was forced to miles. Which
pressure unit reads as normal is a locale convention rather than a
metric/imperial split, so each catalog now names its own via
`commands.gwx.pressure_unit` (`mmhg` for `ru`, `hpa` elsewhere) — previously
`commands.gwx.pressure_unit` (`mmhg` for `ru`, `hpa` elsewhere)—previously
every non-English locale inherited mmHg from the English catalog, whose
`pressure_mmhg` string contained Russian text ("мм рт. ст."), giving German and
French users Cyrillic pressure units. The Russian `visibility` string, which the
@@ -425,26 +425,26 @@ semantic versioning.
documented in the function docstring but not in the file, and hardcoding `H:`/`L:`
remains available for operators who want English labels regardless of language.
- `!gwx` high/low labels follow the reply's language. `_format_high_low` passed
- `gwx` high/low labels follow the reply's language. `_format_high_low` passed
`bot.translator` rather than the per-message translator, so with
`auto_detect_language` on, an English-default bot answering a Russian sender
localized the rest of the line but not `H:`/`L:`. The same call in `wx_command`
is fixed alongside it.
- `!gwx` no longer overruns the RF byte limit on multi-byte locales. The check
- `gwx` no longer overruns the RF byte limit on multi-byte locales. The check
guarding the extra conditions block compared a character count against a budget
derived from bytes, while the rest of the function used `_count_display_width`
(UTF-8 bytes). Cyrillic is two bytes per character, so the check saw roughly half
the real size and appended the block after the budget was already spent.
- `!wx hourly` no longer answers `commands.wx.hourly_not_available` when NOAA
- `wx hourly` no longer answers `commands.wx.hourly_not_available` when NOAA
returns no hourly periods. The key was never in any catalog, so the raw key
path reached the user; predates this branch, found while auditing every
translation key the weather modules reference.
- Restored nine `commands.gwx` English strings that the localization pass reworded
for no functional reason, including the configuration hint in
`mqtt_weather_no_subscriber` — "MQTT weather subscriber is not active (enable
`mqtt_weather_no_subscriber`—"MQTT weather subscriber is not active (enable
[MqttWeather] and custom.mqtt_weather.* topics)" had become "MQTT weather
subscriber not active", dropping the only pointer to the two keys a
mis-configured operator needs. The rewordings had also diverged from the
@@ -459,7 +459,7 @@ semantic versioning.
gap. The reporter's message was heard directly (`SNR 13.25`, 0 hops) and again via
repeater `f0` 185 ms later (`SNR 12.0`, 1 hop); both rows carry packet hash
`392926C85DCB87D0`, but the check saw only the echo, disagreed on path length and
SNR, and left the route unresolved — so the bot withheld a path it had decoded
SNR, and left the route unresolved—so the bot withheld a path it had decoded
correctly. The cache is now searched for the row the payload matches instead of
testing just the most recent one. Rows that agree must resolve to a single packet
hash, so two unrelated packets that happen to agree stay a fallback and #80's
@@ -469,7 +469,7 @@ semantic versioning.
- MQTT brokers no longer flap in a reconnect storm (#248). Three things stacked up.
First, the packet-capture watchdog ran `client.reconnect()` from its own thread
every 30 seconds whenever `is_connected()` was false — which includes every moment
every 30 seconds whenever `is_connected()` was false—which includes every moment
paho's network thread is inside its own backoff. Two threads driving one client's
socket produced duplicate CONNACKs, spurious `MQTT_ERR_PROTOCOL` disconnects, and a
fixed-interval retry that flattened paho's 1→120s backoff into a hot loop. The
@@ -480,7 +480,7 @@ semantic versioning.
one cluster (`mqtt-a` and `mqtt-b` of the same service) evicted each other's session
on a six-second cycle. IDs are now distinct per broker. Third, a disconnect logged a
bare `rc=`, which reads against the CONNACK table even though paho reports
`MQTT_ERR_*` there — `rc=2` is a protocol error, not "client identifier rejected" —
`MQTT_ERR_*` there—`rc=2` is a protocol error, not "client identifier rejected" —
so it now names the code.
- A renewed MQTT auth token is now actually put in force. MQTT presents credentials
@@ -557,7 +557,7 @@ semantic versioning.
- `message_stats.path` no longer reports another packet's route (#80). When RF
correlation failed, `find_recent_rf_data` fell back to the most recent packet in the
cache, and the caller attributed that packet's route to the message — which is how a
cache, and the caller attributed that packet's route to the message—which is how a
multi-hop message was occasionally recorded as a single direct hop. Correlation
results are now tagged with how they were matched, and an uncorrelated fallback is no
longer allowed to supply a route for either channel messages or DMs. The route is
@@ -582,14 +582,14 @@ semantic versioning.
- Stale-contact cleanup no longer retries forever (#176). When the device refuses to
remove a contact the contact stays in the list, so every sweep re-selected it and
logged the same failure again — hundreds of `Failed to remove stale contact` warnings
logged the same failure again—hundreds of `Failed to remove stale contact` warnings
that only a restart cleared. A contact is now dropped from cleanup after
3 consecutive refusals, with one summary warning explaining that the list may stay
near its limit. A successful removal clears the count.
- Contacts whose device clock was never set are no longer treated as stale. MeshCore
seeds an unset clock with a hardcoded time — `1715770351` (15 May 2024) or
`1772323200` (1 Mar 2026) — so a never-synced node advertises that seed rather than
seeds an unset clock with a hardcoded time—`1715770351` (15 May 2024) or
`1772323200` (1 Mar 2026)—so a never-synced node advertises that seed rather than
a real observation. The bot read it as extreme staleness, which put unsynced but
perfectly active contacts at the top of the removal list, consuming the whole
per-sweep budget and repeatedly trying to evict live nodes. This is what the
@@ -605,7 +605,7 @@ semantic versioning.
- Command execution failures log a full traceback instead of a bare exception
message, so the failing file and line are visible without reproducing the error.
The reply sent over the mesh is unchanged — it still carries only the exception
The reply sent over the mesh is unchanged—it still carries only the exception
text, with no filesystem path and no extra airtime.
## [1.0.0] — 2026-08-07