SQLite DELETE marks pages free but doesn't shrink the file, so the
new retention job would keep DBs at their bloated size forever without
a follow-up VACUUM. Add db.vacuum() that runs PRAGMA-free VACUUM and
reports size_before/size_after/elapsed so callers can surface results.
The retention job now calls vacuum() automatically when it deleted at
least 1000 rows. Threshold avoids the multi-second VACUUM cost on quiet
days. Failure is logged, not raised — a missed VACUUM never crashes
the scheduler.
Power-user override: new "Optimize now" button in the Database Backup
modal triggers VACUUM on demand via POST /api/db/vacuum, alongside a
GET /api/db/size that drives the live "Current size" label. This way
users don't have to wait until 03:30 to reclaim space after the first
big retention pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a Settings > Analyzer tab letting users CRUD custom MeshCore Analyzer
services with a star-toggle default and inline disabled switch. The chart
icon under each group-chat message now resolves at click time: built-in
Letsmesh when no enabled customs, the default when set, or a chooser
modal otherwise. Backend stops shipping the prebuilt analyzer_url and
emits packet_hash instead — the frontend substitutes {packetHash} in the
chosen URL template.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
dm.html is a standalone template (not extending base.html), so it didn't
receive the .layout-wide class — the DM contact list never appeared as
a sidebar even on wide screens.
Added an inline script in dm.html <head> mirroring the base.html logic.
Also subscribes to the `storage` event so the iframe re-applies the
class live when the user changes the breakpoint in the parent window's
Settings -> Interface tab.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The threshold above which the channel/DM list shows as a sidebar (vs.
collapsing to a top dropdown) is now user-configurable in
Settings -> Interface -> Layout. Persisted per device in LocalStorage
(key: mc-webui-sidebar-breakpoint, default: 992px, range: 600-2000).
Implementation: replaced hardcoded `@media (min-width: 992px)` with a
`.layout-wide` class on <html>, toggled by JS based on window.innerWidth
vs. the user's breakpoint. An inline script in <head> applies the class
synchronously to prevent layout flash on page load (same pattern as theme).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Stop persisting "Disconnected" / "Failed to connect" — these are session-local events; saving them made every reopen begin with a stale red error.
- Scroll to the bottom after restoring transcript so reopens land at the latest entry instead of the top.
- Add a floating chat-style jump-to-latest button that appears whenever the user scrolls more than ~80px above the bottom and disappears once they're back at the latest entry.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Rename "meshcli Console" to "mc-webui Console" (modal title + docs).
- Drop redundant "Connected to..." messages; replace intro with a one-line "Type 'help' for available commands." hint.
- Use a teal device-name style so the header label is readable on the dark background.
- Display contact paths with commas (D1,90,05,54) instead of arrows in `contacts` and `path`, matching the standard MeshCore client.
- Fix `change_path`: previously read only args[2] after shlex split, silently writing a 1-byte path. Now joins remaining args, accepts comma/space/continuous-hex, validates hex, auto-deduces hash_size from comma-chunk length (1/2/3-byte hops), and routes through _change_path_async so path_hash_mode is set and the contacts cache is invalidated.
- Update `help` line and add a usage hint for the no-args form.
- Add capped persistent output transcript: GET/POST/DELETE /api/console/output (cap 500 entries). Console restores prior entries (faded) above a divider on open and exposes a trash button to clear it.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Lets users choose where each action appears: in the floating Quick
Access (FAB) bar or the slide-out Main Menu. Adds a "Hide Quick Access"
master switch and a per-item placement table in Settings -> Appearance.
Removes the stale "Refresh Messages" menu item (legacy of polling era,
WebSocket already covers it) and moves the Notifications toggle from
the menu to a dedicated Settings -> Notifications tab.
Each of the 11 configurable items is rendered in both locations;
applyItemPlacements() toggles d-none based on localStorage. Badges for
DM unread and pending contacts propagate to the Main Menu copies so
they stay in sync regardless of placement.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the click-the-selected-radio-again gesture with a top-row
"None — use firmware default" radio, mirroring the per-channel region
picker. Users found the toggle gesture unintuitive; an explicit option
matches the picker pattern they already know.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Click the already-selected radio to clear the default; new
DELETE /api/regions/default endpoint also pushes an empty CMD 63 to
the firmware so its persistent default is wiped too.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two small follow-ups after initial deployment.
- Rename the Settings tab 'Channels' -> 'Regions' (id now tabSettingsRegions).
The tab manages the region registry, not channels; the old label was
confusing. The per-channel picker still lives under Manage Channels as
before.
- Graceful handling of firmware rejection: CMD_SET_DEFAULT_FLOOD_SCOPE
(63) and CMD_GET_DEFAULT_FLOOD_SCOPE (64) were introduced in firmware
v1.15.0; on v1.14.x the device replies with a generic ERR frame and
our toast showed the unhelpful 'Firmware error: unknown'. Now the
device_manager translates the empty/timeout reason into a concrete
message naming the v1.15 requirement, and the api handler appends
'Your choice is saved locally' so the user knows the local state
still persists. Same treatment for the delete-default-region clear
path.
Final slice — small but completes the feature.
- index.html: add #regionIndicator pill to the chat status bar, inline
with the connection-status dot. Hidden by default; click opens the
region picker for the current channel.
- app.js: loadChannelScopes() fetches /api/channels/scopes at page init
(right after loadChannels). updateRegionIndicator() toggles the pill
based on currentChannelIdx + window.channelScopes and is called on
every successful loadMessages + after saveChannelScope.
- DM view deliberately untouched — region scope applies to flooded
channel sends only, not DMs.
Fourth slice — the feature is now functional end-to-end from UI to radio.
- Manage Channels modal: each row now has a pin-map button between Mute
and Share that opens a region picker for that channel; rows show an
inline badge with the assigned region name.
- Region picker modal (new #regionPickerModal): radio list of regions
with a "(None) — use firmware default" option at the top. Empty-state
shows a "Manage Regions" CTA that deep-links to Settings > Channels.
- api.py: two new routes —
- GET /api/channels/scopes → bulk map for UI rendering
- PUT /api/channels/<idx>/scope → {region_id: int | null} set/clear
- device_manager.send_channel_message: looks up the channel's scope,
then — under _send_lock — pushes the 16-byte key via CMD 54 before
the actual send_chan_msg. Channels without a mapping get an all-zero
key so a previously-set scope doesn't leak across channels (firmware's
send_scope is sticky until overwritten, not one-shot).
Third slice — users can now curate their device-wide region list. No
per-channel mapping yet; that's PR #4.
- base.html: new Channels tab in the Settings modal with an info banner
pointing at regions.meshcore.nz, the list container, and an add-region
form.
- app.js: loadRegions / addRegion / deleteRegion / setDefaultRegion
mirroring the loadContactsSettings / saveContactsSetting pattern. Client
-side name validation (isValidRegionName) mirrors the firmware
RegionMap::is_name_char byte-rule exactly so users get instant feedback
on invalid chars without a round-trip.
- api.py: four routes under /api/regions —
- GET /api/regions → list registry
- POST /api/regions {name} → derive key + insert; 409 dup
- DELETE /api/regions/<id> → cascade channel mappings; if
the deleted region was firmware default, best-effort clear on device
- POST /api/regions/<id>/default → flip DB flag + push CMD 63;
if firmware push fails, DB still flips and response includes a
non-blocking `warning` for a toast
Reduce wrapper min-width 100px → 80px and tighten the form-select
chevron padding (2rem → 1.5rem right, 0.6rem → 0.5rem left) so the
navbar fits in one row on ~360px-wide phones while keeping the
chevron and text both visible.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reduce wrapper min-width from 140px to 100px (with 140px max-width) so
the bell + channel selector + menu button all fit in one navbar row on
~412px-wide phones, without sacrificing readable channel names when
there's room to grow.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replace the native <select> channel picker (used on narrow screens) with
a custom searchable dropdown matching the DM contact picker UX: type to
filter, arrow/Enter keyboard nav, click-outside to close, per-channel
unread badges, muted styling. Wide-screen sidebar (lg+) is unchanged.
Also align the DM picker dropdown font with the wide-screen DM sidebar
(0.88rem / weight 400) — was inheriting larger/bolder from form-control.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
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>
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>
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>
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>
- Add Settings quick-access button to both main chat and DM views
- Make FAB container draggable via toggle button with position saved to localStorage
- Add button size and spacing sliders in Settings > Appearance tab
- Use CSS custom properties for dynamic FAB sizing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
Replace 3-way branching (configured_paths/has_path/else) with
4-scenario matrix based on (has_path × has_configured_paths):
- S1: No path, no configured paths → FLOOD only
- S2: Has path, no configured paths → DIRECT + optional FLOOD
- S3: No path, has configured paths → FLOOD first, then ŚD rotation
- S4: Has path, has configured paths → DIRECT on ŚK, ŚD rotation, optional FLOOD
Key changes:
- S3: FLOOD before configured paths (discover new routes)
- S4: exhaust retries on current ŚK before rotating ŚD
- S4: dedup ŚG/ŚK to skip redundant retries on same path
- Add _paths_match() helper for path deduplication
- Update tooltip text for settings clarity
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The contact list in Existing/Pending Contacts was not using all available
space due to calc(100vh - ...) and max-height rules overriding the
flexbox layout. Remove fixed height constraints from #pendingList and
#existingList in both contacts_base.html and style.css, letting the
flexbox chain (body > main > container > pageContent > list) fill the
remaining viewport space.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Create theme.css with CSS custom properties for light/dark themes
- Dark theme inspired by demo landing page (deep navy palette)
- Update style.css: replace ~145 hardcoded colors with CSS variables
- Extract inline styles from index.html, contacts.html, dm.html to style.css
- Add Appearance tab in Settings modal with theme selector
- Bootstrap 5.3 data-bs-theme integration for native dark mode
- Theme persisted in localStorage, applied before CSS loads (no FOUC)
- Console and System Log panels unchanged (already dark themed)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
'No Flood' was confusing next to the 'Reset to FLOOD' button.
'Keep path' better describes the behavior: don't auto-reset
the path to FLOOD after failed direct retries.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
The path creation form is now a separate modal (z-index 1070) that
opens above Contact Info with its own backdrop, making the UI layers
clearly distinguishable. Map picker modal bumped to z-index 1080 so
it stacks correctly above both Contact Info and Add Path modals.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Raises the z-index of the map modal (1070) and its backdrop (1060)
so the Contact Info modal behind is visually grayed out, making it
clear which modal is active.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Map modal no longer closes on Add - resets selection instead so user
can pick multiple repeaters in sequence. Cancel button renamed to Close.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds a map button (geo icon) next to the list picker in the path form.
Clicking it opens a modal with a Leaflet map showing repeater locations.
User clicks a repeater marker, then clicks Add to append its ID prefix
to the path hex. Includes Cached toggle to show all DB repeaters vs
only device-known ones. Respects current hash size setting (1B/2B/3B).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ID mode searches by first N hex chars of public key (2/4/6 chars
depending on selected hash size). Placeholder updates dynamically.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
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>
Reorganize menu from 2 sections (Network Commands, Configuration) into 4:
- Messages (top, no header) - daily actions
- Network - advert commands
- Tools - Map, Console
- System - Device Info, System Log, Backup, Settings placeholder
Increase navbar button/select touch targets (min 40px) for mobile usability.
Widen offcanvas menu from 280px to 300px.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Changed from target="_blank" link to fullscreen modal with iframe,
matching the pattern used by Console, DM, and Contacts modals.
Iframe loads on open and clears on close to manage WebSocket lifecycle.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- Replace fixed calc() heights with flexbox layout so the contact list
fills all remaining viewport space on any screen size
- Make body/main/container chain flex columns so the list can grow
- Reduce vertical spacing between contact name, public key, and
last advert rows for more compact cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove redundant 'Contact Types:' label
- Move type badges above search input
- Place search, Approve, and Ignore buttons in single responsive row
- Add tooltip (i) on Filters header with usage hint
- Add batch Ignore button to ignore all filtered pending contacts
- Remove duplicate filtered count badge from Approve button
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Shows an X button when a conversation is selected, allowing quick
clearing of the search field to find another contact.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>