Adds a Share tab to the Device Info modal that generates a QR code
and copyable URI (meshcore://contact/add?...) for sharing the device
contact with other users.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
On screens >= 992px (lg breakpoint), show a persistent sidebar panel:
- Group chat: channel list with unread badges, active highlight, muted state
- DM: conversation/contact list with search, unread dots, type badges
- Desktop contact header with info button replaces mobile selector
- Mobile/narrow screens unchanged (dropdown/top selector still used)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
/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>
Used c.last_advert (numeric Unix timestamp) instead of c.last_seen
(ISO 8601 string) which caused formatTimeAgo() to return "Invalid Date".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
Replace vertical form fields with table rows for less screen space.
Descriptions moved to (i) tooltip icons on hover/touch.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- Existing Contacts: Ignore and Block buttons are now disabled when
contact is protected, matching the existing Delete button behavior
- updateProtectionUI: toggling protection now also enables/disables
Ignore, Block, and Delete buttons dynamically
- Chat: Ignore and Block buttons are hidden in message bubbles for
protected contacts (loads protected pubkeys on init)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
The backend already emits 'echo' SocketIO events when RX_LOG_DATA arrives
with route/path data, but the frontend wasn't listening. Now the frontend
handles echo events with a debounced loadMessages() refresh (2s delay) to
pick up computed pkt_payload, analyzer_url, hops, and route info.
This fixes messages appearing without metadata until manual page refresh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The SocketIO new_message emit for channel messages was missing snr,
path_len, pkt_payload and analyzer_url fields, causing messages received
via WebSocket to render without metadata until a full page refresh.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Channel names from device already include # prefix — removed hardcoded #
from search results badge. Added (?) help button with search syntax
examples and link to FTS5 docs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Stats: battery fallback used ${bat} on an object — now uses battery_mv
from core stats when dedicated get_bat returns null
- Info: remove old v1 regex JSON parsing, use v2 dict response directly
- Search FAB: change from bright orange to muted teal
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
The /chat namespace had no server-side connect handler registered. With
python-socketio 5.x (always_connect=False), client connections to
unregistered namespaces are silently rejected. This caused all SocketIO
events (new_message, ack, echo) to never reach the frontend — messages
only appeared via the 60s polling fallback.
Fixes:
- Add @socketio.on('connect', namespace='/chat') handler in main.py
- Add optimistic message append: sent messages appear instantly before
API round-trip (eliminates 3-4s serial command delay)
- Skip own-message SocketIO events to prevent duplicates
- Add connect_error handler for frontend debugging
- Bump SW cache to v6
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Own device shown as red star marker on map (from self_info GPS)
- Contact popups now show "Last seen: X min ago" from last_advert
- New formatTimeAgo() utility for relative timestamps
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
- 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>
- 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>
Instead of reloading all messages 3 times (1s, 6s, 15s) after sending,
the sent message now appears instantly via SocketIO new_message event.
Only one deferred reload remains at 15s to pick up echo data.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The Map modal (from main menu) now has a "Cached" toggle switch that,
when enabled, also displays contacts stored in the DB cache alongside
device contacts. Cached markers are slightly smaller and more
transparent to visually distinguish them from device contacts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
showToast is only defined in contacts.js, not app.js. The chat page
uses showNotification. The ReferenceError was silently caught, preventing
loadBlockedNames() and loadMessages() from executing after blocking.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Added client-side blocked name filtering in displayMessages() as
defense-in-depth alongside server-side filtering. This ensures blocked
sender messages are hidden immediately after blocking from chat.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
meshcore library doesn't always provide SNR in CHANNEL_MSG_RECV events.
Use the first echo path's SNR as fallback for inline display.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use array-based metaParts.join(' | ') instead of string concatenation
to avoid ugly leading "| Hops: 0" when meshcore lib doesn't provide SNR.
Also revert temporary INFO-level debug logging back to DEBUG.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
- Add SocketIO /chat client to app.js for real-time message updates
- Listen for new_message (channel) events, refresh current channel
- Update unread badges for other channels in real-time
- Listen for device_status to update connection indicator
- Reduce polling interval from 10s to 60s (fallback only)
- Include socket.io.min.js in base.html template
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add confirm() dialog before marking all messages as read, showing
list of unread channels with counts. Remove channel key/ID from
Manage Channels modal to save vertical space on mobile.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add ability to mute notifications for individual channels via Manage
Channels modal (bell/bell-slash toggle button). Muted channels are
excluded from unread badge counts, browser notifications, and app icon
badge. Bell icon click now marks all channels as read in bulk.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add person icon button in filter bar that inserts the current device
name into the search field, for filtering own messages
- DM filter bar already benefits from the CSS sibling push-down rule
added in previous commit (same class names used)
- Add collapsible FAB toggle to DM view, same pattern as channel chat
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add collapsible FAB container with chevron toggle button to
temporarily hide floating action buttons that overlap messages
- Make filter bar push messages down instead of overlaying the first
matched message (CSS sibling selector adds padding-top)
- Add @mentions autocomplete to filter search bar - typing @ shows
contact list dropdown, selecting inserts plain name (not @[] format)
so all messages from/mentioning that user are found
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contacts cache accumulates all known node names from device contacts
and adverts into a JSONL file, so @mentions work even after contacts
are removed from the device. Background thread scans adverts every
45s and parses advert payloads to extract public keys and node names.
Existing Contacts page now shows merged view with "Cache" badge for
contacts not on device, plus source filter (All/On device/Cache only).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
bi-flask was added in Bootstrap Icons 1.12+, but project uses 1.11.2.
Replace with bi-clipboard-data which is available and conveys
"data analysis".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generate analyzer_url from computed pkt_payload for all incoming
channel messages, not just those with echo path matches. This means
the analyzer button appears even when no route paths were captured.
Also change analyzer button icon from bi-search (magnifying glass)
to bi-flask (lab flask) to better convey "analysis/inspection".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace unreliable timestamp-based heuristic (±10s window) with exact
cryptographic matching for incoming channel message routes. Compute
pkt_payload by reconstructing the AES-128-ECB encrypted packet from
message data (sender_timestamp, txt_type, text) + channel secret, then
match against echo data by exact key lookup.
Also accumulate ALL route paths per message (previously only last path
was kept due to dict overwrite), and display them in a multi-path popup
showing SNR and hops for each route.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Compute packet_hash from pkt_payload (SHA-256 of type byte + payload)
and generate analyzer.letsmesh.net links. Button appears on both sent
and received messages when echo data is available.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tooltips don't work on touchscreens. Added a popup that appears on
tap/click, shows the full path, and auto-dismisses after 4 seconds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Deduplicate 2-char repeater codes in echo badge (same repeater via
different routes was shown twice, e.g., "3 (d1, 5e, 5e)")
- Use deduplicated count for unique repeaters, not unique full paths
- Improve incoming path correlation: widen window to 10s, prefer
path_len match but fall back to timestamp-only if needed
- Add debug logging for incoming path correlation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Show repeater path codes in sent message echo badge (e.g., "2 (5e, d1)")
- Capture and display route path for incoming messages in message meta
- Persist all echo data to .echoes.jsonl (survives container restarts)
- Load echo data from disk on startup with 7-day retention and compaction
- Combine sent echo and incoming path data in single /echo_counts response
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add sender name above outgoing messages in group chat
- Display emoji-only messages in larger font size
- Fix leading space before text in quoted replies
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add filter-utils.js with diacritic-insensitive search and text highlighting
- Add FAB filter button (gray funnel icon) to channel chat and DM
- Filter bar slides in as overlay at top of chat area
- Real-time filtering with debounce (150ms) as user types
- Matched text highlighted with yellow background
- Support for Polish characters (wol matches wół)
- Keyboard shortcuts: Ctrl+F to open, Escape to close
- Match counter shows X / Y filtered messages
- Filter persists during auto-refresh
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Echo counts weren't appearing until sending another message because
auto-refresh only triggers when new messages arrive. Added 6s and 15s
delayed reloads after sending to catch echoes as they arrive.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Track how many repeaters heard and forwarded sent channel messages,
similar to the Meshcore mobile app's "Heard X repeats" feature.
- Bridge: Detect GRP_TXT echoes from stdout, track unique paths
- Bridge: New /register_echo and /echo_counts API endpoints
- API: Register messages for echo tracking after send
- API: Merge echo counts into /api/messages response
- Frontend: Display green badge with broadcast icon next to Resend button
- CSS: Echo badge styling with dark mode support
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Semi-transparent button appears when user scrolls up
- Positioned at bottom-right of messages container
- Clicking scrolls to latest messages and hides button
- Smooth fade-in/out animation
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Resend button under own messages in channel chat and DM
(allows easy re-sending of failed messages)
- Change channel chat limit from 140 to 135 bytes
- Change DM limit from 140 to 150 bytes
(experimentally verified Meshcore limits)
- Remove misleading Hops info from DM message bubbles
- Update README with new byte limits
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>