68 Commits
Author SHA1 Message Date
Kaj Schittecat 8954fe06df Merge repeater-admin: repeater and room server admin app
# Conflicts:
#	deploy/apps/lang/bg.lang
#	deploy/apps/lang/cs.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/i18n_builtin.h
2026-10-18 15:34:38 +02:00
Kaj SchittecatandClaude Opus 5.5 2a4e48bc50 touch: repeater and room server admin app
The contact sheet's Admin button (repeaters, and now room servers) opens a
full admin app instead of a password box and a raw console. Login as admin
or guest (remembered password logs in at once), then an overview with the
node's status (GET_STATUS), firmware and owner info (GET_OWNER_INFO),
neighbours (GET_NEIGHBOURS, paged, newest or strongest), telemetry, the
access list with role changes (GET_ACCESS_LIST + setperm), settings pages
(identity and location, radio, repeating and limits, adverts, passwords and
access, regions) and actions (adverts, clock sync, discover neighbours,
reset counters, reboot, firmware update). The old CLI console stays as
Advanced > Console.

Every CLI line carries the "wX|" correlation prefix MeshCore echoes back, so
a reply always lands on the field that asked; one request is in flight at a
time, reads retry once, writes never, and a write is followed by a re-read.
Radio changes go through a confirm that shows old and new values, with a
10 minute tempradio trial before a saved change, and a reboot offer after.

MyMesh gains uiSendBinaryRequest (UiReqKind::Binary -> onBinaryReply) and
uiLastLoginInfo (the LOGIN_OK ACL byte and firmware level). The repeater
ping toast read uptime from the airtime field (offset 16, not 20); fixed.
Parsing lives in RepeaterAdminProto.h with a host test. Translations for all
14 languages. -DDOC_CAPTURE -DDOC_AUDIT_FOCUS -DDOC_ADMIN_AUDIT captures the
app against a node in range as a guest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:17:08 +02:00
Kaj Schittecat 33426ffaa4 Merge p4-at-recovery: the AT C6 resets and rejoins when it stops answering (#685) 2026-10-18 15:12:14 +02:00
Kaj SchittecatandClaude Opus 5.5 ce620f2671 tdisplay_p4: reset and rejoin a wedged ESP-AT C6 at runtime (#685)
On units whose C6 runs LilyGo's factory ESP-AT image, Wi-Fi died after
about an hour: scans came back empty, Connect did nothing, and only a
reboot helped. The AT worker brought the SDIO link up once at boot and
never again, so a C6 that wedged mid-session left every command timing
out for the rest of the session.

Link health: AT commands now report OK / ERROR / TIMEOUT / BUSY. A
timeout or busy reply asks for a plain "AT" ping; a ping that times out,
or three command timeouts in a row with no answer between them, is a
stall. ERROR replies are answers and never count. Connects to dead hosts
(CIPSTART) and missing CIPSEND prompts only ask for a ping. The decision
logic lives in c6_link_health.h with a host test.

Recovery: on a stall the worker takes the link down at once (queued
requests fail fast, links read closed so the TCP/WS companion servers
prune them), then runs sdio_bring_down + the XL9535 C6 reset +
c6at_begin, the same path as boot. Resets back off 30 s, 60 s, 120 s,
240 s, then every 5 min; 10 min of a healthy link resets the ladder.
After a successful reset it rejoins the last network and the kept
listener want-flag re-arms AT+CIPSERVER, so port 5000 comes back without
app-side changes. The Wi-Fi status reads "reconnecting" meanwhile, and
main.cpp no longer treats the reset as "radio off".

Join/disconnect: one latest-wins slot instead of the 6-deep queue, so
the user's Connect can no longer be dropped behind 10 s retry pairs and
a stale CWQAP can no longer undo a fresh join. c6at_join_busy() gates
main.cpp's retry while a join is pending or running.

Bring-up now also sends AT+SLEEP=0 and sets AT+CWRECONNCFG=5,0 when the
AT build supports it (query first; ERROR on old images is ignored).

The esp-hosted build is unchanged: everything is in the c6_at component
or behind !TDP4_C6_HOSTED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 15:05:54 +02:00
Kaj Schittecat 75074e9d2a Merge PR #688: backups get one meshcore-backup-<date> name, no overwrite without a clock (#686) 2026-10-18 14:43:17 +02:00
Michael A. CojocariandClaude Opus 5.5 c67d07e252 Backups: one meshcore-backup-<date> name, no overwrite without a clock (#686)
With the clock unset, Export backup named the file after uptime seconds
(meshcore-backup-86.json), which read as a broken date and repeated after
every reboot, so a later export could overwrite an earlier backup.

Every timestamped backup is now meshcore-backup-YYYYMMDD-HHMMSS.json (was
meshcore-YYYYMMDD-HHMMSS.json), so the date always ends the name. Without a
set clock it is meshcore-backup-nodate-N.json; the Backups page picks the
first N not already on the card or in internal storage. The web download
uses the same pattern without the storage probe. Older names still list
and restore (the filters match meshcore-*.json).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MjAKa5hXgyBykCLqnwcjEy
2026-10-17 19:35:48 -04:00
Confituurke 8acb68415d feat(sensecap-indicator): SX1262 behind the TCA9535 via a RadioLib HAL
The SX1262's NSS, RESET, BUSY and DIO1 sit on the TCA9535 (P0.0..P0.3)
while SCK/MISO/MOSI are real GPIOs. IndicatorRadioHal derives from
ArduinoHal, keeps hardware FSPI at 4 MHz and maps the virtual pins
0x40..0x43 onto expander transfers; every other pin goes straight to
the Arduino pin and interrupt functions, as ArduinoHal does. The
meshcomod core is used as is: CustomSX1262 and its wrapper run
unchanged on top of the HAL.

DIO1 has no GPIO edge. The expander's open-drain /INT on GPIO42 has an
IRAM ISR that only notifies a dispatch task, because the read needs
I2C and the bus mutex. That task reads both input bytes under the bus
lock: the TCA9535 releases /INT only when the port that changed is
read, and several port-1 inputs float, so a port-0-only read could
leave GPIO42 low for good. Every port-0 byte feeds a small edge
detector (Dio1Edge) through the expander's port-0 observer, which
calls the callback RadioLib attached to DIO1 once per rise. That
callback is the core's setFlag, so TX-done and RX-done reach the core
exactly as they would from a DIO1 interrupt.

A BUSY poll is a port-0 read as well, and it clears /INT. Sampling DIO1
in those reads means a rise that lands while RadioLib waits on BUSY
fires the callback from that read instead of being lost; the dispatch
task's next read sees the level already high and does not fire again.
The task also re-reads every 50 ms, so a rise whose /INT was missed
still fires the callback, at most 50 ms late. MESH_RADIO_DIO1_POLLED is
not set: polling the IRQ register would add an SPI transfer, and with
it expander writes for NSS, on every pass of the receive path.

Floating port-1 inputs can keep /INT toggling, each toggle an I2C read
at high priority. The task counts ISR wakes and timeouts and logs them
for each of the first six 10 s windows, then only above 500 wakes/s
and at most once a minute, so the idle wake rate is visible at
bring-up without flooding the console later.

TCXO or crystal comes from the radio strap read at boot: the env's
SX126X_DIO3_TCXO_VOLTAGE calls indicatorTcxoVoltage(), which returns
2.4 V for the TCXO radio and 0 for the crystal one. radio_init() starts
the radio with the strap's setting and, if that fails, with the other
one, because std_init only falls back from TCXO to 0 V and a TCXO board
whose strap misread as crystal would never start otherwise. main.cpp's
one retry runs both again. The log says which voltage was requested and
which one the radio came up with.

The board reports GPIO42 (indicator::kExpanderIntGpio, shared with the
HAL) as its IRQ line and never light-sleeps: the inherited ESP32Board
versions would treat the virtual DIO1 pin as a GPIO, and /INT is active
low while the light-sleep wake source is high-level.

test_indicator_dio1 covers the virtual-pin routing and the
once-per-rise rule across dispatch and BUSY reads; run-host-tests.sh
gets its include path.

Signed-off-by: Confituurke <9595028+Confituurke@users.noreply.github.com>
2026-10-17 22:39:29 +02:00
Confituurke d8200c35b3 feat(sensecap-indicator): board skeleton, TCA9535 expander and build target
Add the Seeed SenseCAP Indicator D1L as a new touch target. This step
builds and boots to a blank screen; the panel, touch and radio drivers
follow in their own commits so each can be reviewed against the
hardware it drives.

The SX1262's NSS, RESET, BUSY and DIO1, the LCD CS/reset and the touch
reset all sit on a TCA9535 at 0x20 on the SDA39/SCL40 bus, not on ESP32
GPIOs, so everything after this commit depends on the expander. The env
gives the radio lines virtual pin numbers 0x40..0x43 for a radio HAL to
map onto expander bits; until that HAL exists the radio is a plain
Module that links but cannot drive the chip.

The expander driver is header-only so a host test can cover it:
- its output and config shadows are seeded from the chip, because a
  warm reset or the previous firmware may have left pins driven and
  writing the power-on defaults back would glitch the radio, panel and
  RP2040 resets;
- a failed write rolls both shadows back to the last state written in
  full, and the next write brings the chip back in line with them;
- polarity inversion is cleared when a previous firmware left it set,
  since it would invert BUSY, DIO1 and the strap; it is written only
  when it is not already 0;
- every read of input port 0 is reported to an observer, so a DIO1 edge
  is not lost when another reader clears /INT.
A five-sample majority of the P1.3 strap selects the TCXO (2.4 V) or
crystal radio at run time, so one noisy read cannot pick the wrong
oscillator.

The expander, the touch driver and the display init serialise on one
recursive mutex over the shared Wire bus (IndicatorIo::BusLock). It is a function-local static, so the compiler
guards its creation when two tasks reach the first lock together.
IndicatorIo::begin() logs the registers it inherited and names the
step that failed, since a dead expander takes the panel and radio down
with it and is otherwise silent.

The board has 8 MB of flash. The partition table has one 4.5 MB
factory app slot, a 3.375 MB SPIFFS (as on the 16 MB boards) and a
64 KB coredump, with no map tile partition. The factory slot is larger
than the 0x3E0000 OTA slots of the 16 MB boards, so an image that fits
those fits here. CAP_OTA is off: with one app slot there is no in-place
update. CAP_BATTERY is 0: no battery is fitted.

Montserrat 18/20/24 for the UI-size presets are the generated faces; the
board joins their gate in ui_font_18/20/24.c, gen-touch-fonts.sh,
hwFitFont and luaHostFigureFont. LVGL is built at -O2 with the Arabic and newlib-memcpy
settings the other envs use.

run-host-tests.sh builds every test/test_*.cpp, so the new expander
test gets its include path there in the same commit.
build-upload-monitor.sh gets --sensecap-indicator.

Signed-off-by: Confituurke <9595028+Confituurke@users.noreply.github.com>
2026-10-17 22:39:16 +02:00
Kaj SchittecatandClaude Opus 5.5 ed896b3404 touch: history never drops records at the segment cap; host test for the scheduler
fcb392a merged two neighbouring segments at the 24-file cap and, when no pair
fitted in one file, retired the oldest segment. A host simulation showed that
fallback does fire for heavy users (many quiet chats next to a few busy ones):
up to 626 old messages dropped over 60 reboots on 3 of 8 seeds. Now 24 is a
soft cap: at 24 pending compactions run first, then the best merge, and only
when no pair fits may a new segment open, up to a hard cap of 48. Below 41
files some adjacent pair of the 5000 ring records always fits, so the hard
cap never stops a save and nothing is dropped; files only pass 24 in that
heavy case, so older builds still read every file for almost everyone.

test/history_segments/run.sh lifts the scheduler out of UITask.cpp verbatim
and runs it against a simulated card: mixed, many-quiet, one-busy and
reboot-heavy workloads plus power cuts between a merged write and the
absorbed file's removal. 60k messages per run, every live record must
survive every reboot with no duplicates. beta_95's code loses ~48k of 60k in
the same run; this passes every mode (max 29 files).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 17:59:29 +02:00
Kaj SchittecatandClaude Opus 5.5 5198813f99 usb files: Lua apps can be dropped into the apps folder
/meshcomod/apps on the card and /apps internally now take top-level .lua and
.json files, so an app can be side-loaded without pulling the card (Discord).
Everything else there stays read-only: the folder itself, subfolders (<id>.d
data), app save files (.sav, .wgt), hidden names and 8.3 aliases. A change
from the page invalidates the cached app listing, so the drawer and the Store
pick the new app up the next time they open. Host tests cover the policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 11:54:47 +02:00
Kaj SchittecatandClaude Opus 5.5 75ec6759dd test: the M9 keyboard test follows #628's one-read-per-poll driver and checks its 70 ms repeat filter
#662's test queued three keys and read them all after one poll; #628 (for #625,
keys typing twice) reads one key per poll and drops the same key again within
70 ms. The test now polls per key and also checks that a repeat inside 70 ms is
dropped and one after it is a new press. Host tests: 18 pass, 2 skips.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:34:12 +02:00
Terry Carr 7e539c3373 Merge branch 'ALLFATHER-BV:main' into hash-channels-links 2026-10-13 11:38:23 +01:00
Kaj Schittecat 01182d1be1 Merge PR #624 (jcyrio): Pager: Fn+V pastes the clipboard (no touch path to the edit menu) 2026-10-13 09:18:18 +02:00
bfindlay ad13d42a12 Fix M9 keyboard detection at the V1.1 I2C address
Signed-off-by: bfindlay <bfindlay@acm.org>
2026-10-13 14:43:28 +11:00
Terry Carr 6c99a085a4 fix: enhance hex color validation in ChatHashtag and update tests 2026-10-11 23:00:01 +01:00
Terry Carr ace2023026 feat: add script for chat link highlighting and testing 2026-10-11 20:40:54 +01:00
Terry Carr cc4f0f318a fix: link highlighting and drawing for chat messages 2026-10-11 20:31:02 +01:00
Terry Carr 8cc4a5aacf Merge branch 'ALLFATHER-BV:main' into hash-channels-links 2026-10-11 12:05:52 +01:00
CyrioandClaude Fable 5.1 5a3c1be63d Pager: paste chord on Fn+Enter instead of Fn+V
Review follow-up (B0N3-GD): Fn is the Pager's only symbol key, so a
held-Fn letter chord takes that letter's symbol away -- Fn+V cost the
question mark with Fn held. Fn+Enter types nothing today (Enter has no
entry in the symbol map), so the chord moves there and costs nothing.

- PagerKeyboardState: V_POS -> ENTER_POS (row 1, col 9); the chord is
  still resolved by matrix position before the symbol map, only while
  Fn is physically held, and cleared by discardAlt().
- PagerKeyboard.h/.cpp, UITask.cpp: renamed to *AltEnterChord*; the
  paste path itself (txtPasteInto, hex-key extraction, toasts, drain
  sites) is unchanged.
- test_pager_keyboard_state: chord on Enter fires once and swallows the
  press; a plain Enter still returns '\r'; tap-Fn-then-Enter is not a
  chord and types nothing, as before; Fn held + V types '?' again.
- TLORA_PAGER_SHORTCUTS.md: both rows now say Fn held + Enter, and the
  em dash is gone from the prose.

Rebuilt and flashed on a Pager (RAM 37.8 %, Flash 90.0 %, unchanged).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VKtRiAcddFBAUDogBWmZk4
2026-10-10 13:57:18 -07:00
Terry Carr c5042b71d3 fix: improve hashtag span validation and enhance test coverage for edge cases 2026-10-10 21:56:21 +01:00
Kaj SchittecatandClaude Opus 5.5 d68f276b06 touch: beta_93, contact nicknames, new control panel, tooltips, Pager shortcuts, speed and battery
Contact nicknames: TouchPrefsStore nick blob (48 x 38 B), display-only lookups
(contactShownName, threadShownName, senderShownName), Contact options >
Nickname on the shared text prompt (an empty OK clears it).
Control panel restyled in the launcher's tile language, with a Battery saver
tile; keyboard tooltips (navSetTip), an amber cursor and touch long-press
tooltips (the T-Deck touch read hid them on every poll).
T-LoRa Pager: M/C/A/S on Home, H/S tap and hold, Backspace = Back, N = jump to
new, held Enter = long press, per-tab focus memory, Power off confirm; LVGL
-O2 and IRAM, INT-gated keyboard, idle yield, status-bar setter guards, instant
tabs; PagerGps (u-blox software standby for rests, GPS rail cut when off),
panel sleep, amp off at boot, power-off parks the radio (NSS held), GPS, panel
and keyboard light.
Fixes: the DM duplicate check keys on the attempt and a path reset clears it,
the direct timeout masks the packed path byte, web remote hops (#636);
changelog over http (#638); Lua SDK send budgets and duplicate guard (#606);
GPS speed note (#613, CAP_GPS_BAUD_AUTO); emoji chat icon (#640); default
wallpaper (#626).
Heltec V4: Tetris, 2048 and Breakout unseeded and the built-in Lua stripped of
comments (identical bytecode), 27 KB freed; the baked language table is pinned
to beta_92's files (scripts/build/lang-pin-beta92) so it still fits.
Translations: 39 new keys in 14 languages, language store v24.
Site: theme, dark mode, aurora and guide refresh, SDK send limits, nicknames.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:08:16 +02:00
Terry Carr 4ed3fdb52c fixed formatting 2026-10-10 17:34:47 +01:00
Terry Carr e6490b5f7b fixed formatting 2026-10-10 17:33:56 +01:00
Terry Carr bf8c2255c0 fix: handle trailing hyphens and underscores in hashtag spans 2026-10-10 16:14:04 +01:00
Terry Carr 6cbc9892cb test: enhance hashtag span tests for better coverage and edge cases 2026-10-09 20:53:53 +01:00
CyrioandClaude Fable 5.1 e3baa774f5 Pager: Fn+V pastes the clipboard (no touch path to the edit menu)
On the T-LoRa Pager there was no way to paste: the in-RAM clipboard is
only consumed by the Cut/Copy/Paste edit menu, which opens solely from a
touch long-press on a textarea, and the Pager's encoder never reaches
LVGL's press events. Copy from a message bubble worked; nothing could
paste the result -- the 64-hex contact key and 32-hex channel secret
fields being the painful cases.

Physical Fn(Alt) held + V now pastes into the field being edited:

- PagerKeyboardState: V_POS; a held-Alt V press is resolved by matrix
  position before the symbol map, swallowed, and reported through
  alt_v_chord_pending_/consumeAltVChord() (same shape as Alt+Backspace
  and the Pro's Alt+B). discardAlt() clears it. !HAS_TDECK_PRO only.
- PagerKeyboard.h/.cpp: pagerKeyboardConsumeAltVChord().
- UITask: the Paste cell's logic is factored into txtPasteInto(ta)
  (selection replaced, pasteTextFor hex-key extraction kept) and reused
  by updatePagerAltVChord(), which uses the same "actually editing" test
  as the Alt+Shift Caps chord, hides any open edit menu/accent popup,
  and toasts "Pasted" / "Clipboard empty". Drained at the remote-mirror
  and screen-off discard sites like the other chords.
- TLORA_PAGER_SHORTCUTS.md: documented.
- test_pager_keyboard_state: chord fires once and swallows V;
  tap-Fn-then-V still returns '?'; discardAlt() drops a pending chord.

Trade-off: Fn+V is '?' on this layout, so only a HELD Fn chords. The
one-shot (tap Fn, then V) and locked (double-tap Fn) symbol layers still
type '?', the same compromise the Pro's physical Alt+B makes with '!'.

Tested on tlora_pager_sx1262_companion_radio_touch: Copy from a bubble
then Fn+V in the composer pastes; Fn+V in a key field pastes just the
key out of a copied message; empty clipboard toasts; tap-Fn-then-V
still types '?'.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VKtRiAcddFBAUDogBWmZk4
2026-10-09 02:34:07 -07:00
Kaj SchittecatandClaude Opus 5.5 662665e6de touch: beta_89, the UI overhaul
Every screen redesigned to the Oct 2026 mockups, on one design system:
palette, type roles (generated Montserrat faces with Lucide line icons on the
LV_SYMBOL codepoints), a theme apply-cb that styles every LVGL widget, button
kinds, list rows, sheets as action lists, toasts and key-glyph avatars.

Rebuilt: status bar, Chats (filters, + and menu), conversation (route header,
hop meta, "Heard by N repeaters", delivery states, pill composer), lock screen
(mark, clock, 24 h activity, unread cards), Constellation screensaver, Home
launcher and launcher sheet, Status page, grouped Settings home, animated boot
splash, and the message glance as a notification card.

New: optional PIN lock (salted SHA-256, keyboard / touch pad / number pad per
board, backoff that survives reboot), screensaver on battery with its own
duration, T-Deck text sizes, trackball hold to switch Scroll and Cursor (Scroll
default, prefs v69/v70), hold Settings tab for the control panel. M9 bottom
notices removed. Every screen measured for the Large and Huge text sizes.

Fix: adverts over 2/3-byte path hashes never reached the recently heard table
(packed path_len compared as a byte count); ADVERT_PATH replies now carry the
whole multi-byte path.

English placeholders for the 204 untranslated keys in every .lang file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 23:26:41 +02:00
Kaj SchittecatandClaude Opus 5 a8f813c5f9 Merge PR #599 (oumike): battery saver, Condense Nav, Pager keyboard nav
Closes #591, #592, #593 and the M9 Bluetooth status-bar overlap museifu696
reported. Also the T-Pager battery saver zomgugoff asked for and the status-bar
clock fix.

Review notes, since this landed on top of a day of P4 and M9 work:

* The prefs change is done the right way. condense_nav is appended at the TAIL,
  CURRENT_VERSION goes 65 -> 68 with v66/v67/v68 each described, and the
  trailing static_assert moves to the new last field. v67 and v68 are one-time
  per-board resets with no new field, which is the pattern v63 and v65 used.
* Six conflicts, all in test_touch_prefs_schema.cpp, all the same shape: we had
  both repaired the same broken sums. His version is a superset (it adds
  condense_nav on top of the report_ping/report_done_n I added), so his wins.
  Verified afterwards that both sides survived rather than trusting the merge:
  the channel-send guard, the P4 tile-FS fix, statusPctOverflow, m9NoticeSet,
  m9DiscoveredUnseen and TDP4_C6_ID are all still present, as are his kbd_nav
  and Condense Nav work. Host suite 17 pass / 0 fail at v68.

Also here: PUBLISH the translations the PR added.

It added five strings to all fourteen language files with real translations, but
left "# ver: 22" alone and did not touch langs.json. Devices fetch lang/<ver>/,
so the version is the only thing that makes one re-download: without a bump the
work sits in the repo and reaches nobody, and every non-English device shows
those strings in English. The baked-in table was regenerated, so compiled-in
languages were fine -- it is the downloaded ones that would have silently
stayed behind. Bumped to 23, snapshotted into lang/23/, and langs.json points
at it. deploy-apps.sh validates: 14 languages and 14 apps, every referenced
file present.

That store still has to be pushed with the release; editing deploy/apps/ is not
publishing it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-05 12:30:55 +02:00
Kaj SchittecatandClaude Opus 5 820bc21f46 build: verify the P4 artifacts, and make the host tests runnable
The T-Display P4 ships EIGHT images: panel (AMOLED/LCD) x radio (SX1262/LR2021)
x C6 stack (esp-hosted/ESP-AT). The C6 axis is new and it is the dangerous one,
because the two builds are identical apart from the artifact name they carry,
and flashing the wrong one at an older unit used to boot-loop the board.

scripts/build/p4-release.sh builds all eight and VERIFIES each image against the
OTA name baked into it before accepting it. That is the same string the device
builds its self-update URL from, so a disagreement with the filename is exactly
the failure that bricks people -- the T-Deck Max self-updating to Pro firmware,
and this table has been wrong three times. A mismatch aborts the release.

It earned that check immediately: building the eight by hand produced two
mislabelled images, and every mechanism meant to prevent it was itself broken.

  * build.sh fullclean has NEVER worked from a plain shell. It ran `cmake -E
    remove_directory` above the esp-idf/export.sh that puts cmake on PATH, so it
    printed that it had removed the tree, exited 0, and removed nothing. Now a
    plain rm -rf, and it verifies the directory is gone instead of assuming.
    This matters beyond tidiness: the hosted and ESP-AT builds SHARE a build dir
    (the C6 axis does not change P4_BUILD_TARGET), the backend is read from the
    environment at CMake CONFIGURE time, and CMake does not reconfigure because
    an env var changed -- so a clean that silently did nothing yields an
    incremental build of the OTHER variant, with no configure output to show for
    it. That is precisely how the two mislabelled images were produced.
  * Deleting CMakeCache.txt alone is not a clean either; it leaves CMakeFiles/
    behind and the next configure dies in CMakeDetermineCCompiler.
  * Detecting failure by grepping a log for "error:" misses a failed CMake
    configure, which prints "Configuring incomplete" and no such line -- and the
    previous application.bin is still sitting in the directory to be picked up.
    Exit codes now.

gen-flasher-meta.py gains the four ESP-AT entries, labelled "older unit, ESP-AT
C6": someone choosing in the web flasher has no serial log and needs to pick on
something they can check.

scripts/run-host-tests.sh runs test/ as one command. Nothing ever did, which is
why test_touch_prefs_schema had been failing to COMPILE since v64 appended
report_ping and report_done_n without updating its sums -- and a static_assert
that does not compile is not a guard. That one exists to catch the mid-struct
insert that broke v44 and shipped the poisoned beta_57 layout. Repaired, with a
note where the next person appending a field will read it. 17 pass, 0 fail, 2
skipped with their reasons stated rather than quietly dropped.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-05 11:31:36 +02:00
Michael A. CojocariandClaude Opus 5.5 08750ab3a1 feat(ui): Condense Nav, a landscape nav rail with icon-only Home launchers (#592)
On short landscape panels (the T-Pager is 480x222) the bottom nav bar
spends a full-width strip on five icons. New Settings > Display switch
"Condense Nav", off by default, applies after a restart:

- The nav bar becomes a rail on the right edge (LV_DIR_RIGHT tabview).
  Pager cells show the key letter under the icon; the Tanmatsu keeps its
  coloured F-key shapes; the T-Deck / P4 key hints sit under the icons.
- Home's launcher column (Advert, Terminal, Files, Apps, Control) shows
  icons only; the buttons keep their height and spacing, and the status
  text, chart and info card widen into the freed space.
- The active-tab marker turns vertical beside the active cell, the
  unread and update badges move to the top-right of their cells, and the
  rail stays solid (and the marker visible) on the Map.
- Content that kept the bottom bar visible now stops at the rail
  instead: tabContentW() subtracts the rail, and the map canvas, Settings
  landing grid, chat-list rows, Mentions and the app drawer use it.
  Swipe-left on the rail opens the drawer (swipe-up on the bottom bar).

Portrait layouts and the M9 are unchanged: the mode is decided once in
buildUiTree from the pref and the orientation at boot, the switch is
greyed out in portrait and absent on the M9.

TABBAR_H becomes a runtime value (0 with the rail) next to a new
TABBAR_W. The pref is a new trailing field, condense_nav (schema v66);
the schema test's layout asserts now count the v64 fields they missed.
Strings added to every deploy/apps/lang file and i18n_builtin.h
regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019M5VJ43X3nUULPqVcgrAH2
2026-10-04 13:47:46 -04:00
Michael A. CojocariandClaude Opus 5.5 dbecef0b6f feat(crowpanel-35): add Elecrow CrowPanel 3.5" (ESP32-S3, ILI9488 + GT911) touch target
- New crowpanel_35 variant: display, SD, board and target files, plus board JSON
- Portrait mode is drawn rotated 180°; landscape stays at rotation 3
- Share the GT911 touch driver between Wio Tracker L2 and CrowPanel
  (WioTrackerL2Touch.cpp -> src/helpers/input/LgfxGt911Touch.cpp)
- platformio envs, flasher/site entries, release and merge-bin support
- Tests for the CrowPanel SD codec and merge-bin

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0132MkJ7RUV32mV9F7o4PJMm
2026-10-02 20:16:06 -04:00
Michael A. Cojocari a3090aebbe fix(tdeck-pro): split v1.0 and v1.1 targets 2026-09-23 16:43:29 -04:00
Kaj SchittecatandClaude Opus 5 4df42eedf4 Merge PR #531 from oumike: device usability, input and accessibility
M9 Back and Bluetooth-keyboard Esc stop at the locked app-drawer root, M9
focus fixes, optional M5Stack CardKB on the V4 and V4-R8, V4-R8 touch and
text-size accessibility, Wio L2 SD retry, high-contrast day and night themes
(#544), channel region discovery (#541) and the Czech translation
contributed in #540 by brebtatv.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 13:54:09 +02:00
Michael A. Cojocari a08342a3c0 feat(ui): discover and select channel regions 2026-09-20 19:59:56 -04:00
Michael A. Cojocari 0aa2f28c16 feat(ui): add high-contrast day and night themes 2026-09-20 19:08:45 -04:00
Kaj SchittecatandClaude Opus 5 6fe6b9f063 touch: beta_83, USB Files (files.wadamesh.com over the USB cable)
A built-in USB Files app (T-Deck and Heltec V4 for now) lets the page at
files.wadamesh.com browse, upload, download, rename and delete files on the
SD card, internal storage and the map tiles over Web Serial. File level, not
USB mass storage: the device keeps its filesystems mounted, the radio keeps
running, and the firmware enforces what may change. Live data (identity,
contacts, settings, history; /meshcomod on the card) is download-only.

- UsbFilesProtocol.h: framed messages (E7 5A, CRC32), a resyncing parser,
  path checks and the access policy; host tests in test/.
- UsbFilesSession: one request at a time (both Arduino USB serial drivers
  drop bytes when their RX queue is full), RX queue sized per session, SD
  data through a DMA-capable bounce buffer, FatFs listings with sizes, a
  beacon so the page never talks first, SD Scan's malware verdict per file.
- The companion link and MyMesh's console step off USB while it runs.
- Website: labelled side buttons, a USB Files button and card; the
  files.wadamesh.com page, vhost and deploy script.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 11:52:34 +02:00
Kaj SchittecatandClaude Opus 5 6868911f59 touch: SD Scan, find and remove Windows malware on the SD card
Some ThinkNode M9 cards shipped with a dormant Windows worm (Elecrow
security advisory, September 2026). An infected card seen since carries
autorun.inf in the root, launching xlfqf.pif on open, explore and autoplay
with random-junk comment lines in between: the Sality autorun pattern.

- SD Scan store app (deploy/apps/sdscan/1.0, requires "sd", not seeded):
  walks the card a small page per tick, lists what it finds and why, and
  removes it after a confirmation screen with Cancel first. It says on
  every screen that it only removes files it recognises and that
  formatting the card is the safe fix. On older firmware it still finds
  threats by name but cannot remove them.
- Firmware: wada.sd.check(path) and wada.sd.remove(path), plus paging for
  wada.sd.list(path, start, max) and caps().sd_clean. What counts as a
  threat lives in SdThreat.h: autorun.inf, Windows program, script and
  shortcut extensions, or a real MZ+PE header under any name. remove()
  classifies again in firmware and refuses anything else, so no app can
  use it to delete tiles, backups or chat history. It clears read-only,
  hidden and system first, because FAT refuses to delete a read-only file.
- A warning when a card with Windows malware in its top folder is mounted,
  at boot or on insert, offering SD Scan (or the Store).
- Tests: test/test_sd_threat.cpp, and SD Scan harness scenarios including
  the real infected card's root. Removal checked on a T-Deck.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:33:30 +02:00
Kaj SchittecatandClaude Opus 5 ce37110515 touch: web unlock, a way out of console mode, paste keys, M9 update notice
- Web interface: unlock a manually locked screen (#506). The device publishes
  its lock state over the mirror socket and the page shows an Unlock button
  while it is set. The lock screen absorbs taps by design and only a held
  trackball or BOOT press unlocks on the device, so a browser had no way back
  from a screen it had locked itself.
- Console mode: hold the panel for three seconds to leave it (#507). The
  banner and `help` both say so. Console mode also applies the "Older keyboard
  protocol" setting now: that is applied in the graphical startup path, which
  console mode returns before, so the console ran on protocol detection alone.
  On a T-Deck that needs the older protocol every keystroke there is garbage,
  which is why `ui` could not be typed and the reporter had to side-load a
  second firmware to get the device back.
- Console mode: a touch wakes a dark panel again on the touch-only boards,
  where the keyboard and button wake paths sit below the console branch's
  return. The waking press is swallowed so it cannot also type.
- Paste into a key field lifts the key out of the surrounding text (#526): a
  32 hex digit channel secret or a 64 hex digit public key, spaced keys
  included. Before, the field filled with prose and the length cap cut the key
  off.
- ThinkNode M9: a waiting firmware update is visible (#443). The red "!" over
  the bottom-bar gear is built in the #else of that board's block, so the M9
  had no update signal at all; it gets a third slot in its own notice row,
  steady amber rather than blinking. The Settings tile in the app drawer
  carries an "!" on every board.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 20:43:06 +02:00
Kaj Schittecat 02734a4642 Merge PR #523 from oumike: SD recovery, offline map upload, glance emoji, T-Deck Pro and V4 kit input
- Pager: the SD card mounts again after the card was used on a computer
  (#522): one SD-rail power cycle and retry, and power-off unmounts and powers
  the card down
- At a glance: emoji render instead of squares (#521)
- Transfer page: upload an offline OSM tile folder to /tiles on the SD card
  (#515), with strict path checks and resumable, atomic writes
- T-Deck Pro: touch release debounce, hardware-timed chat-row holds, and a
  typed space no longer starts the screen lock
- Heltec V4 with the original Expansion Kit: the IO button goes back (tap)
  or home (hold), larger keys with a magnified preview, and larger status-bar
  targets (#525)
2026-09-17 19:55:51 +02:00
Kaj SchittecatandClaude Opus 5 793a673c64 touch: Bluetooth LE keyboards on every S3 board
Bluetooth serves either the phone app or a keyboard (Settings > Bluetooth).
Pairing lists keyboards in pairing mode and pairs with or without a code;
the paired keyboard reconnects by itself.

Keys are read the way phones and computers read them: the keyboard's Report
Map says which report carries the keys and how, so media keys and touchpads
are left alone, and the boot protocol is only the fallback. Key positions are
translated with the chosen layout (US, UK, German, French, Belgian), with
AltGr and dead keys.

While a keyboard is connected:
- text goes into the focused field, and touchscreen-only boards keep the
  on-screen keyboard down (a second tap on a field brings it up anyway)
- the arrows, Tab, Enter and Page Up/Down drive the focus highlight
- Esc is the back button; a "Back key" setting picks another key for
  keyboards whose Esc key types a character
- tab hotkeys and their hints over the tab bar are switched on
- Command tapped alone opens the emoji picker in a chat
- the status bar shows a keyboard instead of the Bluetooth glyph

The Bluetooth page is now a flex column with the choice on top, and the
settings page refit keeps it scrollable when its content grows.

Settings: prefs v61 (mode, layout, paired keyboard) and v62 (Back key).
Tests: test/test_hid_report_map.cpp, test_touch_prefs_schema.cpp.
Not on the P4 boards (Tanmatsu, T-Display P4).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 19:52:21 +02:00
Michael A. Cojocari e83bb7582e feat: upload offline maps to SD 2026-09-16 20:53:54 -04:00
Kaj SchittecatandClaude Opus 5 2f6984443d touch: fix the chat page-scroll panic on long chats (#428, #475)
Pressing down past the last message page-scrolls the chat list with an
LVGL animation. In a long chat the list uses compressed scroll
coordinates, and chatVirtRemap1To1Scroll re-anchored the position on
every LV_EVENT_SCROLL with lv_obj_scroll_to_y(LV_ANIM_OFF). That deletes
the running animation from inside its own step. LVGL 8.4 reads the
animation again after the step, and when the step was also its last
(the UI loop was busy for longer than the animation) it finished the
freed animation and freed it a second time. By the next round that
memory belonged to something else, and anim_timer called a garbage
get_value_cb: the jump to 0x00020000 in the beta_79 and beta_80 M9 dumps,
both at the same call site.

- chatVirtRemap1To1Scroll follows a scroll that an animation drives and
  leaves the re-anchor to chatVirtOnScrollEnd. The page scroll now also
  completes on long chats; the early re-anchor stopped it after a frame.
- scripts/build/patch_lvgl_anim_uaf.py skips LVGL's completion check when
  the animation list changed during the step, as LVGL 9 does. Applied to
  every touch env as a pre-script and to the vendored P4 copy
  (fetch-deps.sh, build.sh). Idempotent, fails closed on source drift.
- test/lvgl_anim_uaf/run.sh reproduces it on the host under
  AddressSanitizer: stock LVGL with the old handler reports the
  use-after-free in anim_timer, and either fix alone runs clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 14:43:14 +02:00
Michael A. Cojocari fdde0dd0cc Merge remote-tracking branch 'refs/remotes/pr/497' into integration/all-open-prs-20260913 2026-09-13 18:58:22 -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 2a481ef64c Add M9 lock-home-to-drawer option 2026-09-10 20:04:57 -04: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
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
Michael A. Cojocari 0fafb09044 Expand browser file transfer management (#448) 2026-09-07 14:46:56 -04:00
Michael A. Cojocari 28fb57ef4c Fix Attaky usability issues (#423) 2026-09-06 16:55:41 -04:00
Kaj Schittecat 6db04d3e1d Merge PR #413 (oumike): authenticated browser file transfer 2026-09-06 11:11:57 +02:00