on_full_message_close() runs from the Close button's own callback, and the button
is a descendant of the modal — so lv_obj_del(modal) freed the button mid-dispatch
(use-after-free). lv_obj_del_async defers the delete until the event completes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
Long-press a bubble to open a scrollable full-message view with Copy/Close.
Bubbles render truncated for scroll performance, so the handler recovers the FULL
stored content (row -> hash -> item) for the view -- which also fixes Copy, which
had regressed to copying the truncated label text after the render cap.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
A message with multi-KB content (e.g. a large bz2-delivered payload) rendered
untruncated, so LVGL laid it out as a 50+ line wrapped bubble and re-drew the
whole thing while scrolling past it -- crawling the UI. Cap the *displayed* text
to MAX_DISPLAY_CHARS; the full content stays stored. (Decompression is unrelated:
it happens once in Resource::assemble() at receive, content is saved already
decompressed, and load_message_metadata never re-decompresses.)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
on_scroll() loaded a full MESSAGES_PER_PAGE batch synchronously under the LVGL
lock, which froze scrolling. Make it trigger the same incremental
tick_background_fill() streaming as the open path instead. _bg_fill_active is now
std::atomic since on_scroll() (LVGL task) sets it while tick_background_fill()
(main loop) reads it; the target is written before the flag for visibility.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
Open a conversation by rendering only the 3 newest messages synchronously (fast),
then stream the rest of the first page in BG_FILL_BATCH (2) at a time from
UIManager::update() via tick_background_fill(). Each step holds the LVGL lock
only briefly, so a large conversation no longer freezes the UI or trips
LVGLLock's 5s timeout (which previously asserted/crashed). Runs on the main loop
rather than a task because MessageStore shares one _json_doc between save and
load and is not safe for concurrent access.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
Repins microReticulum + microLXMF onto the upstream-0.4.1 graft and adapts
pyxis to the new src/microReticulum/ layout and 0.4.x APIs. The far-diverged
0.3.0 fork's Resource/Transport/Identity work is subsumed by upstream's
reimplementation; only the still-needed fixes ride on the pinned branches
(PKCS7/HMAC/X25519 crypto -- proven byte-identical to python RNS 1.3.1 --
Packet link-proof callback, Identity short-sig guard, and the bz2 layer +
decompress-on-receive in Resource::assemble()).
Consumer-side changes:
- platformio.ini: pin microReticulum @2f21fee (pyxis-fixes-on-0.4.1) and
microLXMF @33760d0 (chore/microreticulum-0.4.1-layout); bump microStore
ceea8f5 -> c5fb69d (0.4.x requires the new BasicFileStore::init API);
-std=gnu++11 -> gnu++17 (upstream requires C++17).
- Namespace all microReticulum includes (angle + quote) to <microReticulum/...>
for the relocated layout; shim-local Utilities/Stream.h|Print.h preserved.
- Interface::send_outgoing now returns bool: update TCP/BLE/SX1262/Auto
overrides with correct success/failure returns.
- SDArchiveFileSystem::init(bool reformatOnFail=true) to match new microStore.
- Static Transport::get_path_table() -> path_table(); instance getter unchanged.
- Remove duplicate shim Cryptography/BZ2 (microReticulum provides it now; keep
lib/libbz2 as the ESP32 bzlib provider).
- patch_littlefs_paths.py: normalize microStore's LittleFS adapter paths to a
leading "/" -- ESP32 Arduino LittleFS rejects "./"-prefixed paths, which
silently broke the path store (no peer paths learned, all messaging blocked).
Validated on T-Deck Plus: builds (RAM 27.5% / Flash 77.7%), boots stable
(no WDT/panic), and a full on-device LXMF e2e (DIRECT + OPPORTUNISTIC +
bz2-compressed-Resource receive) passes 5/5.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWZuYkHBRqNb6BZHV8sTG5
Two fixes from the same testing session:
1. Make SplashImage.h a checked-in artifact rather than a per-build
gitignored regen. The pre-build script generate_splash.py needs
cairosvg + Pillow in the PlatformIO python env, but its import
block silently skips on ImportError — every contributor missing
those deps would silently get a black-screen splash with no clear
signal. Checked-in artifact means a fresh checkout works
regardless of local python state; the script's
should_regenerate() only refreshes when pyxis-icon.svg is newer
than the header, so the committed copy stays in sync for
contributors who DO have the deps.
2. ChatScreen now uses the same three-tier display name resolution
as ConversationListScreen (live announce → MessageStore-cached →
truncated hash) and writes through to the persistent cache when
the live cache hits. Previously it always fell to the truncated
hash if Identity::recall_app_data was empty, even when the
MessageStore had a cached name from a prior session.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add CallScreen with ring/active/ended states and call controls
- Add call state machine to UIManager (link establish, identify, ring, answer)
- Add call button to ChatScreen header
- Add call initiate/hangup with Reticulum Link management
- Add StatusScreen call status display
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split T-Deck firmware from microReticulum examples/lxmf_tdeck/ into its
own repo. microReticulum is consumed as a git submodule dependency pinned
to feat/t-deck. All include paths updated from relative symlinks to bare
includes resolved via library build flags.
Both tdeck (NimBLE) and tdeck-bluedroid environments compile successfully.
Licensed under AGPLv3.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>