Commit Graph
128 Commits
Author SHA1 Message Date
Torlando a1c2ec8569 fix(lxmf): set send marker in the LVGL lock section (close completion race)
Greptile round on ba5af76 (4/5) correctly rejected the first attempt:
the submitted-text marker was assigned in ChatScreen::on_send_clicked
AFTER the mailbox publish returned, so the main loop could take() +
admit the send and enter clear_composer() while the marker was still
empty — neither clearing the submitted text nor associating the commit
with its submission.

The marker is now recorded by the send callback itself
(UIManager::on_send_message_from_chat) immediately after the mailbox
accept, in the same LVGL lock section as the publish. The click handler
runs on the LVGL task with the LVGL mutex held (LVGLInit.cpp:160-179
wraps the whole lv_task_handler in the recursive mutex), so the marker
is visible to the main loop only after the mailbox entry is — the
take() + admit + clear sequence can never observe an empty marker for
an accepted send. clear_composer() additionally no-ops on an empty
marker, which is the retained-text path for rejected/retry sends.

The contract test is tightened to assert the marker is NOT assigned in
the click handler and IS assigned in the callback, so the race cannot
silently regress.

Verification: 181/181 contracts, tdeck + tdeck-release green.
2026-09-07 01:16:34 +00:00
Torlando ba5af761fb fix(lxmf): preserve draft on async send; re-gather hidden-peer history (Greptile P1s)
Greptile P1 remediation on the exact head (round: 1a34c55):

1. Send Completion Erases Draft (UIManager.cpp:1780). The async send
   deferral (1c68860) leaves the composer un-cleared between the send
   click and the main-loop's ADDED commit, so input typed into the
   composer while persistence/admission is in flight was wiped by the
   unconditional clear_composer(). ChatScreen now captures the exact
   submitted text when the send is accepted into the mailbox, and
   clear_composer() only clears when the composer still holds that
   text. A rejected send still retains input (unchanged), and a fresh
   draft can no longer be erased by a late completion.

2. Same-Peer History Stays Stale (ChatScreen.cpp:177). The same-peer
   early-return (ce92e80) skipped the store re-read, so a message for
   this peer that persisted while the chat was hidden (
   on_message_received only appends to the visible chat) never surfaced
   on re-open. The early-return now compares the store's in-memory
   conversation count (get_messages_for_conversation — pure slot
   lookup, no LittleFS, safe under the LVGL lock) against the count at
   the last prepare commit and falls through to the peer-change path on
   a mismatch, which resets the list and re-arms prepare so the main
   loop re-gathers off-lock and rebuilds with the new message.

Verification: 181/181 build-script contracts (5 new pins), tdeck +
tdeck-release green. Compose path audited and unaffected: the single
send slot makes a second send a no-op until the first commits, and its
clear rides on the route replacement (render_route).
2026-09-07 00:46:07 +00:00
Torlando 3c6275d9e6 chore(diag): gate [SENDT] and [PG] instrumentation behind build flags
[SENDT] send-pipeline timing and the microReticulum [PG] path-store
call-site counters now compile to no-ops unless explicitly enabled:
  - DPYXIS_SEND_DIAG / -DRNS_PATHGET_DIAG added to env:tdeck base flags
  - both removed by env:tdeck-release build_unflags (no-op in release)

So production tdeck/tdeck-release builds are merge-clean, while the
instrumented build stays one env/flag away for the path-request
spammer hunt. Bumps microReticulum pin to 921b3aa (same endpoint
hot-path read gate, counters gated behind RNS_PATHGET_DIAG).

Contract suite: 176/176.
2026-09-06 22:42:16 +00:00
Torlando db3033d3b0 fix(propagation): gate endpoint hot-path path-table reads (microReticulum bump)
Bumps microReticulum pin to e2c9d4d (diag/path-get-caller on cd0338e):
Transport::inbound() and path_request() no longer perform a full
microStore get() (flash write + read) for every inbound packet / path
request on endpoint-only nodes. The read is gated on the exact
conditions where destination_entry is consumed, and the local-destination
path-request answer uses the in-memory _destinations table, so the
device stays discoverable. The build still carries the temporary [PG]
counters for the live before/after capture; counters are stripped in a
follow-up before anything merges.
2026-09-06 19:14:44 +00:00
Torlando 8a0eabf432 diag: bump microReticulum to ef07187 (path-get caller counters)
TEMPORARY DIAGNOSTIC PIN. ef07187 = cd0338e + per-call-site
_new_path_table.get() counters ([PG] summary every 30s on serial).
Purpose: identify which Transport call site drives the ~1.5s full
FileStore get() on the offline propagation node (6b9f6601...).
Revert this commit (back to cd0338e) after the capture.
2026-09-06 06:12:05 +00:00
Torlando 0c8418d674 fix(propagation): stop 2s path-table polling during sync (microLXMF bump)
Bumps microLXMF d7e05fd -> 82d2e54 (fork branch
fix/sync-path-wait-backoff): LXMRouter::process_sync() PR_PATH_REQUESTED
now re-polls Transport::has_path() at most once per PATH_REQUEST_WAIT
instead of every main-loop iteration. In the CBA microStore-backed
microReticulum fork has_path() is a FileStore exists() = a LittleFS
read, so the old code issued a path-table read every ~2s for the whole
60s sync window while the propagation node was unreachable. Measured on
the T-Deck: one destination fetched 293x in 600s (~every 2s), all on
the same 2MB partition as message storage, with [DISP] flush stalls
interleaved. Worst-case sync cycle cost drops from ~30 reads to ~3-4.

The periodic message sync was already 4h default with a user setting
(Settings 'Prop Sync Interval (hrs)', sync_int=14400) — unchanged.

Adds tests/build_scripts/test_propagation_sync_poll_contract.py:
pins the re-poll gate ordering, the PATH_REQUEST_WAIT window, the
untouched one-shot check in request_messages_from_propagation_node,
and enforces platformio.ini <-> audit-tool pin agreement.
2026-09-06 04:24:15 +00:00
Torlando f0e705e529 diag(send): temporary SENDT pipeline timing for save-latency measurement
[SENDT] marks: queue_wait (mailbox wait), identity_recall,
router_lock, save_message (isolated), admission_done, ui_commit_done.
Instrumentation only; to be removed before merge. Loosens one contract
pin to the save call expression (invariant preserved: save runs in
persistOutgoingMessage, not service_pending_sends).
2026-09-06 01:20:09 +00:00
Torlando ce92e8073e fix(lxmf): move conversation-open store I/O off the LVGL task
Opening a conversation crashed the same way sending did. load_conversation()
(LVGL task, under the LVGL lock held by replace_route) ran the full open
pipeline synchronously: identity recall (ustore), display-name read, the
message-index read, and the per-message metadata reads. On this device's
degraded LittleFS each op is 0.4-2s, so a cold open of a dozen-message
conversation held the LVGL mutex past the 5s deadlock guard and asserted at
LVGLLock.h:45. The send path already got the mailbox fix; the open path never
did.

Restructure with the same pattern:
- load_conversation() (LVGL task) now only navigates + resets the list and
  shows the truncated hash in the header. Same-peer re-opens return early
  with zero store I/O (rows are still built).
- prepare_conversation() (main loop, called from update()) does the store
  I/O between a short guard lock and a short commit lock, then commits the
  header name + initial bubbles + background-fill arming under a brief
  LVGL_LOCK. A generation counter discards a stale in-flight prepare when
  the conversation changes mid-I/O.
- refresh() re-arms the prepare instead of re-reading under the lock.

The 1Hz store 'not found in index' fetch is pre-existing (present on
2527c6d) and is being tracked separately as a flash-wear follow-up.

Build tdeck SUCCESS, 170/170 contract tests pass.
2026-09-05 14:39:32 +00:00
Torlando 1c688608b5 fix(lxmf): move outgoing-send persistence off the LVGL task
Every message send on the device was deterministically rebooting it:
send_message() ran the full pipeline (identity recall, message
construction, RouterLock-scoped router admission, and LittleFS
persistence) synchronously on LVGL's 8 KiB task while holding the LVGL
mutex. On this device's degraded filesystem a single save takes ~7s of
400ms-2s per-op gaps, tripping the 5s LVGL deadlock guard and asserting
at LVGLLock.h:45 (assert failed: LVGL mutex timeout (5s)). The receive
path already carries the fix pattern for exactly this failure class
(see on_message_received); the send path never got it.

Restructure the send path as a mailbox handoff, following the existing
CallStartMailbox / LocationShareCommandMailbox precedent:

- send_message() (LVGL task) now only validates and publishes
  (destination, content, source) into a mutex-guarded single-slot
  OutgoingSendMailbox. No router lock, no I/O, no message construction.
- update() services the mailbox in service_pending_sends() on the main
  loop, before the big LVGL_LOCK() — the only place in the send path
  that may take the router lock, block on admission, or wait on
  LittleFS.
- On acceptance, a brief LVGL_LOCK in apply_outbound_result() commits
  the UI (add_message / clear_composer / compose->chat navigation,
  route-guarded). The admitted packed form is unpacked for display
  with incoming/state flags restored.
- On rejection (storage error, router busy, queue full) the user's
  input is retained for retry, matching the old behavior.

The 500-char UI cap bounds the mailbox payload.

Build tdeck SUCCESS, 170/170 contract tests pass.
2026-09-05 05:23:02 +00:00
Torlando dfb2a73876 feat(settings): hub-and-spoke card layout (Columba-inspired)
Replace the flat Settings screen with a card hub + dedicated sub-views.
- Hub: 8 navigation cards (Status, Network, Identity, Radio, Delivery,
  Appearance, Advanced, Transport) reusing the Network-screen card widget;
  Transport stays last (danger invariant preserved).
- Tap a card -> dedicated sub-view holding that area's controls; no accordion.
- Network sub-view gains a LoRa interface toggle alongside TCP/Auto/BLE;
  it two-way-mirrors the Radio page's canonical lora_enabled switch.
- Save model: simple controls apply immediately; a Save button appears only
  on the form sub-views (Network, Radio, Identity).
- Identity sub-view adds a View Identity row routing to the existing lxma://
  QR screen (the Status Share button already reached it).
- Focus group rebuilt per view (only-visible objects) so the auto-scroll-to-
  bottom class of bug cannot recur; each sub-view entry scrolls to top.

Contracts updated for the new structure; 170/170 build-script tests pass.
tdeck build green: RAM 23.1% (75,848 B, unchanged), Flash 92.7%
(2,914,713 B, +2,640 B). Not flashed, not PR'd.
2026-09-04 18:26:39 +00:00
Torlando 28ea218a18 refactor(settings): move status readouts to Status screen, reorganize by use
Settings held live status (GPS fix, storage/RAM/identity) that belongs
on the Status screen, plus a per-second tick() doing SPI flash stat
reads and label churn mid-scroll — the main cause of laggy scrolling.

- GPS section (sats/location/altitude/HDOP/time) -> StatusScreen
- System Info (firmware build, storage, RAM) -> StatusScreen, with
  storage/RAM stat reads throttled to ~5s and stack-buffer snprintfs
  instead of Arduino String concatenation
- Settings gains a Status link row (trackball-reachable) that opens
  Route::STATUS; the per-second SettingsScreen tick/refresh is deleted
- Reordered sections by frequency of use: General (name/brightness/
  timeout/kb-light), Notifications, Network (now includes the
  TCP/Auto/BLE interface switches), Radio (LoRa + params), Delivery,
  Advanced, DANGER: Transport Mode (still final)
- Identity/LXMF hashes shown in Settings were truncated duplicates of
  the Status screen's full display; removed
- main.cpp publishes firmware build + GPS to the Status screen
- Contract test for the storage readout follows the code to StatusScreen
2026-09-04 04:56:07 +00:00
Pike 2816c2db10 test(map): enforce pin label color placement in marker loop
Greptile note: the derive-before-apply position check could pass even if
the color call regressed to the wrong branch. Tighten the contract test
to assert exactly one label-color call, located after the marker loop's
hidden-marker continue (i.e. in the main render branch, not the hide
path), and that the once-per-frame derivation precedes it.
2026-09-03 00:34:54 +00:00
Pike 902df6554c ui(map): contrast pin labels against active basemap
Marker (pin) labels previously inherited the app's default text color,
which reads white and disappears on light basemaps. Set each label's
text color in applyFrame() against the active style: black by default
(light basemaps osm-bright/positron/toner) and white only on the one
dark basemap (dark-matter). Re-evaluated every frame so a style switch
re-colors visible labels on the next applied frame.

Add a contract test pinning the dark-basemap detection and the
black-by-default / white-on-dark ternary.
2026-09-02 21:20:24 +00:00
Torlando 40c1869765 fix(lxmf): drop SIGNATURE_INVALID inbound messages
A message whose source identity is KNOWN but whose signature fails to
validate is spoofed or malicious and must not be rendered. The
opportunistic (on_packet) and direct (on_resource_concluded) router
paths already reject these, but the propagated (store-and-forward) path
in process_propagated_lxmf queues them without a signature check, so
UIManager::on_message_received is the single choke point that covers
all three inbound routes.

Drop the message at the top of on_message_received — before the key
request, location ingest, persistence, chat render, and notification
beep — when !signature_validated() && reason == SIGNATURE_INVALID.
SOURCE_UNKNOWN (first contact) is untouched: those still render and
trigger the bounded key request from PR #92. Validated messages are
unaffected.

Add a source-level contract test locking in the drop gate's ordering
relative to every side effect and its enum specificity.
2026-09-02 17:51:52 +00:00
Torlando a10e5381ce chore: bump map installer cache tag for the progress fix 2026-08-31 15:28:00 +00:00
Torlando 410f65984c fix: close browser review findings (slot parity, smoke asserts, cache-bust) 2026-08-31 06:07:44 +00:00
Torlando a3d1e04ffb docs: define browser map installer safety contract 2026-08-31 04:43:30 +00:00
Torlando f6d1317a96 fix: scope browser map lock to same-origin tabs 2026-08-31 03:18:06 +00:00
Torlando e3008cf025 test: align map installer filesystem mock with browsers 2026-08-30 20:37:01 +00:00
Torlando 9897f1d895 docs: document PMPK v3 CLI installation contract 2026-08-30 05:56:05 +00:00
Torlando 7c96ffac82 fix(maps): cancel stale tile rendering safely 2026-08-20 04:53:00 +00:00
torlando-agent[bot] 01fc9adc79 release: ship merged full-flash binary on releases
Add tools/merge_pyxis_release.py, which assembles the four validated
release assets (bootloader, partition table, OTA selector, application)
into a single 8 MiB image at their fixed flash offsets on an erased
canvas, with per-image read-back and gap checks.

Wire it into release-firmware.yml so every tag build publishes
pyxis-<tag>-merged.bin as a release asset, and document the
provisioning semantics in the README: the merged image is a
first-install image (it erases NVS and LittleFS); data-preserving
updates keep using firmware.bin or the Columba .pyxis package.
2026-08-18 16:28:02 +00:00
torlando-agent[bot] 050289a1bc test: track secure microReticulum pin 2026-08-16 21:44:50 +00:00
torlando-agent[bot] 98dca8e516 feat: add bounded NomadNet forms 2026-08-16 21:36:30 +00:00
torlando-agent[bot] 2a23a14d1e Use published dependency pins 2026-08-15 01:32:56 +00:00
torlando-agent[bot] 1238fa7c2a Pin microStore compaction fix locally 2026-08-15 00:37:32 +00:00
torlando-agent[bot] fd776d7b78 Bound NomadNet pages and preserve decompression headroom 2026-08-14 22:50:00 +00:00
torlando-agent[bot] f605aa386d fix(nomadnet): release directory memory before navigation 2026-08-13 20:58:23 +00:00
torlando-agent[bot] 77e5123455 feat(nomadnet): checkpoint compact page lifecycle
Add bounded compact page rendering, serialized Link and Resource lifecycle handling, TCP reconnect corrections, and deterministic Python RNS conformance coverage.

Physically verified anonymous index retrieval and rendering on T-Deck. Same-destination Link reuse and italic rendering remain follow-up fixes.
2026-08-13 20:58:23 +00:00
torlando-agent[bot] a3d8c55874 fix: keep opened chats at newest message 2026-08-12 20:15:33 +00:00
torlando-agent[bot] b7b39a16df fix: keep navigation responsive during style activation 2026-08-12 19:02:03 +00:00
torlando-agent[bot] 38dab57240 fix: fence map style activation on navigation 2026-08-12 18:40:12 +00:00
torlando-agent[bot] 78300b00ee Merge origin/main into feat/map-style-picker 2026-08-12 18:22:33 +00:00
torlando-agent[bot] 9b7ab160ad fix: keep stable firmware as flasher default 2026-08-12 17:40:55 +00:00
torlando-agent[bot] 6627c9ccbb fix: improve flasher release selector contrast 2026-08-12 17:30:18 +00:00
torlando-agent[bot] aa40e78df3 test: bind prerelease option label 2026-08-12 17:17:32 +00:00
torlando-agent[bot] acfddc4c54 fix: show prereleases in web flasher 2026-08-12 17:13:42 +00:00
torlando-agent[bot] b003e00bf8 test: bind LittleFS lookup arguments 2026-08-12 16:44:56 +00:00
torlando-agent[bot] 7c0daca322 test: lock LittleFS partition selection 2026-08-12 16:40:13 +00:00
torlando-agent[bot] b5aae24e77 fix: safely initialize erased LittleFS partitions 2026-08-12 15:44:26 +00:00
torlando-agent[bot] f6fc95d046 fix: prevent tile worker starvation on unchanged map model 2026-08-11 01:19:07 +00:00
torlando-agent[bot] f03e1c5937 fix: serialize map style activation lifecycle 2026-08-08 18:10:02 +00:00
torlando-agent[bot] c1a27fdf08 fix: version local map installer module 2026-08-08 16:21:18 +00:00
torlando-agent[bot] 06f477e771 fix: refresh map installer archive identity 2026-08-08 16:19:13 +00:00
torlando-agent[bot] 66f551515f feat: add offline map style switcher 2026-08-08 15:21:42 +00:00
Torlando edeb4c9989 Merge pull request #66 from torlando-tech/fix/retire-legacy-map-cache
Build and Deploy Firmware / build-and-deploy (push) Canceled after 0s
Test / Pyxis pytest suite (build_scripts + native) (push) Canceled after 0s
Test / microReticulum native unit tests (PlatformIO native17) (push) Canceled after 0s
fix: retire legacy map tile fallback
2026-08-08 09:48:14 -04:00
Torlando 3f4a70d32c Merge pull request #64 from torlando-tech/docs/map-installer-steps
docs: add guided offline map installation steps
2026-08-08 09:47:57 -04:00
torlando-tech ca59aee704 fix: retire legacy map tile fallback 2026-08-08 05:15:28 +00:00
torlando-tech 84c03e7606 fix: clear stale map status while loading 2026-08-08 04:42:26 +00:00
torlando-tech 1e5be6b617 fix: summarize visible map tile status 2026-08-08 04:31:12 +00:00