- Change DM message input from input to textarea (rows=2) for consistency
- Increase DM form padding (p-2 → p-3) to match channel view
- Increase DM status bar padding (p-1 → p-2) with larger font
- Replace "Last refresh:" with "Updated:" in both views
- Update DM status to show connection states (Connected/Connecting/Disconnected) instead of "Ready"
- Add loadStatus() function to DM page for proper connection monitoring
- Unify message input area height and status bar appearance across both pages
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace localStorage-based message read tracking with server-side storage
to enable unread badge synchronization across all devices and browsers.
Changes:
- Add read_status.py module for server-side read status management
- Add GET /api/read_status endpoint to fetch read status
- Add POST /api/read_status/mark_read endpoint to update read status
- Update app.js to load/save read status from server instead of localStorage
- Update dm.js to load/save DM read status from server instead of localStorage
- Read status stored in MC_CONFIG_DIR/.read_status.json for persistence
Benefits:
- Unread badges sync across all devices (phone, computer, tablet)
- Read status persists across browser sessions
- No more duplicate unread notifications when switching devices
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Remove impractical path length filter from contact cleanup feature.
The out_path_len parameter is rarely useful as most contacts have
value -1 (no route), making this filter unpractical for real-world use.
Changes:
- Remove path_len parameter from backend API endpoints
- Remove path length input field from HTML template
- Remove path_len collection from JavaScript code
- Update documentation (CLAUDE.md, README.md)
- Simplify cleanup filter to: name, types, date field, and days
Backend changes:
- Update _filter_contacts_by_criteria() to remove path_len logic
- Remove path_len validation from both endpoints
- Update API documentation in docstrings
Frontend changes:
- Remove Path Length input section from contacts-manage.html
- Remove path_len from collectCleanupCriteria() function
Documentation changes:
- Update API endpoint descriptions in CLAUDE.md
- Update cleanup instructions in README.md
- Remove path_len from example use cases
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Updated README.md with new features:
- Added mention badges, clickable URLs, and image previews to Key Features
- Added new "Message Content Features" section with detailed usage
- Updated Project Structure to include message-utils.js
- Marked feature as completed in Development Status
Updated CLAUDE.md with technical details:
- Added message-utils.js to Project Structure
- Added new "Message Content Processing" section with:
- Features overview (mentions, URLs, images)
- Technical implementation details
- CSS classes and functions
- Usage examples
- Security notes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented comprehensive message content enhancements for both channel messages and DMs:
- **Mention badges**: @[Username] mentions now display as styled badges similar to Android Meshcore app
- **Clickable URLs**: http:// and https:// URLs are automatically converted to clickable links that open in new tabs
- **Image thumbnails**: URLs ending in .jpg, .jpeg, .png, .gif, .webp display as thumbnails with click-to-expand modal preview
- **Mobile responsive**: Image thumbnails adapt size for mobile screens
- **Security**: Proper XSS protection with HTML escaping and attribute sanitization
Technical implementation:
- Created shared message-utils.js module for content processing
- Added CSS styles for badges, links, images, and modal preview
- Updated both app.js and dm.js to use new content processor
- Used event delegation for image click handlers to support dynamic content
- Responsive design with mobile-optimized image sizes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: #pendingList had max-height: 200px showing only 1-2 contacts
with huge unused whitespace below.
Solution:
- Desktop: height calc(100vh - 280px) - slightly more than existingList
due to extra description text
- Mobile: height calc(100vh - 320px) - adjusted for mobile layout
- Changed from max-height to height for consistent behavior
- Added min-height: 300px for safety
Now pending contacts list properly utilizes available screen space.
Add information about new Map button feature in README.md and CLAUDE.md.
Button allows viewing contact location on Google Maps when GPS coordinates available.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add Map button between Copy Key and Delete buttons on existing contacts cards.
Button appears only when contact has GPS coordinates (adv_lat/adv_lon != 0).
Opens contact location in Google Maps in new tab.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Move toast container from bottom-right to top-left corner
- Reduce notification display time from 3-5s to 1.5s
- Prevents notifications from blocking message input area
- Applied consistently across all pages (main, DM, contacts)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: List was too tall causing the entire page to scroll,
hiding navigation buttons at the top when scrolling to bottom.
Solution:
- Changed from max-height to height for #existingList
- Desktop: calc(100vh - 260px) - slightly smaller to fit all elements
- Mobile: calc(100vh - 300px) - adjusted for mobile layout
Now only the contact list scrolls internally, while navigation
buttons remain visible at the top.
Problem: #existingList ID selector had higher specificity than
.contacts-list-fullscreen class, and max-height values were limiting
the list height regardless of the class height setting.
Changes:
- Desktop: #existingList max-height calc(100vh - 400px) → calc(100vh - 240px)
- Mobile: #existingList max-height calc(100vh - 450px) → calc(100vh - 280px)
This should now properly utilize available screen space.
Changed height calculation:
- Desktop: calc(100vh - 300px) → calc(100vh - 240px)
- Mobile: calc(100vh - 340px) → calc(100vh - 280px)
This should eliminate the ~20% unused whitespace at the bottom
of the contact lists.
Adjusted .contacts-list-fullscreen height to better utilize available
screen space by increasing the subtracted value from 200px to 300px
on desktop and from 150px to 340px on mobile.
This accounts for:
- Navbar (~56px)
- Main container padding (~48px)
- Back buttons (~60px)
- Search toolbar (~50px)
- Filter/sort toolbar (~60px)
- Margins between elements (~26px)
Fixes blank space at bottom of Existing Contacts and Pending Contacts pages.
This merge includes:
- Fixed message loading crash (cleanupBtn null check)
- Fixed contact deletion (use contact name instead of public key)
- Enhanced /api/contacts/detailed to return full contact_info data
- Added GPS coordinates, routing paths, and all meshcli fields
- Improved contact management UI and sorting
- Added detailed logging for debugging
Testing revealed that meshcli's remove_contact command only works
with contact names, not with public keys (neither prefix nor full key).
Example test results:
- remove_contact bd030a71e091 → Unknown contact
- remove_contact bd030a71e091b14e...f7a → Unknown contact
- remove_contact "Progres-SLU-Lubsza-test" → SUCCESS
Changed frontend to send contact name as selector.
Problem:
- meshcli's remove_contact command requires full public key
- We were sending only 12-char prefix, causing 'Unknown contact' errors
- Contacts appeared in list but couldn't be deleted
Solution:
- API now includes full_public_key in /api/contacts/detailed response
- Frontend uses full_public_key when available, falls back to prefix
- Added detailed logging to track deletion attempts
This fixes the issue where contacts (especially with last_seen=4)
could not be removed from the device.
Added logging to see exactly what meshcli returns when removing a contact.
This will help diagnose why contacts are not being deleted despite
returning success status.
The cleanupBtn element only exists on the contact management page,
not on the main chat page. This was causing JavaScript to crash
with 'cannot access property addEventListener of null' error,
preventing messages from loading.
Added null check before attaching event listener.
- Add CSS override to enable scrolling on Contact Management pages
(override global overflow: hidden from style.css)
- Move cleanup description to tooltip on info icon for consistency
with Manual approval toggle pattern
- Add flexbox layout to cleanup section h6 for proper icon alignment
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added technotes/API-Diagnostic-Commands.md with:
- Complete curl examples for all API endpoints
- SSH + docker exec one-liners for easy testing
- Organized by category (health, contacts, device, channels, messages, DM, settings, archives)
- Example JSON responses for each endpoint
- Useful one-liners with jq for advanced queries
- Quick troubleshooting section with logs and health checks
- Notes on ports, methods, and response times
This serves as a quick reference for debugging and testing the mc-webui API.
Previous line-based parsing failed (0 contacts parsed despite receiving data).
New approach:
- Use brace-matching algorithm to find complete JSON objects {...}
- Works for both single-line and prettified (multi-line) JSON
- Added logging of first 500 chars of output for debugging
This handles the case where bridge may return prettified JSON instead
of newline-delimited JSON.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Root cause: apply_to contact_info returns NDJSON (newline-delimited JSON),
not a JSON array. Each contact is a separate JSON object on its own line.
Changes:
- Call apply_to separately for each type (t=1, t=2, t=3, t=4) instead of
using comma-separated list which returns 0 matches through bridge
- Parse NDJSON format: each line is a separate JSON object
- Skip non-JSON lines (prompt echoes "MarWoj|*", summary "> N matches")
- Collect all contacts from all types into single dictionary
- Add detailed logging for each type and total count
This matches the actual meshcli output format observed in interactive mode.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add comprehensive debug logging to trace last_seen data flow:
cli.py (get_contacts_with_last_seen):
- Log apply_to command success/failure with error details
- Log number of bytes returned by command
- Log number of contacts parsed from JSON
- Log sample contact with public_key prefix and last_advert value
- Log raw output on JSON parse errors
api.py (get_contacts_detailed_api):
- Log number of detailed contacts retrieved
- Log number of matched contacts after merging
- Fix case sensitivity: normalize both prefix and full_key to lowercase
This will help identify why all contacts show "Last seen: Unknown".
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add comprehensive "last seen" tracking for all contact types:
Backend (cli.py):
- New function get_contacts_with_last_seen() using 'apply_to t=1,t=2,t=3,t=4 contact_info'
- Fetches detailed contact metadata including last_advert timestamps
- Returns dictionary indexed by full public_key for efficient lookup
API (api.py):
- Enhanced /api/contacts/detailed endpoint to merge last_seen data
- Matches contacts by public_key_prefix (first 12 chars)
- Graceful fallback if detailed fetch fails (contacts still displayed without last_seen)
Frontend (contacts.js):
- formatRelativeTime() - converts Unix timestamps to human-readable format
("5 minutes ago", "2 hours ago", "3 days ago")
- getActivityStatus() - returns status indicator based on recency:
🟢 Active (< 5 min), 🟡 Recent (< 1 hour), 🔴 Inactive (> 1 hour)
- Contact cards now display "Last seen" with status icon and relative time
- Clean handling of missing last_seen data (shows "Unknown")
This feature helps users identify active vs. inactive contacts at a glance,
using the last_advert field from meshcli's contact_info command.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Compact Manual Approval and Pending Contacts sections to give more
space for Existing Contacts list, which is the primary focus.
Changes to Manual Approval section:
- Convert from full section to single-line compact control
- Replace description text with tooltip icon (hover for info)
- Reduce vertical space by ~70px
Changes to Pending Contacts section:
- Reduce header size from h5 to h6
- Compact empty state (1rem padding vs 3rem)
- Reduce icon size (1.5rem vs 3rem)
- Limit list height to 200px (was 600px)
- Remove "Refresh" text from button (icon only)
Changes to Existing Contacts section:
- Dynamic height: calc(100vh - 400px) with 300px minimum
- Adapts to viewport height automatically
- On mobile: calc(100vh - 450px) for better fit
- More contacts visible without scrolling
Other improvements:
- Initialize Bootstrap tooltips in contacts.js
- Smaller fonts and margins throughout
- Better vertical space distribution
Result: ~150px more space for main contacts list on desktop,
~200px more on mobile. Tooltip provides same info without clutter.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix scrolling issue where contact lists with many items (263/350)
couldn't be scrolled on desktop or mobile devices.
Changes:
- Add max-height: 600px to #existingList and #pendingList
- Enable vertical scrolling with overflow-y: auto
- Add smooth scrolling for mobile with -webkit-overflow-scrolling
- Style custom scrollbar (8px, rounded, gray) for better UX
Tested: Lists now show ~8-10 contact cards at once with scrollbar
appearing when needed. Works with mouse wheel and touch gestures.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added comprehensive documentation of discovered differences between CLI and ROOM
contact types when using the add_pending command.
Key findings from real-world testing (2025-12-29):
CLI Contacts (flexible matching):
- ✅ Full name works: "StNMobile T1000e"
- ✅ Name prefix works: "StN"
- ✅ Public key prefix works: "2ce5514"
- ✅ Full public key works
ROOM Contacts (strict matching):
- ❌ Full name fails: "TK room cwiczebny🔆" (UTF-8 issues)
- ❌ Name prefix fails: "TK room"
- ❌ Public key prefix fails: "b3fec489"
- ✅ ONLY full public key works
Root cause: meshcli uses different matching logic for different contact types,
likely to prevent accidental approval of group rooms which have different
security/privacy implications.
Recommendation: UI should always send full public_key (not name) when calling
POST /add_pending to ensure compatibility with all contact types.
Updated sections:
- Selector formats with CLI/ROOM compatibility notes
- New "Important Discovery: Contact Type Differences" section
- Updated test commands with real-world results
- JavaScript code examples for UI implementation
- Best practices for UI design
This documentation will guide the next phase: UI implementation for pending
contact management.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Issue: Parser incorrectly matched JSON lines (adverts, GRP_TXT messages)
as pending contacts because they contained colons. This caused false
positives like {"raw_hex": "..."} being parsed as contact name.
Root cause: meshcli outputs JSON for received messages (due to
json_log_rx=on setting) during pending_contacts command execution.
Original parser only checked for ':' presence, matching any JSON.
Solution: Added robust filtering before parsing:
1. Skip empty lines
2. Skip JSON lines (starting with { or [)
3. Skip meshcli prompt lines (ending with |*)
4. Validate public_key contains only hex characters (0-9, a-f, A-F)
This ensures only valid "ContactName: <hex_pubkey>" format is parsed.
Example of filtered content:
- SKIP: {"raw_hex": "25bc...", "payload_typename": "GRP_TXT"}
- SKIP: MarWoj|*
- PARSE: Skyllancer: f9ef123abc456...
Testing: No false positives with JSON lines in output.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Enabled manual contact approval mode in meshcli session initialization.
This requires explicit approval for new contacts attempting to connect,
providing enhanced security and network access control.
Changes:
- Added 'set manual_add_contacts on' to _init_session_settings()
- Updates session init log message to include manual_add_contacts status
- Created comprehensive technical documentation (technotes/pending-contacts-api.md)
Benefits:
- DoS prevention - blocks flooding with fake contact requests
- Network privacy - control who can see your node
- Trust model - explicit approval for all new contacts
- Spam filtering - reject unwanted connection attempts
Technical notes document includes:
- Problem statement and solution overview
- API endpoint specifications and examples
- Testing procedures and expected workflows
- Future UI integration plans
- Security considerations and recommendations
- Meshcli command reference
When manual approval is enabled, new contacts appear in pending list
(accessible via GET /pending_contacts) and must be approved via
POST /add_pending before they can communicate with the node.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added two new HTTP endpoints to meshcore-bridge for managing pending contacts
(contacts awaiting manual approval when manual_add_contacts mode is enabled):
New endpoints:
- GET /pending_contacts - List all pending contacts awaiting approval
- Parses meshcli output format: "Name: <hex_public_key>"
- Returns JSON array with {name, public_key} objects
- Includes raw_stdout for debugging
- POST /add_pending - Approve and add a pending contact
- Accepts JSON body: {"selector": "<name_or_pubkey>"}
- Validates selector is non-empty string
- Executes meshcli add_pending command via persistent session
Additional changes:
- Added curl to mc-webui Dockerfile for testing endpoints
- Updated README with Testing Bridge API section
- Included example curl commands and expected responses
Implementation notes:
- Uses existing MeshCLISession.execute_command() - no new processes
- Same persistent session and command queue architecture
- Consistent error handling with existing /cli endpoint
Enables future UI for manual contact approval workflow.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Issue: Messages sent to Public channel had visible double quotes around
multi-word text (e.g., "Hello world" appeared as "Hello world" in chat).
Root cause: In interactive mode, meshcli's 'public' command treats quotes
literally as part of message content, while 'chan' command correctly parses
them as argument delimiters.
Solution: Use 'chan 0' for Public channel instead of 'public' command.
This ensures consistent quote handling across all channels.
Before:
- Public (ch 0): public "message" → quotes visible in output
- Other channels: chan <nb> "message" → quotes correctly parsed ✓
After:
- All channels: chan <nb> "message" → consistent behavior ✓
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>