<!-- Target: https://github.com/MapleTechLabs/maple/issues/new -->
<!-- Title: -->
effect-sdk (browser): MapleFlush loses telemetry around keepalive budget, cooldown and page unload, and one oversized flush stops trace export for the rest of the page's life

<!-- Body: -->
## Summary

`@maple-dev/effect-sdk` 0.8.2, browser entry `@maple-dev/effect-sdk/client`, `MapleFlush.make` (some items also affect `Maple.layer`) loses spans, logs and session data in several ways:
- **Keepalive limit:** every OTLP flush is a single `fetch(..., { keepalive: true })` request, and Chromium rejects keepalive requests above a 64 KiB in-flight budget.
- **Cooldown:** a failed flush is restored to the buffer and the signal is paused for 60s.
- **Unload ordering:** the unload flush is serialized behind other flushes and is skipped during the cooldown.

The worst case: a single flush larger than ~64 KiB (about 163 spans with small attributes) **permanently stops trace export for the rest of the page's life**. The restored batch only grows, so every retry is rejected again.

Most findings still reproduce on **0.10.0**.

Full report, standalone repro project (Bun + Playwright + mock ingest), SDK source excerpts with line numbers, and raw evidence from every run:
👉 **https://github.com/user-0a/maple-issue-2026-10-08**

## Findings

| ID | Severity | Finding |
|---|---|---|
| BUG-1 | High | One flush whose keepalive body exceeds Chromium's 64 KiB in-flight budget is rejected with `TypeError: Failed to fetch`. The SDK restores the batch and cools down for 60s, and the batch only grows, so trace export never recovers. A burst of 200 spans meant 0 of 265 spans were delivered over 130s, while logs kept flowing. |
| BUG-2 | Medium | traces, logs and metrics are POSTed concurrently (`Promise.all`), each with `keepalive`, so they share one 64 KiB budget. One signal is rejected even though each request fits on its own. |
| BUG-3 | Medium | While a signal is in its 60s cooldown, the `pagehide` / `visibilitychange→hidden` flush is skipped as well. The failed batch and everything since are lost on reload, navigation or close. |
| BUG-4 | Medium | `makeSerializedFlush` queues the unload flush behind any in-flight flush. With a slow ingest, teardown rejects the in-flight POST, which sets the cooldown, so the unload flush is skipped and the tail is lost. One slow POST also stalls all later exports. |
| BUG-5 | Medium | At unload, the OTLP keepalive posts and the session/replay keepalive posts (meta "ended" row, final events) compete for the same budget without shared accounting. Whichever listener runs first wins. |
| BUG-6 | Medium | The final replay chunk is never uploaded on unload: the sequence number is consumed, then an async gzip runs and the document dies before the POST. The server sees chunks 0, 2. Affects `MapleFlush.make` and `Maple.layer`. |
| BUG-7 | Low | At unload, keepalive requests that the server answered with 200 still reject with `Failed to fetch` in the unloading page. The SDK then prints `traces/logs flush failed; cooldown 60s`, `flush skipped (cooldown …)` and `session replay … POST failed`. These are false alarms, and costly to triage. |
| BUG-8 | Low | Non-keepalive session posts in flight during a navigation are aborted and the batch is dropped, although the message says "will retry on next chunk". |
| BUG-9 | Info | `Maple.layer` has no unload flush (documented). A small user-land fix using Effect's own flusher is demonstrated. |

The report also covers smaller items (10k buffer cap behavior, multi-argument log encoding, a `user-agent` header) and hypotheses we tested and ruled out.

## Reproduce

```sh
git clone https://github.com/user-0a/maple-issue-2026-10-08
cd maple-issue-2026-10-08/repro
bun install
bun run scenarios A0 A   # BUG-1: oversized batch → no span ever delivered (deterministic)
bun run scenarios        # everything (~several minutes)
```

`repro/README.md` describes each scenario and its expected output.

## Workaround we're using

Install a `window.fetch` wrapper before `MapleFlush.make()` that changes `keepalive: true` to `false` when the UTF-8 body is larger than ~60 KiB. Steady-state export then recovers: 200/200 spans delivered, versus 0/200 without it. This is scenario `W` in the repo.

## Suggested fixes

These are described in more detail in section 4 of the report:
- **Split every flush** into chunks under a per-request byte budget measured in UTF-8 bytes.
- **Use `keepalive` only on the unload path,** with one page-wide budget shared by the OTLP and session/replay posts.
- **On a keepalive `TypeError`, retry immediately** without keepalive or with smaller chunks, instead of restoring and cooling down.
- **Never skip the unload flush** because of a cooldown, and don't queue it behind an in-flight flush.
- **Cap what gets restored** (prefer the newest data), and make drops observable with a counter, a one-time warning, or an `onDrop` hook.

## Environment

- **Packages:** `@maple-dev/effect-sdk` 0.8.2 (plus 0.10.0 re-checked), `effect` 4.0.0-rc.112.
- **Browser:** Chromium 143 via Playwright 1.57.0.
- **Runtime:** Bun 1.4.2, Linux x86_64. WebKit and Firefox were not tested.
- **Our setup:** a React + Effect v4 single-page app exporting through `MapleFlush.make({ autoFlushInterval: 1000, ... })` to a same-origin ingest proxy.
