<feed xmlns='http://www.w3.org/2005/Atom'>
<title>meshbay.git/packages/meshbay-hub/tests/test_streamed_download_reliability.py, branch 0.13</title>
<subtitle>MeshBay — read-only public mirror</subtitle>
<id>https://git.meshbay.org/meshbay.git/atom?h=0.13</id>
<link rel='self' href='https://git.meshbay.org/meshbay.git/atom?h=0.13'/>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/'/>
<updated>2026-09-09T10:43:08Z</updated>
<entry>
<title>fix(spa): wake the download worker before handing it a stream</title>
<updated>2026-09-09T10:43:08Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-09T10:43:08Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=dee57df42a525cead93fa30b4e7fa38a489d5b11'/>
<id>urn:sha1:dee57df42a525cead93fa30b4e7fa38a489d5b11</id>
<content type='text'>
Reported from Chrome: a download started while an upload was running took
thirty seconds to begin, every time. The console named it exactly —

    /_mbdl/mtty5btz-sbmgdegx  404 ()
    [MeshBay] the worker did not answer the download within 15s (attempt 1)

A 404 from the hub means the request reached the *network*: the worker looked,
found no entry for that id and let it through. So the worker was alive and
controlling the page, and the message handing it the stream had simply never
been processed.

`pending` lives in the worker's memory, and a worker with nothing to do is
terminated within tens of seconds. A WebRTC upload gives it no events at all,
so minutes of uploading leave it dead; the stream posted to it is lost,
silently, and the iframe then wakes it with nothing to find.

`mbdl-ping` already existed for this exact reason -- sent every ten seconds
*while* writing, because a streaming response does not count as activity.
Nothing sent one before *starting*. So a download now wakes the worker and waits
for the pong, and `sw.js` answers `mbdl-ready` once it has actually stored the
entry, which the page waits for before navigating: confirmed rather than
assumed. A worker that predates the ack sends nothing and the page navigates
anyway, which is what it did before.

This cause was measured and wrongly dismissed hours earlier, with an idle probe
that made the worker work between its own attempts -- it never actually slept.
A measurement that does not reproduce the conditions refutes nothing. The
harness now models a worker that is asleep: a ping wakes it, and anything else
posted while it sleeps is lost, which is what made the failure silent.

`test_backpressure_is_real` read the first `worker.postMessage` in the function
to check that the readable half is transferred rather than copied. The wake-up
put a ping in front of it, so it began inspecting a call that carries only a
port -- and kept passing. It now checks every post, each bounded by its own
call, since the keep-alive ping transfers nothing at all. Same shape as the
upload-seal contract this morning: a guard that reads "the first" stops
guarding the moment something is inserted before it.

Hub suite 851 passed. Both new cases checked against the unfixed source.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
</content>
</entry>
<entry>
<title>fix(spa): stop reloading a healthy page at boot</title>
<updated>2026-09-09T09:21:50Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-09T09:21:50Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=d4adb140b9e7250b0f02d9651e4b9db87b74df81'/>
<id>urn:sha1:d4adb140b9e7250b0f02d9651e4b9db87b74df81</id>
<content type='text'>
Reported from Chrome: connecting to a group triggered a page refresh within
seconds, taking the WebRTC session down with it.

The boot check added with the bypass repair asked its question by *performing a
download* -- a four-byte stream through a hidden iframe. Chrome rations the
downloads a page may start without a user gesture to about three, measured: on
a first visit three consecutive attempts went served, served, refused. So the
check competed with the person's own downloads for that budget, and its answer
depended on how much of the budget was left. On a healthy page it concluded the
worker could not serve, and reloaded.

The same mistake the repair was written to fix, from the other side: paying a
capability to obtain a diagnostic.

The replacement costs nothing and asks nothing. Measured on Chrome, at document
start, before anything registers:

    first visit       controller false, registration false
    ordinary reload   controller true,  registration true
    hard reload       controller false, registration true

Being uncontrolled while an active registration already exists names a
hard-reloaded document exactly, so that is now the whole of the evidence. A
first visit is uncontrolled too and is not a bypass -- the worker is installing
and will claim the page in a moment -- which is precisely the case that was
reloading.

Four cases, each checked against the unfixed source, including that priming
performs no download at all. Hub suite 847 passed.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
</content>
</entry>
<entry>
<title>docs(spa): say that a download with no folder cannot be paused</title>
<updated>2026-09-09T08:59:59Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-09T08:59:59Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=99ae7f6955ffc94cd973822d4e2fd5a8c952f563'/>
<id>urn:sha1:99ae7f6955ffc94cd973822d4e2fd5a8c952f563</id>
<content type='text'>
Reported from testing 7a: pause worked in the desktop app and no button
appeared in Chrome. That is the design working — without a granted download
folder, "save automatically" means the service worker, and that target is a
download the browser already owns — but nothing anywhere said so, and choosing
a folder looked like a question of where files land.

So the Settings line now says what it costs not to choose one, in all ten
catalogues. It renders only where a folder can be chosen at all, which is
exactly the browsers the advice applies to.

Also pins the tier table the pause button is drawn from: a granted folder, a
save dialog and the desktop sink can be paused, a service-worker stream cannot.
Four cases through the real `_openDownloadTarget`, and one more that reads the
value off the real `downloads.js` rather than a stub of it -- the first version
of these stubs did not carry the field at all, so the cases would have passed
while checking nothing.

Hub suite 847 passed.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
</content>
</entry>
<entry>
<title>fix(spa): repair a bypassed page in seconds, not half a minute</title>
<updated>2026-09-09T08:32:13Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-09T08:32:13Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=cb43495f998015850f34829329aa4509bd55d2cb'/>
<id>urn:sha1:cb43495f998015850f34829329aa4509bd55d2cb</id>
<content type='text'>
The repair worked but arrived too late to help: about thirty seconds after a
hard reload, by which time four downloads had been started and hung, and the
page reloading under them read as an unexplained refresh.

Two delays, both removed.

`_claimController` waited its whole control budget before asking for the claim.
A page that is uncontrolled while an active worker exists will never be claimed
on its own -- a document fetched by a hard reload is exactly that shape -- so
the fifteen seconds were spent waiting for something that was not coming. The
claim is now asked for first; waiting is the fallback, not the opening move.
Measured in the harness: 6042ms of a 6000ms budget before, milliseconds after.

And a download that starts while the self-test is still running now waits for
it rather than racing it. Otherwise the click spends both its attempts failing
on a path that is about to be repaired, which is what put four frozen rows on
screen.

Hub suite 836 passed. Both new cases were checked against the unfixed source.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
</content>
</entry>
<entry>
<title>fix(spa): repair a page the download worker cannot serve</title>
<updated>2026-09-09T08:05:30Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-09T08:05:30Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=fc148e185c01b2e25361c7625a67d310d2e1d288'/>
<id>urn:sha1:fc148e185c01b2e25361c7625a67d310d2e1d288</id>
<content type='text'>
Downloads on Firefox failed with "the worker did not answer the download within
15s", every time, for one operator, while the same profile driven from here
succeeded every time. Their own test sequence found it: a freshly started
browser downloaded four files out of four, twice; one Ctrl+F5 and every
attempt afterwards failed; restart, fine again; Ctrl+F5 before any attempt and
the very first one failed.

A document fetched by a hard reload is loaded with the service worker bypassed.
It can still be claimed afterwards, so `navigator.serviceWorker.controller`
comes back and every check in `_claimController` passes — but the navigations
that document starts keep missing the worker, and the hidden iframe a streamed
download needs is a navigation. On Firefox and Safari that is the only way to
write a file too large to hold in memory, so the download cannot happen at all,
for the life of the page.

Being controlled is not being servable, so priming now asks the question
directly instead of inferring it: a four-byte stream and a hidden iframe,
exactly as a real download would, torn down completely so nothing lands in the
download folder. When it goes unanswered the page reloads once, ordinarily,
which puts it back under the worker. The flag lives in sessionStorage rather
than a variable because it has to survive the reload it triggers, and because a
page that is still unservable afterwards must stop rather than loop.

Also stops telling people to change browser. The message said "use the desktop
app, or Chrome or Edge" for a state an ordinary reload undoes, on the one path
Firefox has no alternative to; all ten catalogues now say to reload first.

The hard reloads were on my instruction: the SPA's HTML is served `no-store`,
so a plain reload has always picked up a new build and Ctrl+F5 was never needed.

Hub suite 834 passed.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
</content>
</entry>
<entry>
<title>fix(spa): nothing on the worker path may wait for ever</title>
<updated>2026-09-09T00:45:23Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-09T00:45:23Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=88de521725d8de10eb8bf956b31cf9ce70a81f33'/>
<id>urn:sha1:88de521725d8de10eb8bf956b31cf9ce70a81f33</id>
<content type='text'>
Four downloads on Firefox sat at "preparing" indefinitely, with the target queue
already bypassed there, so each opening was hanging on its own. The node journal
showed `d=0/8(q0) u=0/8(q0)` — no transfer had been asked for yet.

`_claimController` had two waits with no deadline at all,
`navigator.serviceWorker.register()` and `navigator.serviceWorker.ready`, while
SW_CONTROL_BUDGET_MS bounded only the wait that comes after them. `_swPromise`
is shared, so one unsettled wait left every download on the page suspended on
the same promise for the life of the tab.

Measured on Firefox 154, against a local 127.0.0.1 site so no hub was involved:
a worker that installs gives register() in 8ms and ready in 0ms; a worker whose
install handler rejects gives register() in 7ms and a `ready` that never settles
— still pending past ten seconds. register() resolves as soon as the
registration object exists, carrying nothing but an *installing* worker; ready
is what waits for an active one.

Every wait is now inside one budget, with two carve-outs so that a deadline
never costs a capability. A `ready` that times out while registration.active is
set is not fatal: ready may be waiting on a newer worker that cannot install
while an older one serves perfectly well. And the mbdl-claim recovery keeps its
own budget outside the deadline, because giving up there would cost Firefox the
only unbounded way it has to write a download to disk.

A deadline alone would have been a better-explained failure rather than a fix: a
registration stuck with nothing but an installing worker does not heal, and
every later visit finds the same one. So when ready times out with no active
worker, the registration is discarded and asked for once more with a fresh
budget, and the page repairs itself instead of needing developer tools.

Four cases pinned, each checked against the unfixed source. Hub suite 830
passed.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
</content>
</entry>
<entry>
<title>fix(hub): keep the download worker alive, and never hang on a dead sink</title>
<updated>2026-09-08T20:53:53Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-08T20:53:53Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=051100ca32f72dd8f28489e1b6cb084f321b3b54'/>
<id>urn:sha1:051100ca32f72dd8f28489e1b6cb084f321b3b54</id>
<content type='text'>
A download froze part-way through, on Firefox, with an empty console and a node
that stayed perfectly healthy. Three separate measurements cleared the node
(615 MB pulled whole over MNP), the transport (three files interleaved on one
connection, 1.5 GB, all whole) and the service worker (three concurrent 150 MB
streams in real Firefox 154) — because none of them was wrong.

The empty console was the evidence. `_sendAndWait` logs every timeout, so no
chunk request had expired: the client was not waiting on the node. Of the three
awaits left on that path only one was unbounded.

**A service worker with no event for about thirty seconds is terminated**, and
`respondWith(new Response(stream))` does not extend its life while the response
is still being written. The reader vanished mid-file and `writable.write()` then
never resolved and never rejected — no error, no log, no failed transfer, just a
progress bar that stops. The first stress probe wrote 450 MB in two seconds and
passed: fast enough to hide it entirely.

Measured in Firefox 154, writing 1 MB every 2 s: without the ping it stalled at
17 MB after 59 s; with it, 40 MB in 80 s, complete.

  - the page pings the worker every 10 s while it writes, and the worker
    answers. Receiving a message is an event, and an event resets the timer;
  - that interval stops itself after two minutes with no write. A target can be
    opened and never written to — a transfer cancelled while it waits for a slot
    never runs, so nothing calls close() or abort() — and a timer nobody clears
    pings for the life of the page. It also kept the Node test process alive for
    ever, which is the same defect wearing a louder symptom;
  - `writable.write()` is bounded at 60 s and fails with a message naming the
    chunk. That does not fix whatever stopped a sink; it turns an unexplainable
    freeze into a failed transfer that says so, which is the difference between
    a mystery and a bug report.

Also: `Content-Disposition` lost a filename to a single apostrophe.
`encodeURIComponent` leaves `'` alone and `'` is the delimiter in RFC 5987's
`filename*=&lt;charset&gt;'&lt;lang&gt;'&lt;value&gt;`, so the header became unparseable and
Firefox named the file after the URL — 449 MB of film arrived complete as
"mtsshk9w-ohqty535". `(`, `)` and `*` get the same treatment, and a plain ASCII
`filename=` rides alongside.

Co-Authored-By: Claude Opus 5 &lt;noreply@anthropic.com&gt;
Claude-Session: https://claude.ai/code/session_01HCGdheDLxGReuKHga3BtST
</content>
</entry>
<entry>
<title>fix(hub): make the streamed download path reliable, and clean up after an abort</title>
<updated>2026-09-08T11:20:29Z</updated>
<author>
<name>Christophe Besson</name>
<email>cbesson@gmail.com</email>
</author>
<published>2026-09-08T11:20:29Z</published>
<link rel='alternate' type='text/html' href='https://git.meshbay.org/meshbay.git/commit/?id=3f2bb22586d3e1aef765149b555ccc8e174ce7eb'/>
<id>urn:sha1:3f2bb22586d3e1aef765149b555ccc8e174ce7eb</id>
<content type='text'>
On Firefox and Safari the service worker is the only unbounded way to write a
download to disk: neither has the File System Access API, and OPFS is not a
substitute — measured on Firefox 154, its quota is exactly 10% of the volume's
size (389,233,459 bytes of a 3,892,334,592-byte volume, refused to the byte),
which a film exceeds. So when this path declines, a large download has nowhere
left to go, which makes its reliability a correctness property.

Four ways it declined, all of them avoidable:

- it was registered inside the first click on Download, so that click paid
  install, activate and claim while somebody watched a button do nothing;
- `_swReady` cached a null for the life of the page. One slow first click left
  the tab unable to stream anything again, curable only by a reload nobody knew
  to do. Only a successful controller is remembered now;
- control was waited for with a 3 s cap. It is 15 s, and a page that is active
  but not controlled asks the worker to claim again (`mbdl-claim`) instead of
  declaring the path unavailable;
- a missed navigation gave up at once. It gets a second attempt with a fresh id
  and iframe, the failed one torn down completely first.

Also closes a MessagePort leaked per download, and gives the reason a name
(`lastStreamFailure`) so a refusal can say what happened. The timeouts became
parameters: the defaults are the production values, no caller passes any, and
the tests do not spend a minute waiting.

`openTarget` gets an unrelated but adjacent fix, in the same file: it creates
the destination with `getFileHandle({create: true})`, so an empty file exists
before the first byte, and `abort()` leaves the target untouched — every
cancelled download left a 0-byte file behind, and since `freeName` avoids
collisions, three cancels left film.mkv, film (2).mkv and film (3).mkv, all
empty. Its `abort()` now removes the entry. Safe here and only here, because
`freeName` guarantees the name was not taken: the `showSaveFilePicker` path
must not do the same, where the person may have picked an existing file whose
contents `abort()` correctly preserves. Verified by hand in Chrome.

test_streamed_download_reliability.py runs the real module under Node against a
stubbed browser — it fails if the null is cached again.

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