` at
`src/prefetch/prefetch.ts`. When `opts.backgroundFetch` (or
`navigator.serviceWorker.ready.backgroundFetch` resolved from a real
`ServiceWorkerRegistration` in production) is available, the helper
SHALL invoke `backgroundFetch.fetch(id, [archiveUrl], opts.options)` and
resolve with the registration. When the API is missing, the helper
SHALL resolve with `null`. The package SHALL expose
`handleBackgroundFetchSuccess(event, deps)` for the SW's
`backgroundfetchsuccess` listener: on success, the helper SHALL drain
the event's records into the configured cache (default `packed-resources-v1`)
and increment `PackerBrowserCounters.prefetchSuccessTotal`. On failure
or `backgroundfetchfailure` invocations the helper SHALL increment
`PackerBrowserCounters.prefetchFailureTotal`. The two new counters
(`packed_resource_prefetch_success_total` and
`packed_resource_prefetch_failure_total`) MUST be created on the
observability bundle and asserted under a real in-memory
`MeterProvider`.
#### Scenario: missing API returns null
- **WHEN** `prefetchPackedArchive("/x.packed", { backgroundFetch: undefined })` is called
- **THEN** the promise resolves to `null` and no counters are incremented
#### Scenario: present API delegates to BackgroundFetchManager.fetch
- **WHEN** the helper is called with a shim `BackgroundFetchManager` whose `fetch` resolves
- **THEN** the shim's `fetch` is invoked exactly once with the configured id, the archive URL array, and the options bundle, and the helper's resolved value is the shim's returned registration
#### Scenario: success handler drains records into the cache and increments the success counter
- **WHEN** `handleBackgroundFetchSuccess(event, { caches, cacheName: "test", observability })` is invoked against a shim event whose `matchAll` returns one record
- **THEN** the configured cache contains the record's response and `prefetchSuccessTotal` is incremented exactly once
#### Scenario: failure handler increments the failure counter
- **WHEN** `handleBackgroundFetchFailure(event, { observability })` is invoked
- **THEN** `prefetchFailureTotal` is incremented exactly once
**Verified by:** `src/prefetch/prefetch.test.ts::"resolves to null when BackgroundFetchManager is unavailable"` AND `src/prefetch/prefetch.test.ts::"delegates to BackgroundFetchManager.fetch when the API is available"` AND `src/prefetch/prefetch.test.ts::"success handler drains records into the cache and increments prefetchSuccessTotal"` AND `src/prefetch/prefetch.test.ts::"failure handler increments prefetchFailureTotal"` AND `src/observability/observability.test.ts::"prefetchSuccessTotal and prefetchFailureTotal are emitted under a real MeterProvider"`
---
### Requirement: PACKER-BROWSER-21 — Diagnostics dev overlay
The package SHALL expose `createDiagnosticsOverlay(manager, opts?): {
mount(parent?: ParentNode): () => void }` at
`src/diagnostics/diagnostics.ts`. The factory SHALL subscribe to the
manager's `TinyEmitter` events (`load`, `error`, `cacheHit`, `cacheMiss`,
`cacheEvict`), maintain an in-memory ring of the last 50 events, and
render a single `` whose child nodes report cache size, hit-ratio,
miss-ratio, in-flight count, and the event ring. The factory MUST NOT
mutate the manager (subscribe-only). The mount method MUST return a
disposer that removes the rendered element and unsubscribes every
listener. When `document` is `undefined`, `mount` MUST return a no-op
disposer without throwing.
#### Scenario: overlay renders cache stats from the manager
- **WHEN** the manager's cache contains two entries and the overlay is mounted onto a happy-dom ``
- **THEN** the rendered element's text contains the substring `cache.size=2`
#### Scenario: event ring records the last 50 events
- **WHEN** the manager emits 60 `cacheHit` events
- **THEN** the overlay's event-ring snapshot contains exactly 50 entries, all of type `cacheHit`
#### Scenario: dispose removes the element and unsubscribes
- **WHEN** `mount` returns a disposer and the disposer is invoked
- **THEN** the overlay's element is no longer attached to `parent`, and subsequent manager `emit` calls leave the snapshot unchanged
#### Scenario: no-document environment returns a safe disposer
- **WHEN** `mount` is called against an overlay constructed without `document`
- **THEN** the disposer is a callable no-op and no errors are thrown
#### Scenario: overlay does not mutate the manager
- **WHEN** the overlay is mounted and `manager.getCacheStats()` is recorded before and after `mount` + `dispose`
- **THEN** both snapshots are structurally equal
**Verified by:** `src/diagnostics/diagnostics.test.ts::"renders cache.size derived from the manager"` AND `src/diagnostics/diagnostics.test.ts::"event ring retains only the last 50 events"` AND `src/diagnostics/diagnostics.test.ts::"disposer removes the element and unsubscribes every listener"` AND `src/diagnostics/diagnostics.test.ts::"returns a safe no-op disposer when document is absent"` AND `src/diagnostics/diagnostics.test.ts::"mount and dispose do not mutate the manager's cache state"`
---
### Requirement: PACKER-BROWSER-22 — Final subpath split + Playwright E2E coverage
The package SHALL ship three additional subpath exports on top of
PACKER-BROWSER-12's five: `./preload`, `./prefetch`, `./diagnostics`.
Each new subpath MUST be tree-shakeable
(`"sideEffects": false`) and MUST resolve to a built artefact under
`dist/`. The package SHALL ship Playwright spec files under `tests/e2e/`
covering the following scenarios: SW install + packed URL intercept,
range-request 206 response, encrypted archive end-to-end, content-
addressed URL invalidation on SW activate, preload-hint DOM injection,
and diagnostics overlay mount/unmount. The Playwright config SHALL
validate without browser binaries installed; running the suite is gated
on `bunx playwright install chromium` being executed in the runtime
environment.
#### Scenario: every new subpath resolves to a built dist artefact
- **WHEN** the build runs and `package.json.exports` is inspected
- **THEN** `./preload`, `./prefetch`, `./diagnostics` are present and each `import.default` value points at `./dist//index.js`
#### Scenario: subpaths preserve tree-shake invariant
- **WHEN** `package.json` is inspected
- **THEN** `sideEffects === false`
#### Scenario: Playwright config is valid and the E2E spec files exist
- **WHEN** the test harness scans `tests/e2e/`
- **THEN** files exist for SW intercept, range 206, encrypted archive, content-addressed invalidation, preload injection, and diagnostics mount/unmount
**Verified by:** `src/index.test.ts::"every subpath export resolves to a built dist artefact (cluster C)"` AND `src/index.test.ts::"package.json declares sideEffects: false (cluster C)"` AND `src/index.test.ts::"every Playwright E2E spec file exists under tests/e2e/"`