#464 (PD0RCM, T-Deck Plus): "I can hear the sample sound and the volume change,
but not incoming messages." That board has the hardware, and previews and
arrivals go through the same playback function on it, so the audio path was
never the problem.
Every preview on the Sound page played UNCONDITIONALLY: the sample, the volume
steps, and each per-type toggle. An arriving message checks the master Sound
switch and Do Not Disturb. So with the master off the page chimes at you the
entire time you configure it while nothing ever sounds on arrival, which reads
exactly like a broken notification path and is almost certainly what he hit.
A preview is now silent precisely when a real notification would be, and says
which of the two silenced it rather than just going quiet. The master switch
keeps its own confirmation chime, because that fires exactly when you turn it
ON, which is the one case where a sound is the right answer.
Telemetry position (v59, honza_87628, via Discord): the advert displacement of
#399 was extended to telemetry answers because a privacy setting a telemetry
request walks straight around is a hole rather than a feature. He then made the
fair counter-argument that an answer is not a broadcast: it is encrypted to one
contact who already holds the permission, and it only goes out because they
asked. Losing the ability to give trusted contacts a real fix is a real loss.
So make it a choice instead of an assumption. New tail field telem_loc_exact,
OFF by default, so the private behaviour is what you get unless you say
otherwise, and the broadcast advert stays displaced either way. The switch sits
directly under "Share my location when asked", where the decision belongs.
Schema 58 -> 59. The host test enumerates every appended field and caught the
change, as designed; all three of its migration assertions are updated and it
passes.
Reported-by: PD0RCM, honza_87628
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
#415: a Lua app was covered by the app drawer whenever a message arrived.
Reported by jadestarwatcher, reproduced by mysterywavi, both on T-Deck. Mine,
from the #393 badge fix. Apps launched from the drawer deliberately leave it
alive underneath (appTileCb says so outright), and my badge refresh rebuilt it on
every unread-count change, with openAppDrawer() ending in a move_foreground that
threw it on top of the running app.
The refresh now runs only when nothing is drawn in front of the drawer, tested by
sibling order rather than by listing the tools, so a tool added later is covered
without anyone remembering to update a list. The status bar is excluded, since it
legitimately floats above the drawer at all times and treating it as covering
would have stopped the badges refreshing at all. Leaving the signature stale is
what makes it self-healing: the check runs again each tick, so the count is right
by the time the drawer is back in front, with no need to hook every close path.
Scroll position is now preserved across the rebuild too, which my change had also
been resetting.
#410: on the M9 the accent variants appeared but could not be selected. Also mine,
from the #387 work. The picker was handled below m9HandleArrowKey, which takes
LEFT and RIGHT for the caret and returns, so the arrows never reached it: the box
was drawn and nothing could touch it. Lifted above that call, as its own function
rather than a second copy of the logic.
#399, requested by @Danie10: an option to advertise a position near you rather
than your address. Settings, GPS, cycling exact / 100 m / 250 m / 1 km.
The displacement is derived from the node identity, so it is the same offset
every time. That is the whole point rather than an implementation detail: a fresh
random offset per advert would scatter points around the true position, and
averaging a night of them would recover the centre exactly. A fixed displacement
instead looks like a node that sits somewhere else, which is what a manually set
location already looks like. It applies only to our own adverts; the map and the
GPS page keep the real fix, because the aim is to tell other people less, not to
lie to yourself.
Schema v57. The host test caught the new field the moment it was added, which is
what it is for.
All nine S3 envs and both ESP32-P4 targets green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oumike. A firmware-wide appearance setting: Night stays the default and renders
exactly as before, Day is a low-glare light palette with its own semantic roles
for text, panels, fields, borders, controls, focus, charts, status colours and
chat surfaces. Selecting a mode restarts, so every LVGL object is rebuilt from
one coherent palette rather than half-repainted. There is a Night/Day selector
in Settings, Display, the Control Center Theme chip is now a direct toggle whose
sun/moon icon shows the active mode, and the standalone console UI has a
matching Day palette.
Main already carried the palette scaffolding for this, pinned to Night behind a
comment saying it was waiting on this branch, so the merge mostly replaces those
stubs with the real thing.
The prefs schema needed care. The branch appended theme_mode as v54, but v54 and
v55 were taken by boot_wifi_time/boot_wifi_open and loud_alerts before this
merged, so the field moves to the tail behind them and the version becomes v56,
with the trailing-field assert and the migration step moved to match. A packed
struct read back at the wrong offsets is the failure this schema's asserts exist
to prevent, so the invariants from both sides are kept rather than one replacing
the other.
That also surfaced a stale test: the v53 case asserted that v53 plus exactly two
bytes was the whole struct, which stopped being true when loud_alerts landed and
was not caught because the host test was not run then. It now counts every byte
appended since and asserts the new fields come back at their defaults. The
schema host tests pass. While there, the size comment said "about 500 bytes";
the struct is 114.
Built on all nine S3 envs and both ESP32-P4 targets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MeshCore names run to 31 characters (ContactInfo::name[32]), but
UIMessage::sender held 24, so every longer name was cut on the way in.
d304d3f made the cut visible instead of letting it push the author into
the message body; this makes it stop happening. MAX_SENDER_NAME goes to
31 — exactly the wire width, so nothing is lost.
Truncation could never fix three of these. "Ack" and "Mention" insert
@[<sender>] into the composer, and textMentionsMe needs the whole name
followed by ']', so a mention of a long-named node never reached them.
Block-by-name compares the stored name against the sender on the RX
path; for ROOM posts those two came from different widths already — the
check sees the untruncated 31-char author while the stored entry came
from the 24-char field — so a room author with a long name has simply
been unblockable. Info and the chat-list preview showed a cut name.
The obvious change — widening UiHistoryMsg::sender — would have been
quietly destructive, so the on-disk width is frozen at 25 instead and
the overflow is APPENDED to UiSegMsg, after seq. sender sits between
thread and text, so widening it in place shifts text for every record
already on disk, and nothing would have caught that: the segment store
is what actually holds messages now, and uiSegOpenValidated checks only
magic/version/rec_size — it never consults k_ui_history_version or
k_ui_history_min_version, which guard the legacy files alone. An old
segment would have passed validation, prefix-read into the new offsets,
lost the first 8 characters of every message body and read seq as 0 (so
loadMsgsFromSegments renumbers the lot), and the next compaction would
have written that back permanently.
Appending at the tail is the evolution path UiSegHeader already
documents, and it is correct in both directions: an old 233-byte record
zero-fills the new field and yields its original short name, and older
firmware reading a new 240-byte record copies the first 233 bytes and
ignores the tail. That second half is why k_ui_seg_version STAYS 1 —
bumping it would make older firmware reject the file outright and
quarantine a history it could have read. So there is no migration, no
version bump, and no wiped chat history. static_asserts now pin the
offsets an already-written record depends on.
TOUCH_IGNORED_NAME_LEN has to move in lockstep (28 -> 32) or blocking
breaks: a slot narrower than a sender stores a cut name the full-width
check can never match, and the block sits in Settings doing nothing.
Its NVS blob has no header — the entry count is its length divided by
the slot width — so re-slotting in place would mangle every entry after
the first and the next write would make that stick. The key is renamed
ign_nm -> ign_nm2 and the old blob folded in once at first read, which
is unambiguous in a way that sniffing the blob length is not (28 and 32
share multiples at 224 and 448, both inside the 16-entry cap). The
invariant the header only stated in prose is a static_assert now.
Split/rejoin is a host-tested header next to ChannelSenderSplit, with
the field-width bounds spelled out: a corrupt record can leave the name
field unterminated, and the message body is what follows it on disk.
Signed-off-by: Ruben Laban <ruben@tun0.nl>
The split accepted any "X: " prefix once the length test came out, so a post
that carries no author but does contain ": " — "the repeater at Ouderkerk is
back up: full quieting again" — got read as an author of 36 characters. That
invents a sender AND hides the start of the body.
The old `slen <= MAX_SENDER_NAME` test used to catch this as a side effect;
dropping it for long names dropped the plausibility check with it. Restore the
check at the WIRE width (31 — MeshCore keeps every name in a char[32]) rather
than the destination width, so a real 31-char name still splits and truncates
while a 32-plus "prefix" is left in the body where it belongs.
A static_assert pins kMaxWireName >= MAX_SENDER_NAME, so widening the field can
never leave the split rejecting names the field holds fine.
A channel post arrives on the wire as "SenderName: body" — the author is
in the text, not a separate field. Both split sites gave up when the name
was longer than MAX_SENDER_NAME (24), and giving up meant the sender fell
back to from_name, which for a channel is the CHANNEL name. The bubble
then read
#channel date
nick: message
instead of the author over their own message. MeshCore names run to 31
chars (ContactInfo::name[32]), so this hit every post from a node with a
25+ char name — the "occasionally" in the report.
Truncate the label instead of rejecting the split, backing off to a UTF-8
character boundary so a multi-byte name is never cut mid-sequence (the
missing-glyph sanitiser would render the tail as '*'), and mark the cut
with "..." so a shortened name does not read as a different node.
Widening MAX_SENDER_NAME is the real cure but not an option here: sender[]
sits in the MIDDLE of the persisted UiHistoryMsg record, and this file
format only tolerates fields APPENDED to the end — moving `text` would
force k_ui_history_min_version up and discard everyone's chat history.
The two copies of the split are now one host-tested header, matching how
Utf8Text/ReaderContent are factored.
Signed-off-by: Ruben Laban <ruben@tun0.nl>
oumike. Closes#315 (pisti87's request).
Two conflicts, both resolved by keeping BOTH sides rather than choosing:
- sdRuntimeLifecycleBusy() gained an audio-playback source here and a web
reader source in #317. They are independent consumers of the same card and
both have to gate the mount lifecycle. The reader's self-exclusion is kept:
it calls this from its own task while holding the card and would otherwise
deadlock against itself.
- The Lua harness caps table needed sd_list from #312 as well as the audio
flags, and the test order needed the wardrive suite from #324 as well as
audio_api.
Built on all eight S3 envs and both ESP32-P4 targets.
Beta 57 inserted retry_echo into the middle of the packed TouchCfg blob, shifting the remote-mode and later fields during migration. Define a v45 layout with retry_echo appended at the true tail and recover ambiguous v44 suffix bytes to safe defaults while preserving the stable prefix. Add a host regression test for v43, broken v44, and v45 blobs.
Signed-off-by: Pixel Perfect <me@pixp.cc>