<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-hub/src/meshbay_hub/static/hub-client.js, branch 0.20</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.20</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.20'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-10-15T07:33:28Z</updated>
<entry>
<title>feat(hub): one account, several linked nodes</title>
<updated>2026-10-15T07:33:28Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-10-15T07:33:28Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=1e8eae1f3de765fcd8163716de6e5fe5aab5a034'/>
<id>urn:sha1:1e8eae1f3de765fcd8163716de6e5fe5aab5a034</id>
<content type='text'>
An account links up to MAX_NODES_PER_ACCOUNT node keys (node_keys table)
instead of the one users.pk_node_ed25519; the migration carries each
account's key over and the downgrade keeps the last one linked.
/v1/nodes/auth accepts any linked key, and its token names the key that
won it (nk). PUT /me/node_key adds a key, idempotently, 409 past the
limit; DELETE /me/node_key names the key: a browser session any of its
own, a node only its own. Unlinking a key closes that node's socket and
refuses its tokens at once, on REST and on the socket; the account's other
nodes are untouched. /pubkeys no longer gives a node key to other accounts,
and gives the caller its last linked one, which installed desktop apps read.

The node reports waiting_for_node_key while its key is unlinked and signs
in again as soon as the hub says so, so a key linked again is back within
seconds; unlinking itself signs in first to hold a token naming its key.

Profile -&gt; Linked nodes lists each node (connected, last seen, IP, linked
since, This machine in the desktop app) with Copy and Unlink, and adds a
key by hand. The Node page banner, Create group and the desktop app add
this machine's key instead of replacing another's. Messages that pointed
at Settings -&gt; Link Node point at Profile -&gt; Linked nodes.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: browser access, decided in the desktop application</title>
<updated>2026-09-30T15:13:40Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-30T15:13:40Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=b1ebcdeb9082457972c41a47e77494902335d262'/>
<id>urn:sha1:b1ebcdeb9082457972c41a47e77494902335d262</id>
<content type='text'>
Off for an account made there: its identities stay on the device and nothing is
left on nodes. Turned on from the Profile page behind a native confirmation;
each node is settled when its group next opens. The hub keeps a mirror a
browser reads to say why a group will not open; it grants nothing.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(client): the desktop application keeps M and every node identity in its main process</title>
<updated>2026-09-30T14:58:31Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-30T14:58:31Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=6d167392f6f8ede37e2794a68a3738f8ba03131d'/>
<id>urn:sha1:6d167392f6f8ede37e2794a68a3738f8ba03131d</id>
<content type='text'>
keyring.js derives, opens, mints, seals, signs and agrees there; the page gets
public keys and a handle. Argon2 comes from the page's own WebAssembly build
(Electron's crypto has none). Without OS key storage the page keeps its keys as
a browser does. A node's bundle is settled after connecting, re-sealed when the
key changed.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat: bundles sealed per node under the passphrase and the hub's pepper</title>
<updated>2026-09-30T13:06:14Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-30T13:06:14Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=91297944791a36f30302ef8c86dd69ebeb177671'/>
<id>urn:sha1:91297944791a36f30302ef8c86dd69ebeb177671</id>
<content type='text'>
The session key is M = HKDF(Argon2(passphrase) || pepper, account id); each
node's bundle key and the playlist key derive from it. Bundles are MBK3, bound
to account and node; MBK1/MBK2 are refused by name, never replaced silently.
Playlists move to key v2 and are re-sealed over unreadable node copies.

Co-Authored-By: Claude Opus 5.5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>fix(hub): stop keeping a copy of every group's file listing in the browser</title>
<updated>2026-09-19T08:14:27Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-19T08:14:27Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=e1bce3b8d5835c3c70208d39b5b2c66787625e15'/>
<id>urn:sha1:e1bce3b8d5835c3c70208d39b5b2c66787625e15</id>
<content type='text'>
`group_indexes` was an IndexedDB store holding a decrypted copy of each group's
index — every file's name, path, size, hash and uploader — written on every index
and on every delta, from three call sites.

It was the cross-group search of Phase 10b: `doSearch` read `getAllCachedIndexes`
and searched those records instead of dialling anything. On 2026-08-28 Search
began dialling the nodes, and that commit removed the reader and left the writers.
Since then the browser has gone on building a cleartext file listing that nothing
consulted, that no sign-out removed — the key database is a different one — and
that grew with every group ever opened. L7, at rest: kept code that nothing calls
does not sit still.

Drawing a group's files while its node is unreachable is the only thing such a
cache buys, and it is not wanted: a listing that cannot be opened is worse than an
honest absence. So there is nothing to read it with, and the writers go.

The store stays in the schema and is emptied instead. Dropping it needs a version
bump, a version bump is an upgrade another tab can block, and playlists share this
database — so the tidier change is the one with a failure mode. `purgeGroupIndexCache`
runs once per browser behind a flag, which clears what is already on people's
machines; a browser that refuses storage simply runs it again, which is harmless
because it is idempotent.

Three guards, each checked by reintroducing the fault: only `openDB` and the purge
may touch the store, the purge may only clear it, and the purge must actually be
called at start-up — a purge nobody calls is the same defect wearing the opposite
hat.

`test_sticky_header.py[firefox]` reports twelve setup errors in a full run here.
A Firefox instance is open on this machine, which is the trap CLAUDE.md describes;
the same twelve appear with these changes stashed, and the `[chrome]` half of the
same file, covering the same geometry, is clean.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>docs: cite MESHBAY_DESIGN.md and a section instead of the merged notes</title>
<updated>2026-09-19T00:12:47Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-19T00:12:47Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=20a824118c09af15d6c338db4c9480ffe5cbcdb6'/>
<id>urn:sha1:20a824118c09af15d6c338db4c9480ffe5cbcdb6</id>
<content type='text'>
The per-feature design notes were merged into docs/MESHBAY_DESIGN.md and
deleted from the tree on 2026-09-11, but ~230 comments across the three
packages still named them — usually written `docs/musicbay.md §3.2`, as
though the file were still in docs/. A reader had to know §16 existed to
resolve any of them. They now name the section directly.

Every mapping comes from §16, the concordance, which already records where
each old section landed: musicbay -&gt; §9.8, mediacenter -&gt; §9.7 for the
Videos app and §6.5 where the subject is derived data, photos -&gt; §9.9,
auth-confirm -&gt; §3.6, refactoring-search -&gt; §9.11, invite-pairing-v1 -&gt;
§3.4, per-node-identity-v1 -&gt; §3.2, captcha -&gt; §7.7, chat-sender-keys -&gt;
§4.5, apps/refactor-groups -&gt; §9.1–§9.4, desktop-client-v1 per section.
Bare citations of the same documents (`draft-v6 §2.11`, `§4.8`, `§3.4`)
are retargeted too: those collide with real section numbers in the design
document, so leaving them would have been worse than the named form.

Four cases the concordance does not cover, each decided rather than guessed:

  Sub-item references into documents that no longer exist — mediacenter's
  `§3.3 row 4`, `§3.4b/c`, `apps.md §3b` — name rows and sub-items §9.7 and
  §9.2 do not reproduce. The module-level citation stays; the sub-item
  pointer is dropped.

  The V-findings keep their labels but lose the dead `§10.1/` prefix.
  §13.8 lists V1–V13 as per-application open items, which is not what the
  labels mean in these comments, so pointing them at §13.8 would have been
  a false citation.

  `apps.md §5`'s virtualization requirement has no counterpart anywhere in
  the design document. The requirement is stated in the comment itself, so
  the citation is dropped rather than aimed at a section that does not say
  it.

  Comments that attributed a *sentence* to an old note — musicbay's "several
  thousand files" example, its "what got measured" note, its measured
  ~11%/~26% cover-art figures, the "original no root, whole shared tree"
  call — state the fact without attribution now. §9.8 does not contain those
  sentences and citing it for them would have been wrong.

CLAUDE.md's "a reference to a document that no longer exists" row now says
the concordance is for git history and out-of-tree material; the code cites
sections directly.

Verified: 2851 passed, 4 skipped. The 12 errors in the run are the Firefox
leg of test_sticky_header.py's browser harness, which is broken at the
browser level on this machine — headless Firefox (snap) dies with
`[GFX1-]: RenderCompositorSWGL failed mapping default framebuffer`, renders
nothing, and the probe exits `{"error": "no measurement"}` after its full
90s wait. Chrome runs the same 12 assertions in 3.2s and passes. Nothing
here can affect it: every changed line in style.css is inside a comment.
Also checked: ast.parse on every changed .py, `node --check` on every
changed .js, the /* */ balance in style.css, and that no changed line
exceeds the width its file already used.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>auth: a sign-out during a renewal must not write half a session</title>
<updated>2026-09-17T08:26:09Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-17T08:26:09Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=1fd284dbe33d05fd5037b172fe652a8a98f7b68d'/>
<id>urn:sha1:1fd284dbe33d05fd5037b172fe652a8a98f7b68d</id>
<content type='text'>
`{ ..._auth }` after the await spreads a null _auth to {}, so the renewal
stored a token with no username and no userId. The app renders the
signed-in interface from that and throws on user.username[0] — a blank
page on every load, in localStorage, until the site's data is cleared.

The sign-out wins the race now, and loadAuth treats an identity-less
object as signed out so a browser already holding one heals itself.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>spa: a blank page can never be silent again</title>
<updated>2026-09-17T01:40:52Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-17T01:40:52Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=f4fd6db8faa15bf02f38a14b652cc46936f8d6bf'/>
<id>urn:sha1:f4fd6db8faa15bf02f38a14b652cc46936f8d6bf</id>
<content type='text'>
boot-guard.js is a classic script loaded before the module graph, so it
survives the graph failing to link. If #app is still empty after ten
seconds it names what failed and offers a reset of this origin — cache,
storage, databases and the service worker, which clearing the cache does
not touch.

Two real defects found building it: openDB never settled when an upgrade
was blocked by another tab, and a connection it gave up on stayed open
and squatted the database.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>playlists: the store, and putting it on nodes</title>
<updated>2026-09-16T09:52:03Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-16T09:52:03Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=2e973795383b71f63ae9e3bef0e5dfc7930b4c90'/>
<id>urn:sha1:2e973795383b71f63ae9e3bef0e5dfc7930b4c90</id>
<content type='text'>
playlists.js is IndexedDB, WebCrypto and a transport, and node has no
IndexedDB — so it is driven in Chrome against a node stubbed to record
what it was handed, which is also how what leaves the browser is
checked to be sealed.

Sync asks the node what it holds (user_blob_list) rather than comparing
against the merged watermark, which says nothing about that node: the
first version pushed every body on every sync. A tombstoned playlist's
body is deleted as each node is reached, or the quota fills with graves.

The database version and its stores stay in hub-client.js — two modules
opening one database at versions of their own is a VersionError thrown
at whichever runs second.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
</content>
</entry>
<entry>
<title>feat(hub): session lifetime is an admin setting, and a browser signs out when idle</title>
<updated>2026-09-15T00:21:01Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-15T00:16:39Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=73ad8e4eb566fe682107fa7e50ef624591199e99'/>
<id>urn:sha1:73ad8e4eb566fe682107fa7e50ef624591199e99</id>
<content type='text'>
Browser idle sign-out (media playback counts as activity; not the desktop app),
refresh idle window and maximum session length, in hours. Sign-out now revokes
on the hub, and the profile has "sign out everywhere".

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01XuNrwLf5EFWCMHzfoEvnpm
</content>
</entry>
</feed>
