Commit Graph
1064 Commits
Author SHA1 Message Date
Michael A. Cojocari e5eb3f9f2d Merge remote-tracking branch 'refs/remotes/pr/476' into integration/all-open-prs-20260913 2026-09-13 18:56:57 -04:00
Michael A. Cojocari a6fdbfcf36 Merge remote-tracking branch 'refs/remotes/pr/469' into integration/all-open-prs-20260913 2026-09-13 18:56:57 -04:00
Michael A. Cojocari fdaa3d22b7 Tweaks and suggested changes. 2026-09-09 14:23:25 -04:00
Michael A. Cojocari bc3229d27e M9: fix settings detail navigation (#427) 2026-09-09 10:37:26 -04:00
Kaj SchittecatandClaude Opus 5 16d09696ca touch: beta_79 release notes
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
beta_79
2026-09-09 13:06:36 +02:00
Kaj SchittecatandClaude Opus 5 b6440d01cd touch: charging icon on every board, not just two (#473)
Two Discord reports a day apart on different boards: museifu696 (M9, beta_78)
had no charging symbol at all, and thesupergeek (V4-R8) found charge state not
being detected after comparing against a T-Deck.

For the M9 this was structural. batteryIsCharging lived inside

  #if defined(HAS_TDECK_GT911) || defined(HELTEC_LORA_V4_R8)

whose #else returned a flat false, so on the M9 -- and the Pager, RAK Tap, Wio
L2, Attaky, P4 and Tanmatsu -- no voltage could ever produce a charging icon.

That split was a real, evidence-based decision, but it was about the Heltec V4
alone: its noisier ADC path made the EMA drag the average down so the threshold
never tripped (the note above the block explains it). The #else then answered
the question for every OTHER board too, purely by falling through, and nobody
had evaluated those.

So share only the verdict, not the sampler. The EMA stays gated exactly as
before, which means battery PERCENTAGE on the direct-read boards is untouched
and the V4's original complaint cannot recur: the direct read is what it keeps,
and its plugged-in reading already sat high.

Sharing is sound rather than assumed: the same mv feeds batteryPercentFromMv(),
so a board reporting the USB rail instead of the battery would already show a
permanent 100% and someone would have said so.

Kept deliberately PURE. batteryMvSmoothed()'s charge_flip calls this twice with
different arguments to compare the old and new verdicts, so a latching Schmitt
trigger would corrupt itself; flap is bounded by the 20 s publish hold and the
50 mV margin instead. The threshold against each board's uncalibrated ADC still
wants on-device confirmation, as the V4-R8 note already says.

Two follow-ons: kBattChargingMv (4250) is removed, dead and misleading since the
test actually uses batteryFullMv() + 50; and the idle-power-save comment that
reasoned from "the M9 is in batteryIsCharging's #else branch, so the USB-powered
gate can never block" is corrected, because it now can, which is the point of
that gate.

Reported-by: museifu696, thesupergeek
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 11:59:21 +02:00
Kaj SchittecatandClaude Opus 5 565fd883ae touch: a Lua app error must not reset the device (crash dump, T-Deck)
Kaj's T-Deck coredump: abort() on core 1, decoding to luaG_callerror ->
abort() reached from luaAppPress(). luaG_callerror is "attempt to call a ...
value", and the abort is Lua's DEFAULT panic function, because lua_atpanic was
never installed anywhere in this file. So any error reaching Lua with no
protected frame resets the device.

The callback itself was never the problem: guardedCall wraps it in pcall with a
traceback handler and turns a fault into a toast plus a clean close, which is
the host's whole contract. The hole is everything that runs BEFORE that pcall
is armed:

  - pushCallback() used lua_getfield, which honours __index. An app table
    carrying a metatable whose __index is not callable raises exactly
    luaG_callerror, unprotected. It is now a raw lookup, which is also the
    correct semantics: callbacks are plain fields on the table the chunk
    returned, and no shipped app uses setmetatable.
  - the argument marshalling in sendInput/sendKey pushes a table and several
    values before the pcall exists, and those raise on stack growth or OOM.
    lua_checkstack appeared ZERO times in this file; pushCallback now reserves
    the room up front, and checkstack reports failure rather than raising.

lua_atpanic is installed as well. It cannot prevent the abort -- Lua aborts once
a panic function returns and there is no safe frame to unwind to -- but the
error text now reaches the serial log and a toast instead of vanishing, which is
the difference between a reproducible report and a mystery reset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-09 11:47:12 +02:00
Kaj SchittecatandClaude Opus 5 503b2fe1f2 touch: sound previews obey the master switch (#464), exact telemetry position (v59)
#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>
2026-09-09 11:37:54 +02:00
Kaj Schittecat 459698df5a Merge pr468 2026-09-09 11:16:52 +02:00
Michael A. Cojocari cfb09edcf1 docs: record T-Deck Pro test status 2026-09-08 16:57:00 -04:00
Michael A. Cojocari 971ac93496 Merge remote-tracking branch 'upstream/main' into feat/tdeck-pro-target 2026-09-08 16:56:02 -04:00
Michael A. Cojocari ff499bf938 M9: fix conversation navigation (#467) 2026-09-08 16:53:17 -04:00
Michael A. Cojocari 9715cac742 Work 2026-09-08 16:31:52 -04:00
Michael A. Cojocari f3136ea951 Merge branch 'main' into feat/tdeck-pro-target
# Conflicts:
#	deploy/apps/lang/bg.lang
#	deploy/apps/lang/de.lang
#	deploy/apps/lang/el.lang
#	deploy/apps/lang/es.lang
#	deploy/apps/lang/fr.lang
#	deploy/apps/lang/hu.lang
#	deploy/apps/lang/it.lang
#	deploy/apps/lang/nl.lang
#	deploy/apps/lang/pt-br.lang
#	deploy/apps/lang/ro.lang
#	deploy/apps/lang/ru.lang
#	deploy/apps/lang/sr.lang
#	deploy/apps/lang/uk.lang
#	src/ui-touch/UITask.cpp
2026-09-08 15:41:45 -04:00
Kaj SchittecatandClaude Opus 5 57b8adca8a touch: leading type icon on Discover rows (#459)
jesshampshire asked for the Discover pane to present like the Chats and
Contacts panes. It was the only one of the three without a leading icon, so a
repeater and an ordinary peer looked identical until you read the meta line
under the name.

Same glyph set and the same 22 px column the Contacts rows use: antenna for a
repeater, loop for a room, person for everything else, rendered in g_font_16 so
the FontAwesome PUA codepoints resolve through its fallback chain. The name and
meta labels shift right by that column and lose the width to it.

The type stays spelled out in the meta line as well: the icon cannot tell a
sensor or chat node from a plain peer, so dropping the word to buy the width
back would lose information on exactly the rows where it matters. On the narrow
boards that line ellipsises as it already did.

Requested-by: jesshampshire
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 20:38:50 +02:00
Kaj SchittecatandClaude Opus 5 5fc4608c37 touch: contacts cursor across a list rebuild (#467), idle-saver blocker breakdown (#465)
#467 (Buko84): leaving a page stashes the focused row so the cursor can go back
where it was, and it stashed it as a raw pointer. Reading a channel's unread
messages clears its badge, which rebuilds every thread row -- so on the way out
that pointer is either dead or, worse, recycled by the allocator onto a
DIFFERENT row, which is how the cursor lands on an unrelated channel. With no
unread messages nothing rebuilds and it works, which is exactly the split he
identified.

Identify the row by the slot it occupied instead. A list rebuilt in place keeps
its order, so the slot is reliable where the address is not; the pointer is only
consulted when the number of targets actually changed. The restore also scrolls
that row back into view now: the rest of the rebuild deliberately runs with
focus-scroll suppressed, which is right for the transient focus during
recollect, but it left the restored cursor off-screen whenever the rebuild had
also reset the list's scroll position.

#465 (jrote1): his follow-up screenshot shows the gate passing (178406 cycles,
last wake timer) but only 15% asleep. 178406 parks at the 50 ms throttle is
~2.5 h, which against 15% puts uptime near 16.5 h -- so every park runs its full
length and the parks are not the problem. The device simply was not ELIGIBLE
for the other 85%, and nothing reported which of the six gate conditions was
holding it shut.

So charge the wall time between checks to whichever condition was blocking at
that moment, and surface the biggest one ("held by mesh busy 62%") on the
Battery page and the live Lock-settings diag. gatePasses() becomes gateBlocker()
returning the condition rather than a bool. Gaps over 5 s are dropped rather
than blamed on a condition, and the clock is stamped AFTER a park so a completed
park is never mis-attributed to the next blocker.

Also: device_caps.h computed CAP_SOUND from HAS_UI_SOUND, which UITask.cpp does
not define until ~1400 lines AFTER that header is included -- so CAP_SOUND was 0
on every board. Nothing consumed it, so it was silent rather than broken, but
any future `#if CAP_SOUND` would have compiled notification sound out
everywhere. The board list moves into device_caps.h (every macro involved is a
-D on the command line, so all of them are genuinely visible there) and
HAS_UI_SOUND now derives from CAP_SOUND. Verified per board: on for T-Deck,
V4-TFT, V4-R8, M9, Pager and P4; off for RAK Tap, Wio L2 and Attaky, which is
the pre-existing behaviour, unchanged.

Reported-by: Buko84, jrote1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 20:28:34 +02:00
Kaj Schittecat 5b5ce2adef Merge pr466 2026-09-08 20:14:09 +02:00
Michael A. Cojocari 796504445d Wio Tracker L2 tweaks 2026-09-08 13:56:39 -04:00
Kaj SchittecatandClaude Opus 5 04f0a1043a touch: stop the T-Display P4 advertising a lock screen it never had (#451)
Piguyraspberry reported the P4 lock screen never appearing and its settings
page rendering blank. Both are the same thing: the board declares
CAP_LOCK_SCREEN but nothing behind it was ever wired for it.

  - nothing can REACH the lock. The control-center Lock button is compiled
    only for HAS_TDECK_GT911; the other triggers are the Tanmatsu volume key
    and the Pager's own hold handler.
  - nothing can LEAVE it. Every unlock path is a physical-control branch
    (T-Deck trackball hold, Tanmatsu Vol-, M9 d-pad, Pager Backspace, Wio
    wake button) and the P4 matches none. The on-screen hint even falls
    through to "hold the trackball to unlock" on a board with no trackball.
  - the DSEC_LOCK settings body is gated on HAS_TDECK_GT911 || HAS_THINKNODE_M9
    rather than on the capability, which is why the page came up empty.

The Heltec V4-R8 sits in exactly this position and is deliberately 0, with a
comment saying enabling it is a feature rather than a cap flip. Same call
here. The settings card is already skipped under !CAP_LOCK_SCREEN, so this
removes the empty page rather than papering over it.

#451 stays open as the tracking issue for actually building it: a touch
reveal + hold-to-unlock path, a P4 hint string, and capability gates on the
settings section.

Reported-by: Piguyraspberry
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:14:22 +02:00
Kaj SchittecatandClaude Opus 5 f931fdfeda touch: Known regions back-out (#449), idle power-save gate (#450), review follow-ups
Known regions (#449, DenRalle on Tanmatsu): s_regions_modal was in no popup
registry at all. Without a row the Back ladder never peels it, so the page
stays on screen, and the open-popup COUNT stays zero, so whatever you open
next opens underneath it. Both halves of the report ("screen remains
visible", "pages open only in the background"), one missing line.

Idle power-save (#450, jrote1): the Battery page in the report shows the
gate blocked on "Wi-Fi on" with Wi-Fi disabled and cycles stuck at 0.
tsWifiOff() ANDed the wifiConfigGetRadioEnabled() pref with the real radio
state, and that pref DEFAULTS TO ENABLED when its NVS key was never written
-- so anyone who had not explicitly toggled Wi-Fi in this firmware read as
"Wi-Fi on" permanently. The gate is asking whether the modem draws power,
and WiFi.getMode() == WIFI_OFF answers exactly that on its own.

The other half of the same bug is in main.cpp: the power-down ran only if
wifi_started, but a scan or the version check can bring STA up without
going through that state machine, and then disabling Wi-Fi skipped
mode(WIFI_OFF) and left the modem running. getMode() != WIFI_OFF proves the
driver is inited, which is what the guard actually wanted to test, so it
closes the leak without reintroducing the low-heap esp_wifi_init crash the
original comment guards against. The block is one-shot behind
wifiConfigConsumeApplyRequest(), not per-loop.

Follow-ups from reviewing the merged PRs:

- #457 moved the Lua app page to the slim bar, which exposed that bar_h was
  measured BEFORE appPageBegin* installs the chrome -- so the body was sized
  against the OUTGOING page's bar. Install first, then measure (#236 again,
  one step earlier).

- #453 makes /meshcore-*.json downloadable, and those carry "private_key"
  plus every channel secret. The transfer is opt-in, LAN-only and rate
  limited (3 tries, 30 s lockout), so the surface is sound -- but it is
  plain HTTP, so say so on the page and flag backup rows in the list.

Reported-by: DenRalle, jrote1
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:06:59 +02:00
Kaj Schittecat 26597a40cc Merge pr457 2026-09-08 14:01:08 +02:00
Kaj Schittecat 1052688396 Merge pr458 2026-09-08 14:01:04 +02:00
Kaj Schittecat 542bf4db2c Merge pr453 2026-09-08 14:00:57 +02:00
Kaj Schittecat 86fd38d80b Merge pr456 2026-09-08 14:00:57 +02:00
Kaj SchittecatandClaude Opus 5 3a4917d5c1 touch: contacts list no longer freezes on every advert (#463)
Buko84 reported the #433 Contacts delay still happening on the M9, with a
precise tell: smooth when nothing is being discovered, "hanging in the
middle" when something is.

That is exactly what the advert path did. Every advert sets
s_ct_contacts_dirty, and while the Contacts tab was visible that forced a
full teardown+rebuild of the list past its count-cache -- ~1.3 s at ~570
contacts -- once every 2.5 s for as long as adverts kept arriving. With no
adverts the flag never sets, refreshContactsList() hits the cache and
returns immediately, and the tab feels fine. #433 moved the rebuild after
the 150 ms tab slide, so the slide now paints first and the freeze lands
mid-transition instead of before it.

Everything an advert actually changes on a row that is already rendered is
the name-fill of #73 and the Heard age, so write both in place instead:
ctRefreshAgeLabelsInPlace() becomes ctRefreshRowsInPlace() and also
re-renders the name (re-picking LONG_DOT vs LONG_SCROLL_CIRCULAR from the
name-column geometry the last full build recorded). A brand-new or deleted
contact changes getNumContacts(), which the count safety net right below
catches on its own, so the #73 guarantee still holds.

Ordering and filter eligibility do still need a real rebuild -- an advert
can move a contact in the last-heard sort, or hand it the GPS fix the "has
location" filter wants -- so that converges on the same 60 s cadence the
age tick already uses rather than every 2.5 s.

Reported-by: Buko84
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 14:00:42 +02:00
Kaj SchittecatandClaude Opus 5 1a17e727d3 Location privacy: fix a leak when changing radius, cover telemetry, repaint in place
Three fixes to the setting shipped in beta_78, all from users reading it
critically after release.

The offset leaked the true position if you changed the radius. jesshampshire
spotted it on #399: bearing was derived from the node identity alone, so it was
the same at every radius, and switching from 100 m to 1 km moved the reported
point further along the SAME ray from where you actually are. Anyone who saw
both could intersect them and recover the exact location, which is the one thing
this setting exists to prevent. The radius is now folded into the derivation, so
each setting lands on an unrelated bearing. Checked: the three radii now differ
by 225 degrees of bearing, while a given radius stays stable, so the original
property holds too. That stability is deliberate and not laziness: a fresh random
offset per advert would scatter points around the true position and averaging a
night of them would recover the centre.

Telemetry answered with the exact position, spotted by honza_87628. querySensors
adds a GPS entry from the live coordinates, so a node that asked for telemetry
got the real location while adverts were displaced, making the setting a hole
rather than a feature. The displacement now applies across that call and the true
values are restored immediately after, so the local map and GPS page are
unaffected.

Selecting a radius broke the settings page. Reported by honza_87628 and confirmed
by Buko84 on an M9: the callback called buildDeviceSettings(), which is the entry
point for OPENING a category, so it stacked a second page over the live one. The
back button went stale, Close did nothing, and the change looked reverted because
you were seeing the page underneath. The four buttons are now repainted in place.

All nine S3 envs and both ESP32-P4 targets green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 13:35:14 +02:00
Kaj SchittecatandClaude Opus 5 6ef5ee061d Site: collapse "What's new" by default
A release with twenty-odd notes pushed the whole install flow off the screen, so
the box that is meant to be a summary had become the page.

It is a native <details> now, collapsed on load, reading "See what's new in
beta_78 - 24 changes". The count matters: a bare "click here" asks people to
gamble a tap, while a number lets them decide whether they care. Native rather
than scripted so it opens with the keyboard, works without JavaScript, and does
not need a click handler; the JS only fills the count and forces it closed on
each channel switch, so flipping stable/beta cannot leave a stale panel open.

The default disclosure triangle is hidden and replaced with one that rotates,
because that marker renders differently in every browser.

Markup verified as balanced and the element confirmed to carry no "open"
attribute, which is what makes it start collapsed. I could not render it in a
browser to check visually: the harness refuses to navigate to localhost, and a
file:// page is served as a static snapshot with no JavaScript, so the count and
the collapse behaviour are unverified on screen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 09:04:34 +02:00
Michael A. Cojocari 0d31fd381b M9: fix Quick Reply focus cursor (#430) 2026-09-07 19:33:14 -04:00
Michael A. Cojocari db1bc69b64 Store apps: auto-hide launch title (#455) 2026-09-07 18:55:14 -04:00
Michael A. Cojocari 44f0a7ba0f M9: make Ctrl toggle control panel (#454) 2026-09-07 18:43:15 -04:00
Michael A. Cojocari 0fafb09044 Expand browser file transfer management (#448) 2026-09-07 14:46:56 -04:00
Kaj Schittecat ba1667b4d1 touch: beta_78 release notes beta_78 2026-09-07 16:43:11 +02:00
Kaj SchittecatandClaude Opus 5 c106d47b9b At-a-glance body drops to 20 px on the M9 (#446)
Buko84: the notification text is too big on the M9. It is, and the fix was
already made once for the same reason.

28 px was chosen so the message is legible from across a room, which it is, but
on a 240x320 panel it fills the glance and crowds out the message it is supposed
to be showing. The T-Deck was moved to 20 px for exactly this and the M9 has the
same size panel; nobody widened the gate at the time.

Three things had to change, not one: the extern declaration, the 20 px Latin
fallback font's own build gate, and a LV_FONT_MONTSERRAT_20 flag on the M9 env,
which was not building that size at all. Missing any of them is a link error
rather than a small font, which is why it is worth naming.

All nine S3 envs and both ESP32-P4 targets green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 16:32:51 +02:00
Kaj SchittecatandClaude Opus 5 1aa4496530 Lock-screen glance: say who and how far, and stay dark for muted channels
Two changes to the at-a-glance notification, both asked for by Kaj.

The title now carries the sender and the hop count, not just the thread. A
channel post reads "#public - Alice - 3 hops"; a direct message reads
"Alice - direct" without naming the sender twice, since a DM is already titled
with them. Zero hops says "direct" rather than "0 hops", and 0xFF is
OUT_PATH_UNKNOWN, an advert that arrived by flood, so it is omitted rather than
printed as "255 hops" -- the same trap that produced the Discovered list bug.

The point is that a count off a dark screen tells you nothing you can act on,
whereas a named contact one hop away against channel chatter from five hops out
is the reason to pick the device up at all.

Muted channels no longer light the screen. That was arguably backwards: muting
silenced the chime while the message still woke the display and printed itself
in large type, which is the louder half of the notification rather than the
quieter one. Muting is a statement about a conversation, so it now covers both.

Mentions keep their own mute bit, matching how the chime already treats them, so
a channel muted for chatter still surfaces an @-mention. If mute should instead
mean nothing at all gets through, that is a one-line change.

All nine S3 envs and both ESP32-P4 targets green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 15:54:37 +02:00
Kaj SchittecatandClaude Opus 5 37aac2736c Accent colour picker: the swatch you are on no longer hides its own colour (#431)
Buko84 again, and the same defect I fixed for the lock-screen picker: each swatch
set its colour for the default state only, so the theme was free to repaint the
focused one. On a d-pad board the swatch you are standing on is always focused,
so it turned white and hid the colour you were choosing.

I fixed the lock-screen picker and missed this one, which is the second instance
of the same mistake in the same file. The colour is now pinned across the
focused, pressed and checked states here too, and focus draws as a ring outside
the swatch so it can never cover the colour.

The swatches are also scaled rather than raw pixels now, the same class as #238.

All nine S3 envs and both ESP32-P4 targets green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 15:01:29 +02:00
Kaj SchittecatandClaude Opus 5 6160184531 Translation placeholders for the strings from oumike's seven PRs
Five new user-facing strings arrived with #429/#439/#440/#441/#442/#444/#445.

All nine S3 envs and both ESP32-P4 targets green on the merged result.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 14:35:56 +02:00
Kaj Schittecat 6ee33990a3 Merge PR #445 (oumike) 2026-09-07 14:20:11 +02:00
Kaj Schittecat 8b75132f07 Merge PR #444 (oumike) 2026-09-07 14:20:09 +02:00
Kaj Schittecat 03d05d7e57 Merge PR #442 (oumike): focus the M9 composer when opening chats
Conflict with the M9 pending-focus block from #440 resolved by keeping both and
making the ordering explicit. #442 reworks the focus-hint logic so a hint
belonging to the page behind a newly opened overlay cannot suppress the
new-chat composer fallback (#436); #440's block places focus deliberately and
sets focus_set. Without a guard the reworked chain fell through to the
just-opened-chat branch and moved that focus, which is precisely what #440's
block exists to prevent, so the chain now has an explicit first arm for it.
2026-09-07 14:19:49 +02:00
Kaj Schittecat a4dc077d80 Merge PR #441 (oumike) 2026-09-07 14:18:06 +02:00
Kaj Schittecat 74594d3d54 Merge PR #440 (oumike) 2026-09-07 14:18:05 +02:00
Kaj Schittecat c18916835a Merge PR #439 (oumike) 2026-09-07 14:18:04 +02:00
Kaj Schittecat 7352a58701 Merge PR #429 (oumike) 2026-09-07 14:18:03 +02:00
Kaj SchittecatandClaude Opus 5 96ba457984 Fix a Wi-Fi callback race that crashed at boot, and make the GPS privacy setting reachable
Two things, both found by looking rather than by being told.

A Heltec V4-R8 crash dump from beta_77 decodes to a data race in the Arduino
core's Wi-Fi event list. Correcting for the unwinder mangling Xtensa return
addresses, the event task was inside _eventCallback copying a WiFiEventCbList
entry, iterating the callback vector, while loopTask was still in setup() inside
_M_realloc_insert growing that same vector through WiFi.onEvent(). The
reallocation frees the buffer the event task is walking, so it then calls
through a dangling function pointer, which is the garbage program counter in the
dump.

Neither side locks, and the vector is the core's, not ours. What is ours is the
ordering: both handlers were registered AFTER the stack was started, one of them
after WiFi.begin(), so a STA_DISCONNECTED arriving during boot association lands
in the window. A failed first association is ordinary, which is why this happens
at all. Both now register before WiFi.mode(WIFI_STA), so nothing mutates the
vector once events can flow and the race is impossible rather than unlikely.
This is a crash during setup(), so on an unlucky unit it is a bootloop, which
makes it worth more than its single report.

The moved handler drops its WIFI_DEBUG_PRINTLN tracing, which comes from a
header not in scope that early. The prints compile to nothing in a release build
and the reconnect flag they accompanied is unchanged.

Separately: the location-privacy setting shipped in beta_77 on the wrong page.
It was inside the Sensors section rather than GPS, because the insertion was
anchored to a comment belonging to the Sensors block, and that section is
described in its own code as V4-with-kit only. So on most boards it was not
reachable at all, which is exactly what two people reported: they went looking
and correctly concluded it was not there. It now sits in the GPS section, is
named "Location privacy" rather than "Position in adverts" (nobody searching for
a privacy control scans for the word "adverts"), and offers four buttons instead
of a cycling one, so every option and the current choice are visible without
touching anything. Given the failure here was discoverability, showing the most
without interaction is the point.

All nine S3 envs and both ESP32-P4 targets green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 14:17:50 +02:00
Michael A. Cojocari f1f12fb30f Add Pager special-character picker shortcut (#435) 2026-09-06 21:00:27 -04:00
Michael A. Cojocari 5c3a7a5d46 Avoid M9 Contacts transition delay (#433) 2026-09-06 20:45:18 -04:00
Michael A. Cojocari 530b539957 Focus M9 composer when opening chats (#436) 2026-09-06 17:39:11 -04:00
Michael A. Cojocari 7f20ebe522 Show M9 battery saver in status icon (#437) 2026-09-06 17:29:33 -04:00
Michael A. Cojocari 565eb7d954 Fix M9 contact search navigation (#438) 2026-09-06 17:24:16 -04:00
Michael A. Cojocari 28fb57ef4c Fix Attaky usability issues (#423) 2026-09-06 16:55:41 -04:00