- Changed hard limit from 8 to 40 channels (most LoRa devices support up to 40)
- Added soft warning when exceeding 7 channels (some devices may have lower limits)
- Warning displayed in UI after successful channel creation/join
- Updated error message to reflect new 40-channel maximum
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Complete the offline support implementation by hosting emoji-picker-element locally, eliminating the last external CDN dependency. Application now works 100% offline without any internet connection.
Changes:
- Download and host emoji-picker-element 1.28.1 locally (~100 KB)
- index.js, picker.js, database.js
- Download and host emoji data JSON (~429 KB)
- en/emojibase/data.json with full emoji database
- Update index.html and dm.html:
- Replace CDN emoji picker import with local version
- Also migrate Bootstrap CSS/JS to local in dm.html (was missed before)
- Configure emoji picker to use local data source in app.js and dm.js
- Set picker.dataSource to local JSON path
- Update Service Worker (v2 → v3):
- Add emoji picker files to pre-cache list
- Total offline cache size: ~1.2 MB
- Update documentation:
- README.md: Add emoji picker to offline support features
- CLAUDE.md: Document emoji picker implementation and file structure
Total offline package breakdown:
- Bootstrap CSS/JS: ~307 KB
- Bootstrap Icons: ~398 KB
- Emoji Picker: ~100 KB
- Emoji Data: ~429 KB
- Total: ~1.2 MB
Benefits:
- Zero external dependencies (no CDN calls)
- Full emoji picker functionality offline
- Faster page load (no external requests)
- Perfect for air-gapped mesh network deployments
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Previous implementation always disabled notifications when permission
was granted, regardless of current state. This made it impossible to
re-enable notifications after disabling them.
Now properly checks localStorage state and toggles:
- If currently enabled → disable (show "Notifications disabled")
- If currently disabled → enable (show "Notifications enabled")
This fixes the issue where clicking the toggle when disabled would
show "Notifications disabled" instead of enabling them.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes:
- Convert all Polish UI text to English (buttons, labels, messages)
- Menu: "Powiadomienia" → "Notifications"
- Status badges: "Włączone/Wyłączone/Zablokowane" → "Enabled/Disabled/Blocked"
- Toast messages: all notification messages translated to English
- Notification bodies: "Nowe: X kanały" → "New: X channels"
- Fix notification toggle UI bug
- Badge now correctly shows "Disabled" when user turns off notifications
- Previously showed "Enabled" whenever permission was granted (ignoring localStorage)
- Now checks both permission AND localStorage state
This ensures the UI respects the international nature of the project
and fixes the toggle state display issue found during Android testing.
Files modified:
- app/templates/base.html
- app/static/js/app.js
- app/static/js/dm.js
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements Progressive Web App notification capabilities:
- Service Worker foundation for PWA installability and offline support
- Browser notification toggle in side menu with permission management
- Local notifications triggered on new messages (channels, DMs, pending)
- App Badge API integration showing total unread count on app icon
- Smart notification logic (only when app hidden, only for NEW messages)
- Graceful degradation for unsupported browsers
Technical details:
- Service Worker with network-first fetch strategy for dynamic content
- Notification permission stored in localStorage (mc_notifications_enabled)
- Delta-based notification tracking (prevents spam on page load)
- Notification tag prevents duplicate alerts
- App badge auto-clears when user returns to app
Limitations (documented):
- Android may freeze background JS after 5-10 minutes (OS behavior)
- Full "wake device" support requires Web Push API (future enhancement)
- Works best for active users who check app regularly
Files modified:
- app/static/js/sw.js (new)
- app/templates/base.html
- app/static/js/app.js
- app/static/js/dm.js
- app/static/manifest.json
- app/static/css/style.css
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add badge for pending contacts on Contact Management FAB button (orange)
- Move DM badge from notification bell to Direct Messages FAB button (green)
- Add universal updateFabBadge() function for all FAB badges
- Add updatePendingContactsBadge() function with localStorage type filter support
- Update badges every 10 seconds with auto-refresh
- Update badges when modals are closed
Frontend changes:
- New CSS classes: .fab-badge, .fab-badge-pending, .fab-badge-dm
- position: relative added to .fab for badge positioning
- Removed DM badge code from notification bell
- Added modal event handlers for badge updates
Backend: No changes (uses existing /api/contacts/pending endpoint with types parameter)
This improves UX by showing notification counts directly on relevant FAB buttons
instead of crowding the notification bell. DM badge moved from bell to DM button,
and new pending contacts badge added to Contact Management button.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Removed Node Discovery feature that was experiencing persistent timeout
issues. Feature attempted to scan mesh network for nearby repeaters but
consistently failed due to bridge timing constraints.
Changes:
- Remove node_discover() function from cli.py
- Remove 'node_discover' from SPECIAL_COMMANDS in api.py
- Remove Discover Nodes button and modal from base.html
- Remove discoverNodes() and displayNodeDiscoveryResults() from app.js
- Remove Discover Nodes documentation from README.md
IMPORTANT: Advert message cleanup ("Advert sent") is preserved and
working correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented new "Discover Nodes" feature in Network Commands menu:
- Added .node_discover command to meshcli wrapper (cli.py)
- Created interactive modal with sortable table showing nearby repeaters
- Displays SNR, RSSI, path length, and signal quality indicators
- Added refresh functionality to rescan for nodes
Fixed advert notification to show clean "Advert sent" message
instead of full meshcli output.
Technical changes:
- app/meshcore/cli.py: Added node_discover() function with JSON parsing
- app/routes/api.py: Updated SPECIAL_COMMANDS and execute_special_command()
to handle node_discover return type and clean advert message
- app/templates/base.html: Added "Discover Nodes" menu button and modal
- app/static/js/app.js: Added discoverNodes() and displayNodeDiscoveryResults()
- README.md: Added documentation for Discover Nodes feature
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The main page (app.js) was missing viewport recalculation logic
that was already present in dm.js, causing layout corruption when
returning from other pages in PWA mode.
Changes:
- Add viewport recalculation in DOMContentLoaded:
- window.scrollTo(0, 0) - reset scroll position
- window.dispatchEvent(new Event('resize')) - trigger reflow
- document.body.offsetHeight - force layout recalculation
- Add pageshow event listener:
- Handles page restoration from browser cache (Back button)
- Recalculates viewport when event.persisted is true
- Add visibilitychange event listener:
- Handles PWA returning from background
- 100ms delay to ensure proper timing
This fixes the issue where:
1. User opens menu on main page
2. Navigates to Contact Management
3. Clicks Home button to return
4. Status bar is hidden under Android system buttons
The viewport wasn't being recalculated after navigation,
leaving the layout with incorrect heights from cached state.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Root cause: Bootstrap offcanvas leaves backdrop/body classes in DOM
when navigating via window.location.href, causing viewport issues.
Changes:
- Remove hamburger menu button from DM navbar (caused overflow)
- Reduce menu button icon size on channels (remove font-size override)
- Add global navigateTo() function in app.js and contacts.js
- Function closes offcanvas, removes backdrops, clears body classes
- Replace all window.location.href calls with navigateTo()
- Updated navigation in: base.html, contacts*.html templates
- Add 100ms delay before navigation to ensure cleanup completes
This fixes the issue where:
1. Opening offcanvas menu on main page
2. Navigating to DM or Contact Management
3. Returning to main page
Results in corrupted viewport with hidden status bar
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- 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>
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>
- 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>
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.
Two improvements to DM functionality:
1. Removed DM button from message blocks in channel view
- Users should use the DM page directly instead
- Cleaner UI without redundant buttons
2. Filter only CLI (client) contacts in DM dropdown
- Added filter_types parameter to parse_contacts()
- get_contacts_list() now returns only CLI contacts
- Repeaters (REP), rooms (ROOM), and sensors (SENS) are excluded
- You can't send DMs to repeaters anyway!
Updated README.md to reflect these changes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed DM approach from conditional button visibility to showing all
available contacts directly in the DM page dropdown. This provides better
UX and performance.
Changes:
- Reverted conditional DM button visibility in app.js (button always shows)
- Removed contacts loading from main page (app.js)
- Added loadContacts() function to dm.js to fetch contacts from API
- Modified populateConversationSelector() to show:
1. Existing conversations (with history) first
2. Separator: "--- Available contacts ---"
3. All contacts from device who aren't in conversations yet
- Users can now start new DM conversations with any contact
- Updated README.md with new DM workflow description
Benefits:
- Simpler and more intuitive UX
- Better performance (no checks on every message)
- Users can proactively start conversations
- Clear visibility of who's available for DM
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
The DM button is now only shown for users who are in the device's contacts
list, ensuring that direct messages will actually be delivered. This prevents
users from attempting to send DMs to recipients who cannot receive them.
Changes:
- Added parse_contacts() and get_contacts_list() functions to cli.py for parsing
meshcli contacts output
- Created /api/contacts endpoint to retrieve contact names from device
- Modified frontend app.js to fetch and cache contacts list on page load
- Updated createMessageElement() to conditionally render DM button only when
sender is in contacts list
- Updated README.md with note about DM button visibility requirement
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Replace DM modal with full-page view at /dm route for better mobile UX
- Add workaround for meshcore-cli bug where SENT_MSG contains sender's
name instead of recipient - now saving sent DMs to separate log file
- Fix DM button styling to match Reply button (btn-outline-secondary)
- Add dm.js for DM page functionality
- Add dm.html template with green navbar for visual distinction
- Update menu link to navigate to /dm instead of opening modal
- Remove unused DM modal functions from app.js
- Update documentation with new DM workflow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Parse PRIV (incoming) and SENT_MSG (outgoing) message types
- Add DM API endpoints: conversations, messages, updates
- Implement conversation grouping by pubkey_prefix or name
- Add timeout-based delivery status (pending → timeout)
- Add DM modal with conversation list and thread views
- Add dual notification badge (blue=channels, green=DM)
- Add DM button next to Reply on channel messages
- Include message deduplication for both incoming and outgoing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add new menu section "Network Commands" with two special commands:
- Send Advert: sends single advertisement (recommended for normal use)
- Flood Advert: floods network with advertisement (for recovery only)
Changes:
- cli.py: Add advert() and floodadv() functions
- api.py: Add POST /api/device/command and GET /api/device/commands endpoints
- base.html: Add Network Commands section to slide-out menu
- app.js: Add JavaScript handlers with confirmation for floodadv
- README.md: Document new Network Commands feature
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Allow joining public channels (starting with #) without encryption key
- Frontend: Make key field optional with validation for # channels
- Backend: Update API to accept optional key parameter
- CLI wrapper: Build meshcli command dynamically based on key presence
- Implement automatic message cleanup when deleting channels
- Add delete_channel_messages() function to remove channel history
- Integrate cleanup into DELETE /api/channels endpoint
- Prevents message leakage when reusing channel slots
- Update documentation with new features and usage instructions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Change message byte limit from 200 to 140 bytes
- Remove keyboard hint text to save space
- Simplify navbar to show only notification bell and channel selector
- Add slide-out offcanvas menu for less-used options:
* Refresh Messages
* Manage Channels
* Message History (archive selector)
* Settings
- Increase font sizes in dropdowns and menu for better readability
- Add icons and text labels to all menu items
- Auto-close menu after selecting options
- Update clipboard API to modern navigator.clipboard with fallback
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add timeout protection (10s for channels, 15s for updates)
- Implement fallback mechanism to ensure Public channel always exists
- Fix race condition by loading channels before setupAutoRefresh()
- Add proper validation for API responses and channel data
- Improve error handling with detailed logging
- Prevent checkForUpdates() from running before channels are loaded
This fixes the recurring issue where the channel dropdown would
randomly become empty, especially visible on mobile devices.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaces the blind 60-second refresh with a smart polling system that only updates the UI when new messages actually arrive.
Key improvements:
- Lightweight update checks every 10 seconds (vs full refresh every 60s)
- Chat view refreshes only when new messages appear on active channel
- Notification bell with global unread count across all channels
- Per-channel unread badges in channel selector (e.g., "Malopolska (3)")
- Last-seen timestamp tracking per channel with localStorage persistence
- Bell ring animation when new messages arrive
Backend changes:
- New /api/messages/updates endpoint for efficient update polling
- Returns per-channel update status and unread counts
Frontend changes:
- Smart auto-refresh mechanism with conditional UI updates
- Unread message tracking system with localStorage
- Notification bell UI component with badge
- Channel selector badges for unread messages
- CSS animations for bell ring effect
This dramatically reduces network traffic and server load while providing better UX through instant notifications about activity on other channels.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix critical JavaScript error that prevented channel selector from working:
- Add null check for selectedOption before accessing .text property
- Prevents "can't access property 'text', undefined" error at line 106
- This error occurred when selector options were being rebuilt
Additional cleanup:
- Remove duplicate escapeHtml() function definition
- Add detailed console logging to loadChannels() for debugging
Root cause: When populateChannelSelector() clears and rebuilds options,
the change event can fire with an invalid selectedIndex, causing
e.target.options[e.target.selectedIndex] to return undefined.
Bug reported by user: TypeError at app.js:106 when switching channels
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fix issue where channel selector would show empty or missing channels
after auto-refresh. The populateChannelSelector() function was clearing
all options but not rebuilding them from API data correctly.
Changes:
- Rebuild entire dropdown from API response (including Public channel)
- Use currentChannelIdx from global state for selection
- Fallback to Public (index 0) if saved channel no longer exists
- Remove unused variable (IDE warning fix)
Bug: Channel selector showing empty after first auto-refresh
Root cause: API returns ALL channels including Public at index 0,
but the function was clearing all options and not adding them back.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added a professional emoji picker widget to make it easier to insert emoji on desktop browsers where native emoji input is not readily available.
Changes:
- Added emoji-picker-element library from CDN (~50KB)
- New emoji button (😊) next to the Send button
- Full emoji picker with categories and search functionality
- Emoji inserted at cursor position in textarea
- Automatic byte counter update after insertion
- Mobile responsive: 6 columns on mobile, 8 on desktop
- Click outside to close picker
Benefits:
- Easy emoji access on Windows/Linux desktop browsers
- No need to use Win+. shortcut or copy-paste
- Professional UI with search and categories
- Still works with native emoji keyboards on mobile
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements automatic daily archiving of messages to improve performance
and enable browsing historical chat by date.
Backend changes:
- Add APScheduler for daily archiving at midnight (00:00 UTC)
- Create app/archiver/manager.py with archive logic and scheduler
- Extend parser.py to read from archive files and filter by days
- Add archive configuration to config.py (MC_ARCHIVE_*)
API changes:
- Extend GET /api/messages with archive_date and days parameters
- Add GET /api/archives endpoint to list available archives
- Add POST /api/archive/trigger for manual archiving
Frontend changes:
- Add date selector dropdown in navbar for archive browsing
- Implement archive list loading and date selection
- Update formatTime() to show full dates in archive view
- Live view now shows only last 7 days (configurable)
Docker & Config:
- Add archive volume mount in docker-compose.yml
- Add MC_ARCHIVE_DIR, MC_ARCHIVE_ENABLED, MC_ARCHIVE_RETENTION_DAYS env vars
- Update .env.example with archive configuration section
Documentation:
- Update README.md with archive feature and usage instructions
- Update .claude/instructions.md with archive endpoints
Key features:
- Automatic daily archiving (midnight UTC)
- Live view filtered to last 7 days for better performance
- Browse historical messages by date via dropdown selector
- Archives stored as dated files: {device}.YYYY-MM-DD.msgs
- Original .msgs file never modified (safe, read-only approach)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented 200-character limit for messages due to LoRa/MeshCore constraints:
- Added maxlength=200 to textarea
- Added live character counter (0 / 200)
- Visual warnings: orange at 75%, red at 90%
- Counter updates on input, reply, and send
- Backend validation with descriptive error message
- Added technotes/limity.md documentation about MeshCore limits
Based on MeshCore LoRa payload constraints (~180-200 bytes safe limit).
This prevents message fragmentation and improves transmission reliability.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented core backend functionality:
- Flask application structure with blueprints
- Configuration module loading from environment variables
- MeshCore CLI wrapper with subprocess execution and timeout handling
- Message parser for .msgs JSON Lines file format
- REST API endpoints (messages, status, sync, contacts cleanup)
- HTML views with Bootstrap 5 responsive design
- Frontend JavaScript with auto-refresh and live updates
- Custom CSS styling for chat interface
API Endpoints:
- GET /api/messages - List messages with pagination
- POST /api/messages - Send message with optional reply-to
- GET /api/status - Device connection status
- POST /api/sync - Trigger message sync
- POST /api/contacts/cleanup - Remove inactive contacts
- GET /api/device/info - Device information
Features:
- Auto-refresh every 60s (configurable)
- Reply to messages with @[UserName] format
- Toast notifications for feedback
- Settings modal for contact management
- Responsive design (mobile-friendly)
- Message bubbles with sender, timestamp, SNR, hop count
Ready for testing on production server (192.168.131.80:5000)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>