#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>
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>
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>
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>
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>
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.
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.
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>
#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>
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>
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>
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>
Day/Night themes, the Seeed Wio Tracker L2, the M9 panel actually sleeping,
P4 Wi-Fi honouring its own setting, three Lua SDK fixes, and reboot notices
that are visible.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The forward declaration went in among the keypad-nav ones, which sit inside
#if CAP_KEYPAD_NAV. On every board without keypad navigation the declaration
vanished while the three calls stayed, so the Heltec V4 TFT and the Wio Tracker
L2 did not compile. Same trap the popup-registry declaration already documents.
Moved to where every touch board sees it. Caught by building the matrix rather
than the one board in front of me, which is the whole reason for building it.
All nine S3 envs and both ESP32-P4 targets green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Changing the theme restarted the device with nothing on screen, which reads as a
crash rather than as the setting working. Kaj reported it on hardware.
The cause is that showAlert() only CREATES the notice; it is painted by the next
LVGL frame, and rebootDevice() never returns to one. So announcing a reboot and
then calling it immediately shows nothing at all. My first attempt at this was
exactly that mistake, and it changed nothing on the device.
The language switch already solved it, and the comment there names the failure:
the device went dark under the user's finger, so the reboot is scheduled on a
one-shot timer and the notice gets a frame to paint. That is now a shared
rebootWithNotice() helper, and the theme switch uses it.
Screen rotation and remote mode had the identical defect. Both announced a
reboot and then rebooted immediately, so neither notice had ever been visible;
both now go through the same helper. Any future setting that reboots should.
Verified on a T-Deck: clean software reset, no panic, and the earlier crash
report did not recur. The coredump partition was erased first, so that is a real
observation rather than an absent one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things Jade reported, the first of which is blocking an app submission.
Missing keys on the T-Deck. A Lua app there never received A, P, Q, Enter or
Backspace. The rule that withholds them exists so an app can never swallow its
own exit, which is right on a board where a reserved key IS the only way out.
On the T-Deck it is not: an app page is closed by tapping the back bar, and the
app host is not in the popup registry, so those keys closed nothing while an app
was running. They were simply dead inside every app. They are now forwarded.
The M9 keeps its reservation, because its reserved keys are sentinel bytes for
Back and Home that are never typed text and it has no touch to tap out with, and
the Pager already reserved nothing. That is why this was T-Deck-only.
The trackball click. With an app open the trackball branch forwarded motion and
explicitly discarded the button, so an app could be steered but never clicked.
It now delivers the same synthetic centre tap the touchless boards' select key
sends, on the press edge: the flag is a per-frame "held" level, so forwarding it
raw would have fired a tap every frame the button stayed down.
Repeat counts. The firmware already counts repeaters heard rebroadcasting an
outgoing flood, keyed on a fingerprint taken at transmit time; it is the refresh
glyph on a sent bubble. wada.mesh.send() now returns that fingerprint as its
second value on success, and wada.mesh.repeats(fp) reads the count back. Additive,
so an app reading only the first return is unaffected. The count grows as repeats
arrive, so it is polled rather than read once, and a direct message has none
because it is not flooded. Documented on the SDK page.
Built on all nine S3 envs and both ESP32-P4 targets.
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>
oumike. This is the actual merge: the previous commit claimed it and did not
contain it. The merge had refused because the working tree still held the
swatch fix, and the error was hidden by a filter on the command's output, so
only the swatch change was committed under a message describing both.
What lands here is oumike's work. Keypad navigation could skip a whole row on
the Send advert screen: candidates were scored as axis distance plus an off-axis
penalty with no notion of rows, so a wide dropdown two rows down beat the narrow
right-aligned switch directly beneath. navMoveDir now finds the nearest row band
first and only scores within it, which is direction-generic and so fixes the
same skip sideways.
Also: the focus highlight was invisible on two dropdowns created without inline
styling, since reverse-videoing to white on a stock-light-theme card is white on
white; an open dropdown list was clipped by the title bar because the existing
clamp callback was never wired to those two; and the Do Not Disturb chip now
uses the crescent moon the status bar already uses instead of a slashed bell
sitting beside the Sound chip.
navMoveDir is shared by every keypad board, so the whole matrix was rebuilt on
the real merge rather than on the earlier commit that lacked it: all nine S3
envs and both ESP32-P4 targets, all green, with both sides of the merge verified
present in the tree.
Tested on hardware by the author on T-Deck, ThinkNode M9 and T-LoRa Pager.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oumike. Keypad navigation could skip a whole row on the Send advert screen,
because candidates were scored on axis distance plus an off-axis penalty with no
notion of rows, so a wide dropdown two rows down beat the narrow right-aligned
switch directly beneath. navMoveDir now establishes the nearest row band first
and only scores within it. That is shared navigation code, so the whole matrix
was built: all nine S3 envs and both ESP32-P4 targets, all green, and nothing
from today's work was reverted by the merge.
Also in it: the focus highlight was invisible on two dropdowns that were created
bare, since reverse-videoing to white on a stock-light-theme card is white on
white; an open dropdown list was clipped by the title bar because the existing
clamp callback was never wired to those two; and the Do Not Disturb chip now
uses the crescent moon the status bar already uses, instead of a slashed bell
sitting next to the Sound chip. Tested by the author on T-Deck, M9 and Pager.
Separately, the lock-screen colour swatches (#389, Buko84). Each swatch had its
colour set only for the default state, which left the theme free to repaint the
focused and pressed ones. On a d-pad board the swatch you are standing on is by
definition focused, so it turned white and you could not see the colour you were
about to choose, which is what Buko84 described. The colour is now pinned across
those states and focus shows as a ring outside the swatch rather than a fill
over it.
That is the picker, not the reported black lock text, which is still open.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four small UI fixes, all reported on the ThinkNode M9 build.
Keypad nav could skip a row. navMoveDir ranked candidates by
"primary + 8 * cross" (gap along the pressed axis, plus a heavy penalty
for being off-axis), with no notion of rows. On Send advert, DOWN from
the Flood button landed on the "Flood every" dropdown two rows below it:
the dropdown is wide enough to overlap Flood's x-range, so its cross
penalty was 0, while the "On boot" switch directly beneath is narrow and
right-aligned, so its horizontal gap out-weighed a whole extra row of
distance. navMoveDir now walks the candidates twice — the first pass
takes the nearest element's rectangle as the row (or column) band, the
second discards anything lying entirely past that band before applying
the existing score. A press can no longer jump over an intervening
focusable, while alignment still decides within a row. Split the shared
geometry into navDirCandidate() and navDirMetrics() so both passes use it.
The focus highlight was invisible on the advert dropdowns. LVGL's stock
theme here is the light one (nothing calls lv_theme_default_init, and
lv_conf.h leaves LV_THEME_DEFAULT_DARK at its default), so a dropdown
with no explicit styling renders as a near-white card — and navFocusCb's
highlight reverse-videos the focused widget to COLOR_TEXT, painting white
on white. The settings pages never hit this because they set COLOR_PANEL
inline on every dropdown they build; the two on Send advert were created
bare.
An open dropdown list was clipped by the title bar. lv_dropdown's
constructor parents its list to lv_obj_get_screen(), which for a page on
lv_layer_top is that layer, so the list sat below the status bar in the
child order and got covered where they overlapped. clampDropdownListCb
already exists for exactly this ("wire onto every settings dropdown") and
was simply never wired here.
Both of those are now handled by one styleDropdown() helper — panel
colours and the 12px font on the button and its list, an accent-filled
selected row (LVGL draws it in LV_PART_SELECTED with the CHECKED state,
which the stock theme fills with its own blue primary rather than the UI
accent), and the clamp — applied to the two advert dropdowns. The
settings pages keep their inline styling for now.
Do Not Disturb gets its usual crescent moon. The control-center chip used
a bell that gained a slash when DND was on, which reads as "notification
sound" and collided with the Sound chip beside it; the status bar already
used the moon. The chip's own fill (solid accent on, faint off) carries
the state, so the glyph no longer changes with it. No new font asset:
sleepicons_font already carries U+F186 and is spliced into the g_font_16
fallback chain that ccToggle renders its icon in.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jrote1 is fitting a battery-backed DS3231 to the T-Deck's I2C bus so the device
keeps time across a full power-off without needing GPS or Wi-Fi. The RTC adapter
already had the hard parts, refusing an untrustworthy read and revalidating every
write, so this teaches it a third chip rather than adding a second path.
Three things differ from the soldered PCF parts and all three are handled by
normalising into the existing decoder rather than duplicating its rules:
- It lives at 0x68 rather than 0x51. That is also what makes it safe to add: it
cannot be mistaken for either chip an existing board carries.
- Day-of-week and day-of-month are swapped in its calendar block.
- It reports "my time is not trustworthy" as OSF in a status register instead of
in bit 7 of seconds, so that bit is folded into the one the decoder already
refuses on. A module whose backup cell went flat is therefore rejected the
same way a powered-off M9 is, rather than having its frozen calendar believed.
0x68 is a busy address, so identification requires a positive DS3231 trait
rather than an ACK: its temperature register pair, whose low byte is a quarter
degree in the top two bits and zero elsewhere, with a high byte in a range a
working chip could be at. A module that has never been set by us can also arrive
in 12-hour mode, which is converted rather than decoded as if it were 24-hour.
Transparent when no module is fitted, which is nearly every T-Deck: reads come
from the software clock, writes stop there, and the probe logs that nothing
answered. CAP_HARDWARE_RTC stays 0 for the board, since this is an aftermarket
addition rather than something a T-Deck has, so the cold-boot network time sync
stays on offer for everyone else.
Unverified on hardware: neither of us has the module wired yet. The host tests
for the shared decoder still pass.
Built on all nine S3 envs and both ESP32-P4 targets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The M9's screen was never really going off (#390, Buko84, who spotted the lock
screen still faintly visible in bright light). Backlight off is not screen off:
the ST7789 keeps refreshing the same static image behind a dark backlight, which
is exactly what retains an image into the glass. The panel-sleep command the
T-Deck and V4 use was a no-op stub on this board and the M9's backlight path
never called it anyway. Both halves are fixed. The M9 takes the display driver's
default constructor branch, on the global SPI instance it shares with the radio
and with no PIN_TFT_SCL/SDA defined, so the command goes over that same bus
object inside a transaction rather than a second one on pins this board does not
declare.
The Discovered list showed impossible hop counts (#394, jesshampshire). 0xFF is
OUT_PATH_UNKNOWN, meaning an advert arrived by flood with no known path, and it
was being printed as a number: "255 hop". It now says the path is unknown, says
"direct" for zero hops, and pluralises, so a two-hop node no longer reads as
"2 hop".
Keyboard backlight controls are hidden on the older T-Decks (#382,
jesshampshire), which have no controllable backlight, so those controls did
nothing while still looking like settings. The "Older keyboard protocol" switch
added in beta_74 already declares which keyboard is fitted, so it gates these
too rather than asking the same question twice. The other half of that request,
swallowing the backlight key combo so it does not type a stray character, needs
to know which character actually appears; asked on the issue.
Also removed a duplicated pair of declarations in the prefs header.
Built on all nine S3 envs and both ESP32-P4 targets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Buko84 asked whether the M9's notification sounds can be made louder. There is
no volume setting because there is nothing to turn: the buzzer on these boards
is a bare piezo on a GPIO driven by tone(), a fixed-duty square wave, so
amplitude is simply whatever the part does at that pitch.
Frequency is the lever that does exist. A piezo is far louder near its mechanical
resonance, typically around 4 kHz, than at the 1 to 2.6 kHz the chime uses, so
"Loud alerts" in Settings, Sound plays the same three-note shape shifted into
that band. Same chime, so it still reads as the same alert rather than a new one,
and toggling it plays it so the difference is audible while the switch is still
under your finger.
Off by default and opt-in, because the exact resonant peak varies by part and
this has not been measured on an M9. Schema v55, field appended at the tail.
Built on all nine S3 envs and both ESP32-P4 targets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nordic letters on the M9 (#387, leon699). The letters were never missing: ø is
in the 'o' accent set and å and æ are in the 'a' set. The picker that reaches
them was switched off on this board because it is tap-to-pick and the M9 has no
touch, and the key-driven path built for the Pager was gated to the Pager. The
M9 does have a d-pad, and its mention box already uses it, so the accent box now
works the same way: left and right walk the variants while the box is up, Enter
takes one, Back or Backspace dismisses. There is no arming step, because the box
only exists for the moment after typing a base letter, so the arrows go back to
the caret as soon as it closes. The Accent popups setting is now shown on the M9
as well, since it finally controls something there.
Found while doing it: the key-selection cap was 8 while the 'a' set holds 9
variants, æ having been appended for #237 without bumping it. So æ specifically
was unreachable by key selection on the Pager too, which is the exact letter
class this issue is about.
The app drawer's message count was stale after unlocking (#393, Buko84). The
badges are a snapshot taken when the grid is built and nothing refreshed them,
so a message arriving while the drawer was open stayed invisible until you
navigated away and back, which is precisely what the reporter found. The refresh
tick now rebuilds on an actual change of the counts, not on every tick.
Also a guard, prompted by chasing a wrong theory: the whole prefs struct is
persisted as one blob, and the file-backed store rejects any value over 2048
bytes silently, so crossing that ceiling would stop every preference saving with
nothing in the log. It is about 500 bytes today and grows most releases, so the
ceiling is now a build error instead of a future support thread.
Built on all nine S3 envs and both ESP32-P4 targets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Wi-Fi came back on after a power cycle on the T-Display P4 while every switch
in the UI read off, and the radio really was on: it downloaded a map tile
(#373, wb6zsu, with photographs of both states). The C6 runs its own AT
firmware with auto-connect enabled, so it rejoins the last access point by
itself at power-on, before the firmware ever asks it to. The loop that enforces
the radio pref only acts on a change, and on the first pass it recorded the
state instead of applying it, so "off" never became a transition and the join
the C6 made on its own was never torn down. That is also why toggling Wi-Fi on
and then off fixed it: that made a transition. The state is now applied on the
first pass, once the AT link is actually up. The Tanmatsu carried the same
first-pass logic and got the same fix.
The map's own location marker is a bare white glyph and vanishes on light
basemaps (#366, 100monkeys). The contact markers a few lines below it already
carry a dark border for exactly this reason and self never got one, so it now
gets the same: a dark chip behind the glyph, which reads on any basemap without
spending a colour that already means something else.
Wi-Fi passwords can be revealed while typing (#381, jesshampshire). Entering a
PSK blind on a device keyboard is a real failure mode: the reporter needed about
ten attempts to join their own network. The toggle sits on the password label's
line so it costs no vertical space, and it switches the keyboard mirror too, or
the characters stay masked in the one field being looked at.
The composer shows a character count (#350, jrote1). The 160-character cap was
silent: typing just stops, which reads as a broken keyboard. It appears only in
the last quarter and marks the limit when reached, so an ordinary short message
carries no readout. Counted in codepoints to match how the cap is enforced;
by bytes an emoji would read as four characters.
Built on all nine S3 envs and both ESP32-P4 targets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The PR added the board and its env, which makes it build. It does not make it
ship. Three lists decide that and none of them knew about it: release.sh ENVS
(what gets built and named into a release), gen-flasher-meta.py BOARDS (which
manifests the web flasher gets) and the flasher page itself, which lists boards
by hand.
Without these the board compiles cleanly forever and never appears in a release
or on the flasher, which looks like nothing is wrong.
Named wadamesh-wio-tracker-l2, its own Seeed Studio section on the flasher,
marked beta-channel-only like the Attaky was on arrival, since stable is still
beta_65 and this board has never been in a stable build.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oumike. ESP32-S3, 16 MB flash, 8 MB octal PSRAM, quad-SPI 320x240 panel via
LovyanGFX, GT911 touch, SX1262 at 22 dBm, GPS, microSD on SD_MMC. New board
file, new variant, one new env. Seeed start selling it next week and the
maintainers have the hardware; we do not.
Merged on their hardware bring-up rather than ours: display, touch, GPS,
microSD, battery and buttons are confirmed on-device by the author. What this
side can prove is that it does not cost anyone else anything, so the whole
matrix was built: all nine S3 envs including the new one, plus both ESP32-P4
targets.
The PR also carries accumulated touch-UI work from the author's development
line, which he flagged himself and offered to split out. Merged as-is because
the three-way merge came through with zero conflicts and nothing from today was
reverted: the prefs schema is still v54, and kb_force_legacy, boot_wifi_open,
the P4 gpsEnsureBigRxRing fallback and the chat-store migration logging are all
still present. Verified explicitly rather than assumed, since the diff against
main showed 1706 deletions purely because the branch predates today's merges.
Two bring-up findings worth keeping: Arduino's Serial1.setPins() takes (rx, tx),
so PIN_GPS_TX is the pin we receive on -- the same naming trap the V4-R8 and
Pager envs already carry, and it left this board's GPS listening on a silent
pin. And the panel is fixed landscape (CAP_ROTATABLE 0), so the keyboard's
rotate arrows are not built there.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
oumike's #384closes#374: MeshCore seeds _txt_last_ts from an unrestricted
random uint32 and sendMessage() then raises the real RTC timestamp above it, so
outbound direct messages carried future dates on most boots. Patched in the
vendored core, fail-closed and idempotent.
#386closes#383: hardware RTC for the Pager's PCF85063A and the M9's PCF8563
with validation on both sides, time retained through a full power-off, and an
opt-in cold-boot Wi-Fi time sync. Prefs schema to v54, appended at the tail with
the assert updated.
cvhviz's #385 makes the M9's battery saver reachable at all (the machinery was
wired but the only enable switch was compiled for two other boards), fixes
GPS-off leakage, and enlarges the GPS UART ring mid-session.
Two build problems, neither visible to the authors:
- #384 wires its core patch into the IDF builds as a fail-closed VERIFY, and
the vendored copy in this checkout predated it. Applying the same patch step
fixes it; the error already said to run fetch-deps.sh, which is the right
design.
- #385 calls gpsEnsureBigRxRing() from UITask.cpp and MyMesh.cpp, which every
target compiles, while the definition is in src/main.cpp, which the two
ESP32-P4 targets never compile. Both P4 builds failed to link. A no-op
definition for those boards lives in UITask.cpp now, which is the standing
rule for anything shared between the UI and the Arduino main. A no-op is
honest here: neither P4 drives GPS through Serial1.
Built on all eight S3 envs and both ESP32-P4 targets.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
byrdo1308's Tanmatsu sits permanently on "migration pending, Last save FAIL",
and his boot capture contains NO migration lines at all. That is the useful
fact: the migration is not failing verification, it is bailing before anything
loggable happens.
There were two silent early returns and no success line either, so from outside
"never attempted", "gave up immediately" and "ran and failed" were
indistinguishable. Now each says which:
starting
no scratch buffer (allocation failed)
read-back verify FAILED across N segment(s); old file kept, will retry
cannot create the commit marker; old file kept
OK, N segment(s), M bytes
His other answer narrows it further and is worth recording: contacts and
Discovered DO persist across reboots on that device, so FFat works and this is
specific to the chat store's migration rather than the filesystem.
No behaviour change, only diagnostics. His next boot log should name the step.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three battery fixes plus the record in M9_PORT.md:
- The touchSleep battery saver was unreachable on the M9: the settings
switch and its callback were compiled only for the T-Deck and V4-R8,
and the pref defaults OFF with no other setter. Both #ifs now include
HAS_THINKNODE_M9 (the M9 is in batteryIsCharging's #else branch, so
the "USB powered" gate can never wrongly block there).
- WadaNmeaLocationProvider parked GPS_RESET asserted while the GPS was
stopped, per the core's convention. On the M9 that polarity is
inverted through R46 into NPN Q16, so "asserted" sourced base current
the entire time the GPS was OFF. Park the line LOW instead — the
zero-current level for both circuit styles; every start path still
pulses reset() explicitly, so the clean-reset guarantee holds.
- A mid-session GPS enable ran the whole session on the stock 256 B
Serial1 RX ring (the 4 KB TTFF ring is boot-gated on the persisted
pref, and setRxBufferSize is a no-op on a running UART) — and with
the saver newly reachable, one 50 ms park overflows that ring at the
M9's 115200 baud. gpsEnsureBigRxRing() cycles the UART to 4 KB from
both "gps on" funnels (toggleGPS, CMD_SET_CUSTOM_VAR) right after a
successful start, so a refused enable never spends the DRAM.
Also corrects both stale "keyboard S2 on the switched rail" comments
(schematic truth: always-on 3V3 via R3 — it decides the deep-sleep
drain budget). All 8 envs compile; on-device VERIFY list in M9_PORT.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MYDmpc53KpdmTLKzUNsX2s
Read the caveat before taking this one: it does NOT fix a live bug. The
sweep was suspected of causing repeated ~1.7 s ui:lvgl stalls that appear
only while the Spectrum page is open. Instrumenting it disproved that -
measured on M9 hardware the sweep costs 5181-5904 us/bin, so the constant's
own "~5.5 ms/bin -> ~44 ms UI block" estimate was right all along and eight
bins really is on budget.
That measurement is the "re-verify with a micros() log on hardware" the
standby(0x01) comment has been asking for; the verified figure is now
recorded at the constant so nobody re-derives it, along with a pointer that
the real cost is in the REDRAW path (spectrumPushWaterfall /
spectrumDrawTrace - ~1.8 s of LVGL compute against only ~190 ms of panel
flush), not the sweep.
What remains is a cheap guard: bound the tick by wall clock rather than a
fixed bin count, so a slower radio or a busier bus can never turn it into a
multi-second block. It is clamped so it can only ever REDUCE bins per tick,
never raise them above SPEC_CHUNK - on hardware it simply settles at 7-8,
i.e. today's behaviour, on every board. Hysteresis stops it flapping 7<->8,
since 8 x ~5.5 ms sits inside the jitter band of the budget.
Drop this commit if you would rather not carry the guard; nothing else
depends on it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Christopher Van Hoose <cvhvisuals@icloud.com>