Commit Graph
1036 Commits
Author SHA1 Message Date
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 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
Michael A. Cojocari b692fa403c fix(m9): add selectable special characters 2026-09-06 15:03:47 -04:00
Kaj Schittecat 8e94e25036 beta_77 notes: add the download-mode change beta_77 2026-09-06 18:16:48 +02:00
Kaj SchittecatandClaude Opus 5 3713b11829 Download mode now says how to leave it (#104)
dreirund reported the device as frozen at "Download mode... reflash over USB".
It was not frozen: that is the state working as designed, waiting to be flashed
with its own UI stopped. They accepted that once told, and made the fair point
that the button should say what it will do.

So the menu entry now reads "Download mode (wait for USB flash)", and the
message shown before the UI stops says that RESET cancels, which is the piece of
information whose absence turned a working feature into a bug report. It also
stays on screen long enough to be read.

A state a user cannot leave without already knowing the way out is a bug in the
telling, even when the code is right.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 18:06:54 +02:00
Kaj SchittecatandClaude Opus 5 2d6536af7a beta_77 release notes, ready for the slice
Written now so cutting it is a build and a publish rather than a writing session.
Not tagged and not published.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 14:17:54 +02:00
Kaj SchittecatandClaude Opus 5 281e95fd04 Scale the remaining text-bearing containers, found by sweeping for #238's class
#238 was a card laid out in raw pixels while the text inside it scaled, so the
contents outgrew the box. That is a class rather than one bug, so I swept the
file for containers sized in raw pixels large enough to hold text and found
eight more: the lock and unlock progress cards, the pager locking bar, the
on-screen keyboard's text mirror, and the accent-colour hex entry row.

None of these has been reported. They are latent rather than theoretical: the
same conditions that produced #238 apply, and #418 is a live report of
translated labels overflowing their buttons, so a longer translation inside a
fixed box is a real case rather than an imagined one.

SC() is identity at 100%, so this changes nothing on a board that already looks
correct, and only grows the box where the text inside it has already grown.

Also swept for the other documented recurring trap in this UI, a geometry read
taken before layout has run. Zero instances left, so that one is genuinely
closed rather than merely quiet.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 14:16:35 +02:00
Kaj SchittecatandClaude Opus 5 c2a12b1901 Break the SD lifecycle deadlock cvhviz traced (#364)
Filed as a code trace rather than a reproduction, with the caveat that it would
rather be challenged than sat on. Challenged, and it holds: every link is still
there in current main.

sdHealthTick asserts the tile-backend pause and then returns while holding it if
the SD lifecycle is busy. Holding it across busy ticks is deliberate, so queued
tile requests cannot keep winning the boundary while recovery waits, and for
every busy reason but one that is fine.

The exception is a pending About-page SD-usage rescan. The tile worker services
that request near the top of its loop but takes the backend lease later in the
same pass, so a request armed in between can only be cleared by the very task
the pause has parked. The flag keeps sdRuntimeLifecycleBusy() true, the tick
keeps returning, the pause keeps the worker parked, and nothing else can break
it: the card is locked out until reboot. Opening Settings, About is enough to
arm it.

So for that one reason the pause is released and the worker allowed to drain.
Recovery is not abandoned, it retries on the next tick; it just stops holding
the door shut on the only task that can open it. That is the same reasoning as
the reader excluding itself from this predicate a few lines above, with a
comment saying it would otherwise deadlock, so the pattern already had a
precedent in this file.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 13:04:33 +02:00
Kaj SchittecatandClaude Opus 5 ecec85e788 Two P4 layout glitches that are really scale bugs (#236, #238)
Both reported on the T-Display P4 by D-Melhede, and neither is P4-specific once
you look: they are hardcoded pixel values in a UI whose text scales.

#236, a Lua app's first line cut in half by the status bar: the app host set its
body offset from a hardcoded 44, commented as matching the AppPage tall bar.
That is only true where STATUSBAR_H is its 22 default. The bar is
SB_TOP_PAD + SB_ROW*2 on the round-corner phone panel and SC(22) everywhere
else, so it grows with the UI scale, and anywhere it exceeded 44 the app drew
under it. The host now asks for the height the bar actually has. The same
mistake was already fixed elsewhere in the file, with a comment saying to
measure with statusBarCurH() rather than assume; the Lua host had not caught up.

That also means this was never only a P4 bug. Any board at Large or Huge scale
had it, which includes the Tanmatsu, whose default has been Large since v29.

#238, the drawer options not fitting: the card is laid out in raw pixels while
the text inside it scales, so the contents outgrew the box. Now scaled with SC(),
which is identity at 100% and therefore changes nothing on a board that was
already correct, and the card is additionally clamped to the sheet's height so a
larger scale cannot push it off screen.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 12:50:35 +02:00
Kaj SchittecatandClaude Opus 5 7bcaacfb93 Translation placeholders for the file-transfer strings
Twelve new user-facing strings arrived with #413. Added as placeholder rows so
the audit is clean and translators have the keys to fill in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 11:30:04 +02:00
Kaj Schittecat 6db04d3e1d Merge PR #413 (oumike): authenticated browser file transfer 2026-09-06 11:11:57 +02:00
Kaj SchittecatandClaude Opus 5 ec96ca4c84 Guard #405's keypad-nav drawer pointers so the non-keypad boards build
s_nav_drawer_corner and s_nav_drawer_gear are declared inside #if CAP_KEYPAD_NAV
but were assigned unconditionally, in both drawer-close paths and twice while
building the grid. On a board without keypad navigation the declarations are not
there and the assignments are, so the Heltec V4 TFT and the Wio Tracker L2 did
not compile.

Same trap as twice already today. It is invisible on the board the change was
written for and only the matrix finds it, which is the argument for building the
matrix rather than the board on the desk.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 11:10:49 +02:00
Kaj Schittecat d4a430c03e Merge PR #409 (oumike): Wio Tracker L2 wake and screen controls
Conflict with #407 resolved in favour of the newer gesture: #407 changed the M9
unlock from a hold to a double-press, and #409 branched before that and still
described the hold. The M9 strings keep #407's wording; #409's Wio Tracker L2
arm is taken as written, since that board's gesture is not what changed.
2026-09-06 10:54:53 +02:00
Kaj Schittecat 0719834cc1 Merge PR #407 (oumike) 2026-09-06 10:54:31 +02:00
Kaj Schittecat 6c0aebef35 Merge PR #412 (oumike): M9 control centre navigation
Conflict with #414 resolved by keeping both intents: the theme chip's icon stays
the sun in either state, which is #414's point and matches the DND chip since
#396 (the fill carries the state, so a changing glyph reads as a different
control), while taking the keypad-nav id #412 adds. #412 branched before that
decision rather than disagreeing with it.
2026-09-06 10:54:24 +02:00
Kaj Schittecat 8436ba39e4 Merge PR #405 (oumike) 2026-09-06 10:53:58 +02:00
Kaj Schittecat afd9436b42 Merge PR #414 (oumike) 2026-09-06 10:53:58 +02:00
Kaj SchittecatandClaude Opus 5 993e147f3e Set the clock by hand, for devices with no GPS, network or phone (#105)
Requested by dreirund: with no GPS fix, no Wi-Fi and no companion app there was
no way to set the clock at all. That is not cosmetic, because message timestamps
are built from it, so a device used purely offline could not hold a correct one.

Settings, Clock now takes local time as "YYYY-MM-DD HH:MM". mktime() reads it
through the configured zone, the same zone the clock is displayed in, so what you
type is what you see rather than something you convert to UTC yourself, and
tm_isdst is left at -1 so summer time is not an hour out. The field is prefilled
with the current reading, which makes a correction an edit of a digit or two
rather than typing a full stamp on a device keyboard.

Rejected values are refused the same way every other clock source is: below the
send-timestamp floor is not accepted, so a mistyped year cannot walk the ratchet
backwards.

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

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 10:11:31 +02:00
Kaj SchittecatandClaude Opus 5 ee15fcb335 Fix two beta_76 regressions of mine, and advertise a position without your address
#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>
2026-09-06 09:57:25 +02:00
Michael A. Cojocari ff21019560 Keep control center theme icon consistent 2026-09-05 15:23:53 -04:00
Michael A. Cojocari 9f25f963d6 Add authenticated browser file transfer 2026-09-05 14:32:08 -04:00
Michael A. Cojocari 4709bfbc2c Fix M9 control center navigation (#411) 2026-09-05 12:50:56 -04:00
Michael A. Cojocari c9482b9262 Fix 2026-09-04 16:20:03 -04:00
Michael A. Cojocari ce6ec9e3a8 Fix Wio Tracker L2 wake and screen controls (#406)
Drive the L2 I2C backlight during sleep and wake, poll its expander button reliably, and remove irreversible software power-off. Make a two-second wake-button hold lock or unlock, and keep Home and empty-map chrome clear and readable.

Signed-off-by: Michael A. Cojocari <michael.cojocari@gmail.com>
2026-09-04 14:25:33 -04:00
Michael A. Cojocari 420e13cafb Improve M9 message actions and unlock gesture (#402)
Open the selected message action menu with a normal Enter press. Replace the lock-screen long wait with a d-pad-center double press while preserving directional behavior and the controller long-press fallback.

Signed-off-by: Michael A. Cojocari <michael.cojocari@gmail.com>
2026-09-04 14:00:17 -04:00
Michael A. Cojocari 85e3e155bb Fix app drawer edge navigation (#404)
Restrict the drawer settings gear to Up or Right from the top-right tile so other top-row Up presses stay put. Hide the M9 unread-message blinker while the drawer is frontmost.

Signed-off-by: Michael A. Cojocari <michael.cojocari@gmail.com>
2026-09-04 13:25:51 -04:00