Commit graph

149 commits

Author SHA1 Message Date
torlando-tech
da560668ff fix: clarify hash verification flow and fix flaky ImageCache test
- Add explicit log message and comment in RMSP client to clarify that
  hash verification happens before server object creation (addressing
  code review concern about P0 security vulnerability)
- Add resetForTest() to ImageCache to properly reset hit/miss stats
  between tests, fixing flaky getStats test

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 22:22:57 -05:00
torlando-tech
398ebfc957 fix(tests): use explicit global coverage marker in Server D test
Server D needs ["*"] instead of [] for global coverage after the
covers_geohash behavior change.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:44:49 -05:00
torlando-tech
2f5ed6c24f fix: align Python covers_geohash with Kotlin behavior
Both now require explicit "*" for global coverage; empty list means
no coverage data available. This prevents servers with incomplete
announce data from incorrectly matching all geohash queries.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 21:02:14 -05:00
torlando-tech
90ac143fa3 fix: use fixed 30s path timeout instead of percentage-based
Previous 10s max was too short for mesh networks. Now uses 30s fixed
timeout (or half of total for short timeouts), giving path establishment
reasonable time while still leaving most of the timeout for data transfer.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:57:25 -05:00
torlando-tech
59f8c73b70 security: fix destination direction in hash verification
RMSP servers announce with IN direction (listening for requests),
not OUT. Using OUT direction caused the hash computation to always
fail for legitimate servers, defeating the security check.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 20:47:03 -05:00
torlando-tech
dfc179384a fix: simplify path waiting with fixed polling interval
Replace exponential backoff with fixed 250ms polling for path requests.
Reduce path timeout to 30% of caller's timeout (max 10s) to avoid
nested timeout confusion.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 19:24:54 -05:00
torlando-tech
2b622f2551 fix: increase tile count limit from 100K to 1M
100K tiles was too restrictive for large region downloads (~100km radius).
Updated limit to 1M in both Kotlin and Python code with corresponding tests.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 19:23:30 -05:00
torlando-tech
51570c92fd security: verify destination hash matches public key in RMSP client
Compute expected destination hash from provided public key and compare
with claimed dest_hash to prevent server impersonation attacks.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 19:14:01 -05:00
torlando-tech
1e2b5f47a7 fix: improve RMSP error response detection using msgpack format markers
Instead of unreliably checking if response < 1000 bytes, detect error
responses by checking for msgpack map format markers (0x80-0x8f fixmap,
0xde map16, 0xdf map32). This correctly handles small valid tiles and
avoids misinterpreting binary tile data as errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 18:59:51 -05:00
torlando-tech
38c994680b fix(test): make sleep count assertion less strict in path request test
The test was expecting exactly 10 sleep calls, but background threads
(from other tests or the same test) may also call time.sleep, causing
the count to be higher. Changed to assertGreaterEqual(10) which still
verifies the retry loop ran while being robust to background activity.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 18:25:50 -05:00
torlando-tech
d739efca58 fix: add warning log for truncated tile data in Python unpack_tiles
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 17:46:52 -05:00
torlando-tech
f46a888070 test: add Python unpack_tiles validation tests
Tests for:
- Excessive tile count (>100,000) rejected
- Maximum tile count (100,000) accepted
- Excessive tile size (>1MB) rejected
- Maximum tile size (1MB) accepted

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 14:24:18 -05:00
torlando-tech
6c78dc8827 fix(security): add validation to Python unpack_tiles function
Add tile_count (max 100,000) and tile size (max 1MB) validation to
match the Kotlin implementation. Prevents DoS attacks from malicious
RMSP servers sending invalid counts or sizes.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 14:17:23 -05:00
torlando-tech
6563e29a57 perf: use exponential backoff for RMSP link status polling
Replace fixed 0.1s sleep with exponential backoff starting at 50ms,
capping at 1s. Reduces CPU usage during link establishment while
maintaining responsiveness.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 13:32:48 -05:00
torlando-tech
1b9fbf0a3b perf: use exponential backoff for RMSP path establishment
Replace fixed 0.5s sleep with exponential backoff starting at 100ms,
capping at 2s. This improves responsiveness when paths are established
quickly while reducing CPU usage during longer waits.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 13:32:17 -05:00
torlando-tech
cef6995ad5 test: add comprehensive tests for offline maps feature
Add Robolectric UI tests for offline maps screens:
- OfflineMapsScreenTest: tests for EmptyOfflineMapsState, StorageSummaryCard,
  OfflineMapRegionCard, StatusChip, and delete dialog interactions
- OfflineMapDownloadScreenTest: tests for LocationSelectionStep,
  RadiusSelectionStep, ConfirmDownloadStep, DownloadingStep status states

Also includes:
- OfflineMapsViewModelTest: comprehensive ViewModel tests
- TileDownloadManagerTest: tests for geohash, tile coords, RMSP downloads
- MBTilesWriterTest: tests for MBTiles file creation
- Python tests for rmsp_client.py and reticulum_wrapper.py

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 01:09:51 -05:00
torlando-tech
8c53d12d21 Add RMSP tile download via AIDL with multi-geohash support
Implements RMSP (Reticulum Map Service Protocol) tile fetching through
the cross-process AIDL interface, enabling offline map downloads over
mesh networks.

Key changes:
- Add fetchRmspTiles() and getRmspServers() to IReticulumService.aidl
- Implement RMSP methods in ReticulumServiceBinder with Python interop
- Add ServiceReticulumProtocol.fetchRmspTiles() for suspend function access
- Update ReticulumModule to expose ServiceReticulumProtocol for injection
- Add TileSource.Rmsp sealed class for RMSP tile source abstraction
- Implement multi-geohash coverage calculation for complete region downloads
  - decodeGeohashBounds() to get bounding box from geohash
  - geohashesForBounds() to find all cells covering a region
- Update downloadRegionRmsp() to iterate over all geohash cells
- Add RMSP server discovery UI in OfflineMapDownloadScreen
- Update rmsp_client.py with announce waiting and path resolution
- Add public key parameter for server identity reconstruction

The RMSP download now fetches tiles for all geohash cells covering the
requested region, matching HTTP download coverage behavior.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 01:09:31 -05:00
torlando-tech
0c93ccd397 fix: Complete offline maps feature with navigation and bug fixes
- Fix MapSourcesCard toggle not working (preserve state in combine flow)
- Add navigation routes for OfflineMapsScreen and OfflineMapDownloadScreen
- Add Offline Maps FAB on MapScreen with navigation callback
- Hide bottom nav bar on offline maps screens
- Fix FAB positioning with navigationBarsPadding
- Fix OpenFreeMap tile URL (add version string)
- Fix SQLite threading issues:
  - Remove transactions (incompatible with coroutine thread pools)
  - Add mutex for serialized database writes
  - Use beginTransactionNonExclusive for safer transaction handling
- Add logging to TileDownloadManager for debugging

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 01:09:31 -05:00
torlando-tech
e74d1fd4ee feat: Phase 3-6 - MapLibre integration, RMSP client, Kotlin bridge, and settings
Phase 3: MapLibre offline style integration
- Add OfflineMapStyleBuilder for generating MapLibre style JSON from MBTiles
- Add MapTileSourceManager for source selection (offline/HTTP/RMSP)

Phase 4: RMSP Python client port
- Add rmsp_client.py with RmspClientWrapper class
- Support server discovery, query, and tile fetching over Reticulum
- Parse RMSP announces and track servers

Phase 5: Kotlin bridge for RMSP
- Add RMSP methods to PythonWrapperManager (getRmspServers, queryRmspServer, etc.)
- Update PollingManager to handle rmsp.maps announces with RMSP fields
- Update reticulum_wrapper.py with RMSP announce handler and methods
- Register rmsp.maps aspect for announce handling

Phase 6: Settings & source selection
- Add map source preferences to SettingsRepository (HTTP/RMSP toggles)
- Create MapSourcesCard UI for settings screen
- Wire MapTileSourceManager to use settings from repository
- Add validation to prevent disabling all map sources

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 01:09:31 -05:00
torlando-tech
6830ac7532 test: add unit tests for reticulum_wrapper coverage
Add tests covering previously untested code paths:
- Transport.active_links fallback for establish_link and get_link_status
- link.identify() exception handling during link establishment
- image_data_path file reading and cleanup in send_lxmf_message_with_method
- Missing image file error handling
- delivery_method field in error responses

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 23:45:26 -05:00
torlando-tech
270954042d test: add unit test for link.identify() backchannel behavior
Verify that establish_link calls link.identify() with the router's
identity after the link becomes active. This enables the remote peer's
LXMRouter to add the link to their backchannel_links dictionary.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 22:32:25 -05:00
torlando-tech
638e4edfaf chore: remove debug logging from link status functions
Clean up verbose debug logging added during troubleshooting of
incoming link detection. The Transport.active_links fallback is
now a simple, silent check since backchannel_links should be
populated via link.identify() in normal operation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 22:30:13 -05:00
torlando-tech
87933ede1f refactor: consolidate link management into ConversationLinkManager
Remove LinkSpeedProbe and enhance ConversationLinkManager to provide
all link metrics needed for image quality recommendations:

- Add link status indicator next to online indicator in conversation
- Enhance LinkState with expectedRateBps, nextHopBitrateBps, rttSeconds,
  hops, and linkMtu fields from Python establish_link response
- Add periodic refresh to detect incoming links from peers
- Call link.identify() after link establishment to enable backchannel
  detection on the remote peer (populates their backchannel_links)
- Update ImageQualitySelectionDialog to use LinkState instead of ProbeState
- Remove LinkSpeedProbe.kt and related probe_link_speed Python function
- Update tests to use ConversationLinkManager instead of LinkSpeedProbe

The key fix: when establishing a link, we now call link.identify() which
triggers the remote peer's delivery_remote_identified callback, adding
the link to their backchannel_links dictionary. This allows bidirectional
link detection without requiring a message to be sent first.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 22:28:03 -05:00
torlando-tech
acf026e23a fix(ble): load BLEInterface.py from correct package
BLEInterface.py is in ble_reticulum, not ble_modules. This was
accidentally changed in 94c37167, causing the BLE interface to
fail deployment silently and preventing the AndroidBLE interface
from loading.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 19:37:42 -05:00
torlando-tech
824f3af111 feat: prefer expected_rate from prior transfers for link speed estimation
- LinkSpeedProbeResult.bestRateBps now prefers expectedRateBps (actual
  measured throughput from prior transfers) over establishment_rate
  or interface bitrate
- For propagated delivery mode, also check backchannel link for
  expected_rate from the recipient
- Remove unused conversationLinkManager from LinkSpeedProbe
- Add comprehensive unit tests for LinkSpeedProbeResult (23 tests)
- Add Python tests for backchannel expected_rate in propagated mode

This improves transfer time estimates by using actual measured
throughput from prior image transfers rather than heuristics.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 19:29:16 -05:00
torlando-tech
0497b60463 fix: address additional Greptile review comments
- Change ORIGINAL preset maxDimensionPx from Int.MAX_VALUE to 8192
  to prevent OOM errors on large images (Android Canvas limit ~16K)

- Add negative transfer time validation in formatTransferTime()
  to handle edge case of invalid probe data

- Fix backchannel_links cleanup: when a stale link is found during
  establish_link(), clean up both direct_links and backchannel_links
  dictionaries using both key formats (bytes and hex string)

- Update tests to expect new 8K dimension limit for ORIGINAL preset

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 15:56:03 -05:00
torlando-tech
983ee484d9 fix: update Python test to expect success status for propagated probe
Test now expects 'success' instead of 'no_link' when propagated delivery
has no active link, matching the code change that returns success with
heuristic data for compression recommendations.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 14:40:29 -05:00
torlando-tech
86a2ac43c3 fix: return success status for propagated probe with heuristics
When no active propagation link exists, return status="success" instead
of "no_link" so Kotlin UI uses the nextHopBitrateBps heuristic for
compression recommendations instead of treating it as a failure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 14:01:36 -05:00
torlando-tech
aae4368e15 fix: always check created hash for existing links and clean up stale
Remove conditional that only checked recipient_dest.hash when hashes
didn't match - links are stored under recipient_dest.hash so this check
must always run. Also clean up stale links from both direct_links and
backchannel_links when found.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 13:58:18 -05:00
torlando-tech
9da7682048 fix: clean up stale links and prevent race in direct_links cleanup
- Clean up stale/closed links when discovered during existing link check
- Use identity comparison in finally block to only remove our own link,
  preventing concurrent calls from incorrectly removing each other's links

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 13:55:08 -05:00
torlando-tech
bf146f1335 fix: add link cleanup in exception handler for establish_link
Clean up stored link and call teardown() when an exception occurs,
matching the cleanup done on timeout. Safely handles cases where
recipient_dest or link may not be defined yet.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 13:04:09 -05:00
torlando-tech
adb23e992a fix: only request path when none exists to reduce network traffic
Previously, request_path() was called unconditionally even when a path
already existed. This generated unnecessary network broadcasts. Now we
only request a path if we don't have one. If the existing path is stale,
link establishment will fail and can be retried.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 12:41:53 -05:00
torlando-tech
152ef248e2 fix: add link.teardown() on timeout to properly release resources
When link establishment times out, the link object was being removed
from direct_links but teardown() wasn't called. This left the underlying
RNS link in limbo, potentially consuming resources. Now properly closes
the link before removing from the dictionary.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 12:11:45 -05:00
torlando-tech
cd7b5dd7f6 test: add comprehensive tests for link management functions
Add 35 new tests for link management in reticulum_wrapper.py:
- TestEstablishLink: 12 tests for establish_link()
- TestCloseLink: 7 tests for close_link()
- TestGetLinkStatus: 6 tests for get_link_status()
- TestProbeLinkSpeed: 8 tests for probe_link_speed()
- TestNextHopBitrate: 2 tests for next_hop_bitrate functionality

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 10:56:12 -05:00
torlando-tech
5fcc17b0bd fix: add LXMF mock to TestOnLXMFDelivery and fix hop count test
- Add LXMF mock in setUp/tearDown for TestOnLXMFDelivery tests
  (LXMF is None in test environment, causing AttributeError)
- Update test_get_hop_count_returns_none to properly mock RNS.Transport
  (function now implements hop count, test needed updating)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 09:26:36 -05:00
torlando-tech
6797a18aab fix: address Greptile code review issues
- Add missing delivery_method field to error returns in send_lxmf_message_with_method
- Use try/finally for temp image file cleanup to prevent leaks
- Add race condition guard in LinkSpeedProbe.probe() to skip if already probing
- Populate next_hop_bitrate_bps in probe_link_speed results from RNS interface
- Add try/finally bitmap recycling in ImageUtils to prevent memory leaks
- Fix None→0 conversion in RoutingManager helpers to omit field instead

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 00:55:24 -05:00
torlando-tech
6375054163 refactor: simplify probe_link_speed to use establish_link
Instead of sending empty LXMF messages to probe link speed, now uses
the reliable establish_link() function. Also checks backchannel_links
for existing incoming links from peer.

This removes ~80 lines of complex probe message handling code.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 00:28:29 -05:00
torlando-tech
ae6b947e3a fix: check backchannel_links for bidirectional link status
- Always request fresh path even when one exists (avoids stale routes)
- Check backchannel_links in addition to direct_links for existing links
- Links are bidirectional, so incoming links from peer count as "active"

This fixes the issue where Device A shows "Online" but Device B shows
"last seen X minutes ago" even though they have an active link between them.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 00:28:29 -05:00
torlando-tech
48912950c6 fix: manual link establishment now works correctly
Fixed 3 bugs in establish_link() that prevented manual link establishment
from working while LXMF DIRECT messages succeeded:

1. Missing identity recall fallbacks - now tries destination hash,
   identity hash, and local cache (matching send_lxmf_message pattern)

2. Reversed path request logic - now requests path when one doesn't
   exist instead of when one already exists

3. Wrong key for storing links - now uses recipient_dest.hash
   (created destination hash) instead of input hash

Also updated close_link() and get_link_status() with same hash
handling logic for consistency.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 00:28:29 -05:00
torlando-tech
301466e67c feat: add conversation link management for real-time online status
- Add ConversationLinkManager to track active links per conversation
- Open link when entering conversation, close after 5min inactivity
- Update online indicator to show "Online" when link is active
- Add establish_link(), close_link(), get_link_status() to Python wrapper
- Add AIDL methods and protocol support for link management
- LinkSpeedProbe now uses existing links from ConversationLinkManager

Note: Manual link establishment times out (investigating), but detecting
existing links from LXMF DIRECT message exchange works correctly.

🐾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
71eb8e6954 fix: handle large images by writing to temp file for Binder IPC
Large images (>500KB) exceed Android Binder IPC transaction limits (~1MB).
Now large images are written to temp files and the path is passed through
AIDL instead of the raw bytes.

Changes:
- Add imageDataPath parameter to AIDL sendLxmfMessageWithMethod
- ServiceReticulumProtocol writes large images to temp files
- Python reads from file path and deletes temp after reading
- Cap loadBitmap to MAX_PREVIEW_DIMENSION regardless of preset
  (prevents Canvas crash for ORIGINAL preset with huge images)

🐛 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
386d2e9847 fix: link speed probe now correctly finds and uses existing links
- Fix direct_links key lookup (LXMF uses hex strings, not bytes)
- Add find_direct_link() helper to check both key formats
- Fix float-to-long conversion for link rates from Python
- Reset probe state before starting new probe
- Check for late link establishment after timeout

🐛 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
f412365fff feat: improve link speed probe with LXMF message-based probing
- Send empty LXMF message to establish link instead of raw Link
- Add delivery method parameter to probe (direct vs propagated)
- Filter empty probe messages on receive side to prevent rendering
- Fix UnboundLocalError for has_text_content in message filtering
- Add next_hop_bitrate_bps field for heuristic-based recommendations
- Monitor message state for SENT/DELIVERED detection

🐛 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
5febe58167 feat: add link speed probe infrastructure for adaptive compression
Add ability to probe actual link speed to a destination by establishing
a Link and measuring the establishment rate. This provides end-to-end
path speed measurement that accounts for all intermediate hops.

Python layer:
- probe_link_speed() establishes link, measures rate, tears down
- Reuses existing active links if available
- Returns establishment_rate_bps, expected_rate_bps, rtt_seconds, hops

Kotlin layer:
- LinkSpeedProbeResult data class with helper methods
- Full AIDL/Service/Protocol stack integration
- Mock implementation for testing

Next: UI integration for quality selection dialog

🤖 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
2026-01-09 00:28:29 -05:00
torlando-tech
90b3c26833 test: improve coverage for messaging, utilities, and UI components
Add 110+ new tests to improve patch coverage:
- Python: 29 tests for propagation functions (set_incoming_message_size_limit,
  _extract_file_summary, _fail_message_permanently, on_alternative_relay_received,
  _send_pending_file_notification)
- MessagingViewModel: 50 tests for image/file state, reactions, location sharing,
  sending state, retry, sync delegation
- ImageUtils: 17 tests for isAnimatedGif, CompressedImage.isAnimated
- FileUtils: 25 tests for formatFileSize edge cases, constants, MIME types
- MessageMapperTest: 40 tests for MessageUi computed properties, reactions
- SyncStatusBottomSheet: 28 UI tests for all sync states and progress display

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 10:22:08 -05:00
torlando-tech
26c2ccb9cb fix: properly scope LXMF mock and update tests for new fallback behavior
1. PropagationTestBase: Use setUp/tearDown to properly scope the LXMF
   mock instead of setting it at module level (which leaked between tests)

2. test_propagated_falls_back_to_direct_without_propagation_node: Updated
   test to match new behavior - PROPAGATED now falls back to DIRECT when
   no propagation node is set (instead of failing)

3. test_on_message_failed_retries_via_propagation: Set mock_message.fields
   to empty dict so "5 in fields" check works correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-06 00:25:44 -05:00
torlando-tech
ae77042a46 fix: properly mock LXMF.LXMRouter constants in propagation tests
The get_propagation_state() method uses LXMF.LXMRouter.PR_* constants to
map state integers to human-readable names. When LXMF was mocked as a
plain MagicMock(), these constants became MagicMock objects instead of
integers, causing the state lookup to fail.

Fix: Define all PR_* constants in the mock setup and set
reticulum_wrapper.LXMF to the mock (since the module-level variable
is only populated during initialize() which tests skip).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 23:42:16 -05:00
torlando-tech
2cc5cdc9a1 fix: prevent stamp generation deadlock and improve early startup
- Move native stamp generator registration to pre-init callback
  (prevents Python's multiprocessing.Manager() from hanging on Android)
- Start PropagationNodeManager early after service binding
- Add retry logic for relay sync when Python not yet initialized
- Fall back to DIRECT when PROPAGATED requested but no relay set

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-05 23:16:14 -05:00
torlando-tech
321838491f style: fix ktlint and detekt issues
- Run ktlintFormat to fix formatting issues
- Refactor supersedePendingFileNotifications to use functional approach
  instead of loop with multiple continues
- Extract tryParseNotificationMatch helper to reduce return statements
- Use PropagationState constants from ReticulumProtocol instead of
  duplicating them in ServiceNotificationManager
- Simplify complex condition in MessageMapper with helper variables

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 01:29:38 -05:00
torlando-tech
30075665b8 feat: improve file transfer UX with deferred notifications and manual sync toast
- Defer pending file notification until propagation is confirmed on relay
- Track hasFileAttachments before extraction to fix supersede matching
- Add service-side supersede logic for pending file notifications
- Only show sync complete toast for manual syncs, not automatic/periodic
- Add _isManualSync flag to PropagationNodeManager for toast control
- Add real-time progress tracking with 1% threshold for callbacks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-03 23:04:52 -05:00