Commit Graph
367 Commits
Author SHA1 Message Date
JakubandClaude Opus 5.5 b99ca6b4ce fix(ui-lvgl): unlock fades away on release, channel names shown as named
- Unlocking fades the lock screen out while the screen underneath drifts
  into place; the slider unlocks on letting go at the end and springs back
  when let go earlier
- Channel names no longer get a "# " prefix (thread title, channel popup,
  live share targets) -- "#" marks a hashtag channel; favourite contacts in
  the live share list are starred instead

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 13:18:43 +02:00
JakubandClaude Opus 5.5 3fd204489e feat(ui-lvgl): roadmap stage 6 - light motion, unified style, accent picker, splash
- Anim.h: screens emerge from the middle (background cover fades, content
  drifts 6 px), Home pages slide, popups and toasts rise and fade in
- Theme.h: accent rules (fill = primary action, dim fill = selected, text =
  names/counts/modes), theme layer over the default theme, radius tokens,
  accent colour picker (Settings > Display & power > LOOK, NVS mc_ui)
- Splash with the Solo and upstream versions
- Status icons in equal cells, L1-style relay marks, path window with a hop
  diagram, tile download popup fits the screen (WiFi only in Settings)
- Perf: uncompressed fonts, two 120-line PSRAM buffers (~48 -> ~33 ms per
  transition frame); UI_PERF_TEST harness

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 13:06:22 +02:00
JakubandClaude Opus 5.5 5124587b26 feat(ui-lvgl): roadmap stage 5 - screen PIN, live map tiles, OTA from GitHub releases
- Screen PIN (Settings > Display & power > Security): 4-8 digits in NVS,
  keypad on the lock card after every wake and on boot, 5 misses -> 30 s pause
- Live map tiles: missing tiles at the current zoom are fetched over the saved
  WiFi, written to the card and drawn; WiFi drops 30 s after leaving the map;
  "Live tiles" switch in Map tools
- Firmware update (Settings > System): checks the fork's latest release,
  streams the -ota.bin into the spare app slot over TLS verified with the
  Arduino 3.x CA bundle, checks image magic/chip id, restarts
- Release workflow publishes *_solo_lvgl envs (merged image + -ota.bin)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:52:53 +02:00
JakubandClaude Opus 5.5 087bac89b4 feat(ui-lvgl): roadmap stage 4 - tools grouped by context, Settings in L1 order, full status icons
- Map tools hold trail, live share and arrival alert, with their options
  (schema page "Map options") behind one button; back returns to the map
- Nearby gets an advert button: send now (nearby / everyone) + auto-advert
- Home page 3: Repeater, Admin (list of repeaters and room servers),
  Diagnostics
- Settings: display, sound, radio (Bluetooth, WiFi), system, keyboard,
  contacts & messages; units moved to Display & power
- Status bar: Bluetooth, GPS, alarm, mute, then auto-advert, trail, live
  share, repeater, arrival alert in the accent colour
- Fonts: key and route glyphs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 09:14:58 +02:00
JakubandClaude Opus 5.5 ba4d01aa77 feat(ui-lvgl): roadmap stage 1 - keyboard close key, all bot placeholders, WAKE/USER button roles with hold-to-mute, WiFi switch, shorter descriptions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 01:23:54 +02:00
JakubandClaude Opus 5.5 f502d2c619 refactor(ui-lvgl,ui-new): shared segmented and text field widgets, favourites through contactctl, one age formatter
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 00:55:36 +02:00
JakubandClaude Opus 5.5 d588193d0d feat(ui-core,ui-lvgl): message placeholders and quick messages in the Core, L2 quick messages, advert, Bluetooth toggle, read all, node waypoint/pin
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 00:46:34 +02:00
JakubandClaude Opus 5.5 938dfefba6 feat(ui-core,ui-lvgl): sound in the Core, L2 speaker via ES8311, Sound settings, melody editor
- genericBuzzer I2S path (BUZZER_I2S): an audio task synthesises the notes
  with eased edges and keeps the DMA queue fed with silence between sounds;
  ES8311 setup; amp powered through the board only around sounds
- ui-core/SoundControl.h (mode, volume, built-in sounds, melody model),
  SoundNotifier moved to the Core, chanctl::melody, schema Sound page
- L2: notifications, alarm / timer ring, locator sounds, Settings > Sound,
  melody editor with the playing note lit, per-chat sound, mute icon
- noteIndex() on every buzzer backend; scrolling popups past screen height

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-26 00:27:52 +02:00
JakubandClaude Opus 5.5 2624ab0548 feat(ui-core,ui-lvgl): repeater control in the Core, L2 repeater mode
Min SNR now defaults to off on a fresh device (was 0 dB without a prefs file).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 23:33:11 +02:00
JakubandClaude Opus 5.5 6f5fc07cd0 feat(ui-core,ui-lvgl): user presets and battery level in the Core, L2 my presets, scopes, battery display, clock seconds
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 23:21:38 +02:00
JakubandClaude Opus 5.5 63db1e2e07 feat(ui-core,ui-lvgl): diagnostics rows in the Core, L2 diagnostics, auto-advert setting, compass
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 23:01:42 +02:00
JakubandClaude Opus 5.5 8663d50412 feat(ui-core,ui-lvgl): favourites pinning in the Core, L2 node name, reboot, lock screen, favourites dial, 12-hour clock
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:51:00 +02:00
JakubandClaude Opus 5.5 6d82da877e feat(ui-core,ui-lvgl): room sessions and contact helpers in the Core, L2 rooms, conversation options, message actions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:27:32 +02:00
JakubandClaude Opus 5.5 1c8206a510 feat(ui-core,ui-lvgl): bot settings table in the Core, L2 bot screen
BotConfig (tabs, rows, the NodePrefs field behind each, reply placeholders)
moves into ui-core; ui-new's Tools > Bot renders it. L2: Home > Bot with
switches, list pickers, text popups with placeholder buttons, quiet hours.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:09:02 +02:00
JakubandClaude Opus 5.5 e2399b5e0d feat(ui-core,ui-lvgl): remote admin session in the Core, L2 admin screen
AdminSession (login with saved password, CLI round trips, timeouts, field
table, typed values) moves into ui-core; UiCore offers login answers and
CLI replies to it first. ui-new's Tools > Admin becomes a view over it.
L2: node detail > Admin with tabs and per-field popups.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:59:00 +02:00
JakubandClaude Opus 5.5 79fd5a50fb feat(ui-core,ui-lvgl): channel management in the Core, L2 channel screens
ChannelControl (add Public/Hashtag/Private, rename, delete, notif/fav/scope)
and Favourites slots move into ui-core; ui-new uses them. Contact / channel
pref cleanup moves from ui-new's UITask into UiCore so it runs on every
frontend (L2 had none). L2: channel options popup (hold a row or the gear
in a channel) and the Add / Edit channel form.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:42:44 +02:00
JakubandClaude Opus 5.5 84d8a4df2a feat(ui-lvgl): navigation map, clock tools, settings pages, radio
- Home > Map is the navigation map: waypoints (list, add at GPS / by
  coordinates / long-press spot), live shares, trail recording + tools
  (save, load, GPX export, track back), live share controls, target bar
  with ETA; the nodes map opens from Nearby.
- Map: smooth panning (no decode during a drag, idle read-ahead, cached
  coarser stand-ins), overzoom limited to 2 levels, resumable downloads,
  map credit behind a "©" and in Settings > About; target ring and trail
  computed in double.
- Home in swipeable pages; Clock (alarm, timer, stopwatch, ring card).
- Settings rendered from the Core schema (display & power with a
  brightness slider, messages & contacts, trail / live share / alerts),
  GPS switch, Radio screen; low-battery shutdown.
- WioTrackerL2Display: honour the 0-4 brightness level.
- docs: L2 hardware checklist, ui-core notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 19:20:42 +02:00
JakubandClaude Opus 5.5 0fa7966280 feat(ui-lvgl): on-device map download over WiFi
The map's download button fetches the visible area (overview levels up to
a chosen zoom) into /sdcard/maps from OpenTopoMap or maps/source.txt,
skipping tiles already on the card and rejecting placeholder answers.
Settings > WiFi stores the network in NVS (with scan); WiFi is on only
while a download runs. HTTP runs on a worker task on core 0; the popup
shows link, internal heap and the last TLS/HTTP error.

Internal RAM was the blocker (~13 KB free with WiFi + Bluedroid): the L2
LVGL env now allocates the_mesh in PSRAM (MESH_IN_PSRAM, static RAM
210 -> 87 KB) and mbedTLS allocates from PSRAM. Confirmed on hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 11:26:44 +02:00
JakubandClaude Opus 5.5 64321bf12b feat(ui-lvgl): offline raster map from the microSD
Map screen (Home > Map): Web-Mercator tiles from /maps/{z}/{x}/{y}.png on
the card (Meshtastic MUI layout; per-column TPK1 .pak also read), drag to
pan, +/- zoom, re-centre/follow GPS, Nearby nodes with a position as
markers opening node detail. Tiles decode one per loop pass into a
12-slot RGB565 cache in PSRAM. map/TileProvider.h is the swap point for a
later vector renderer: the screen only calls renderTile(). The tile set's
credit line comes from maps/attribution.txt.

L2 mounts the card over SDMMC 1-bit (CLK 2, CMD 3, D0 1). LVGL's lodepng
is enabled; note its LVGL patch returns an lv_draw_buf_t*, not pixels.
LVGL pool 2 MB for PNG decoding. Fonts gain map / users icons.

tools/maps/fetch_tiles.py prepares a card for an area (presets for
Poland), requires an explicit tile URL (tile.openstreetmap.org blocks
offline use) and stops on placeholder images. The LVGL sim preloads
web/maps into the L2's /sdcard and puts its GPS on Kraków.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 10:57:46 +02:00
JakubandClaude Opus 5.5 3e5ccd28c7 feat(ui-lvgl): Nearby screen, node detail, scan popup; NearbyModel in the Core
ui-core/NearbyModel.h builds the Nearby list (contacts + live [LOC] shares
+ heard adverts, or discover-scan results) with type filter and sort,
moved verbatim out of ui-new's NearbyScreen, which now inherits it.

ui-lvgl: Home is three tiles (Messages, Nearby, Settings). Nearby lists
known nodes with filter chips and a Dist/Recent toggle; tapping a row opens
node detail (distance/bearing, position, last heard, ID) with Message,
Ping, favourite, Add and two-tap Delete. Scan is a popup over the list
with its own model: nodes in range now, not the known set. Fonts gain the
FontAwesome star for favourites. The LVGL sim page adds an optional
repeater so Scan has something to find.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 00:53:23 +02:00
JakubandClaude Opus 5.5 87fad71af3 feat(ui-lvgl): 320x240 browser simulator, compose field fixes
Simulator: SIM_UI=lvgl variants/sim/build_wasm.sh builds ui-lvgl for the
browser. SimLcdDisplay blits LVGL's RGB565 flush to a 320x240 canvas and
LvglPort.h's sim branch feeds the mouse in as touch; LVGL comes from the
L2 env's libdeps, its objects cached in web/build/obj_lvgl/. web/lvgl.html
runs it beside a ui-new peer on a direct link, with a window.sim API for
headless checks. New hooks: sim_lcd_touch, sim_lcd_button,
sim_test_send_channel_msg.

Compose: the theme's padding left less than one line in the field, so it
scrolled vertically and text jumped; pad it to fit exactly one line. The
cursor is drawn only while FOCUSED, now set while the keyboard is up.
With the keyboard up the screen header hides so the conversation keeps a
message in view; the accent popup stays inside the keyboard, clear of the
field; channel bubbles put the age next to the sender (one line shorter);
the thread scrollbar shows only while scrolling.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 00:36:26 +02:00
JakubandClaude Opus 5.5 bf1e6fcaa0 feat(ui-lvgl): European fonts and phone-style keyboard
Fonts: Noto Sans generated with lv_font_conv (ui-lvgl/fonts/generate.sh,
OFL) replaces ASCII-only Montserrat: European Latin (Latin-1, Extended-A/B),
Greek, Cyrillic + supplement, typographic punctuation, currency, LVGL
symbols + globe; 12/14/16/20 px, clock 40 px digits only (RLE, so
LV_USE_FONT_COMPRESSED is on).

Keyboard (ui-lvgl/Keyboard.h): one layout per script (Latin QWERTY,
Cyrillic, Greek) picked by the same keyboard_main/alt_alphabet prefs as
ui-new, globe key to switch, hold a key for its variants in a popup above
it, one-shot shift, symbols page; compose limit counted in UTF-8 bytes.
First Settings screen (Keyboard: Main / Additional).

Long-press variants and UTF-8 case mapping move to ui-core/KeyboardData.h
and are shared with ui-new, whose Hold-Enter accents gain Romanian,
Turkish, Hungarian, Baltic and Icelandic letters (and ș/ț, Ukrainian /
Serbian letters now capitalise).

Verified on L2 hardware.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 00:08:15 +02:00
JakubandClaude Opus 5.5 cd926b7e45 feat(ui-lvgl): LVGL 9 skeleton frontend for Wio Tracker L2
New examples/companion_radio/ui-lvgl/ over the shared UI Core (env
Wio_Tracker_L2_companion_solo_lvgl, LVGL 9.2.2, pool in PSRAM):
status bar, home (clock, unread), conversation list (channels + recent
DMs with unread badges), contact picker, conversation view with bubbles,
delivery / relay markers and on-screen keyboard, toasts for Core events,
display auto-off with wake on touch / button. Board glue (LovyanGFX
flush + GT911 touch) isolated in LvglPort.h; design tokens in Theme.h.

UiCore gains the first frontend actions, sendDirectText() and
sendChannelText(), and drives DM auto-resend + unread reconcile from its
own loop (removed from ui-new's loop). MessageHistory exposes newest-first
DM ring iteration and a public contactByPrefix().

Verified on L2 hardware: navigation, send/receive, sleep/wake.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:49:42 +02:00
JakubandClaude Opus 5.5 74db0d69a5 refactor(ui-core): UiCore becomes MyMesh's Listener for ui-new
ui-new hands MyMesh the UI Core (UITaskBase::meshListener()). The Core
applies the display filter, labels room posts, files history, keeps DM /
channel / room unread and the queue count, and reports incoming messages
and adverts as MessageArrived / AdvertHeard events. UITask now derives
UITaskBase + UiCoreHost: it answers "is this conversation on screen" and
keeps the selection after an insert, forwards not-yet-extracted parts
(room login / admin, bot device actions, removal cleanup, shutdown), and
turns events into alert / wake / sound, drained at the start and end of
its loop(). Mesh callbacks no longer touch the display or buzzer.

AbstractUITask is split into UITaskBase (lifecycle, link state, helpers)
and the Listener glue ui-orig / ui-tiny keep using unchanged. Screenshot
handler reaches ui_task directly instead of casting the Listener.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:28:58 +02:00
JakubandClaude Opus 5.5 124265b8be refactor(ui-core): move GPS trail sampling into TrailEngine
TrailStore, background sampling, auto-pause and the pre-shutdown
auto-save move from UITask to ui-core/TrailEngine.h. TrailScreen still
drives the store directly. The 512-point store now lives on the heap
with the rest of UiCore (static RAM -8 KB on Wio L1, same total).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:17:03 +02:00
JakubandClaude Opus 5.5 9e5ce72c86 refactor(ui-core): move locator into LocatorEngine
Active target (set/clear/resolve person position), geofence crossing
state machine and proximity beeper move from UITask to
ui-core/LocatorEngine.h, emitting LocatorCrossed / LocatorBeep events.
UiEvent gains a flag and a 24-byte text. tickCore() now runs at the end
of UITask::loop(), where the moved engines used to run, so a fresh GPS
wake re-seeds the locator before it evaluates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:15:18 +02:00
JakubandClaude Opus 5.5 74baaddd0c refactor(ui-core): move live location sharing into LiveShareEngine
Auto-[LOC] session (duration, movement/heartbeat gate, scoped send) and
the peers' LiveTrackStore + expiry move from UITask to
ui-core/LiveShareEngine.h. "Live share ended" becomes a
UiEventType::LiveShareEnded event; UITask keeps thin wrappers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:09:26 +02:00
JakubandClaude Opus 5.5 79a3a47ff1 refactor(ui-core): move position + course over ground into CourseEngine
COG ring sampling (every ~1 s from UiCore::loop), outlier rejection and
currentCourse/currentLocation move from UITask to
ui-core/CourseEngine.h. UITask keeps one-line wrappers; screens unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:07:32 +02:00
JakubandClaude Opus 5.5 311a3484e2 refactor(ui-core): move ping state into PingEngine
Single in-flight ping + last result move from UITask to
ui-core/PingEngine.h; MyMesh's ping callback routes to the engine
instead of casting the Listener back to UITask. UITask keeps thin
wrappers (and the "not supported" alert), NearbyScreen is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:05:39 +02:00
JakubandClaude Opus 5.5 dd826a447e refactor(ui-core): move clock tools into ClockEngine; add Core event queue
Alarm / countdown / ring logic moves from UITask to ui-core/ClockEngine.h
unchanged. New ui-core/UiEvents.h: fixed-size Core -> frontend event
queue; UITask::tickCore() runs UiCore::loop() and reacts to ClockAlert /
ClockRingEnded (wake, alert overlay, melody). ClockToolsScreen API unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:03:57 +02:00
JakubandClaude Opus 5.5 3b7aa28ff9 refactor(ui-core): add UI Core skeleton; move MessageHistory + DM unread into it
New examples/companion_radio/ui-core/ (header-only for now, reached from
ui-new/UITask.cpp by relative include, so no platformio.ini changes):
UiCore facade, MessageHistory (moved verbatim), DmUnreadTable (extracted
from UITask). UITask heap-allocates the Core in begin(); MessagesScreen
binds to core().history. Behaviour unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 23:00:46 +02:00
JakubandClaude Opus 5.5 2a8d4598d4 refactor: port upstream MyMesh::Listener; MyMesh no longer calls the UI directly
Ports upstream's "Abstract UI overhaul" (PR #3431 + follow-ups) with
identical names/signatures. MyMesh talks to the UI only through
MyMesh::Listener (setListener()); a second block holds Solo extensions
(own-send mirroring, relay echoes, room login/admin replies, [LOC] shares,
contact/channel removal, bot device actions, requestShutdown), all no-op
by default.

AbstractUITask becomes the Listener and carries the glue MyMesh used to
run for the UI (display filter, room-post labels, notifications,
connection polling). BLE PIN randomisation moves to main.cpp as upstream.
Also fixes ui-orig's stale advertReceived event on buzzer boards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 22:54:40 +02:00
JakubandClaude Opus 5.5 f768e73f9f docs: UI Core + frontends design (draft)
Shared hardware-independent UI Core (engines, models, declarative
settings schema) behind MyMesh's AbstractUITask, with ui-lvgl (rich,
Wio Tracker L2 first) and ui-new (lite, OLED/e-ink/nRF52) as frontends.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 22:22:37 +02:00
JakubandClaude Opus 5.5 1620df6c10 fix: move FEAT_RX_POWERSAVE to Features.h; no extra [LOC] on "Stop after" change
- FEAT_RX_POWERSAVE lived in MyMesh.h, but SettingsScreen.h only pulls
  Features.h -- it saw the flag only via include order, and an undefined
  macro in `#if` silently reads as 0, which would split the build the day
  it's flipped to 1. Now defined in Features.h (the home of FEAT_* flags),
  which MyMesh.h includes; comment/doc pointers updated.
- Changing Live Share's "Stop after" restarted the whole session, forcing
  an immediate re-announce [LOC] although the position hadn't changed. It
  now restarts only the duration clock (restartLocShareClock()).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 15:50:37 +02:00
JakubandClaude Sonnet 5 81cee3846f feat(ui): flag unread counts understated by a full history ring
DM and channel history each live in a fixed-size ring (32 DM / 48
channel entries). A conversation that outpaces reading can fill its
ring, evicting the oldest entries — including unread ones that were
never opened — so the unread badge silently caps at ring capacity
instead of reflecting how many messages actually arrived.

MessageHistory now tracks a sticky per-channel overflow flag, set at
the exact point an unread channel entry gets evicted (mirroring the
existing unread-decrement logic) and cleared once the count reads back
down to 0. DMUnreadEntry gains the same bit for DM contacts, detected
by comparing the raw per-contact counter against actual ring occupancy
on each new message (the DM ring, unlike the channel ring, doesn't
proactively decrement on eviction).

DisplayDriver::drawUnreadBadge/unreadBadgeWidth/fmtBadgeCount take an
optional overflow flag (default false, existing call sites unchanged)
that appends "+" to the badge. Wired into every place an unread count
is shown: DM/favourites and channel lists, the DM/Channels/Rooms
mode-select screen, favourite tiles, and the aggregate "Msgs" field on
the clock and lock dashboards plus the Messages home-carousel page.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-23 13:45:07 +02:00
JakubandClaude Sonnet 5 26e6bc877a fix(radio): disable Pwr save (RX duty-cycle) — unsafe across mixed firmware
Field report: a user on the stock SF8 "EU/UK (Narrow)" preset saw reception
drop from ~1-5 msg/min to ~1/3h with Pwr save on, unaffected by a better
antenna. Root cause, confirmed against the SX1262 datasheet and RadioLib's
own maintainers (jgromes/RadioLib#1597, closed as inherent chip behaviour):
the SX126x's duty-cycle preamble detection needs the sender's actual
preamble to closely match what we've configured our receiver to expect.
A mismatch — e.g. a repeater still on pre-v1.16 firmware sending 16 symbols
against our 32 — doesn't cost a little sensitivity, it silently drops every
packet from that sender regardless of signal strength. There's no local
parameter fix: shortening minSymbols to tolerate shorter preambles directly
shortens the wake-window's correlator dwell time, trading the preamble
mismatch failure mode for a marginal-signal one instead. Checked whether
IoTThinks' MeshCore fork solved this differently — it doesn't hit the
problem at all, because its "power saving" only light-sleeps the MCU on the
radio's own GPIO interrupt and never touches RX duty-cycle.

examples/companion_radio/MyMesh.h now defines FEAT_RX_POWERSAVE 0, gating
out the Settings row, the Diagnostics RXPS watchdog row, and every call site
that would apply rx_powersave to the radio or CAD auto-enable — including
forcing setPowerSaving(false) unconditionally so a stale rx_powersave=1 byte
from before this change can't do anything either. The real duty-cycle
implementation in RadioLibWrapper/CustomSX1262Wrapper is left in place,
unneutered, for if a network-wide compatibility mechanism (e.g. the
still-unused ADV_FEAT1/FEAT2 advert bits) is ever built to make it safe.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 20:04:42 +02:00
JakubandClaude Sonnet 5 4af6d7d013 feat(live-share): own scope for [LOC] posts and time-limited sessions (#23, #24)
- Live Share gets a "Scope" row (Target / * / any scope from the shared
  list). It applies to [LOC] sends only, beats the app's send_scope and a
  channel's own pick, and never touches normal messages. Target keeps the
  previous behaviour. removeScope() fixes the stored index up.
- Auto share now always ends: "Stop after" 1/2/4/8/12 h (default 1 h, no
  unlimited option). On expiry it switches itself off and shows "Live share
  ended". The session clock is RAM-only, so a reboot starts a new session.
- NodePrefs: loc_share_scope + loc_share_duration_idx appended, sentinel
  0xC0DE002C -> 0xC0DE002E, sizeof unchanged (2824).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 14:22:27 +02:00
JakubandClaude Sonnet 5 461107e31e fix(ui): keep a gutter beside chat bubbles; docs + release notes catch-up for v1.28
- Message bubbles are capped at the row width minus a gutter (2 char widths,
  min 8px) so a long message no longer fills the whole row and loses the
  left/right anchoring that shows who sent it. Body wrapping uses the capped
  width. DM and channel history.
- Docs: shared scope list (Settings, Repeater Extra scopes, channel Scope row),
  contact Expire / Prune now, history hop count / wrap / scope tag, external
  PA TX curve build flags.
- release-notes.md: v1.28 gains GAT562 30S TX power, Heltec V3/V4 joystick
  Enter fix, prefs-migration removal note, and the bubble gutter fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 12:58:37 +02:00
JakubandClaude Sonnet 5 aa73400414 fix(ui): community v1.27 feedback — GPS altitude field, noise floor, Msg wake location, units
- Add a separate "Altitude (GPS)" dashboard field (label AltG) alongside
  the existing barometric one (now labelled "Altitude (Baro)") -- the
  original single Altitude field only ever read a barometric sensor's
  CayenneLPP telemetry, showing "--" on any board without one even with
  a perfectly good GPS fix.
- Every altitude reading (baro + GPS, Clock and Lock screen) and the GPS
  home page's "alt" row now go through a shared fmtAlt() helper and
  respect Settings > System > Units, same as every other distance in the
  UI -- none of them checked units_imperial before. Same fix for
  Locator's Radius row, which always showed metric regardless.
- Radio home page's noise floor showed "n/a" whenever Pwr save (RX
  duty-cycle) was on, on the assumption duty-cycle RX can't sample the
  floor. That's stale: RadioLibWrapper already periodically recalibrates
  it (noiseFloorCalibCheck(), every 60s) even under duty-cycle, which is
  exactly why Diagnostics' unconditional read of the same value was
  already showing something real. Radio home page now matches.
- Moved "Msg wake" from Settings > Sound to Settings > Display -- more
  logical home for a display-wake toggle than notifications. Menu-order
  only; NodePrefs storage is unaffected by this enum's ordering.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 20:12:29 +02:00
JakubandClaude Sonnet 5 34c2792099 docs: catch up solo_features docs with this cycle's UI changes
- settings_screen.md: document Settings > Sound > Msg wake
- clock_screen.md: document the new Sats dashboard field
- screen_lock.md: mention the lock screen's title bar (PR #32)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 11:35:19 +02:00
Jakub 1025d88045 docs: trim redundant text in tools/settings docs and v1.27 release notes
Cut meta-commentary asides, a restated default, and a stale changelog
footnote that didn't help someone using the feature -- plus tightened
a few release-notes bullets that repeated the same rationale two or
three times over.
2026-08-31 20:42:24 +02:00
Jakub 8573543dd5 fix(ui): Admin's remote Reboot now confirms like Start OTA
Reboot fired on a single Enter with no way back, unlike Start OTA on
the same tab -- inconsistent given both take an unattended remote node
out of action for a while. Reuses the same beginConfirm() idiom.
2026-08-31 20:31:42 +02:00
Jakub 0b34eac41e fix(ui): Tools > Repeater shows its settings before the switch is on
Network mode, the dedicated profile and the flood filters were only
built into the row list once client_repeat was already ON, so setting
up a repeater meant enabling it blind first. Every other on/off tool
in ui-new (Bot, Live Share, Locator) already keeps its settings
visible while off; Repeater now matches.
2026-08-31 20:28:32 +02:00
Jakub d012ad7ee9 fix(ui): Diagnostics' Reset counters now confirms like every other destructive action
The Hold-Enter popup was a single "Reset counters" item with no Cancel
row, so one Enter zeroed all stats immediately -- the same shape the
Trail/channel/preset resets had before beginConfirm() fixed them.
2026-08-31 20:28:16 +02:00
Jakub 691e2ab506 fix(ui): confirm destructive actions, retire last Hold-Enter-cancel, de-dup labels
Continuing the consistency review: sweep for the same three defect shapes
elsewhere in ui-new/ (own read pass plus two parallel research agents),
verified against source before acting.

Three destructive actions fired on a single Enter with no way back, unlike
contact-delete's existing confirm-defaulted-to-Cancel popup: Trail's "Reset
trail" (wipes the whole recorded route, no undo short of a prior manual
Save -- reuses Trail's own multi-level menu machinery, alongside its
GPS-off confirm), Messages' channel Delete, and RadioPresetPicker's saved-
preset delete (shared by Settings > Radio and Tools > Repeater, so one fix
covers both). All three now confirm the same way, defaulting to Cancel.

KeyboardWidget was the one place Hold-Enter still doubled as Cancel: Shift,
Backspace and a Latin letter's accent popup already have real, kept
meanings under a hold, but every other special-row cell (Space, OK/Done,
the {} placeholder) fell through to a bare CANCELLED, closing the keyboard
exactly like the real Cancel key. Now a no-op there too, matching the "only
Back closes it" rule already applied to popups and screens.

MessagesScreen defined the same two label arrays (Notif states, melody
slots) four times over, once per context-menu handler. Hoisted to one
pair of static class members -- constexpr wasn't enough to get the linker
to emit them on this toolchain, so they follow the same declare-in-class/
define-out-of-class shape NearbyScreen::FILTER_LABELS already uses.

Alert text: "Advert sent!"/"Advert failed.." and "Sent!" were the only
toasts anywhere with trailing punctuation; normalized to the plain style
every other confirmation uses. Unpinning from the Favourites Dial reported
the freed slot number from the Messages screens but not from Nodes or the
dial's own tile menu; now consistent everywhere pinning already was.

DiagnosticsScreen's Live/System/Font tab renderers hand-rolled the same
scroll-clamp/loop/indicator skeleton drawList() already bundles; switched
both to drawList (passing the screen's own _scroll as its `sel` too, since
neither tab has a row cursor -- makes drawList's internal clamp a no-op and
leaves clampScroll() as the only thing bounding it, unchanged). Pure
internal tidy, no behavior change.
2026-08-31 19:52:43 +02:00
Jakub f40748ba61 fix(ui): unify the four navigate views, add Set as target everywhere
Continuing the consistency pass: the four screens that show the
distance/bearing "navigate to a point" view (Nodes, Waypoints, Trail's
Track back, and navigating to a location shared in a message) had drifted
apart in three ways.

Only two of the four passed an EtaTracker to navview::draw(), so only
Nodes and Track back showed the closing-speed/ETA line -- navigating to a
waypoint or a shared location left it off for no reason. All four get one
now. They also left the view on three different key sets (Back alone,
Back+LEFT/RIGHT, Back+Enter); Back is now the only way out of any of them,
so a stray sideways nudge can't drop you out of a running track-back.
Messages' renderNav() also switched from reading node_prefs directly to
the shared useImperial() helper the other three already used.

Set as target -- the row Nodes and Waypoints both offer for a coordinate
-- was missing from the message-location Options menu; added alongside
Navigate and Save waypoint.

Nodes' own Set as target required a full 32-byte public key, which a
name-only live-track entry (someone sharing position on a channel who
isn't a saved contact -- the group-outing case this exists for) never
has. One flag was doing two jobs: "can be pinged" (needs the full key)
and "can be identified" (needs only the 6-byte prefix a person target
actually uses). Split into has_key/has_prefix; Set as target now only
needs a position, resolving to a person target (follows them) when a
prefix is available and a place target (pinned where they were) when it
isn't -- the same distinction Locator's own picker already draws.

Locator's target picker separately still listed the people pinned to the
Favourites Dial as its privileged top tier, which stopped making sense
once pinning and favouriting became separate concepts. It now leads with
favourites instead, matching every other list in the firmware.
2026-08-31 19:25:08 +02:00
Jakub 6d1e71cd3f fix(ui): one rule for value rows, one meaning for Hold Enter
Three interaction inconsistencies found while auditing the favourites work,
all of the same shape: the same gesture meaning different things depending
on which screen you were on.

Value rows in popup menus. Rows like "Notif: ON" or "Sort: Dist" show a
value the user steps through with LEFT/RIGHT, but Enter treated them as
ordinary menu picks and closed the popup, so changing two of them meant
reopening the menu in between. Trail's settings submenu was the lone
exception, working around it by rebuilding and re-selecting after each
Enter. PopupMenu now knows the difference: addValueItem() marks a row, and
Enter on it returns the new VALUE_NEXT instead of SELECTED, leaving the
menu open. Only Back closes a menu now. Applied to the Messages
contact/room/channel menus, Nodes, the Ringtone editor and Trail, which
drops its reopenSettingsAt() workaround. The LEFT/RIGHT cycling bodies
moved into one helper per menu, since Enter and RIGHT now share them.

Nodes' Fav row was the worst case: LEFT/RIGHT did nothing there at all, so
the only way to toggle a favourite was an Enter that dismissed the menu on
every flip. Its label moved to a member buffer (as the Pin row already had)
so it can be retitled in place.

Settings rows Auto-off, Low battery, GPS pwr and Battery ignored Enter,
though their options wrap exactly like the melody/keyboard/clock rows
beside them, where Enter has always stepped forward. They accept it now.
Rows that ramp between fixed ends (Brightness, Volume, TX Pwr, Timezone,
SF/BW/CR) stay LEFT/RIGHT-only -- there is nothing to wrap to.

Hold Enter no longer doubles as Back. It quietly meant "go back" on Tools,
Locator, Live Share, Repeater, Bot, Auto-Advert, GPIO, Compass, the
Dashboard config and the Messages navigate view, while elsewhere the same
long press opens a context menu. It now only ever opens a menu, or does
nothing where there is none. Same for dismissing an open popup, which it
used to do. Checked that this strands nobody: every board that can reach
these screens has a real Back key (back_btn on joystick boards, Esc on
CardKB/TCA8418/T-Deck). Single-button boards produce no KEY_ENTER at all,
so they never leave the home pages in the first place.
2026-08-31 15:43:10 +02:00
JakubandClaude Opus 5 57851627cf feat(ui): make favourites mean one thing across every list
Three names had grown around one idea. "Favourite" was a filter in
Settings, an invisible app-only flag on a contact, a device-settable bit on
a channel, and — on the Nodes screen — a menu row that actually pinned to
the Favourites dial. Nothing marked a favourite on screen, and the dial
only took chat contacts.

A favourite is now the starred flag (ContactInfo::flags bit 0 for contacts
and rooms, ch_fav_bitmask for channels), settable on the device everywhere
via a Fav: ON/OFF row, marked with a star on its row, and sorted to the top
of the list — in Messages, Tools > Nodes and the Locator target picker.
Settings > Contacts > "Favs top" turns the sorting off; it defaults on, and
is stored inverted so an upgraded prefs file reads back as on rather than
off.

MyMesh::setContactFavourite() writes the same bit the app sets and bumps
lastmod, so the two stay in sync. The DMs/Rooms = Fav list filters no
longer depend on having starred someone in the app first.

Pinning is now separate and explicit, and the dial takes contacts, room
servers and channels. Slots carry a kind (NodePrefs::favourite_kinds,
schema 0xC0DE0029) — a channel slot holds an index, so emptiness is decided
by the kind first, since channel 0's payload is all zeroes. Choosing what
to pin reuses the Messages screen's own Direct/Channels/Rooms browse
instead of a second picker built on the dial, which drops that picker, its
key/label tables and the now-unused getRecentDMContacts(). A filled tile
gained Unpin/Replace under Hold Enter.

Fixes a room server being pinnable and then unremovable: the picker's
recent-conversations tier didn't filter by contact type and room posts
share the DM history, opening one from the dial skipped the login
handshake, and Unpin only existed in the chat contact list that rooms never
appear in.

Also: PopupMenu::_visible was written and never read (render recomputes the
cap from display height), which is why menus with more items than the
"visible" argument always worked; Settings' all/fav values and the
dm_show_all comment said things that were not true.

Built for Heltec V3, Wio Tracker L1, Wio Tracker L1 e-ink and T-Echo Card.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-31 15:02:01 +02:00
JakubandClaude Sonnet 5 355816eafd polish(ui): text/icon consistency pass across Tools, Settings, and menus
- RepeaterScreen: drop the redundant "Rpt " prefix on the profile fields
  (Preset/Freq/SF/BW/CR), matching Settings > Radio's own terminology --
  the screen is already dedicated to the repeater's own profile, so the
  prefix disambiguated nothing.
- Channel context menu: "Fav: yes/no" -> "Fav: ON/OFF", matching every
  other toggle in the app.
- Settings (System tab): six labels left as raw concatenated identifiers
  (AutoOff/AutoLock/TimeZone/LowBat/BattDisp/BzrVol) now read like their
  space-separated neighbours (Auto pwr/Pwr save/DM sound/GPS pwr), and
  BzrVol no longer clashes with "Buzzer" one row above it for the same
  feature. Three value-label arrays also had one mismatched-case entry
  fixed to match its siblings: Auto-off's "never" -> "OFF", Batt display's
  "icon" -> "Icon", Sound's "built-in" -> "Built-in".
- ToolsScreen: re-enabled the mini-icons next to each tool, which had
  been commented out ("don't fit visually"). Root cause: the screen's own
  drawIcon() centred against lineStep() (line height + inter-row gap)
  instead of getLineHeight() alone, 2px too generous for a near-full-
  height icon -- now delegates to the already-correct miniIconDraw() used
  elsewhere (e.g. the message-list ack checkmark). Also gave Admin and
  GPIO their own icons (padlock, 3-pin header) instead of both sharing
  System's cog.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 21:37:50 +02:00
JakubandClaude Sonnet 5 bdd2ed379d feat(mesh): show which repeaters relayed/confirmed a message's path
Extends the existing single-boolean channel relay-echo marker into a
full count + list of distinct confirming repeaters, since each
repeater retransmit already appends its own identity hash to the
packet's path and the echo-matching hash deliberately ignores that
mutable path -- so every distinct repeater's echo of one send now
matches the same tracking slot instead of only the first.

Symmetrically captures the hop path a received DM/channel message
actually took, so a new "Path"/"Relayed by" row in the existing
Hold-Enter Options popup can show the resolved sequence of repeaters
(by contact name, or a hex fallback for an unknown one).

Also fixes a real bug caught during testing: the popup row's own
label ("Path (N hops)"/"Relayed by (N)") was built into a stack-local
buffer handed to PopupMenu, which only stores the pointer -- it
rendered as garbage once the building function returned. Moved to a
persistent member buffer.

Bumps the dev-build fallback version and adds release notes/docs
for this plus the two other 1.27 features already on this branch
(BLE retry backoff, marquee-scroll for selected long text).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 18:04:06 +02:00