Commit Graph

108 Commits

Author SHA1 Message Date
MarekWo cbcdbdcae9 fix: prevent duplicate channels from concurrent add/join requests
Two near-simultaneous POSTs to /api/channels/join (observed 7 ms apart
in demo-server logs) each found a different free slot and both
succeeded, producing two entries for the same channel name on the
device. This also shifted the sidebar so each channel rendered the
next one's messages.

- Wrap free-slot detection + set_channel in a module-level lock so
  concurrent requests serialize instead of racing.
- Idempotency: if a channel with this name already exists, return the
  existing slot with already_existed=true instead of creating a
  duplicate. Applies to both POST /api/channels and /api/channels/join
  (skipped when caller targets an explicit index).
- Disable submit buttons on create/join forms while a request is in
  flight, and guard against double-registration of the channel-link
  click delegate to stop a single click from firing N POSTs.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 20:45:39 +02:00
MarekWo 3b4ed26c50 feat: path_hash_mode selector in Settings + global Close button
Adds a Path hash mode dropdown (1B/2B/3B) to Settings → Device → Public
Info, so the mode can be switched from the UI instead of the meshcli
console. The Settings modal now has a persistent Close button in the
footer, visible on every tab.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-22 07:32:50 +02:00
MarekWo 3dd1c52687 feat: contacts settings tab with suppress + auto-ignore options
Move Manual approval toggle into a new Contacts tab in the global
Settings modal and clean up the Contact Management panel (drop the
duplicated Settings/Manage Contacts headers, shorten the Existing
Contacts blurb). Add two new persisted options gated on Manual
approval being ON: Suppress new advert notifications (frontend hides
FAB badge + browser notification while the Pending list itself stays
populated) and Automatically add new contacts to "Ignored" (advert
handler marks the new contact ignored before emitting pending_contact,
so the user is silenced end-to-end while contacts remain in the cache
for promotion via "To Device").

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-18 10:01:58 +02:00
MarekWo bd0a6b492e feat: configurable route popup + toast display time and position
Users complained that the route popup under group-chat messages and the
top-of-page notification toasts auto-close before they can read them, and
some users wanted to move the toasts out of the top-left corner.

Adds to Settings modal:
- Group Chat tab: route popup auto-close timeout + "don't close" switch
  (applies to both channel popups and DM route popups)
- New Interface tab: toast auto-close timeout, "don't close" switch, and
  five position options (top-left/top-right/bottom-left/bottom-right/center)

Persisted as chat_settings (extended) and a new ui_settings row in the
app_settings table, with /api/chat/settings and /api/ui/settings endpoints.
Default toast delay bumped from 1.5s to 2s.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 08:12:51 +02:00
MarekWo bbfca38d34 fix: use adv_lat/adv_lon keys for device coordinates
Device info from meshcore uses adv_lat/adv_lon, not lat/lon.
Fixed in get_param, set_param (lat/lon individually), and the new
/api/device/config endpoint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 19:26:42 +02:00
MarekWo 58d7d9af18 feat: add Device settings tab with Public Info and Radio Settings sub-tabs
Add a Device tab as the first tab in the Settings modal with two sub-tabs:
- Public Info: device name, coordinates with map picker, advert location sharing
- Radio Settings: frequency, bandwidth, SF, CR, TX power with region presets

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 17:08:57 +02:00
MarekWo 29e5e6982d fix(chat): prevent poll-triggered reload after send by using server timestamp
The 60s checkForUpdates poll was detecting has_updates due to clock skew
between client and server timestamps. Now the send API returns the server
timestamp, and the frontend uses it for markChannelAsRead — ensuring the
poll sees no updates for own sent messages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 10:28:54 +02:00
MarekWo 6eb2250d88 fix(chat): remove separator line in bubbles and use WebSocket for echo updates
Remove unnecessary border-top separator above action buttons in message bubbles.
Replace 15s deferred loadMessages() after send with real-time echo updates via
WebSocket — API now returns msg_id so optimistic message gets linked to DB record.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-31 10:17:10 +02:00
MarekWo e8f271f4ef feat(path_hash_mode): add hop_count and path_hash_size to API responses
Stage 2 of path_hash_mode support. All API endpoints and SocketIO
emissions now include decoded hop_count and path_hash_size fields
alongside the raw path_len, so the frontend can display and segment
paths correctly for any hash mode.

Changes:
- Import decode_path_len in api.py
- GET /api/messages: add hop_count, path_hash_size, echo_hash_sizes
- GET /api/messages/<id>/meta: add hop_count, path_hash_size, echo_hash_sizes
- GET /api/dm/messages: add hop_count, path_hash_size
- SocketIO new_message emission: add hop_count, path_hash_size

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 10:00:03 +02:00
MarekWo 1d9742a1ee style(contacts): change existing contacts badge to show total + device count
Format changed from "X/350 (Y cached)" to "Y (🖥 X/350)" where Y is
total known contacts and X is device count, with bi-cpu icon for device.
Applied consistently to both the manage tile and existing contacts header.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-30 08:35:00 +02:00
MarekWo 147a12c8f5 fix(dm): persist delivery_status='delivered' on ACK receipt
DM delivery status was lost when switching conversations because
_confirm_delivery() only stored the ACK record and emitted a socket
event, but never set delivery_status='delivered' in direct_messages.

During retries, each attempt generates a new ACK code. The DM record
stores the initial expected_ack, but the actual ACK may arrive for a
later retry's code. The ACK lookup by expected_ack then fails to match.

Now _confirm_delivery() also sets delivery_status='delivered', and
message loading checks this DB field first (like it already did for
'failed'), so delivery persists across page navigations.

Also fixed 213 existing DMs on server via data migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 14:49:37 +02:00
MarekWo 710f69c350 feat: add BLE transport support for companion devices
Integrate meshcore library's BLE connection (via bleak) as a third
transport option alongside serial and TCP. Priority: BLE > TCP > Serial.

Config: MC_BLE_ADDRESS and MC_BLE_PIN environment variables.
Docker: bluez/dbus packages, NET_ADMIN cap, D-Bus socket mount.
UI: transport type badge in navbar, transport_type in /api/status.
Watchdog: skip USB reset for BLE connections (same as TCP).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-29 10:03:45 +02:00
MarekWo 7dbbba57b9 feat(dm): add real-time retry status and persistent delivery info
Show retry progress in DM message bubble via WebSocket:
- "attempt X/Y" counter updates in real-time during retries
- Failed icon (✗) when all retries exhausted
- Delivery info persisted in DB (attempt number, path used)

Backend: emit dm_retry_status/dm_retry_failed socket events,
store delivery_attempt/delivery_path in direct_messages table.
Frontend: socket listeners update status icon and counter,
delivered tooltip shows attempt info and path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-28 12:25:35 +01:00
MarekWo 5df9b4b4a2 fix(ui): refresh Contact Info path display in real-time
Path info in Contact Info modal was stale due to 60s server cache
and no refresh after path operations. Now:
- Invalidate contacts cache after reset_path, change_path, path_update
- Emit 'path_changed' socket event on PATH_UPDATE from device
- UI listens and re-renders Contact Info when path changes
- Reset to FLOOD button immediately refreshes the path display

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-27 20:29:26 +01:00
MarekWo 878d489661 feat(contacts): add contact UI with URI paste, QR scan, and manual entry
Stage 2 of manual contact add feature:
- POST /api/contacts/manual-add endpoint (URI or raw params)
- New /contacts/add page with 3 input tabs (URI, QR code, Manual)
- QR scanning via html5-qrcode (camera + image upload fallback)
- Client-side URI parsing with preview before submission
- Nav card in Contact Management above Pending Contacts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 20:54:41 +01:00
MarekWo 0973d2d714 fix(contacts): invalidate contacts cache after push/move operations
The /api/contacts/detailed endpoint has a 60s cache. Without invalidation
after push-to-device or move-to-cache, the UI showed stale data until
cache expired, making it look like the operation didn't work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 18:25:03 +01:00
MarekWo 9ee63188d2 feat(contacts): add push-to-device and move-to-cache operations
Enable moving contacts between device and cache directly from the
Existing Contacts UI:
- "To device" button on cache-only contacts (pushes to device)
- "To cache" button on device contacts (removes from device, keeps in DB)

This helps manage the 350-contact device limit by offloading inactive
contacts to cache and restoring them when needed.

- Add DeviceManager.push_to_device() and move_to_cache() methods
- Add API endpoints: POST /contacts/<pk>/push-to-device, move-to-cache
- Add UI buttons with confirm dialogs in contacts.js

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-24 18:06:26 +01:00
MarekWo dfc3b1403a fix: prevent page hang when device channel queries block
/api/messages and /api/messages/updates called get_channels_cached()
which blocks on device communication when cache is cold (up to 240s).
Now uses DB-cached channels for pkt_payload computation instead.

Frontend loadMessages() now has a 15s timeout with auto-retry
and clears the loading spinner on error instead of leaving it
spinning indefinitely.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 21:26:26 +01:00
MarekWo 23687b2973 fix(paths): use _get_dm() instead of nonexistent cli.get_device_manager()
The reset_flood endpoint was calling cli.get_device_manager() which
doesn't exist. All other endpoints use the local _get_dm() helper.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 10:39:07 +01:00
MarekWo c82fb9f334 fix(paths): fix reset_flood endpoint error handling and add logging
Endpoint now returns error if device reset fails instead of always
returning success:true. Added logging to both endpoint and
device_manager.reset_path to diagnose reset failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 10:32:56 +01:00
MarekWo 08b972b891 fix(paths): separate Reset to FLOOD from Clear Paths
Reset to FLOOD now only resets the device path without deleting
configured paths from the database. New Clear Paths button deletes
all configured paths from DB without touching the device. This lets
users reset to FLOOD to discover new paths while keeping their
configured alternatives intact.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-23 10:10:40 +01:00
MarekWo 8cc67f77d5 feat(dm): add multi-path management and per-contact no-flood toggle
- New `contact_paths` table for storing multiple user-configured paths per contact
- New `no_auto_flood` column on contacts to prevent automatic DIRECT→FLOOD reset
- Path rotation during DM retry: cycles through configured paths before optional flood fallback
- REST API for path CRUD, reorder, reset-to-flood, repeater listing
- Path management UI in Contact Info modal: add/delete/reorder paths, repeater picker with uniqueness warnings, hash size selector (1B/2B/3B)
- "No Flood" per-contact toggle in modal footer

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 21:20:51 +01:00
MarekWo ce8227247f feat(chat): add quote dialog with configurable quote length
Add Group Chat tab in Settings with configurable quote byte limit.
When quoting a message longer than the limit, a dialog asks whether
to use full or truncated quote (with editable byte count).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 15:25:38 +01:00
MarekWo 33a71bed17 refactor(ui): rename contact type label CLI to COM (companion)
The MeshCore community uses "companion" not "client" for type 1 nodes.
Rename the CLI label to COM across all UI, API, JS, and docs to align
with official terminology. Includes cache migration for old CLI entries.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 14:37:30 +01:00
MarekWo 6f1a5462e9 feat(settings): add Settings modal with configurable DM retry parameters
Replace hardcoded DM retry logic with user-configurable settings stored
in app_settings DB. Settings modal opens from menu with tab-based UI
(ready for future settings tabs). Defaults: 3 direct + 1 flood retries
(was 8+2), 30s/60s intervals, 60s grace period.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-21 13:18:03 +01:00
MarekWo 0110e65b97 feat: add System Log viewer with real-time streaming
In-memory ring buffer (2000 entries) captures all Python log records.
New /logs page streams entries via WebSocket in real-time with:
- Level filter (DEBUG/INFO/WARNING/ERROR)
- Module filter (auto-populated from seen loggers)
- Text search with highlighting
- Auto-scroll with pause/resume
- Dark theme matching Console style

Menu entry added under Configuration section.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 20:34:29 +01:00
MarekWo 4f25d244b1 refactor: migrate .webui_settings.json to database + fix NEW_CONTACT edge case
All settings (protected_contacts, cleanup_settings, retention_settings,
manual_add_contacts) moved from .webui_settings.json file to SQLite database.
Startup migration auto-imports existing file and renames it to .json.bak.

Added safeguard in _on_new_contact: if firmware fires NEW_CONTACT for a
contact already on the device, skip pending and log a warning. Also added
diagnostic logging showing previous DB state (source, protected) when
contacts reappear as pending.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-20 20:14:15 +01:00
MarekWo f66e95ffa0 fix: contact delete by pubkey, cache contacts as pending, cache delete button
1. Delete button now sends public_key instead of name to avoid matching
   wrong contacts when multiple share similar names.
2. _on_advertisement adds cache-only contacts to mc.pending_contacts when
   manual approval is enabled, so they appear in the pending list after
   advertising (even if meshcore fires ADVERTISEMENT instead of NEW_CONTACT).
3. Added Delete button for cache-only contacts with dedicated
   /api/contacts/cached/delete endpoint and hard_delete_contact DB method.
4. approve_contact/reject_contact now handle DB-only pending contacts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 08:01:56 +01:00
MarekWo 5ecb48c772 fix(api): fix NameError on out_path variable rename
Variable was renamed to out_path_raw but one reference was missed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 21:30:32 +01:00
MarekWo 3622619ba4 fix(contacts): decode path correctly using MeshCore V1 encoding
Path buffer from firmware contains trailing garbage bytes beyond the
actual hop data. out_path_len encodes both hop count (lower 6 bits)
and hash size (upper 2 bits). Now we:
- Truncate out_path to meaningful bytes (hop_count * hash_size)
- Format as readable E7→DE→54→54→D8 instead of raw hex string
- Show hop count derived from actual path arrows

Example: out_path_len=5 with out_path="e7de5454d81c49dfb86f8a"
now correctly displays as "E7→DE→54→54→D8 (5 hops)" instead of
showing the full 11-byte buffer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 21:26:06 +01:00
MarekWo 5f72f40742 feat(contacts): show path/route info in UI and split console commands
- Console `contacts` now shows device-only contacts with path info
  (matching meshcore-cli format: name, type, pubkey, path)
- New `contacts_all` command shows all contacts (device + cached from DB)
- Contact cards in UI now always show routing mode for device contacts
  (Flood, Direct 0 hop, or hex path with hop count)
- Fix path_or_mode computation: prioritize out_path over out_path_len
  to handle firmware edge case where out_path exists but out_path_len=-1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 21:01:15 +01:00
MarekWo 3a26da18fd fix(websocket): update message metadata in-place without full chat reload
Instead of reloading the entire message list when echo data arrives,
now updates only the affected message elements in the DOM:

- Add data-msg-id attribute to message wrappers for targeted lookup
- Add GET /api/messages/<id>/meta endpoint returning metadata for a
  single message (computes pkt_payload, looks up echoes, analyzer URL)
- Replace loadMessages() echo handler with refreshMessagesMeta() that
  finds messages missing metadata and updates them individually
- Fix path_len=0 treated as falsy (use ?? instead of ||)

Flow: message appears instantly via WebSocket (with SNR + hops), then
~2s later echo data triggers targeted meta fetch → route info and
analyzer button appear smoothly without any chat window reload.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-15 12:49:56 +01:00
MarekWo 65b33b4af6 fix(phase3): fix database/device access in search, backup, stats, map endpoints
- Search, backup, stats endpoints used current_app.config.get('DEVICE_MANAGER')
  which doesn't exist — replaced with _get_dm()/_get_db() helpers
- /api/device/info used old v1 CLI — replaced with DeviceManager.get_device_info()
  returning structured dict instead of string (fixes map own device marker)
- Moved search button from navbar to FAB menu (between filter and DM buttons)
- Bump SW cache to v7

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:02:23 +01:00
MarekWo ab01e6f17a feat(stats): add device statistics dashboard with Info/Stats tabs
- New GET /api/device/stats endpoint (core, radio, packets, DB stats)
- Device Info modal now has Info and Stats tabs
- Stats tab shows: battery, uptime, TX/RX air time, packet counts,
  DB row counts, and database size

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 07:25:45 +01:00
MarekWo 4ecab9b307 feat(backup): add backup API endpoints and UI
- POST /api/backup/create — trigger immediate backup
- GET /api/backup/list — list backups with sizes
- GET /api/backup/download — download backup file
- Backup modal accessible from menu with create/download buttons
- Daily automatic backup via APScheduler (configurable hour/retention)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 07:22:50 +01:00
MarekWo d6e2a3472a feat(search): add global message search with FTS5 backend
- New GET /api/messages/search endpoint using existing FTS5 indexes
- Search modal accessible from navbar search icon
- Debounced search (300ms) across all channel and DM messages
- Results show source (channel/DM), sender, timestamp with highlights
- Click result navigates to the relevant channel or DM conversation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 07:20:04 +01:00
MarekWo 3fb1c09dc1 fix(chat): exclude blocked contacts from unread message counts
Both /api/messages/updates (channel) and /api/dm/updates endpoints
now filter out blocked contacts when computing unread counts, so
badge numbers no longer include messages from blocked users.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-11 06:45:38 +01:00
MarekWo b0076c3739 feat(contacts): name-based blocking, fix CSS breakpoint
- New blocked_names table for blocking bots without known public_key
- get_blocked_contact_names() returns union of pubkey-blocked + name-blocked
- POST /api/contacts/block-name endpoint for name-based blocking
- GET /api/contacts/blocked-names-list for management UI
- Block button always visible in chat (falls back to name-based block)
- Blocked Names section shown in Existing Contacts Blocked filter
- CSS breakpoint for icon-only buttons: 768px → 428px (iPhone-sized)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 21:03:19 +01:00
MarekWo 0d5c021e40 fix(contacts): type label bug, responsive buttons, remove Copy Key
- Fix pending contacts always showing "CLI" — compute type_label in API
- Remove Copy Key button from pending cards, make key clickable instead
- Responsive contact buttons: icon+text on desktop, icon-only on <=768px
- Add flex-wrap for button rows on small screens

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 07:16:04 +01:00
MarekWo 2a3a48ed5f feat(contacts): add ignored and blocked contact lists
- New DB tables: ignored_contacts, blocked_contacts (keyed by pubkey)
- Ignored contacts: cached but excluded from pending/auto-add
- Blocked contacts: ignored + messages hidden from chat (stored in DB)
- Backend: filter in _on_new_contact, _on_channel_message, _on_dm_received
- API: /contacts/<pk>/ignore, /contacts/<pk>/block toggle endpoints
- API: filter blocked from /api/messages and /dm/conversations
- Frontend: Ignore/Block buttons on pending cards, existing cards, chat messages
- Frontend: source filter dropdown with Ignored/Blocked options
- Frontend: status icons (eye-slash, slash-circle) on contact cards
- Frontend: real-time blocked message filtering via socketio
- Name→pubkey mapping for chat window block/ignore buttons

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 21:10:21 +01:00
MarekWo 09fbc56956 feat(contacts): complete cache functionality, fix display bugs
- _on_new_contact() in manual mode: upsert to DB as cache (source='advert')
  so contacts appear in @mentions and Cache filter before approval
- _on_advertisement(): check mc.pending_contacts for name/metadata fallback
- get_pending_contacts(): include last_advert in response
- /api/contacts/cached: return numeric last_advert timestamp
- contacts.js: fix adv_lat/adv_lon field names (was c.lat/c.lon),
  use last_advert timestamp instead of last_seen datetime string
- upsert_contact: source priority — never downgrade 'device' to 'advert'

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 08:51:25 +01:00
MarekWo 5b757e9548 feat(dm): add delivery confirmation, retry, and receiver-side dedup
- Fix ACK handler bug: read 'code' field instead of 'expected_ack'
- Add DM retry (up to 3 attempts) with same timestamp for receiver dedup
- Add receiver-side dedup in _on_dm_received() (sender_timestamp or time-window)
- Add PATH_UPDATE as backup delivery signal for flood DMs
- Track pending acks with dm_id for proper ACK→DM linkage
- Return dm_id and expected_ack from POST /dm/messages API
- Add find_dm_duplicate() and get_dm_by_id() database helpers

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 07:02:58 +01:00
MarekWo 9f249a4521 feat(echoes): add RX_LOG_DATA echo tracking + sent message pkt_payload correlation
- Subscribe to RX_LOG_DATA events to capture repeated radio packets
- Parse GRP_TXT (0x05) payload to extract pkt_payload and path
- Classify echoes as sent (pending echo correlation) or incoming
- Register pending echo when sending channel messages for pkt_payload capture
- Add update_message_pkt_payload() DB method for sent message correlation
- Return echo_paths/echo_snrs for ALL messages (not just own) in GET /messages
- Frontend: build paths from echo_paths for incoming message route display
- Emit SocketIO 'echo' event for real-time badge updates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 16:53:12 +01:00
MarekWo 44832ada5e fix(analyzer): fix pkt_payload computation for block-aligned text + trailing whitespace
Two bugs in Analyzer URL generation:
1. Firmware omits null+padding when header+text exactly fills AES block boundary
   (len % 16 == 0), but our code always added \0+padding → wrong MAC → wrong hash
2. content.strip() in event handler removed trailing whitespace that was part of
   the original packet. Now uses raw_text from raw_json (preserves original text)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 15:53:59 +01:00
MarekWo 37694dde09 fix(contacts): remove from pending list after approve + add reject/clear
approve_contact now removes the contact from mc.pending_contacts after
successful approval. Added reject_contact (remove without adding) and
clear_pending_contacts methods with API endpoints.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 08:14:10 +01:00
MarekWo 499759931c feat(analyzer): compute pkt_payload from channel secrets for Analyzer URLs
meshcore v2 doesn't provide pkt_payload in events, so compute it
lazily in the API from channel secrets + message data. Analyzer URLs
now appear for ALL messages (own and incoming), not just own.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 07:38:44 +01:00
MarekWo b034a181ce feat(retention): add message retention scheduling (Task 2.6)
- Add daily retention job that deletes old channel messages, DMs, and
  advertisements based on configurable age threshold
- Add GET/POST /api/retention-settings endpoints
- Extend cleanup_old_messages() to optionally include DMs and adverts
- Wire up APScheduler in create_app() (also enables existing archiving
  and contact cleanup schedulers that were never started in v2)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:28:54 +01:00
MarekWo d89e276054 feat(api): add advertisement history API endpoint (Task 2.8)
Add GET /api/advertisements with optional pubkey filter and limit.
Enriches results with contact name lookup from cache.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:26:37 +01:00
MarekWo e18ad0f7a3 fix(v2): Fix echo enrichment bug + add analyzer URL to channel messages
Bug: echo enrichment at api.py:384 used leaked `row` variable from
previous loop — all messages got echo data from the LAST DB row.

Fix: include pkt_payload in message dict during conversion loop,
then enrich each message with its own echo data and analyzer URL.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 17:16:50 +01:00
MarekWo 752c60f02d feat(v2): Import archive .msgs files + DB-based message history
Migration now imports all archive files (oldest first) in addition to the
live .msgs file, with deduplication. Archives endpoint and message history
now query SQLite by date instead of reading .msgs files.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 13:05:33 +01:00