<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-hub/tests/test_transport_contracts.py, branch 0.5</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.5</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.5'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-08-17T15:38:00Z</updated>
<entry>
<title>fix(hub): a session that renews itself</title>
<updated>2026-08-17T15:38:00Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-17T15:38:00Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=6562665c80f96f30fef95af83a0abcf71f41795f'/>
<id>urn:sha1:6562665c80f96f30fef95af83a0abcf71f41795f</id>
<content type='text'>
Reported: after an hour of watching a film, every action answers "token expired
or invalid", with signing out and back in as the only way on. Reopening the tab
the next day did the same.

The access token lasts an hour and the refresh token thirty days, and nothing
used the second one. `hubFetch` reported a 401 like any other error, and
watching a film is precisely an hour in which the hub hears nothing at all,
because the video travels over WebRTC. So the token aged out with no request to
notice, and a tab reopened the next morning presented a stale token with a
perfectly good refresh token sitting beside it in localStorage.

Underneath was the reason it could not be recovered from. The hub *rotates*:
the refresh endpoint revokes the token presented, returns a replacement, and
treats a revoked one presented again as theft, revoking the whole family. The
client kept only the access token out of that response. So the refresh token was
spent on first use and the second attempt did not merely fail — it destroyed the
family. Which is exactly the reported symptom.

Renewal now happens on a margin, on returning to the tab, on mount, and on a 401
with the request replayed. Concurrent renewals share one request: two 401s
racing would otherwise present the same refresh token twice, and the hub cannot
tell that from theft, so the remedy would have been worse than the fault. A
refusal signs out cleanly rather than leaving a session that fails every call
while looking signed in.

The lifetime goes to four hours, which is not what makes long sessions work —
renewal is — but is what someone has to notice by if renewal itself breaks. An
hour was less than a feature film. Twelve was considered and declined: it widens
the window in which a leaked token cannot be turned off, and it lets the renewal
path go a whole day between uses, which is how it came to be broken here without
anyone noticing. Production sets this in its own hub.toml, so both moved.

The tests run the shipped code against a hub that enforces rotation, because a
stub that accepted the same refresh token twice would have passed against the
broken client. Checked that dropping the rotated token reproduces the revoked
family, so the guard is guarding something.

Also widens the orphan-setter rule to ignore `setX` functions declared in the
module: `setAuth` is not a hook setter, and a rule that cries wolf is one
somebody eventually silences. Verified it still catches a real orphan.
</content>
</entry>
<entry>
<title>feat(hub): chat, presence, a Profile page, and downloads that do not freeze</title>
<updated>2026-08-16T13:29:11Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-08-16T13:29:11Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=84e778d5cdd1bb5cded8a7c0238797c17d48666c'/>
<id>urn:sha1:84e778d5cdd1bb5cded8a7c0238797c17d48666c</id>
<content type='text'>
Chat opens on the newest hundred messages, loads fifty older on demand with the
reading position anchored — the distance from the *bottom*, since everything
above the viewport just grew — and follows new messages only when the reader was
already at the end. Day separators, sender grouping, an unread marker, and a
jump-to-latest pill. Messages are keyed by id: index keys plus prepending makes
Preact reuse the wrong bubbles.

A presence dot per group in the sidebar, three states, each backed by something:
the hub's registry, or a connection this browser made or failed to make. Never
colour alone — red and green are the pair colour-blind readers cannot separate —
so each dot carries a title and an aria-label.

Profile is split out of Settings: identity, node link, pinned node identities and
account deletion. Mixing them put an irreversible button two scrolls under a
theme picker. The create-group page loses its centred 520 px card, which left
190 px of margin either side, and its two button panels become a radio group —
a button conveys no chosen state to a screen reader, and side by side they read
as two independent actions rather than one either/or.

The Files toolbar shows its actions as icon buttons the moment Select is on,
disabled when they do not apply rather than appearing and vanishing. On a phone
the right-hand group could not wrap and ran 130 px off the screen.

Streamed downloads no longer freeze after one chunk. `registration.active` says
a worker exists, not that this page is controlled by it — and an uncontrolled
page's requests never reach its fetch handler, so the worker took the stream and
was never asked for it, leaving `writer.write()` waiting on backpressure that
would never lift. The page now requires control and the worker confirms it
actually served the request before the sink is trusted.

Fixed on the way: `setActionsOpen` outlived the state it belonged to and threw
on every Files action; the chat scrollbar stopped short of the bottom; the
owner's row sat lower than the rest; About showed a version hardcoded two
releases ago.

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