<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-hub/src, branch 0.16</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.16</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.16'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-28T10:29:20Z</updated>
<entry>
<title>fix(hub): say so when a lazily loaded view cannot be fetched</title>
<updated>2026-09-28T10:29:20Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-28T10:29:20Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=34576a1ccda1ee192e8efbc653cb436111a9c5e8'/>
<id>urn:sha1:34576a1ccda1ee192e8efbc653cb436111a9c5e8</id>
<content type='text'>
A tab opened before a hub deploy got 404 for every module it had not
loaded yet, and lazy.js kept its spinner for good. It now shows a notice
with a Reload button and logs the failure.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(hub): keep a show's detail modal open under the player</title>
<updated>2026-09-28T09:09:12Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-28T09:09:12Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=694a3832f4b57d007b79b3b21dd47b55e6ef3c42'/>
<id>urn:sha1:694a3832f4b57d007b79b3b21dd47b55e6ef3c42</id>
<content type='text'>
Closing the player lands back on the season being watched, with the
episode just started marked. A film's modal still closes on Play.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(hub): the sidebar's Node section follows the node link</title>
<updated>2026-09-27T20:21:07Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-27T20:21:07Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=c7899e2218af26f3bab6a157c70acf5eb7871da6'/>
<id>urn:sha1:c7899e2218af26f3bab6a157c70acf5eb7871da6</id>
<content type='text'>
Reported on a real install: after the first click on Create group, the Node
section (Node, Create group) disappeared from the sidebar until a reload,
although the group was created and the node ran.

hasNodeKey was read once per session change and never again, so a node the
wizard linked stayed out of the sidebar; and that read swallowed its errors,
so a session blip (a refused renewal, then the desktop app's silent device
sign-in) followed by one failed request hid the section for good. The wizard
now tells the app when it has linked or started a node, the app asks again
then and after a group is created, and a failed read is retried -- never
applied to a session that has changed meanwhile.

The wizard also starts the node on its own for a node in waiting_for_hub.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(hub): say "No operator paired" only when the node says so</title>
<updated>2026-09-27T20:21:07Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-27T20:21:07Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=7422a777297347dc83e6c5b2e89c1a6fa99a36b0'/>
<id>urn:sha1:7422a777297347dc83e6c5b2e89c1a6fa99a36b0</id>
<content type='text'>
The Node page showed the banner whenever operator_paired was not true, so a
node that had not yet read its roster -- one still signing in to the hub --
was reported unpaired while its pairing was intact. The node now answers null
until it knows; the page shows the banner for false only.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix: Windows installer and desktop app start and stop the node one way</title>
<updated>2026-09-27T20:20:53Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-27T20:20:53Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=8c7e39b6dca758badec6867ab6610fd5e8d93d1e'/>
<id>urn:sha1:8c7e39b6dca758badec6867ab6610fd5e8d93d1e</id>
<content type='text'>
A 0.16 upgrade in service mode left the previous node running: setup's
unelevated taskkill cannot reach session 0, and it ran in customInstall, which
electron-builder inserts after the files are copied. The locked exe was not
replaced, and the new app talked to the old node ("started but could not link",
"No operator paired").

Installer (build/installer.nsh, build/stop-node.ps1):
- customCheckAppRunning, which runs before uninstallOldVersion and extraction,
  stops the node with an embedded stop-node.ps1: control API, then schtasks
  /end, then Stop-Process, and refuses to half-upgrade if one survives.
- An upgrade keeps the mode it finds (task, launcher, previous install),
  restores the sign-in launcher the old uninstaller deletes, and restarts the
  node the way that mode runs it. A silent upgrade of an "at sign-in" install
  used to end with no autostart and no node.
- The uninstaller removes the task and firewall rules only on a real
  uninstall, not on an update.

Desktop app (src/main.js):
- Start, Stop, Restart and node:start go through the CLI's lifecycle verbs
  instead of a second implementation; a child spawned by Electron also held
  Electron's sockets after the app quit.
- "Only while MeshBay is open" is a real mode: the app starts a provisioned
  node at launch and stops the one it started when it quits.
- Switching modes stops the node first -- deleting a task does not end its
  instance, and a new service found the port taken -- keeps the firewall
  rules every mode needs, and starts the node again. A declined or unanswered
  UAC prompt restores the node instead of leaving it stopped, and says that
  nothing changed.
- waiting_for_hub counts as a node that is up; linking waits for a node that
  answers, with a longer deadline, and reports a version mismatch.

Packaging (packaging/win):
- The service task gets no 72-hour limit, runs on battery and ignores a second
  start; service.ps1 status reports a stale registration so setup re-registers
  it; remove ends the running instance before deleting the task.
- build-node-runtime.ps1 starts the frozen daemon in a throwaway profile
  (smoke-node-runtime.ps1) instead of only asking for --help.

The mode that was "Off (start manually)" is labelled "Only while MeshBay is
open" in all ten catalogues.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(hub): rate-limit per client, not per proxy</title>
<updated>2026-09-27T20:20:14Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-27T20:20:14Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=4fbd2e5c5e21ab0d26bb16245dd656557f87765b'/>
<id>urn:sha1:4fbd2e5c5e21ab0d26bb16245dd656557f87765b</id>
<content type='text'>
Behind Caddy every request's TCP peer is loopback, and the limiter was keyed
on that peer (slowapi's get_remote_address), so each limit was one bucket for
the whole internet: ten node sign-ins a minute shared by every node. A node
starting while others signed in got 429 and sat in waiting_for_hub, which the
desktop app took for no node at all. Key it on client_ip, which already
resolves X-Forwarded-For from a trusted proxy and was written for this.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(node,client): query TMDB only once a language is chosen, in that language</title>
<updated>2026-09-26T12:28:13Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-26T12:28:13Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=eccd465f8954bfd49c3f7d8f02446bc2aa5c4338'/>
<id>urn:sha1:eccd465f8954bfd49c3f7d8f02446bc2aa5c4338</id>
<content type='text'>
TMDB fiches are fetched lazily, on browse, and the fetch used to run whatever
the moment it was first triggered — routinely before the operator had opened
settings and picked a language, so it queried in TMDB's English default. Then
the fiche was cached by tmdb_id alone, with no note of language and a 30-day
TTL, so switching to the intended language afterwards changed nothing: the
English fiche was served until it expired. The operator's only recourse was to
find and wipe the cache by hand (found live 2026-09-26: a whole library indexed
in English although "Français" had been chosen).

Two rules now, both there to make the first fetch the right language rather
than English-then-corrected, and to stop the doubled requests that eventually
get a node rate-limited:

- No language configured, no query. media_meta_req/season_meta_req answer
  confidence 0 and make no TMDB call while tmdb_language is unset; the fetch
  waits for the operator's choice, so the first (and only) query is in it.
  English is now a first-class choice (en-US), not the default of skipping the
  setting.
- Changing the language wipes the metadata cache (ops.set_tmdb_config), so the
  new language takes effect on an already-browsed library. The file-&gt;tmdb
  matches are language-independent and kept. The client refetches on the
  tmdb_config_ack that carries the new language, so the grid updates without a
  page reload.

docs/MESHBAY_DESIGN.md §9.7 states both rules; tests cover the gate and the
cache wipe, and two existing handler harnesses now declare a language.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(hub): default email opt-outs and match Register to Login styling</title>
<updated>2026-09-26T08:26:51Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-26T08:26:51Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=1c07ae23f3694b972e307c50c9c09f1c429f2ea2'/>
<id>urn:sha1:1c07ae23f3694b972e307c50c9c09f1c429f2ea2</id>
<content type='text'>
- Invitation-by-email and recovery-key-by-email boxes now start unchecked;
  mailing a code/key is opt-in. The invite choice still remembers itself
  per account once set.
- Align the two invite-email checkboxes with their label (center, not
  flex-start).
- Register (and its verify/recovery/done steps) now sits on the same dark
  gradient backdrop and frosted card as Login, via a shared AuthShell.
- Make the gradient's top-left corner very slightly less bright
  (#86a3c4 -&gt; #809cbc), on both auth pages.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(hub): the NAT-punch signal needs a shared active group, like the offer relay</title>
<updated>2026-09-26T00:44:52Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-26T00:44:52Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=4500fe3854fd3a499d1139bc89ccc488d104cc29'/>
<id>urn:sha1:4500fe3854fd3a499d1139bc89ccc488d104cc29</id>
<content type='text'>
POST /v1/nodes/{id}/incoming checked only the caller's own address, then revealed
whether the node was connected (404 vs 504) and, with QUIC on, made it punch —
so any authenticated account could poll it for a node's liveness or make a
stranger's node emit a UDP probe. The membership gate webrtc_offer did inline is
now require_shared_active_group() in api/signaling.py, called by both routes; in
notify_incoming it runs before anything depends on the node's connection state,
so a non-member gets one uniform 403 whether the node is up or not.

test_incoming_membership.py holds it (a non-member is refused with a membership
403 whether the node is connected or not; a member passes the gate); red before,
green after. The offer relay is unchanged in behaviour (it now calls the shared
helper); signaling/availability suites pass. Design §7.2 updated.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(protocol): bind the MNP token to the node it is for (E10)</title>
<updated>2026-09-26T00:03:50Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-26T00:03:50Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=2ccb6653e8841d4d6f3ab933f84746cce4e2fe2b'/>
<id>urn:sha1:2ccb6653e8841d4d6f3ab933f84746cce4e2fe2b</id>
<content type='text'>
The audience split stopped a member's node credential from opening the hub API.
It did not stop the credential being *replayed to another node*: the MNP token
carried the member's whole group set and named no node, so a token handed to
node A's operator could be presented to node B the member also belongs to. That
does not read content on B — the handshake still requires proving node B's group
key, which the operator lacks — but it reaches B's pre-proof window and fetches
the member's *encrypted* keypair bundle for B (offline-attackable, bounded,
audited): a disclosure §2.4 says should not follow from hosting a member on A.

The token now names the node it is minted for (a `node` claim = that node's
Ed25519 key), and authorize_token refuses one that names a different key. The
client already knows the target node's key (from /v1/groups/{id}/nodes) and asks
for a token bound to it: POST /v1/nodes/mnp-token takes node_pk, and
transport.connect threads it (group-page, the connection pool and rewrap pass
n.pk_node; reconnect preserves it). A token that names no node is still
accepted, because the hub only mints one for the authenticated requester, so an
unbound token grants nothing across accounts — which also keeps non-binding
callers working with no churn.

Done before deploy, so it folds into the MNP 4.0 flag day rather than needing
its own. Docs: §5.2, register E10, MESHBAY_NODE_PROTOCOL.md §6.3.
test_handshake.py and test_mnp_token.py hold the binding (a token for node A is
refused by node B, accepted by node A; an unbound token still works); red
before, green after. common/node/hub suites green.

Co-Authored-By: Claude Opus 4.8 &lt;noreply@anthropic.com&gt;
</content>
</entry>
</feed>
