diff --git a/CHANGELOG.md b/CHANGELOG.md index 13a5386e..26033535 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v1.8.4-alpha (draft — date set at cut) +## v1.8.4-alpha (2026-08-20) - **Apps with their own login can now skip the node's login screen — Gitea and BTCPay Server do so out of the box.** Some apps bring a complete account system of their own, and putting the node's password page in front of them broke real workflows: git clients can't answer a browser login, and a BTCPay checkout link handed to a customer must open for that customer. These apps are now served directly on their own login, while the node still fronts the connection for everything else it does (embedding fixes, the "app is restarting" page, Tor). Every app gets a new **Settings → app → Access control** switch, so you can put the node login back in front of any app — or take it away from one — with one click, effective immediately. App developers declare the default in their manifest (`auth: open`), documented in the developer guide. diff --git a/neode-ui/src/views/settings/AccountInfoSection.vue b/neode-ui/src/views/settings/AccountInfoSection.vue index 258084eb..131b17f0 100644 --- a/neode-ui/src/views/settings/AccountInfoSection.vue +++ b/neode-ui/src/views/settings/AccountInfoSection.vue @@ -362,6 +362,22 @@ init()
+ +
+
+ v1.8.4-alpha + August 20, 2026 +
+
+

**Apps with their own login can now skip the node's login screen — Gitea and BTCPay Server do so out of the box.** Some apps bring a complete account system of their own, and putting the node's password page in front of them broke real workflows: git clients can't answer a browser login, and a BTCPay checkout link handed to a customer must open for that customer. These apps are now served directly on their own login, while the node still fronts the connection for everything else it does (embedding fixes, the "app is restarting" page, Tor). Every app gets a new **Settings → app → Access control** switch, so you can put the node login back in front of any app — or take it away from one — with one click, effective immediately. App developers declare the default in their manifest (auth: open), documented in the developer guide.

+

**The phone remote now works inside apps on the TV — tap, scroll, and type everywhere.** The companion remote and keyboard drove the dashboard beautifully but died at the edge of any app screen (Gitea, BTCPay, and friends): for the browser, each app is a separate website embedded in the page, and simulated input is forbidden from crossing that wall. The on-screen display now accepts the remote's input the way a real mouse and keyboard arrive — below the page, through the browser itself — so it lands anywhere on screen, app screens and tabs included. Taps click, two-finger scrolling scrolls the app, and typing goes into whichever field you tapped. Existing kiosks pick this up with the update, no reinstall needed.

+

**While you're driving with the phone remote, the old mouse pointer gets out of the way.** The computer's own pointer used to sit frozen wherever the physical mouse last left it — a second, dead cursor next to the live orange one. It now hides while the remote is in use and returns half a minute after the last remote input.

+

**"Are you sure?" questions no longer freeze the remote.** A handful of confirmations (clearing mesh history, rebooting, deleting a backup, uninstalling an app) used the browser's built-in popup, which stops the whole page — including remote input — until someone clicks it with a real mouse. From the couch, that meant asking a question you couldn't answer. All of them are now proper in-app windows in the house style, fully driveable by remote.

+

**A mesh radio now connects no matter which port it's plugged into — or replugged into.** Moving a radio to a different USB port could leave the mesh silently down: the node only checked a short fixed list of port names (a radio landing outside it was invisible), a hand-set serial-port override quietly outranked the device you'd just approved in the "Radio detected" window, and one whole family of boards (Espressif-based radios like recent Heltec/T-Deck models) never received a stable device name at all — the exact combination found live on a fleet machine this week. All three are fixed: every serial port is scanned, choosing a radio in the detection window clears any stale override, and Espressif boards get the same stable name as everyone else.

+

**Mesh signal strength is honest now.** Every peer heard over Reticulum radio reported a signal strength of exactly 0 — which is also what you'd see with no radio at all, and what peers reached over the internet showed. Real receptions now show their true signal reading, and anything that arrived over a relay or the internet says so by showing none — so "the radio is working" and "the internet is doing the radio's job" no longer look identical. (The reading depends on the radio's firmware reporting it; boards that don't report per-packet signal stats show "unknown" rather than a made-up number, and the new radio diagnostics show at a glance whether yours reports them.)

+

**A background error that repeated every 90 seconds, forever, is gone.** After setting up a node from its recovery phrase, the node kept introducing itself to its federation partners with its old temporary identity papers while signing with its new ones — every partner rejected the introduction, and both sides logged an error about it every minute and a half until the next restart. The identity switch now updates everything at once, a rejected introduction is no longer misreported as delivered, and a partner who has already answered is no longer re-asked on every cycle.

+
+
diff --git a/scripts/sync-whats-new.py b/scripts/sync-whats-new.py index 2afd3420..20980e2e 100755 --- a/scripts/sync-whats-new.py +++ b/scripts/sync-whats-new.py @@ -28,6 +28,15 @@ MONTHS = ["", "January", "February", "March", "April", "May", "June", "July", HEADER_RE = re.compile(r"^## (v\d+\.\d+\.\d+\S*) \((\d{4})-(\d{2})-(\d{2})\)") +# A header that names a version but carries no YYYY-MM-DD date. Such a line +# does not match HEADER_RE, so the version is INVISIBLE here — the tool then +# reports "all present" while the very release being cut has no modal block. +# That is what happened to v1.8.4-alpha on 2026-08-20: the changelog said +# "(draft — date set at cut)", this check passed, and create-release then +# aborted at the frontend step because the built bundle contained no version +# string at all (the What's New modal is the only place one appears). +UNDATED_RE = re.compile(r"^## (v\d+\.\d+\.\d+\S*) \((?!\d{4}-\d{2}-\d{2}\))") + def parse_changelog(): """Return [(version, 'Month D, YYYY', [bullet, ...]), ...] newest-first.""" @@ -48,6 +57,16 @@ def parse_changelog(): return entries +def undated_versions(): + """[(version, whole line), ...] for version headers with no real date.""" + found = [] + for line in CHANGELOG.read_text().splitlines(): + m = UNDATED_RE.match(line) + if m: + found.append((m.group(1), line.strip())) + return found + + def existing_versions(): text = MODAL.read_text() return set(re.findall(r"", text)) @@ -78,6 +97,21 @@ def render_block(entry): def main(): check = "--check" in sys.argv + + # Refuse to reason about a changelog whose newest entry has no date: + # silently skipping it is what makes this check pass while the release + # is in fact missing its modal block. + undated = undated_versions() + if undated: + for ver, line in undated: + print(f"FAIL: CHANGELOG entry for {ver} carries no release date:", + file=sys.stderr) + print(f" {line}", file=sys.stderr) + print("A dated '## vX.Y.Z (YYYY-MM-DD)' header is required before the " + "modal can be synced — without it the version is invisible to " + "this tool and never reaches the built frontend.", file=sys.stderr) + return 1 + entries = parse_changelog() have = existing_versions() missing = [e for e in entries if e["ver"] not in have]