aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--CLAUDE.md41
1 files changed, 41 insertions, 0 deletions
diff --git a/CLAUDE.md b/CLAUDE.md
index f06281e..b02eda7 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -511,6 +511,47 @@ anything that assumes one key per person.
Nothing errored. When a chain degrades, check what the floor costs on every
platform that will reach it
+- **A service worker with nothing to do is killed, and a streaming response is
+ not "something to do".** Firefox terminates an idle worker after about thirty
+ seconds; `event.respondWith(new Response(stream))` does not extend its life
+ while the page is still writing to that stream. So the reader vanished
+ mid-file and `writable.write()` **never resolved and never rejected** — no
+ error, no log, no failed transfer, just a progress bar that stopped near the
+ end. Measured 2026-09-08 in Firefox 154, writing 1 MB every 2 s: stalled at
+ 17 MB after 59 s; with a 10-second ping to the worker, 40 MB in 80 s,
+ complete. The page pings while it writes and the worker answers, because
+ receiving a message is the event that resets the timer.
+ Three things this cost, all worth remembering. **The first stress probe wrote
+ 450 MB in two seconds and passed** — fast enough to hide the bug entirely, so
+ a probe for anything time-based has to be paced like the real thing. **The
+ node was innocent and three measurements proved it** (615 MB pulled whole
+ over MNP, three files interleaved on one connection, three concurrent worker
+ streams), which is exactly what made the fault unfindable: nothing was wrong
+ anywhere anyone looked. And **the empty console was the evidence**, not the
+ absence of it: `_sendAndWait` logs every timeout, so silence eliminated
+ everything that reports itself and left the one `await` on that path with no
+ bound. Every await on a download path is now bounded and says which chunk it
+ gave up on — an unbounded one is a freeze nobody can report
+
+- **Three headers decide whether a page may frame itself, and they must agree.**
+ The same streamed download navigates a hidden iframe to `/_mbdl/<id>`.
+ `frame-src` was reCAPTCHA's two origins with no `'self'`, `frame-ancestors`
+ was `'none'`, and `X-Frame-Options` was `DENY`. Each was fixed in turn, each
+ time costing a redeploy and a retest, and **all three were visible in one
+ `curl -I` against the deployed hub** — which is where that should have
+ started. `'self'`/`SAMEORIGIN` refuse every foreign origin exactly as
+ `'none'`/`DENY` do; what they add is this origin framing itself, which is all
+ the download needed. When a symptom points at a mechanism, enumerate
+ everything that governs that mechanism and check the set at once
+
+- **`encodeURIComponent` does not escape `'`, and `'` is RFC 5987's delimiter.**
+ `Content-Disposition: filename*=UTF-8''<value>` became unparseable for any
+ name with an apostrophe, so the browser named the file after the URL: 449 MB
+ of film arrived complete and correct, called `mtsshk9w-ohqty535`. `(`, `)` and
+ `*` are excluded from attr-char for the same reason. A plain ASCII
+ `filename=` rides alongside now, so the next surprise loses accents rather
+ than the name
+
- **Two elements each claiming `100vh - 52px`, one inside the other's padding.**
`.layout` and `.page-center` both reserved the viewport below the header, and
`main`'s `24px` top and bottom were added on top — a permanent 48px scrollbar