Saltar al contenido

Dona para apoyar el futuro de MeshChatX

Registro de cambios

Notas de versión del repositorio MeshChatX, de más reciente a más antigua. El trabajo no publicado se queda arriba hasta que se publica.

v4.10.3

TBD [unreleased]

v4.10.2

2026-10-07 [released]

Fixed

  • Contacts: the empty-state box no longer sits flush against the search bar separator.

  • Themes: toggle switches now have a border outline in the off state so they stay visible on near-black themes.

  • Windows: shared-instance RPC replies are read through the connection dispatch path instead of os.read, fixing bad file descriptor errors that broke LXMF delivery, interface stats, and interface management on Windows builds when connected to an external rnsd.

  • Messaging: outbound sends that fail the path wait are now persisted as failed messages instead of being dropped, so they resend automatically when the peer announces and can be resent manually. The propagation-node fallback now defaults on for new configurations and is a safe no-op when no propagation node is set.

  • Maps: user-configured tile servers, Nominatim endpoints, and extra CSP sources are no longer stripped from the Content Security Policy in privacy mode. Only the shipped public defaults stay gated. Scheme-less and subdomain-template ({s}, {r}) tile URLs are normalized so OpenLayers and the CSP match real hosts.

  • NomadNet: page translation now works with the sandboxed page renderer. The shell asks the renderer frame for the page text over the frame channel instead of reading an empty shell container, so the translate button opens the translator with real page content.

  • NomadNet: the page shell no longer can show its own scrollbar next to the rendered frame's scrollbar. The container only scrolls for state banners now, which removes the second slider on platforms with always-on scrollbars.

  • NomadNet: serving nodes no longer re-hash the whole source image on every /media request. The hash is memoized by file stat. Failed conversions are marked and skipped for an hour instead of respawning the encoder on every request, which removes the sustained CPU spikes on image-heavy pages.

  • Desktop: every popout window (call, nomad tab, translator, paper message, and the rest) now runs in its own renderer process where the API allows it, so a crash there cannot take the whole app down. A crashed child window is destroyed instead of lingering as a dead frame. The main window's crash recovery dialog is unchanged.

  • NomadNet: rendered pages that use the full-bleed shell (.html, .md, .txt) no longer overflow the viewer by the header and toolbar height. The content container carried a stray min-h-full that beat its min-h-0, so the rendered frame ran ~54px past the bottom of the pane and its scrollbar hung below the page boundary, reading as the extra-slider artifact.

  • Network visualiser: leaving the page during engine setup no longer orphans a live graph instance per visit. The init chain now checks for unmount after every await and destroys anything created in flight.

  • Network visualiser: the loading overlay now has a cancel button. Stopping aborts in-flight fetches, halts physics and batch work, and leaves a fresh load one click away on the refresh button.

  • Telemetry: tracked peers that never answer are no longer polled every interval forever. Each unanswered request doubles the wait up to a daily cap, and after a bounded number of misses the peer is skipped until a telemetry response re-arms it or tracking is re-toggled.

  • Messaging: automatic resends no longer retry feature-only deliveries (commands, reactions, telemetry) to peers that never sent one back, so Sideband and other LXMF clients are not spammed with payload types they cannot parse.

  • Contacts: shared address output now uses the bare 32-hex hash when no public key is known, since not every LXMF client parses an lxmf:// URI. lxma:// output is unchanged when a public key is available.

  • Android: Bluetooth RNode interfaces (BLE and classic) added or imported through the UI now write the Android-native Reticulum keys at save time, so the live attach takes the Bluetooth path instead of treating a bt:// or ble:// port as a dead USB serial device. Leftover Bluetooth keys are also stripped when an interface is switched back to serial or TCP, since the Android implementation lets stale ble_* settings override the port.

  • Android: the RNode form now asks for Bluetooth permission when the BLE or Bluetooth transport is enabled, instead of relying on the bundled startup prompt that can be dismissed and never re-shown.

  • Android (build): the bundled Reticulum wheel now guards paired-device enumeration against devices with no name and denied Bluetooth permissions, so one bad bonding record or a missing grant no longer kills RNode Bluetooth bring-up.

  • Relay chat: the hide join/part preference now applies. A setup-time read shadowed the setting's lookup, so presence lines kept rendering and the toggle wrote the old value back to storage.

  • Tools: the traffic page refresh button no longer flashes on every background poll. The spinner now only runs for manual refreshes.

  • Tools: the traffic page RRC hint now counts connected hubs by the real status constant instead of a string comparison that always read zero.

Added

  • Interfaces: bulk config imports now attach imported interfaces live instead of requiring a restart. The restart banner only appears when live attach could not bring one up.
  • Health: the monitor now tracks file descriptor counts, warning when usage nears the limit and when the count climbs steadily across the check window, catching slow descriptor leaks early.
  • Diagnostics: new Traffic tool shows live upload and download rates per interface and per component, per-peer byte attribution, announce and propagated splits, a rolling activity chart, and plain-language hints explaining what is driving the wire load.
  • Interfaces: I2P community presets can now be quick-added like other presets. They run through the normal add-interface checks, so transport mode and the single-I2P rule still apply.
  • Interfaces: the community presets list now has a filter dropdown for TCP, Backbone, I2P, Yggdrasil, and other types.
  • Messages: the message translate bar now has an auto-translate toggle per conversation. The chosen language pair is remembered per contact, and when auto-translate is on, new incoming messages are translated on arrival and the newest loaded messages are translated when the conversation opens.
  • Maps: the tile server URL now accepts TileJSON endpoints (URLs ending in .json). Tile templates, attribution, zoom range, bounds, relative tile URLs, and scheme:tms are honored, and a bad document falls back to the next provider like a failed tile server.
  • Translator: the pack catalog now shows both directions of a language pair as a single bidirectional entry, and downloading it installs every direction present in the catalog. A search field filters the catalog by language or pair code.
  • Updated Python deps: rns 1.5.7, lxst 0.5.4 floor.
  • Messages: inbound messages that arrive while a conversation is open now enter with a short fade-and-rise instead of appearing instantly. History, outbound messages, and reduced-motion settings are unaffected.
  • Tools: shared tool page headers are slimmer so tool content gets more vertical space.

v4.10.1

2026-10-04 [released]

Fixed

  • Messages: the mobile attachment popup lived outside the paperclip button's click-outside boundary, so taps inside the menu counted as outside clicks. Touch devices closed the menu before the tap could fire any item, and on desktop the add image accordion collapsed the menu instead of expanding. The popup now renders inside the bound element.

Added

  • Relay chat: the composer is now a multiline field that grows with content up to 160px. Enter sends, Shift+Enter inserts a newline, and Escape, ArrowUp message recall, and Tab nick completion behave as before.
  • Interfaces: discovered interface settings gained Max Stored Interfaces plus Mark Unknown After, Mark Stale After, and Delete After day fields, so retention of discovered interfaces is configurable instead of fixed at Reticulum defaults.
  • Interfaces: the discovery list can be exported as a zip of the Reticulum interface store and imported again on another install, supporting offline backup and sharing of discovered interfaces. Import validates entries and keeps old archives in the retention window instead of letting them expire immediately.
  • Interfaces: the discovered list is now paginated at 48 per page with selectable page sizes, so large discovery stores stay responsive.

v4.10.0

2026-10-03 [released]

Added

  • Interfaces: per-interface runtime attach, detach, and reload using the RNS 1.5.5 interface management API. New POST endpoints /reticulum/interfaces/attach, /detach, and /reload, plus a Restart Interface action on attached interfaces in the Interfaces page.
  • Interfaces: interface edits, enables, disables, deletions, and bitrate changes now apply live against the running Reticulum stack instead of always requiring a full RNS restart. The restart banner only appears when a live apply is refused.
  • Interfaces: discovery settings expose the RNS 1.5.5 autoconnect_unverified_implementations toggle, and discovered interface cards show the announcing implementation name and version.
  • CI: a backend test leg now runs the full suite on Python 3.15 (beta) in advisory mode.
  • Sidebar: configurable filter chips on the conversations and announces lists. The funnel menu exposes every filter, a pencil button opens an editor modal to show, hide, and reorder chips, chips can also be dragged directly in the bar, and the layout persists per browser. New filters include Favourites on conversations and Direct, Nearby, Pinned, and Blocked on announces.
  • Fonts: Inter, JetBrains Mono, IBM Plex Sans, and Space Grotesk are bundled and selectable in appearance settings with live preview.
  • Docs: relative image links in in-app documentation now render, and image and clip assets ship with the docs bundle. A declarative guide capture system (tests/ui/guide) produces annotated WebP screenshots and animated clips for documentation.
  • Deployment: Helm chart, Ansible role and playbook, Proxmox template build, a devcontainer, and a standalone Firecracker microVM start.sh.
  • Tooling: UI metrics tracking (test:ui:metrics) records perf, heap, and Lighthouse results per git revision and fails on regressions against a local baseline.
  • Screenshots: the UI screenshot catalog now captures a desktop/mobile x light/dark matrix as WebP at 2x device scale, and guide screenshots support reusable annotation overlays (boxes, spotlights, blurs, arrows, badges, captions) plus a CDP/ffmpeg recorder for animated clips.

Changed

  • Sync messages button is pinned to the top nav bar by default with a stacked mail and sync-state icon

  • Updated Python deps: rns 1.5.6, cryptography 50.0.2, wasmtime 49, miniaudio 1.71, numpy 2.5.3 on Python 3.12+, setuptools 84

  • Backend threads are named for attribution in py-spy and top -H

  • Python 3.15 readiness: event-loop policy calls replaced by get_running_loop(), and the atheris dev dependency is gated to interpreters that have wheels (up to 3.14 on Linux x86_64).

  • Config: update_config now applies a generic pass over every registered config field, so newly added settings can no longer be silently dropped when no explicit handler exists. get_config_dict serializes registered fields generically behind a denylist instead of a hardcoded key list.

  • UI: hardcoded color buttons migrated to semantic action tokens (primary, success, danger, warning) so they follow theme presets, and sidebar chrome uses the raised-surface token so panels separate from the canvas on flat themes.

  • Search inputs use a compact pill variant in sidebars and on settings, call, docs, and debug pages.

Fixed

  • Config: ui_font_family, ui_custom_font_name, nomad_* and other newer settings now round-trip through GET /config instead of silently reverting, and multi-megabyte custom font data serves on demand via /api/v1/app/custom-font.
  • Icons: LxmfUserIcon badges render their glyph again. Percentage padding was measured against the card width instead of the icon and collapsed the SVG to zero size, leaving empty colored circles on bot and contact rows.
  • Docs: ![alt](src) markdown no longer renders as a stray bang plus a link, unsafe image sources are dropped, and relative image paths resolve under /meshchatx-docs/ in-app.
  • UI: the Banished page screenshot path no longer exposes the skeleton grid. Loaded-state markers gate capture.

Security

  • Docs: _safe_href strips ASCII whitespace and control characters before scheme checks so payloads like java\tscript: can no longer reach the page as javascript: links or images, and URLs containing & are no longer double entity-encoded in rendered links.

v4.9.3

2026-09-30 [released]

Changed

  • Reticulum Network Stack updated to 1.5.5, which adds runtime interface attach/detach/reload management, publishes implementation name and version in discovery announcements, and fixes IFAC publishing edge cases.
  • Tutorial: the Back/Skip/Next row is fused back into the bottom of the step content in both modal and page modes instead of a separate pinned footer bar.
  • Visualiser: the radial view is replaced by a cluster view that groups peers into lobes around the interface that announced them, so the layout shows where traffic actually arrives from. Stored radial preferences upgrade to cluster.

Fixed

  • Visualiser: WebGL live layout settles again. Tick damping and the per-step speed cap were tuned so nodes spread out and come to rest instead of ringing at equilibrium.
  • Map: dragging the settings window stays smooth now (backdrop blur is disabled for the drag) and the coordinates readout no longer re-renders the whole page on every pointer event.
  • NomadNet: image size hints now follow upstream semantics. A bare w=/h= number is a character cell count (columns and rows) instead of pixels, w=n renders at the image's native size, and percent values stay relative to the page as upstream intends. Matches markqvist's NomadNet ImageWidget behavior.
  • NomadNet: the w=/h= spec now sizes the loaded image itself instead of only the invisible placeholder box, so w=50 renders 50 columns wide as in upstream NomadNet.
  • NomadNet: image alignment is applied. a=c centers and a=r right-aligns whole-line images, and whole-line images default to centered like the upstream ImageWidget.
  • NomadNet: a loaded image drops the placeholder border and padding and shows the bare image, and the reserved placeholder space is released so pages no longer keep dead space at the bottom.
  • NomadNet: the page shell no longer keeps its own scrollbar next to the rendered frame's scrollbar. The container was always scrollable by its own padding and showed a second slider on platforms with always-on scrollbars.
  • Issue triage: the welcome comment no longer asks reporters for logs or a screenshot when the issue body already contains an attachment or a filled-in logs section.

Security

  • Plugin install and preview paths now jail wasm backend entries under the plugin tree, bound extracted zip content by real bytes and file count instead of declared size, reject symlink members, and keep fallback wasm stubs out of the integrity-checked tree. A failed enable() rolls back to a clean disabled state.
  • RRC hub sessions that never send HELLO are reaped on a timeout so a peer cannot pin session slots, stale link-close callbacks no longer clear a live session's state, and a room-less forced-leave error preserves local room history instead of wiping it.
  • Reticulum config snapshots returned by the API redact secrets before serving, matching the redaction used by the live config view.
  • File sync incoming-file tracking expires and clears on link close so a dead link cannot pin a path against re-request forever.
  • Docs archive import streams members with a real-byte cap and skips symlink entries before anything reaches disk.

Fixed

  • Forwarding: replies sent to a forwarding alias now route back to the original sender. The mapping was keyed by the alias identity hash while replies arrive addressed to the alias destination hash, so the reply path never matched.
  • Messages: auto-resend no longer reclaims a malformed message forever. The attempt budget is spent before attachment parsing, so a bad base64 field cannot loop a resend.
  • Messages: local retention purge keys on when the row was written locally instead of the sender-controlled LXMF timestamp, so a peer can no longer age a message out instantly or keep it forever.
  • Telephone: an outgoing call that stalls mid-dial now times out and hangs up instead of leaving the phone stuck in Calling until teardown, and an incoming ring during a pending outbound call is surfaced instead of silently dropped.
  • Voicemail: the auto-answer timer is bound to the link instance it was scheduled for, the blocklist fails closed when lookup errors, and greeting recording writes to a temp file that only replaces the live greeting on success.
  • Web audio bridge reattaches after an LXST pipeline reconfiguration (answer, profile switch, loudspeaker toggle) instead of leaving mic and speaker paths silently disconnected, and per-client sends coalesce so one slow socket no longer stalls the whole 60fps feed.
  • Geo WASM: MGRS formatting for single-digit zones is correct, OLC shorten/recover no longer panics on edge-case codes, and NaN or non-finite coordinates cannot crash the runtime.
  • Visualiser WASM: the WebGL pick radius holds a screen-space minimum so clicks still land at high zoom, scene handlers validate argument types instead of calling Float on non-numbers, coincident layout nodes get a deterministic separation nudge, and duplicate edges are deduplicated.
  • Relay chat: sent messages now show a gray "sending" hint until the hub relays the message back. If the echo never arrives, or the link drops first, the line switches to "not delivered to hub" with a retry action that resends under a new envelope id.
  • Relay chat: message and sidebar context menus show icons, and the message menu gains "Message user" (opens a direct conversation through the peer's LXMF address) and "Copy user hash".
  • Context menus across the app use a pointer cursor on interactive items.
  • NomadNet: a live announce no longer re-sorts its row to the top of the announces list, so clicking a node no longer makes the sidebar reshuffle under the cursor.
  • Relay chat: a "new messages" divider marks where unread history starts when a room opens, pressing ArrowUp on an empty composer recalls your last sent message, a byte counter appears near the hub's message limit, failed sends retry once automatically on rejoin, and hubs show their measured link latency next to the status line.
  • Context menus support Escape to close and arrow-key navigation between items.
  • RRC: hub reconnect after a restart no longer stalls on a dead link. The connect worker now waits for both the identity and a live path before creating the link, since a link request sent with no known path is dropped silently and only the establishment timeout would recover it. Path requests retry inside the connect window instead of a single shot.
  • RRC: a link stuck PENDING on a dead path no longer parks the hub in CONNECTING for minutes. An establishment watchdog tears it down after a bounded window and lets reconnect backoff retry instead.
  • Live e2e now covers backend restarts (warm storage reconnect within an SLA), link flaps, and network partitions through a control-file chaos proxy between the peer and the backend.
  • Nightly QA workflow runs a soak harness that samples threads, fds, RSS, path table and interface byte rates under a steady mesh workload and fails on any rising leak slope, plus opt-in live backend tests and mutation runs on the highest-risk modules.
  • Backend tests gained a Hypothesis state machine for the RRC hub connect lifecycle and property-based codec/parser tests for the wire format.
  • Shared-instance RNS calls can no longer wedge the web server. The shared RPC recv had no deadline, so a stalled rnsd blocked whatever thread asked, including the aiohttp loop serving /interface-stats, /path-table, path probe, blocklist and telephone endpoints. Route handlers now run those calls in a thread when a shared instance is in use, and every shared-instance RPC recv carries a 10s deadline.
  • RNode Flasher: the page 500s in dev mode because Vite refuses dynamic imports of public assets, and the fallback code imported vendor modules without any SRI check. Vendor bundles now load through a hash-verified blob URL path shared by all flasher scripts, and the dead web-serial polyfill reference (the file was never shipped) is removed.
  • RRC: reconnect backoff can no longer overflow after days of retries (the exponent is capped), a dead link reporting close twice no longer double-counts the backoff, and a connect that crosses a manual disconnect no longer installs its link anyway. A connect attempt epoch now drops links from superseded workers.
  • RRC: hub error text can no longer delete room history files. Only explicit remove/clear actions erase the archive. History files now compact to the retention window instead of growing without bound.
  • RRC: pending-delivery echoes age out on message reads, not only on inbound packets. Active-room tracking normalizes room names so mixed-case paths cannot split unread state or history.
  • Removed dead code: the bot_propagation re-export shim, the never-wired lifecycle deferred_network module, two unused frontend API wrappers, the unused FormSubLabel component, a dead RRC ping path (send_ping was never called, so pong tracking and its bookkeeping were inert), the trusted-publisher OO store cluster, unused config fields, and roughly two dozen unreachable DAO, plugin, sandbox, and diagnostics helpers.
  • Map coordinate format now persists: the PATCH handler and config export were wired (the setting was previously silently dropped).
  • API hardening from OpenAPI contract fuzzing: malformed JSON bodies (arrays or nulls in string fields) no longer 500 on the RRC join-room, hub-create, room key/topic, archive, and LXMF send endpoints. They now fail 400 with a named reason. The lxmf send error response also gained the standard error/code/message fields.
  • RRC room names containing lone-surrogate unicode are rejected at normalize time instead of exploding mid-encode inside CBOR.
  • RNPath tool: a remote query no longer fires without a management identity (it returned a guaranteed 400 on every poll and spammed the console), and the search box is clamped so oversized input cannot overflow the HTTP request line.
  • Testing: an OpenAPI spec now ships under docs/openapi.yaml covering 79 API operations, exercised live by Schemathesis against a loopback backend in CI, plus an atheris coverage-guided fuzz suite over the RRC envelope codec and parsers, golden CBOR wire captures pinned against decode/round-trip drift, deterministic replay of recorded RRC sessions, a SIGKILL crash-consistency suite that kills the backend mid-write and asserts storage integrity on restart, a shared-instance e2e that runs the backend against a real rnsd, and scripted exploratory personas (impatient, adversarial input, data-heavy seeding) over all routes.
  • Testing: axe-core accessibility audit runs across all routes with a baseline ratchet so new serious or critical violations fail while documented debt is tracked to zero. Visual regression screenshots for key routes can be generated per environment through a dedicated Playwright config. A diff-cover gate requires 80% coverage on changed backend lines in pull requests.
  • RRC sessions can now record every routed envelope to JSONL for deterministic replay in tests.
  • Health monitor now samples Linux IO stall pressure (/proc/pressure/io avg10). Sustained high IO pressure warns in the log and over the websocket, throttles the background ratchet persist worker until pressure clears, and reports recovery.
  • Shared-instance RPC: the deadline now also covers the authkey handshake (a stalled rnsd rpc_loop could park the connect path forever) and the whole frame read, not just the first byte. Every remaining route handler that still called a blocking shared-instance RPC on the event loop now offloads it, including rnstatus/rnpath/rnprobe/trace tools, path probe, blocklist writes, telephone dialing, and the reticulum hot-reload shutdown sequence. RPC timeouts surface as 503 instead of a generic 500, and a deadlined packet-metric lookup can no longer skip the message state update it precedes.
  • Command palette: typing with peers or contacts lacking names no longer crashes filtering, and pressing arrows on a query with no matches crashed the render path because the highlight move indexed an empty result set, and null entries in peer or contact lists could throw while building results.
  • IO pressure handling hardens: the gate releases when PSI disappears mid-run or the monitor stops, ratchet writes held during pressure flush synchronously at shutdown, and a dropped re-queue signal can no longer orphan pending ratchet entries.
  • Live e2e covers RRC room history across a backend restart and before_seq pagination, plus an exploratory interaction crawl (buttons, menus, command palette) on every route and an adb-based mobile UI crawl when a device is attached.
  • E2E hardening: the live peer no longer self-terminates at 10 minutes (soak runs were measuring a dead mesh), storm destinations now announce the real rrc.hub aspect, the soak slope oracle uses the correct slope standard error and fails on too few samples or peer death, the stack script frees ports with escalation and kills Vite on cleanup, and fault-injection specs restore chaos modes in finally blocks so one failure cannot poison the suite.
  • RNode flasher: the dead load-polyfill action and a standalone-page import of a file that was never shipped are removed, SRI verification failures log loudly instead of being swallowed, vendor loading deduplicates concurrent runs, and insecure contexts surface a clear error instead of silent crypto.subtle failures.
  • Live e2e now covers an adversarial announce storm (40 synthetic hub announces), a second live peer for cross-peer isolation checks, and an exploratory route crawl that fails on console errors or blank pages. The e2e stack also frees its ports from stale leftovers before starting.
  • Live e2e now also asserts interface byte counters stay bounded during idle announce windows and real chat flows, catching unbounded announce or path-request churn before it becomes a traffic bill.
  • Telephony: dialing by identity hash no longer resolves a wrong identity. The announce fallback rebuilt the peer identity from its public key through a private-key API, producing a constant wrong hash, so calls by identity hash could never find a path.
  • Backend: fixed a self-deadlock in AsyncUtils.run_async where a done-callback could fire inline while the futures lock was held, freezing the main event loop and wedging the server under sustained announce traffic. A live Playwright mesh suite now covers real LXMF delivery and relay-hub connect, join, and echo over a local TCP link.
  • LXMF: a transient blocklist or contact lookup error can no longer bounce inbound long messages with a false REJECTED state. The pre-transfer policy only rejects on a positive verdict, matching the 4.8.8 unknown-peer rule.
  • Health monitor now probes the main event loop and dumps all thread stacks to the log if the loop stops answering, and SIGUSR1 dumps stacks on demand, so silent server wedges self-diagnose.
  • Relay chat: auto-connect now waits for the first interface to come online before sending path requests, so hubs connect quickly after a restart instead of burning a full path-request window.
  • Contacts context menu dismisses consistently on Escape, and the announce sidebar no longer corrupts first-seen timestamps or custom names from slim live announce payloads. Announce list pagination no longer skips server rows when live nodes arrive mid-browse.
  • Sandbox: seccomp denylist now prefers the seccompy backend (pure Python, no libseccomp needed) and falls back to libseccomp via ctypes when it is absent.
  • Crawler: re-queued tasks keep their retry budget instead of resetting to zero, in-flight tasks count toward the per-node page cap, a successful crawl clears the previous skip reason, and a node's own page destinations are never queued for self-crawl.
  • Page nodes write pages and hosted files atomically and open served files with O_NOFOLLOW so a swapped symlink cannot redirect a served path after the jail check.
  • Media conversion holds a process-wide lock around the temporary-directory and environment window so concurrent conversions cannot clobber each other's temp settings.

v4.9.2

2026-09-28 [released]

Added

  • Network visualiser gains a radial view mode that pins nodes on deterministic hop rings around the local node, plus screen-space label decluttering so dense zoomed views stay readable.
  • Settings self-test gains a CBOR roundtrip check that exercises the RRC codec's encode, decode, and stream replay paths.
  • CI performance suite measures cold-load FCP, LCP, SPA route transition latency, API round-trip, and post-mount heap per page with per-page budgets, plus a heap-growth spec that fails when a page leaks listeners, timers, or nodes across mount/unmount cycles.
  • NomadNet: local page nodes rescan their pages and files directories on a timer and on each incoming link, so content dropped in externally is served without a restart and removed content stops answering. Matches upstream NomadNet's page_refresh_interval behavior.
  • Backend fault-injection suite covers malformed RRC wire traffic, reconnect timer storms, hub store corruption, page-node rescan failures, crawler garbage inputs, link-cache teardown errors, and callback exceptions.

Security

  • HTTP-set command_plugins_path is jailed under storage so remote callers cannot point the plugin loader at arbitrary Python files.
  • Reticulum rpc_key and interface secrets are served only to loopback or authenticated callers, and a redacted GET round-trips through PUT via a sentinel so non-privileged edits cannot erase real values.
  • auth_session_epoch is stamped into session cookies and checked on HTTP middleware and the WebSocket upgrade, so password, auth-toggle, and setup changes revoke outstanding sessions. Session secret files are restricted to 0600.
  • Translation-pack extraction is bounded by per-member, total, and file-count caps with streamed copies.
  • /api responses send Cache-Control: no-store, the static auth bypass is scoped to actual asset suffixes, and remote markup rendered into the main document loses id/name attributes so archived pages cannot clobber window globals.

Fixed

  • Messages: opening a raw outbound message no longer 404s once it leaves router memory (delivered or post-restart). The paper URI endpoint rebuilds the signed lxm:// URI from the stored row, preserves the original timestamp so the message hash and ingest deduping hold, verifies the repacked hash against the stored row, and skips the request entirely for inbound messages, which can never be signed by the local sender.
  • Messages: resend preserves title, reply quotes, reactions, app extensions, and correctly decomposed telemetry, and other sessions are told when the old failed row is deleted.
  • Messages: cancel validates the hash, reaches forwarding-alias routers, and reconciles rows stuck in generating/outbound/sending to cancelled after a restart instead of returning ok while nothing changed.
  • Messages: startup recovery fails sent+direct rows that could never advance after restart, and a same-hash inbound delivery can no longer demote an outbound row to incoming or repoint its peer.
  • Messages: hash lookups normalize case across message, attachment, spam, and announce paths.
  • Messages: queued sends snapshot peer and composer state so a cleared composer or peer switch cannot send into the wrong conversation, pending placeholders dedupe correctly, inbound duplicates are dropped, and audio decode is serialized with contexts closed on unmount.
  • Messages: paper URI generation and URI ingest over WebSocket were fire-and-forget. A failed send or dropped reply left buttons disabled or a spinner forever. Sends now check the result, toast on failure, and bound the wait with a timeout.
  • Android: bridge-backed buttons (WiFi Aware grant, nearby permission, native RNode flasher) were silently dead because the injected bridge sat inside a Vue reactive proxy. The bridge now marks itself non-reactive and binds methods to the raw instance.
  • Android: release APKs kept stripping org.meshchatx.locallink.* because the classes are only reached via jclass() from Python, so WiFi Aware stayed disabled in minified builds. R8 keeps them and a dex check gates the build.
  • Android: the native RNode flasher no longer shows a duplicate title hidden behind the status bar.
  • Reticulum: a second AutoInterface bind failure left a zombie singleton that killed every in-process restart. Recovery now releases interface sockets and resets singleton state before rebinding, a colliding interface is rejected at add time, and a guidance notice reports auto-disabled interfaces.
  • NomadNet: image loading now parses the NomadNet 1.4 whole-line image syntax (paren links with w/h/a/s/k/profile fields and percent widths) and sends the key field the /media protocol requires. Verified live against rns.recipes.
  • NomadNet: page and file download events are correlated by request id so stale transfers cannot feed replacement entries, and archive navigation performs full teardown.
  • NomadNet: requesting an unknown page or file from a local node opened a doomed link to the node itself. Local serving now resolves only canonical /page, /file, and /media paths, and the download handlers fail fast instead, reporting archive availability for pages.
  • Relay chat: prefs and drafts are scoped per identity, so hide-join/part and ignore state apply once the identity hash resolves, hub switches reset room state, the composer clears before send to end double-send and lost-text races, and debounced config writes survive identity switches.
  • Relay chat: hub auto-reconnect retried on a 60 s backoff cap forever, so a large set of unreachable hubs produced a constant stream of link and path requests. Backoff now doubles per failure up to a 15 minute ceiling with jitter, startup connects are staggered, path requests dedupe per hub, and a fresh hub announce resets the backoff so a recovering hub reconnects quickly. Manual connects reset the counter for an immediate retry.
  • Service worker: subframe requests are excluded from the shell navigation strategy and only shell documents or extensionless SPA routes use the fallback slot, fixing the stale-cache stuck Loading page. Updates reload exactly once, WebTransport falls back to WebSocket on session death, and a version-mismatched backend triggers a reload after reconnect.
  • Backend: websocket_broadcast from foreign loops forwards onto the owning client's loop, the self-test endpoint runs off the event loop so its own probes cannot deadlock, and the Windows AppContainer probe wait is bounded.
  • Backend: deleting an identity tears down its live context first, telemetry per destination is capped, and in-flight propagation-node tasks are cancelled on shutdown.
  • Electron: protocol links are stripped from backend argv and delivered to the renderer after load, certificate-error bypass is scoped to the local backend, DevTools shortcuts and hardware permissions are gated, and the Electron runtime version invalidates stale caches on upgrade.
  • UI: context menus dismiss on click-off and re-right-click again, popup carets stay off rounded corners, and the map drawing toolbar stays pinned at the top on xl screens.
  • UI: stuck-state follow-through in MiniChat (send failures now toast and fresh timestamps render), ContactsPage lxma import, NomadNet archives spinner, and the archived-pages flush result toast.
  • CI: alpine APKs are built without fpm and verified by actually installing them in an apk-tools container.
  • Locale files gained the missing aware/nearby permission strings in all 15 locales.
  • Backend: a wedged SQLite pool could leave every API call answering 503 until a manual restart. When WAL or SHM files get unlinked under open connections, every statement fails with a disk I/O error and per-connection retries never recover. The provider now spots failures that persist on fresh connections, resets the whole connection pool at once, and if that is not enough restarts the process. Restart attempts are bounded, so a permanently broken store keeps serving retryable 503s instead of crash-looping.
  • Relay chat: a malformed hub or client envelope could raise inside a packet handler and break session processing. Client and server dispatch now contain handler errors per packet, and the announce-reset and path-request rate limits use never-fired sentinels so the first retry is not skipped on hosts with low uptime.
  • HTTP: the safe file response raises a real error instead of asserting when aiohttp hands back no writer.
  • CI: Docker builds install pnpm 12 through npm since the pinned node image's corepack cannot shim its native binary, and the dev container does the same.
  • CI: the shared Node setup keys its corepack cache by runner architecture, so an arm64 pnpm binary can no longer be restored onto the x64 macOS build runner.

Changed

  • The Landlock sandbox now uses landlockpy instead of the custom ctypes plumbing. The enforced filesystem policy is unchanged and Android is unaffected.
  • Relay chat's CBOR codec moved from cbor2 to cborx, a zero-dependency RFC 8949 implementation with an optional compiled fast path. Canonical wire encoding is unchanged, decode rejects trailing bytes instead of ignoring them, and Android packages cborx through a Chaquopy recipe instead of the cbor2 wheel.
  • Visualiser layout spacing widened to match node size, WASM and JS paths gained LOD color, NaN guard, edge-filtering, and dead-scene fallback parity, and visualiser.wasm was rebuilt.
  • UI lighthouse, performance, and heap suites run against the production bundle instead of the Vite dev server, and service workers are unregistered before audits so scores cannot be nulled by a controlled navigation.
  • Backend: the eight per-identity periodic loops (auto-announce, propagation sync, crawler, auto-backup, telemetry, retention, flood cooldown, auto propagation selection) now share one background event loop instead of one thread each. Backups, retention sweeps, and announce table reads run in worker threads so they cannot stall the shared loop. This cuts several threads and thread-local database connections per identity.
  • Backend: debug log writes to SQLite are batched into one transaction per flush, the retention sweep runs at most every ten minutes instead of every five seconds, and MESHCHAT_LOG_DB=0 disables database logging entirely. On SD-card installs this removes a steady stream of small writes.
  • Backend: after identity setup finishes, startup objects are frozen out of the cyclic garbage collector's scan set, and the periodic cleanup calls malloc_trim so freed memory returns to the OS instead of sitting in allocator arenas.
  • Docker images and the Raspberry Pi installer set MALLOC_ARENA_MAX=2 and OPENBLAS_NUM_THREADS=1, and the Pi guide documents both plus MESHCHAT_LOG_DB=0 for low-memory and SD-card deployments.
  • Backend: the LXST telephony stack, numpy, and its audio backends no longer load at startup. The web audio bridge sits behind a lazy proxy that constructs it on first call, and telephone and voicemail resolve their LXST symbols on demand. Text-only installs keep tens of MB of memory and the OpenBLAS worker threads out of the process. If the audio stack fails to initialize or a bridge call keeps raising, the proxy disables audio for the session instead of letting errors reach the messaging paths.

v4.9.1

2026-09-21 [released]

Added

  • Android Nearby: hotspot, WiFi Direct, WiFi Aware, and NFC tap link nearby phones with no router. Peers join through Auto Interface or the bundled AwareInterface.
  • Android satellite readiness: the app declares constrained-data optimization so the OS can route mesh traffic on a satellite attach. Nearby shows satellite state and a low-bandwidth hint.
  • OIDC sign-on for the web UI (Authentik, Keycloak, Pocket ID, and compatible providers) via Authorization Code + PKCE. Set it under Settings, Authentication, or with MESHCHAT_OIDC_* variables. OIDC can require login without a local password.
  • UI heap-profile spec fails CI if a catalog page leaves listeners, timers, or heap above baseline after unmount.
  • Android GPS location source shares your position through the native LocationManager bridge, so location works even when the WebView geolocation API fails. Pick it under Settings, Location.
  • Top navigation bar is customizable. Pin, reorder, or remove section buttons under Settings, Appearance, and NomadNet joins the default set.
  • Reticulum config editor keeps versioned snapshots on every save and can restore an older config.
  • Contact shares carry the display name and LXMF icon (name plus colors), so the card renders like a real contact.
  • Toasts can carry an action button. Errors like a missing propagation node or disabled location now offer a Configure shortcut that jumps to the right settings section.
  • Screenshot tooling: task screenshots captures desktop and mobile views of every page with seeded demo data.

Security

  • Messages: user text could forge markdown placeholders and inject anchors or tokens into linkified URLs. Placeholders now use a per-render nonce, and linkified URLs stop at bracket tokens.
  • Map exchange: the KML sanitizer now matches namespaced, attribute, and CDATA href forms, and treats malformed remote URLs as unsafe.
  • Docs: uploaded Reticulum documentation HTML no longer shares the app origin. The docs iframe and /reticulum-docs/ responses are CSP-sandboxed.
  • Authentication: the public setup endpoint no longer sets a local password on an OIDC-only deployment.

Fixed

  • Windows desktop: the AppContainer child no longer dies at loader init (0xC0000142) when the LPAC token lacks window station access. A failed sandbox still falls back to an unsandboxed backend in auto mode.
  • Desktop: a crashed renderer now offers relaunch, relaunch without GPU acceleration, or quit instead of leaving a dead window. Crash minidumps are kept locally under the Crashpad folder, and GPU process deaths are logged.
  • Desktop: a crash-looping GPU process (which takes the window's renderer down with it) now triggers an automatic fallback, the app disables hardware acceleration once via the disable-gpu marker and relaunches.
  • Packaged builds: .gitkeep placeholders are no longer hashed into backend-manifest.json, so a dropped marker cannot block onboarding.
  • Relay chat: hosted hub announce intervals show hours and days, and accept values like "6h" or "1d".
  • Messages: reopening a conversation no longer shows a stale first page that misses messages sent while the pane was closed.
  • NomadNet: the path-finder menu hides on small screens, and the mobile sidebar gains a URL entry row.
  • Relay chat: Host, Bots, and Search tabs collapse into the overflow menu on narrow screens.
  • Android: a failed Chaquopy start or missing WebView no longer closes the app with no message. Startup and renderer crashes show a readable error.
  • NomadNet: closing a node opened from the announce list returns to that list instead of a new Favourites tab. The sidebar reopens on the last used tab.
  • NomadNet: leaving the browser and coming back no longer reloads the page. Scroll position and field contents survive.
  • Relay chat: backing out of a room opened from Search or Discovery returns to that view.
  • Reverse proxy: trusted proxies can pass X-Forwarded-Proto and X-Forwarded-Port, so TLS-terminating ingress no longer gets 403 on /ws upgrades.
  • Docker: the entrypoint prepends meshchatx when argv starts with a flag, so docker run image --flag and Kubernetes args no longer crash su-exec.
  • Landlock: parent directories of --ssl-cert and --ssl-key are granted as read roots, so TLS files outside the usual roots load.
  • Logging: the Python logger also writes to stdout, so request-handler tracebacks show up in docker logs and kubectl logs.
  • WebSocket: the cost limiter covers binary and invalid JSON frames, and idle timeout drops dead or flooding clients. The telephone audio socket uses the same gating.
  • Authentication: turning auth_enabled off no longer wipes the local password hash while OIDC still enforces login.
  • Settings: an empty OIDC client secret no longer overwrites the stored secret, and an invalid issuer URL is rejected before any keys persist.
  • Backend: concurrent reload_reticulum calls are serialized, and recovery clears a dead instance instead of reusing it.
  • Backend: transport-thread RNS callbacks wake asyncio safely, dropped coroutines are closed, and the memory log handler drops its database handle at identity teardown.
  • Voicemail: greeting text is passed to espeak on stdin, so a leading dash is not treated as a flag.
  • Startup: leftover ratchet files with non-hex names are swept before RNS init, so they do not retrigger the corrupted-ratchet path on every boot.
  • Map: telemetry, announce, and interface coordinates are range-checked before projection, and marker updates stop after the map is torn down.
  • Messages: conversation and relay timelines cap retained items, unmount no longer cancels accepted sends, and MiniChat no longer races local appends against its fetch.
  • Frontend lifecycle: live transport cleans up sockets on destroy and mid-connect mode changes, failed WASM injections can be retried, and a stale NomadNet started event cannot resurrect a cancelled download.
  • Boot: the splash waits for the initial route chunk (4 second cap) before fading, so the shell does not jump onto an empty view.
  • NomadNet: page images no longer stick on Loading when a burst of file downloads was rate-limited. Errors now fail the matching download.
  • Themes: outbound, failed, and waiting message bubbles now take their colors from the active theme instead of a fixed blue. Settings pickers show the theme-resolved color until you override it.
  • Settings: the Reticulum Stack panel no longer leaks into Simple mode or unrelated tabs.
  • Android: location sharing no longer fails when browser geolocation is unavailable. Native GPS is used when selected, with a clear prompt when permission is missing.
  • Messages: shared contacts no longer show the raw payload above the card, and the mobile composer moves attachments into the input with a tidy picker.
  • Relay chat: search, cleanup, options, and leave collapse into a menu on mobile so the members button still fits.
  • Interface cards no longer leave a gap above the title on mobile, and the map info chip no longer touches the zoom controls.
  • Map: switching between online and offline no longer flashes a blank map, offline uses cached tiles when present, and the last view is restored on restart.
  • Voicemail works without espeak: record or upload a greeting even when text-to-speech is unavailable.
  • LXMFy bots get their configured icon colors seeded up front instead of showing a white avatar until first reply.
  • Popup menus show a caret pointing back at the button that opened them.
  • Mobile shows one toast at a time, and settings sections collapse to icon buttons on small screens.
  • Service worker: HEAD probes on hashed assets no longer reject the fetch, fixing the emoji picker data load.
  • Windows: the AppContainer backend sandbox is off by default again after loader-init crashes on some systems. Set MESHCHAT_APPCONTAINER=1 to require it, or auto to enable it with an unsandboxed fallback.
  • Desktop: Chromium GPU and logging flags injected by a crash-fallback relaunch no longer leak into backend arguments, which could break the Windows backend spawn under a Job Object.
  • NomadNet: page and file downloads in flight resume on the new socket after a websocket reconnect instead of spinning forever, and a resend that lands on a dead socket fails the download cleanly.
  • NomadNet: requests that never get a response now fail on a watchdog timeout instead of hanging, and dead links are evicted so the next attempt reconnects.
  • Tutorial: navigation buttons stay pinned below the scroll area in page mode, and the bootstrap-only toggle follows the backend default of off.
  • Map: location search sits inline in the header on desktop and tablet, and the first-run tooltip anchors to the map tools button.

Changed

  • Install docs cover reverse proxies, Kubernetes, and previously undocumented flags and MESHCHAT_* variables.
  • Long sessions bound per-view state (announces, map markers, NomadNet pages, MiniChat, toasts) and clean up timers and streams on unmount.
  • Bump rns to 1.5.4 and lxst to 0.5.3.
  • Hardcoded palette colors across calls, contacts, toasts, voice notes, and docs controls now use semantic theme tokens that follow the active theme.
  • Headless self-check gains a Windows AppContainer launch probe so loader-init failures show up in --self-check and CI.
  • CI checks the packaged Electron backend tree against backend-manifest.json before staging is pruned.
  • GHCR container images publish zstd-compressed OCI layers for faster pulls on modern runtimes. Docker Hub stays on gzip for compatibility.

v4.9.0

2026-09-12 [released]

Added

  • Bergamot WASM offline translation replaces Argos Translate and LibreTranslate. Packs are user-imported from local files and served same-origin. The app never downloads packs.

  • Conversation, Relay chat and the standalone Translator page use the local translation layer with quiet, opt-in message actions, original/translation toggles, remembered target languages and per-message/session caching.

  • Translation pack manager validates imported archives, rejects path traversal, and stores multiple packs under application storage.

  • Relay chat messages can be translated from the message context menu. Translated text is shown inline with a toggle.

  • Backend Landlock test probes cover translation-pack read/write under storage and the old Argos Translate CLI root has been removed.

  • Built-in geo-wasm converts WGS84, UTM, MGRS and Plus Code locally, so the map works offline without a network round trip.

  • The bundled starter_world.mbtiles is now a 30-degree world graticule covering z0-z4 instead of a solid placeholder, so the map is usable offline immediately.

  • Delivery failure tips throttle per peer, order by severity, cap the detail count and deduplicate diagnostic fetches so they do not get noisy.

  • DatabaseProvider closes idle live-thread SQLite handles after 120 seconds and dead-thread handles, preventing unbounded file-descriptor growth in containers.

  • Opt-in image support for Mesh Server and Micron pages. The parser recognizes img=1 image links and /media/ URLs, renders placeholders with alt text and size metadata, and loads the image only after an explicit click or when the policy is set to auto or always.

  • Page nodes serve /media/ images in webp, png, jpg, jpeg, bmp, gif and tiff, converting non-webp formats to webp and caching the result. /file/ stays webp-only, matching NomadNet 1.4.2.

  • Nomad page images download over the Reticulum page socket, report progress, reassemble chunked transfers and are cached per node and page path.

  • Page nodes grant file access only after a page request, so direct links to hosted files fail without first visiting the page.

  • Image loading on Micron pages is controlled by a global policy in Settings: never, manual, auto or always.

  • New tests cover the database file-descriptor oracle, delivery help tips, message cancel-send, raw message view, map bounds/provider handling and Micron image parsing.

  • Micron page output now exposes ARIA landmarks and semantic heading levels for screen readers. Rendered pages are wrapped in a main landmark, section headings receive heading role and level, links and inputs get focus and label hints, and the crash tab iframe document is marked as a document, receives the page path as its title, and advertises the current application locale.

  • New frontend tests cover the Micron accessibility attributes, crash tab iframe title, and crash tab document title.

  • The app shell now exposes a header landmark, a main content landmark, and a skip link so screen-reader and keyboard users can jump to the page body.

  • Primary side navigation is wrapped in a nav landmark, and each link receives an accessible name and aria-current when active.

  • AppModal now traps focus and restores it on close, and toast and loading states are announced with live regions.

  • The command palette is exposed as a dialog with role, aria-modal, and a combobox/listbox pattern for screen-reader users, and the Ctrl/Cmd+K shortcut is now routed through the configurable KeyboardShortcuts system.

  • The default keyboard shortcuts for opening the palette and toggling the sidebar are discoverable in the command palette, and navigation shortcuts are suppressed while a modal is open.

  • New frontend tests cover the command palette ARIA, command palette toggle-sidebar action, keyboard shortcut modal guard, and AppModal focusable-element discovery.

  • Settings sidebar gains a Simple and Advanced mode toggle. Simple mode hides technical sections (interfaces, transport, plugins and friends) while Advanced shows everything. Search always finds advanced settings.

  • The map restores proper tile attribution with a collapsible chip for OpenStreetMap, CARTO and OpenFreeMap sources, and the zoom control and scale line are themed to match the app.

  • Map tabs on wide screens gain a right-click context menu with rename, new tab, close, close others, close tabs to the right and close all.

  • The discovered-nodes toolbar button shows a spinner while nodes are being fetched.

  • Fixed pasting into nomad page input fields on Android. The WebView cannot show its own Paste menu for fields inside the rendered page frame, so long-press now opens an app Paste action that reads the system clipboard and inserts the text at the cursor.

  • Long-press on nomad page content on Android now opens the page/tab context menu, matching desktop right-click.

  • Fixed map tab right-click menu rendering behind the map toolbar: context menus now sit above page chrome.

  • The map scale now sits in a row with the credits chip at bottom-right, and map overlays are capped and stacked so toolbars, search, and info cards no longer overlap each other on narrow screens.

  • Relay chat gains a global Search tab that scans message history across every connected hub and room. Queries support quoted phrases, OR groups, NOT/-exclusion, field filters (from:, room:, hub:, kind:, date:) and fuzzy matching with result ranking. Results jump straight to the room.

  • Chat text now auto-links geographic references: Maidenhead grid locators (6+ characters, or any locator after a geo:/grid: prefix), lat/lon pairs, and other coordinate formats resolve locally and open the map with a marker.

  • Archives can be exported in bulk: an Export all (.zip) action below the search box downloads a zip bundle of the filtered set with a manifest, and each archive card gains an Export action that downloads the snapshot as a .mu file.

  • Relay chat member lists show avatar initials with presence dots, sticky online/offline headers, and a mention hint on hover. Right-clicking a member copies their identity hash.

  • Relay chat room headers now show the hub MOTD inline next to the room and hub name instead of a separate banner row.

  • Discovered interfaces fetch faster: the endpoint runs its filesystem scan and interface stats collection off the event loop and caches results briefly, and the map builds marker features in chunks so large sets stay responsive.

  • Tools page search icon no longer overlaps the placeholder text, and tool groups are now collapsible with count badges and persisted state.

  • The About page sandbox section shows the kernel Landlock ABI version when available.

  • The network visualiser adapts its batch size to measured apply time and pauses physics during silent refreshes when the frame rate is low, avoiding the 1-2 fps freeze on large graphs.

  • The messages split-view drop strip now only appears while dragging a conversation, freeing the space it occupied at rest. A conversation can also be opened in split view from its right-click menu.

  • Relay chat member rows gain a hover/tap direct-message action that lazily derives the member's LXMF address from their identity, waits for a path, then opens the conversation.

  • RRC hub hosts get anti-spam protections: per-peer session caps, a total session cap, and a rate limit on join/part/control traffic, plus a new Status tab in hub moderation showing relayed-message counts, drop counters, and a recent-events log.

  • All search inputs across the app now share a SearchInput component with consistent icon, clear button, loading spinner, and escape-to-clear. The icon-over-placeholder bug class is gone.

  • Offline mode with no basemap now shows a friendly in-map card with restore starter tiles, upload MBTiles and switch-to-online actions instead of a blank placeholder map.

  • Settings search tolerates typos: when strict matching finds nothing, queries like "mesages" still surface the messages settings.

  • Hosted relay hubs answer /history [room] [n] by replaying recent room messages to the requester only. Membership in the room is required, the count clamps to 50, and each request is logged in the host status events.

  • Relay chat gains a Bots tab for running LXMFy bots that join hubs as normal clients. Pick a hub and room list, set a nickname, mention-only mode, command prefix and reply cooldown, and the bot answers commands like uptime, ping, help and status in the rooms it joins.

  • The Bots page gains a custom command template with user-defined canned replies and a welcome message, a per-bot icon picker, and an expanded per-bot LXMF options form covering announce, signature verification, permissions, rate limits and admin hashes.

  • New tests cover the history command privacy and edge cases, RRC bot reply logic and validation, bot option normalizers, and restore hardening.

Fixed

  • The offline MBTiles source is capped to its metadata min/max zoom so OpenLayers stops requesting missing tiles.
  • OpenFreeMap is no longer treated as a raster fallback and Carto providers are now in the failover list.
  • The tile cache clear waits for pending access writes and counts replacement entries before eviction.
  • Successful tile blob application sets the tile state to LOADED, object URLs revoke on image load or error and dark placeholders are 256x256.
  • Cancel-send is shown inline, failed and rejected labels are localized and the raw message modal opens immediately with the paper URI loaded in the background.
  • All debounced save timeouts are cleared on unmount so config saves do not fire after the page is closed.
  • Crash tab render deadlines pause when the tab is inactive and the Network page cancels active page downloads on unmount.
  • Hop filter and announce chunk fetches catch errors instead of rejecting the whole render.
  • Unkeyed toasts with the same message and type replace the existing one instead of stacking.
  • Crash-tab hung toasts clear when the tab recovers, and the warning is skipped when the page is no longer active.
  • UIComponents SettingsPage mocks config patch responses and unmounts wrappers to prevent EnvironmentTeardownError.
  • Live name bindings in the split HTTP/WS modules support comparison, hashing and string conversion, so constants like MAX_EXPORT_ZOOM can be used in chained comparisons and map lookups.
  • AppImage packages now ship the backend data/map/.gitkeep marker, so the integrity check passes and onboarding no longer stalls on Connect to Mesh. A packaging test walks the real source tree and package.json filters so a missing file fails CI instead of the AppImage.
  • Direct links to nomadnet page addresses work when no node is selected yet. The destination hash and page path are parsed up front.
  • Restoring a backup now serializes concurrent restores, validates the restored identity key before replacing the current one, moves the restored tree into the slot matching its own identity hash, and rebaselines integrity state after the restore.
  • The RRC identity LXMF address endpoint now accepts real 16-byte identity hashes instead of only the 32-byte form, so the member direct-message action actually resolves.
  • RRC per-peer session caps no longer evict the host's own loopback client, can no longer be bypassed by links whose identify callback never fired, and cannot evict live sessions through a stale identify on an already-dropped link.
  • The split-view drop strip no longer stays visible if a conversation row unmounts mid-drag. Window-level drop and dragend handlers settle the state.
  • RRC room lists delivered over link resources (oversized /list and /who responses from hubs that negotiate resource envelopes) now populate the available-room list and member lists instead of being recorded as plain text.
  • Markers imported from KML or KMZ files, such as GhostMaps exports, can be selected, moved and edited again. Icon anchors were stored in image pixels but rebuilt as fractions, which pushed icons more than a thousand pixels off their coordinates once the icon image was cached or the drawing was reloaded. The restore and saved-drawing paths also skipped icon styles entirely, so reloaded markers collapsed to plain red dots at wide zooms. Both paths now use the same GeoJSON reader as imports.

Changed

  • The Flatpak Application ID is now com.meshchatx.app, with a matching desktop file, icon and metainfo bundled.
  • The Linux app name and userData path use the new com.meshchatx.app scheme while keeping the existing reticulum-meshchatx data directory.
  • Debug logs, RNode panels and flasher, message entry, conversation viewer and plugin settings now use semantic sem-* tokens instead of raw Tailwind colors. A behavior contract test enforces this for future changes.
  • Calls to print in meshchatx.py, nomadnet_downloader.py and other backend paths now use the application logger.
  • Windows desktop builds spawn the Python backend inside an LPAC AppContainer by default when the APIs are available. Set MESHCHAT_APPCONTAINER=0 to disable. If AppContainer setup fails, the launcher falls back to an unsandboxed backend process.
  • Geo-wasm and the starter MBTiles have been rebuilt and regenerated.
  • Full Ruff, Prettier, ESLint and typecheck pass across the backend and frontend. The ruff ruleset now covers pyupgrade, bugbear, bandit security, comprehensions, performance and ruff rules.
  • Tool pages (Translator, Ping, RNCP, RNProbe, Forwarder, RNS Filesync, Debug Logs) render as one continuous fused panel instead of stacked cards.

v4.8.9

2026-09-09 [released]

Fixed

  • Re-releases the LXMF inbound attachment rejection fix from 4.8.8 with corrected test lint. The delivery resource policy no longer treats the local lxmf.delivery identity as the sender, so large messages transfer before the backchannel identifies the remote peer. Closes #94.

v4.8.8

2026-09-09 [released]

Fixed

  • Fixed LXMF inbound attachment rejection. The delivery resource policy no longer treats the local lxmf.delivery identity as the sender, so large messages transfer before the backchannel identifies the remote peer. Closes #94.

v4.8.7

2026-09-09 [released]

Security

  • Hardened Electron shell, Android navigation, map export, plugin endpoint host checks, identity switch copy, docs ZIP extraction, and local file handling.

Fixed

  • Moved blocking identity teardown and maintenance DB work off the async event loop.
  • Fixed WebSocket coalescing, broadcast ordering, stale cursor recovery, delivery state regression, conversation failed count, active-conversation reset, handler isolation, nomad download race, and map export locking.

v4.8.6

2026-09-06 [released]

Added

  • Bug Reports Extension (off by default): Capture crashes and issues locally, group duplicates, and send a redacted report over the RNS when you choose. Crash screens can open or save into Bug Reports.
  • Release channels: Testing, Beta, and Stable. The sidebar shows which channel you are on. Testing and Beta ask once how to file useful bug reports.
  • Install options: Flatpak channels at https://cdn.meshchatx.com/flatpak/ (testing, beta, stable). Docker images with testing and beta tags.
  • Plugins: Richer plugin pages (tabs, tables, images, and more). Enabled plugins appear as their own destinations in the app. Plugin pages follow the theme and accent colors.
  • Archives: Search shows short previews. Open Micron, Markdown, or HTML previews from a card. Recrawl a page from the viewer. Layout stacks on phones.
  • Smart Crawler: Crawls less aggressively (about one request per node per day). Sites can opt out with # nocrawl or Archives settings.
  • Nomad private tabs: Ctrl+Shift+P opens a purple private tab that is not archived, favourited, or saved in history.
  • Micron publish: Publish can create a mesh server, upload the page, and open it in NomadNet in one step. Publish site uploads several tabs as pages with editable filenames, drag or arrow reordering, and an optional index page linking them all. Editor tabs can be dragged to reorder.
  • Nomad identify-on-connect (schema 58): Sticky per-favourite Identify when connecting, matching NomadNet. Auto-identifies on link before page requests. Fingerprint toggle and favourites import/export carry the identify flag.

Security

  • DeepSource security pass: Fixed or suppressed the first three batches of DeepSource security findings, including front-end rel attributes, Android TLS validation, stricter file permissions, and Python binding and subprocess audit rules.

Fixed

  • Micron WASM: ASCII-art whitespace is preserved again. Consecutive spaces in Micron markup were being collapsed by the WASM renderer, breaking ASCII art. Bumped micron-parser-go to v1.1.5.
  • Micron WASM: PUA/Nerd Font icon glyphs now render with Roboto Mono Nerd Font by wrapping them in a span. Bumped micron-parser-go to v1.1.2.
  • Reticulum config: MeshChatX no longer overwrites an existing .reticulum/config on startup. Existing parseable configs are preserved and only missing [reticulum] or [interfaces] sections are added.
  • Messages: Coming back to an open chat marks it read and clears badges and desktop notifications.
  • Messages (propagated): Propagated send waits for a path to the preferred propagation node. Missing node or path gets a clear error and delivery help tip. Failed pending bubbles stay visible with the error.
  • NomadNet: Opening a node while the app is still connecting no longer sticks on Loading. A stuck page no longer freezes the whole Nomad UI. Switching away and back no longer falsely claims the page renderer stopped.
  • Desktop app: Startup and crash screens respect dark mode instead of flashing white.
  • Map: Interface and telemetry markers update again.
  • UI: Dropdown menus match the theme.
  • Permissions-Policy: Only microphone, camera, and autoplay are listed. Hardware and speaker-selection tokens are left to browser defaults so Brave and Chrome stop logging unrecognized feature warnings.
  • Header buttons: Navbar icons, sync, and compose share one size and hover circle. The language menu icon matches the rest.
  • Stranger banner: The not-in-contacts warning is a slim single row instead of a large box.

Removed

  • ALTCHA: The proof-of-work login challenge is gone from login, setup, the demo stack, and all builds. Password and session auth still apply.

Changed

  • Theme consistency pass: Shared primitives, conversation viewer, message entry, messages, about, settings, tutorial, call, and add-interface pages now use semantic color tokens instead of raw Tailwind classes. The audit script and fixtures track the remaining raw token inventory. The raw Tailwind color count in frontend components dropped from about 4990 to about 3758.
  • Docker files: Dockerfiles and compose files moved under docker/. Update commands to docker/Dockerfile and docker/docker-compose.yml.
  • Dependencies: Electron 44, jsdom 30, vis-network 10, vis-data 8, and assorted patch bumps. Dependency audit reports no known vulnerabilities.
  • Vite and Vitest configs: Renamed to .mjs so the ESM config warning is gone.
  • Dev script: task dev output is colored and single-prefixed.
  • Smart Crawler: Finished crawls stay finished until you refresh. Fewer crawls run at once.
  • Reticulum: RNS 1.5.3.
  • WebTransport: aioquic 1.3.0 is a normal dependency. Android builds ship aioquic and pylsqpack Chaquopy wheels.
  • Micron: Micron-Parser-Go WASM v1.2.0.
  • Docs: Short READMEs at the repo root. Full install and contributor guides under docs/en/.