Repository navigation
Conversation
/ in the Browser tab opens a search line under the Saved and Nodes
tabs: as you type, both lists narrow to what matches by name or
address, as the Network tab's search does (every word must match;
<hash> and hash:/page pasted work). Matches are highlighted; a node
or page found by its address shows the address. The tabs count the
matches ("Nodes 4/120"), Enter keeps the search, Esc clears it, Ctrl-V
pastes, and a click on the line starts one.
Removing a saved page while searching removes the one shown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
A search box under the Browser's Saved and Nodes tabs narrows both
lists by name or address, with the Network search's rules. Nodes are
searched by rettui, so it finds any node heard, not only the newest
200 listed; saved pages narrow as you type. Matches are highlighted,
a saved page found by its address shows it, and the tabs count the
matches ("Nodes 1/252"). / focuses it on the Browser tab, Esc clears
it, and the list's note no longer sends you to the Network tab.
/peers also says how many of the kind were heard before the search
narrowed them ("heard").
Checked in Chromium with 252 nodes and two saved pages, at desktop
and phone widths.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
In the TUI, s with the Nodes list in hand saves the selected node's home page; with the page in hand it saves the page, as before. In the web UI, each node in the list has a star: ☆ saves its home page, ★ removes it. Either way it's the same bookmark as saving the home page once open, so it's never saved twice, and a node whose home page is saved is starred in both lists. Checked in Chromium: a node found by a search saved and removed from its star, without opening it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
NomadNet-Browsing says how to search the Saved and Nodes lists and save a node without opening it, in both UIs; Using-the-TUI lists /, s and Esc in the Browser pane, and Web-UI that / searches the Browser and its search finds any node, beyond the 200 listed. The Browser pane's footer has / search, and s save node in the Nodes list (x remove saved in Saved); while a search is in effect, edit or clear it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Before browsing a node, sending, syncing or joining an RRC hub, rettui sent one path request and gave up after 15 s (rsReticulum's transport ends every wait there, though rettui asked for 30), so a destination whose path request went unanswered could only be reached after its next announce (issue #10). Path requests now go out at 0, 15 and 52 s, with a last wait of 15 s. The second covers a first lost on the way. Python Reticulum (1.5.x and current) folds further requests for a destination into one it's still working on for 45 s (PATH_REQUEST_GATE_TIMEOUT, culled every 5 s), on the destination and on any hub between, so the third goes after that, when a node that heard the first looks again. Between requests, a path that comes with an announce is taken. A Link that isn't answered may have gone over a stale path, or one answered from an out-of-date cache: the path is dropped, a fresh one found, and the Link tried once more. This covers page loads, direct and propagated sends, syncs and RRC hubs (not ping, which times one Link). The terminal browser's load line, the RRC hub status and `rettui fetch` say which path request is out. Checked against a Python RNS 1.5.5 node over loopback TCP: one that ignored the first path request served the page after the third (52 s); one that ignored the first Link request served it after the path was replaced (12 s). Both failed before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
As rnpath does (issue #10). For any address (a peer, a node, a hub or one typed in), rettui shows the path it knows: hops, the transport node it goes through, the interface and how long it's kept. If none is known, it asks for one, at 0, 15 and 52 s as when connecting, and says which request is out. Forgetting a path that has gone stale makes the next use ask for a fresh one. - Terminal UI, Network tab: P finds a path (to the selected row, or any address typed in its place), D forgets the selected row's path. The answer shows as a notice and in the log. - Web UI, Network page: a Path button on each row, and "Find a path…" for any address, open a dialog with the path, how finding it is going, and Find path / Forget path. Checked against a Python RNS node over loopback TCP in both UIs: found ("heard directly on Python side, kept for 6 days"), forgotten, found again; an address nobody has shows "path request 2 of 3" while it's looked for. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
A probe times an answer from a destination, finding a path first the same way a page load does. LXMF addresses and transport probe destinations prove packets, so they get a probe packet, as rnprobe sends. NomadNet nodes, propagation nodes and RRC hubs don't, and rnprobe to one always times out, so they get a timed Link instead, and the answer says so. T probes the selected Network row in the TUI. The web Path dialog has a Probe button, and each Network row a Path button; the actions column is wider and wraps. Checked against a Python node and LXMF peer: "a Link opened in 2 ms, heard directly (NomadNet node)", "answered in 1 ms, heard directly (LXMF address)", and an unknown address shows how finding a path goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
rettui path <address> finds the path to any destination and prints it, asking for one if none is known (three requests over a minute, as the UIs do). -d forgets it, so the next use asks for a fresh one; -t lists every path known. With rettui or rnsd running as the shared instance these are its paths, read and dropped over its RPC, as rnpath does; else, the paths the last run with this Reticulum config saved, which a drop changes for the next run. rettui probe <address> times an answer: a probe packet for LXMF addresses, a Link for nodes, propagation nodes and RRC hubs, which don't prove packets. --name takes a destination's full name for kinds rettui can't tell, which are sent a probe packet. Checked against a Python node and LXMF peer, standalone and as a client of rettui --web as the shared instance: paths found, listed, dropped (and gone from the shared instance's table), both kinds probed, and an unknown address reported after 67 s, with each request as it goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Network says how paths are found (three requests over a minute, and why the third waits for Reticulum's 45 s gate), how stale ones are replaced, finding and forgetting one in both UIs, how probes are answered by each kind, and the commands. Running lists rettui path and rettui probe, Using-the-TUI the P, D and T keys, and NomadNet-Browsing what to do about a node that won't answer. The Network tab's footer shows P (path) and T (probe). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Once a search is typed, the Network footer is another one, and it didn't have P (path) and T (probe), though both work on the rows found. They come right after the search's own keys, before the longer hints, as the footer is cut off at the terminal's edge: a 120-column terminal shows them. While typing, they go into the search box, so that footer stays without them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Each mode's keys are now one list, most used first, with the shortest
labels that still say what they do ("p sync via", "P path", "D forget
path"). The footer shows as many as fit whole, nano style ("P path"),
so none is cut off mid-word, and "? keys" stays at its right edge.
The keys that work in every tab (1-7, A, S, ^L, q) leave the tabs'
footers for that list.
? opens a list of every key of what's on screen, in columns, with
those that work in every tab; any key or a click closes it, and does
nothing else. While typing, ? is typed, so the footer says "F1 keys"
and F1 opens the list, with the keys that work while typing.
Network's footer now shows P path and T probe at 80 columns, searched
or not. Checked in tmux: ? and F1, the list for Network, Messages and
the Network search, and typing ? into the search.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Brings in the path tools and the new footer (#13). The Browser search's hints move into the keys table: / search and s save in the Nodes list, x remove in Saved, edit or clear while a search is in effect, the search box's own keys while typing (F1 for the list), and / search from the page and the source view. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
zevaryx.github.io/rettui is built from the pages in wiki/, which stay the one copy (the GitHub wiki is published from them too). docs/build.py writes them as the site wants them: links between pages point at their files, Home is the front page, each page is titled with its sidebar name, GitHub's [!WARNING] notes become admonitions, and the navigation is wiki/_Sidebar.md. A link to a page or heading that doesn't exist, or a page left out of the sidebar, stops the build, and Zensical builds in strict mode. The Docs workflow builds it for pull requests that change it and publishes it to GitHub Pages from main. The README points at it, and Development describes the workflow. Fixes a link to a heading that NomadNet-Browsing doesn't have, which the check found. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
rsReticulum moves to 1.3.0 (upstream main) with ratspeak/rsReticulum#26 merged in, which node hosting needs (file replies, their metadata and the requester's identity). Until that pull request is merged upstream, the submodule follows the rettui branch of zevaryx/rsReticulum (6825661). From 1.3: path requests a busy or slow interface couldn't take are kept and sent once it can, a failed Link's route is dropped before recovery, Resource transfers have bounded waits, a shared instance client attaches without a startup race, and on Windows a process rettui starts no longer keeps its listeners open. The one breaking change (recursive discovery internals) isn't used here. rsLXMF moves to main, which needs rsReticulum 1.3: delivery and lxmd changes rettui doesn't use, and a propagation node fix for messages stored without a stamp (rettui stores them stamped). No code changes. Checked against Python RNS 1.5.5 and LXMF 1.2.0: path retries (52 s with the first request dropped, 12 s with the first Link dropped), page fetch, ping, probe, direct and opportunistic messages both ways (with ratchets), a propagated message and sync, the CLI as a shared instance's client, and hosting a node (a page, a file with its name, and an executable page given the visitor's identity). rsNomad's own 104 tests pass against rsReticulum 1.3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
All the web UI's addresses are relative now (the page, its script, style, fonts, icons, manifest, service worker, API and live connection, and the login form and redirect), so a proxy can serve it at a site's root or under a path such as /rettui/ with nothing to set. The live connection asks nginx not to buffer it, and an upload a proxy turns away as too large says so (nginx's 1 MB default). Checked in Chromium through nginx 1.24 with only proxy_pass, under /rettui/: login, the API, fonts, a live update and the service worker's scope; and directly, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Off unless asked for at startup, so existing installs stay plain HTTP. --https serves the web UI over HTTPS with rettui's own certificate: a small certificate authority made once (web-tls/ in the data directory), and a certificate it signs for this computer's names and addresses, made again when they change or a month before it ends (397 days, as Apple's devices accept). Devices install the authority from /rettui-ca.crt, or Download under Status, which explains how on Android, an iPhone and a computer. --tls-cert and --tls-key use a certificate of your own (tailscale cert, mkcert, Let's Encrypt). With HTTPS on, a plain http:// request to the port is redirected to https://, and the login cookie is Secure. Handshakes run on their own (a slow one doesn't hold up others) on rustls with ring, as ureq already builds it. The Status page's Identity panel scrolls on its own when its rows are taller than its share, rather than running over Settings. Checked: curl verifies the certificate for localhost and 127.0.0.1 with only the authority; Chromium trusting it (its NSS database) sees a secure context, a Secure cookie, a registered service worker and the notifications API; the download; your own certificate files; and the flags' errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
In the TUI, `/` in Messages opens a search over every conversation: every word typed must be found, in any order, in a message's text, title, attachments' names or notes. Results are newest first (200 at most), with who, when and the part that matched, highlighted. Tab keeps it to the open conversation, and Enter opens the conversation scrolled to the message, picked. In the web UI, a Search messages box above the conversations (or `/`) lists the same, from /api/search?q=...&in=<address>. "In <name>" keeps it to the open conversation, and a result further back than what's loaded loads the rest before jumping to it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
A photo straight from a phone is megabytes: minutes over a radio link, and more than propagation nodes usually take (256 KB). As Sideband, Columba and MeshChat do, pictures are now made smaller first, by the new "Send pictures at" setting (picture_size): small (480 px on the longest side, for LoRa), medium (1024 px, the default), large (2048 px) or original. A picture is turned the way its camera noted (EXIF orientation) and written again as a JPEG, or a PNG if it's partly see-through, which leaves out its metadata, such as where a photo was taken. GIFs go as they are, and so does a picture that wouldn't get smaller. The smaller copy is saved in uploads/, so the picture on your computer isn't changed, and the copy goes when its message is deleted; an uploaded original is replaced. The web UI shrinks uploads before taking the app's lock (a 12 MP photo takes about a quarter of a second), says "sent smaller" on the picture while writing, and no longer warns of a picture's size before it shrinks. The TUI shows "(smaller)" beside the picture, and a message it can't send gives back the files picked, not the copies. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Their icons: LXMF's icon appearance field ([name, foreground, background], a Material Design Icon's name and two 3-byte colours, as Sideband and MeshChat send it) is read, and the newest each person sent is kept with their contact. The web UI shows it beside their name in the conversation list and above the conversation (once anyone has one, the rest get their first letter); the TUI's contact card names it in its colours. A message carrying only an icon updates it without showing as a message. Yours: new Icon, Icon colour and Icon background settings. Once an icon is set it goes with every message (not paper ones); empty, the default, sends none, so nothing changes for existing installs. The web UI picks it from the icons matching what's typed (GET /api/icons?q=), shows colours with colour inputs (a new colour kind of setting), and shows your icon beside your name. The names map to their characters in Nerd Font's icons (src/icons/mdi.txt: the 6,896 MDI 7.4.47 icons the bundled font has), so the server sends the character and the browser needs no table. Checked against Python LXMF both ways. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Drafts in the web UI lived only in the page, and phones reload pages they've put in the background, so a half-written message could vanish. The text of each conversation's and room's draft, and the message it replies to, are now kept in the browser's localStorage: as typed (a moment after), on switching, on sending (a sent draft goes), and when the page is hidden or left. They come back where they were written after a reload or a restart of rettui. Attached files stay in the page only. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Messages past "Messages kept" move to archive/, which neither UI could show. Now they open from their conversation, only to read: - TUI: H in Messages opens them over the tab, newest at the bottom; the arrows, PgUp/PgDn, Home/End and the mouse wheel scroll, and Esc (or a click outside) closes. The note at the top of a conversation says so. - Web UI: the note at the top of a conversation is now a "Show N archived messages" button, which reads them from GET /api/conversations/<address>/archive (away from the app) and shows them above the rest, without reply, react or delete buttons. Their files and pictures still open: the attachment route looks in the archive for a message it doesn't find among those kept. store::read_archive reads a conversation's messages from every month, oldest first, parsing only the lines that name it. Also fixes the conversation header in the web UI reading "null" before the name of someone with no icon (from the icons commit). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
"Ignore unknown senders" was all or nothing: messages from anyone who isn't a contact were shown like anyone's, or dropped. It's now "Unknown senders" (unknown_senders): show (the default, as before), ignore (as the toggle did; a settings file with it on reads as this), or requests. With requests, their messages are kept aside, as Signal does: listed after the rest (under a "Message requests" heading in the web UI, marked "request" in the TUI), with no notification and not counted as unread. Trusting them, leaving them as they are or replying makes it a conversation like any other; blocking or deleting gets rid of it (the web UI's request banner has Delete beside Block). App::conversation_order and App::unread_messages replace the store's order and the summed unread counts in both UIs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Pings now open their Link with track_phy_stats, and say how well the answer was heard when it came in over a radio that reports it (an RNode): "answered in 840 ms, 2 hops away, heard at RSSI -91 dBm, SNR 4.2 dB, link quality 87%", as rnprobe says it, in both UIs and `rettui ping`. The Status tab's interfaces show what rnstatus does of each: its rate, MTU, mode if not full, clients for a server, and announces queued or held and packets dropped when there are any (from Reticulum's interface stats). Received messages still carry no readings: rsReticulum's destination runtime hands packets and Link data to the application without the interface's RSSI/SNR (only an outbound Link session keeps them), so showing them, as Sideband does, needs a change there first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
A pinned conversation stays at the top of the list (newest pinned first; message requests still come last): `*` in the TUI's Messages tab, or the 📌 button above a conversation in the web UI (POST /api/conversations/<address>/pin). Pinned ones are marked in both lists. `R` in the TUI, or "✓ All read" above the web UI's conversations while anything is unread (POST /api/conversations/read-all), marks every conversation read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Forward: a message's text and files go to another conversation, or to an address typed in full, as a new message, by how messages to them go. Its files are copied into uploads/, so deleting either message leaves the other's. In the TUI, `f` on a picked message opens a list to pick from (type part of a name or address; Enter, or a click, forwards); in the web UI, "Forward…" in a message's ⋯ menu opens the same as a dialog (POST /api/conversations/<address>/forward). Export: a conversation, archived messages and all, as text: who wrote what and when, with file names, locations, notes and reactions. `E` in the TUI's Messages tab writes it to downloads/exports/; "Export as text" in the web UI's Contact dialog downloads it (GET /api/conversations/<address>/export). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
…ma:// links "Sign out the others" (Status, Browsers) makes a new login token: every other browser, and scripts using the old token, must log in again with the new link, printed where rettui runs and saved in web_token. The browser that asked keeps a cookie with the new token. Live-update streams opened before it end at once (they'd otherwise go on carrying notifications), and push subscriptions are dropped (the browser that asked subscribes again). POST /api/sign-out-others. `rettui send` takes an lxma:// link as well as an address, keeping the key it carries so the message can go before they've been heard announcing (as `rettui ping` already did). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
The Network list was always last heard first. `s` in the TUI, or the order menu in the web UI (GET /api/peers?sort=), now sorts it by name or nearest first too. Each row says which interface its path goes through, when one's known: the network task reads the path table every 30 seconds and passes on the interface for each destination. `i` in the TUI, or the interface menu in the web UI (?via=), keeps the list to those through one interface, such as what's heard over a LoRa radio rather than the internet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
"Log level" (Status, log_level): how much goes to rettui.log, from error to trace, warn by default as before. It applies at once, through a reloadable filter on the log file's layer. RETTUI_LOG, if set when rettui starts, still decides instead (and changing the setting says so). The Running page gets "Running as a service": a systemd user unit (with lingering, and where the login link goes), and a hardened system unit for a machine of its own (its own user, the dialout group for an RNode, its Reticulum config kept writable under /var/lib/rettui, or rnsd's with ReadWritePaths), with notes on listening, logs and stopping. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
…ht web theme Times and dates: new Clock (24-hour, or 12-hour as "2:05 PM") and Dates (month-day "Oct 07", day-month "07 Oct", or year-month-day "2025-10-07") settings, used everywhere times show: both UIs' lists and messages, the archive, the log on screen, RRC lines, sync times, exported conversations and `rettui listen`. A new clock module formats them; the web UI gets the two settings in /api/state and formats the same way. Terminal colours (tui_theme): dark (as before), light (a darker accent and a light selection, for light terminals) or basic (16 colours, for terminals without full colour). The UI's colour constants became functions of the theme, so a change shows on the next frame. The web UI gets a light theme, picked per browser in Status (Theme: dark, light, or as the device is set, following it when it changes) and kept in localStorage. The stylesheet's remaining fixed colours became variables, coloured backgrounds keep their bright values under dark text, room nicks get darker colours to read on white, and the wordmark is inverted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
LXST, which Sideband's calls use, is published under CC BY-NC-ND 4.0: adaptations of it can't be shared, so it can't be ported into an AGPL program, and there's no separate protocol description to implement it from. Said where voice messages are, which work with the same clients. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
rustfmt.toml keeps the code as wide as it was written: lines up to 140 columns, with calls, struct literals, chains and short blocks on one line while they fit (use_small_heuristics = "Max"); of the widths tried, it changes the least. Formatting only: nothing else changes. The libraries in deps/ aren't touched. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
CI runs cargo fmt --check on pull requests (it needs no submodules: deps/ isn't formatted). .git-blame-ignore-revs names the formatting commit, so git blame (GitHub's, or with blame.ignoreRevsFile set) shows who last changed a line's meaning. Development says to run cargo fmt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Clippy 1.99's chunks_exact_to_as_chunks lint (an error with -D warnings in CI) flags chunks_exact(2) with a constant size. as_chunks::<2> gives the pairs as arrays; a trailing odd byte is left out, as before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
CHANGELOG.md says what's new, changed and fixed in v1.6.0 since v1.5.1: the quality-of-life changes, path tools, Browser search, the keys list, the documentation site and rsReticulum 1.3. Earlier releases are on the releases page. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
The settings applied to the running app only those named in update_settings; anything else was saved to settings.json and waited for the next start, though these three say they apply at once. A setting that applies now, with nothing more to do than take the new value, is now taken whatever its name, so one added later can't be missed. A test changes every setting that applies now (but those that change every test's theme, clock or log level) and checks the running app has it. It caught these three. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Once a day, rettui asks GitHub's API for the newest release (drafts and prereleases left out), in a thread of its own, and compares versions (a release is newer than its own prereleases). When one is newer: the version beside the name is marked ↑ in yellow in both UIs and opens that release's page; Status says which, with a link to what's new; and the log says so, once a run. Nothing is downloaded or installed. What it found is kept in update-check.json, so a restart doesn't ask again and an update found still shows offline. If GitHub can't be reached it tries again an hour on, only in the debug log. It's one HTTPS request, through the environment's proxy, with rettui's name and version as its user agent; the release page linked is always GitHub's. New setting, Check for updates (update_check), on by default; off, it never asks and shows nothing. Tests never go online: they give the check its answer. The web UI's Status page draws again once a refetched status is in, so it no longer shows the one before when its own update finishes first. Installing gets "Updating" (what's shown, how to update each way, and what the check sends), Data-and-Storage the file, and the changelog a line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
…them Check for updates is now off unless turned on: the check tells GitHub the IP address it comes from, which someone using Reticulum to stay off the internet (over Tor or I2P) wouldn't want sent without asking. The getting-started guide, in both UIs, has "Check for updates once a day (recommended)" after picking a propagation node, not ticked to start with, saying what it does and that GitHub sees your IP address. Apply saves it as chosen; opened again, it's as set. It stays in Status too. Installing, Running and the changelog say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
When an update check has found a newer release, a binary from the releases page can put it in its own place: U in the TUI's Status (asking first), Install beside the update in the web UI's Status (confirming first), or `rettui update` (which checks first, whatever Check for updates says, and asks unless --yes). It downloads the release's archive for this system (the Build workflow's names: .tar.gz, or .zip for Windows) with its SHA256SUMS from GitHub, checks one against the other, unpacks rettui, and runs it once with --version beside the old one; only if it says it's the new version does it replace the old one (by a rename; on Windows the running program is moved aside, and removed at the next start). rettui carries on as it was until it's started again, and says so. In a thread of its own; the checksums catch a download gone wrong, not a release that isn't GitHub's. Only release builds do this: the Build workflow marks its binaries with their target (RETTUI_RELEASE_TARGET). A build from source, a container's (the image sets RETTUI_CONTAINER; /.dockerenv and /run/.containerenv count too), one a package manager installed (/usr, Homebrew's Cellar, Nix, snaps), or one in a folder that can't be written to says how to update instead. src/update.rs becomes src/update/ with install.rs. New dependencies: tar, and zip (flate2 only). An online test (ignored unless asked) downloads, checks and runs the real v1.5.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
The program put in place took the old one's permissions as they were;
one that had lost its execute bits (or a test's stand-in) left the new
one unrunnable, and the check that runs it refused it. It now keeps the
old one's permissions and is runnable by whoever can read it.
Found by the online test, through a proxy that inspects HTTPS (with the
system certificates trusted): the real v1.5.1 downloaded, matched its
SHA256SUMS, unpacked, ran ("rettui 1.5.1") and was put in place.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
… too Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Web Push, map tiles and update checks trusted only the root certificates ureq bundles, so behind a proxy that inspects HTTPS with an authority of its own (an office's, antivirus) they all failed, quietly. They now share one TLS configuration that trusts the bundled roots and the system's (its store, or SSL_CERT_FILE / SSL_CERT_DIR), as browsers do; a system with no store at all still has the bundled ones. ureq's own native-certs feature would have replaced the bundled roots instead. Checked through such a proxy: the update check, which failed with UnknownIssuer, now finds the latest release, and map tiles still load. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
The Release workflow published GitHub's generated notes, so the changelog could fall behind unnoticed. Now .github/scripts/release-notes.sh prints the tag's section of CHANGELOG.md (`## v1.7.0`, without the heading; a prerelease takes its own section, else its release's), the tag check fails before anything is built if there isn't one, and the release's notes are that section and the Docker image's name. Development says to add the section before tagging. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
`/` in the Channels tab (the web UI: a search box above the hubs, or `/`) finds what was said in every hub's rooms and whisper conversations, as rettui keeps them: every word, in any order, in the line or the name of who said it, newest first (200 at most), as the message search has it. Tab (In #room) keeps it to the one open. Joins, leaves and errors aren't searched. Opening one shows its room at that line: the TUI scrolls it to about the middle and marks it until another room is picked; the web UI loads the whole room if the line is further back than what shows, scrolls to it and flashes it (GET /api/channels/search?q=&hub=&room=). The search box over the tab is now one drawer for both searches, and the snippet around a match works on any texts. Also puts the message search's doc comment back on its function (the map's view had come between them). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
New Quiet hours setting (quiet_hours): two times of day, by this computer's clock (22:00-07:00, 22-7, 9:30 to 12; past midnight is fine, kept as 22:00-07:00). Between them, notifications are held back wherever they'd show (desktop, browser, Web Push); messages still arrive and count as unread. Trusted break quiet hours (quiet_hours_trusted, on by default) lets trusted contacts' messages through anyway. The check is where every notification is queued, so it covers both UIs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
A live share sends location updates to someone for 15 minutes, an hour,
eight hours or until stopped, then a message saying it stopped
(FIELD_CUSTOM_META {"cease": true}, as Columba sends it and rettui
already reads). Each update replaces the one before it in the
conversation, so a live share is one message there, and the
conversation's header says it's live, until when (the web UI: with
Stop).
- Web UI: "Share live" in the share dialog shares where the device is:
while any share is the device's, the page watches its position and
posts it (POST /api/live/position, at most every 30 s); rettui sends
at most one update a minute, and none from a position older than five
minutes. A page that can't say where the device is shares this
station's Location instead. POST /api/conversations/<address>/live
and .../live/stop.
- TUI: `L` then `live 15m`, `live 1h`, `live 8h` or `live on` shares this
station's Location, every five minutes; `L` again stops it.
Shares last while rettui runs. Checked from Chromium (with a simulated
device location) to Python LXMF: the location arrived, then the cease.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
A backup is a .tar.gz of what's yours in the data directory: settings, the store and its archive, peers, known identities, tickets, RRC history, the node's pages and (unless left out) the identity; attachments only when asked. Caches, logs, ratchets and the web UI's secrets are made again, so they aren't kept. - `rettui backup [FILE] [--with-files] [--without-identity]` and `rettui restore FILE [--force]`, run before anything is loaded (or, in a new data directory, made). - `B` in the TUI's Status tab backs up, with the identity, on the saving thread once what's waiting is written. - The web UI's Status page downloads one, never with the identity. Backups are written to a new file only their owner can read. Restoring takes only rettui's own files, skips links, never writes outside the data directory, and leaves a data directory with an identity or messages alone unless --force, which moves what it replaces aside first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
`a` in the Network tab (or Announces in the web UI) shows every announce in place of the list, as it arrives, of every kind: LXMF peers, NomadNet and propagation nodes, RRC hubs, LXST calls and whatever else announces, named by the name hash it announced under. Names are read as each known kind sends them; for others, text (or a name in msgpack) when there is one. Each row says when it was heard, how far, and through which interface. It follows the newest; moving up holds it, and new ones (and old ones going) don't move the selection. Filter by kind, search and keep to an interface as in the list; Enter opens what rettui can, and P, T, y and D work as there. The newest 1000 since rettui started are kept; the web UI fetches those after the last it has, every 2 seconds while shown. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Each interface keeps its rates in and out at every stats update (every five seconds) for the last ten minutes. The Status tab draws them under it, against its busiest moment, with how fast it's moving now: bars in the terminal UI, an SVG line each way in the web UI. An interface that's been quiet throughout has none, and one gone is forgotten. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
*Offline map* (map_tiles_file) names an MBTiles file: map tiles in one SQLite file, as MOBAC, QGIS or TileMill make them. The web map takes its tiles from it first, with no internet at all; any it hasn't come from Map tiles, if that's set, and failing that (or zoomed in past the file's closest level) the part of the closest tile it has further out, enlarged, up to six levels. Its attribution is credited in the corner. The file is opened read only, and only pictures (png, jpg, webp) are shown: one of vector tiles, or that isn't MBTiles, is refused when it's chosen. It names a file on this computer, so the web UI can't change it, as with the node's folder. SQLite comes built in (rusqlite, bundled). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
A message that came in one packet straight from the sender (sent opportunistically) shows how it was heard, when the interface reports it (an RNode): RSSI, SNR and link quality, beside it in both UIs (📶), and kept with it. Reticulum keeps those readings by packet hash, as Python's Transport.get_packet_rssi reads them; rettui hashes the packet with rsReticulum's own rns-wire and asks the transport (through a shared instance too). Nothing in deps/ changes. Messages over a Link (most direct ones) still don't show it: rsReticulum doesn't say how a Link's packets were heard. The changelog says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
…lease Each release now also carries: - Debian packages of the Linux binaries, rettui_<version>_amd64.deb and _arm64.deb (rettui in /usr/bin, depending on libc6 >= 2.35 and libgcc-s1; a prerelease's version sorts before its release's), in SHA256SUMS too; - for a release, a Homebrew formula (rettui.rb, the ready-built macOS and Linux binaries) and the AUR's rettui-bin PKGBUILD (and .SRCINFO), written from SHA256SUMS, to publish to a tap and the AUR, which need accounts of their own. The scripts are in .github/scripts; Development says how to publish, and Installing how to install each. Installed this way, rettui leaves updating to the package manager, as it already did for /usr/bin and Homebrew's Cellar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Checking an MBTiles file (choosing it as the offline map, or the web UI being refused it) opened it into the cache tile serving keeps open, so it stayed open until another file was. On Windows a file open can't be deleted or replaced: the settings test's cleanup failed there, and a user couldn't swap the file while rettui ran. Checking now opens the file only for the check. Changing or clearing the setting closes the file kept open for tiles. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Checked against every commit on dev since main (v1.5.1): what was missing was the pull requests (#14, #15, #17 and #18 join #12, #13 and #16) and that a page, an RRC hub and `rettui fetch` say which path request is out. Update checks, self-update and the new packages are grouped as Installing and updating. Fixes to features new in this release aren't listed: they never shipped broken. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
A last job of the Release workflow, for a release (not a prerelease): - Homebrew: the formula, written from the release's SHA256SUMS, is pushed to Formula/rettui.rb in the tap (zevaryx/homebrew-rettui, or the HOMEBREW_TAP variable's) with its deploy key (HOMEBREW_TAP_DEPLOY_KEY), GitHub's host key pinned as docs.github.com publishes it. - AUR: rettui-bin's PKGBUILD, with AUR_USERNAME <AUR_EMAIL> as its maintainer and its commit's author, and .SRCINFO as makepkg writes it (in an Arch Linux container), are pushed with AUR_SSH_PRIVATE_KEY, once the AUR's host key matches the fingerprint aur.archlinux.org publishes. The first push makes the package. Each half is skipped, with a notice, until its secrets are set. Pushing is done by publish-homebrew.sh and publish-aur.sh, which push nothing when the tap or the AUR has the release already, so the job can be re-run. Checked against local repositories (an empty tap, one with a README, a new AUR package; publishing, again, and the next release), makepkg's .SRCINFO matches aur-pkgbuild.sh's, and actionlint and shellcheck pass on the new job and scripts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Identity, in Status, has a Version line in both UIs: this version, what kind of build it is (a release build and its target, or built from source), and what the last update check found and when, or that one is on its way, or why one asked for couldn't be made. With Check for updates off and none asked for this run, it says the setting is off rather than show an older answer. A newer release's Update row, with Install, now sits under it, near the top. `u` in the Status tab, or Check now in the web UI, checks at once, whether or not daily checks are on, and says what it found: the newest release, a newer one (shown and installable as one found daily), a build newer than the latest release, or why it couldn't check. A daily check already on its way is said the same way when it's in. The web UI hears when a check or an install finishes (they finish on a tick, which told browsers nothing), so Status shows it, and a check asked for in the page toasts what it found. The Status keys while first steps are shown had lost U (install update) with the qol-2 changes; both lists now have u and U. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
Every announce heard goes to the announce viewer as a Heard event, which the web UI counted as touching everything: browsers refetched all they showed for each one, several times a second on a busy network. The viewer asks for those after the last it has, and the Network list hears the same announce as an Announce, so a Heard event touches nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Tv5VJUy237EdS4U5P6JwjR
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes since v1.5.1.
Added
/)./in Channels).*) and mark all read (R).f), and export a conversation as text (E).H).L/ 📍) in Sideband's telemetry format, once or live (as Columba does: updates for a while, then a message saying it stopped).M). Map tiles are fetched and cached by rettui.P/D, or the Path dialog in the web UI (#10).T).rettui pathandrettui probecommands, like rnpath and rnprobe.s), or keep it to one interface (i).ain Network): every announce as it's heard, of every kind (RRC hubs, calls and others too), with when, how far and through which interface./).s, or ☆ in the web UI).f).--https(rettui's own certificate authority), or--tls-cert/--tls-keyfor your own certificate.?to list them.?(orF1while typing) lists every key.Uin Status, Install in the web UI, orrettui update), checked against the release'sSHA256SUMSand run once before they replace the old one. Builds from source, containers and package managers' are told how to update instead.brew install zevaryx/rettui/rettui) and the AUR (rettui-bin), published with each release, and Debian packages (x86-64 and ARM64) attached to it.Bin Status,rettui backup/rettui restore, or Download a backup in the web UI (without the identity).rettui sendacceptslxma://links.Changed
rettui fetchsay which request is out.rettuibranch of zevaryx/rsReticulum until ratspeak/rsReticulum#26 is merged upstream.Fixed
P/Thints were missing while a Network search was active.Docs and development
wiki/with Zensical.Known limitations