# Add `@suaveplan/react-packer` — React 19 companion for `@suaveplan/packer-browser` **Package**: `@suaveplan/react-packer` **Status**: PROPOSED **Type**: new-package - **Design**: ./design.md - **Spec delta**: ./specs/react-packer/spec.md ## Why `@suaveplan/packer-browser` (genesis Cluster A) ships the manager, cache, loader, signature verification, and Service-Worker interceptor with **no React runtime dep**. Upstream `packer-browser@1.0.17` carried the React hooks (`usePackedResource`, `usePackedImage`, `usePackedAudio`, `usePackedVideo`, `usePackedFont`) inside the same package, forcing every non-React consumer (server bundles, R3F apps that do their own loading, Web-Worker scripts) to ship React via transitive dependency. Upstream `enterprise/packer-browser.md` §3.3 specifically calls for splitting the hooks into a React companion package; that split has been deferred to `@suaveplan/react-packer`, this change. This package provides the hooks at genesis quality — `AbortSignal` on every action, `MessageResolver` plumbed through, `PackedResourceProvider` React context for explicit dependency injection of a `PackedResourceManager`, sensible fallback to the `@suaveplan/packer-browser/singleton` for adopters that want zero-config, and 100/100/100/100 coverage against a real (non-mock) manager backed by real `Response` + `Blob` fixtures. ## What changes This is a brand-new package — every export is additive. No changes to `@suaveplan/packer-browser`'s surface. - ADDED: `usePackedResource(packedUrl, opts?)` — generic hook returning `{ url, blob, loading, error, status, reload }`. - ADDED: `usePackedImage(packedUrl, opts?)` — image-tuned defaults (cache on, `allowedMimePrefixes = ["image/"]`). - ADDED: `usePackedAudio(packedUrl, opts?)` — audio-tuned defaults. - ADDED: `usePackedVideo(packedUrl, opts?)` — video-tuned defaults (cache off — videos are large). - ADDED: `usePackedFont(packedUrl, fontFamily?, opts?)` — injects a `@font-face` rule and waits for `document.fonts.load`. Returns the generic shape plus `fontLoaded: boolean`. - ADDED: `PackedResourceProvider` — React context that supplies a `PackedResourceManager` and (optionally) a `MessageResolver` to every hook in its subtree. - ADDED: `usePackedResourceManager()` — internal hook that resolves the manager from context with a documented fallback to `@suaveplan/packer-browser/singleton`. - ADDED: `REACT_PACKER_CODES` + `ReactPackerError` — stable error code registry (`LOAD_FAILED`, `FONT_LOAD_FAILED`, `ABORTED`). ## Out of scope - Service-Worker registration UI — adopters use `@suaveplan/packer-browser`'s lifecycle helpers directly. - A `` / `` / `` React component wrapper. The hooks are the public surface; consumers compose their own presentational components. This keeps the package focused on the carve upstream `enterprise/packer-browser.md` §3.3 explicitly asked for. ## Source mapping This change derives from upstream `../suaveplan/packages/browser/packer-browser/src/hooks/usePackedResource.ts` and `../suaveplan/enterprise/packer-browser.md` §3.3 ("Move React hooks into a new React companion package"). The genesis rewrite preserves the upstream public symbol names and per-variant defaults while bringing every requirement to enterprise quality (no module singleton, real `AbortSignal`, real `MessageResolver`, real error hierarchy). | Requirement | Source lines | Notes | |---|---|---| | REACT-PACKER-1 (`usePackedResource`) | upstream `usePackedResource.ts` L22-L87 | Rewritten to consume injected manager; adds AbortSignal + error-code wrapping. | | REACT-PACKER-2 (`usePackedImage`) | upstream L89-L101 | Same defaults (cache on); adds `allowedMimePrefixes`. | | REACT-PACKER-3 (`usePackedAudio`) | upstream L103-L115 | Same defaults (cache on). | | REACT-PACKER-4 (`usePackedVideo`) | upstream L117-L129 | Same defaults (cache off). | | REACT-PACKER-5 (`usePackedFont`) | upstream L131-L198 | Preserves `@font-face` injection + cleanup. | | REACT-PACKER-6 (`PackedResourceProvider`) | enterprise/packer-browser.md L42-L51 ("Move React hooks…") | Adds explicit DI surface that upstream did not have (module singleton only). | | REACT-PACKER-7 (error hierarchy) | enterprise/packer-browser.md §63 | Standard genesis bar; upstream raised plain `Error`. | | REACT-PACKER-8 (singleton fallback) | enterprise/packer-browser.md L51 | Adopters who omit `` fall back to `@suaveplan/packer-browser/singleton`. | ### Requirements with no source line None — every requirement either ports an upstream behaviour or implements an enterprise gap explicitly named in `enterprise/packer-browser.md`. ## Verification approach Every requirement carries a `**Verified by:**` line pointing at a concrete `it(...)` title in the planned test files. - `src/hooks/use-packed-resource/use-packed-resource.test.tsx` — covers REACT-PACKER-1 (loading, success, error, reload, abort on unmount, abort on URL change, signal propagation). - `src/hooks/use-packed-image/use-packed-image.test.tsx` — covers REACT-PACKER-2 (`allowedMimePrefixes` enforcement, image defaults). - `src/hooks/use-packed-audio/use-packed-audio.test.tsx` — covers REACT-PACKER-3 (audio defaults). - `src/hooks/use-packed-video/use-packed-video.test.tsx` — covers REACT-PACKER-4 (video defaults including cache off). - `src/hooks/use-packed-font/use-packed-font.test.tsx` — covers REACT-PACKER-5 (`@font-face` injection, cleanup, `fontLoaded` flag, `document.fonts.load` failure path). - `src/provider/packed-resource-provider/packed-resource-provider.test.tsx` — covers REACT-PACKER-6 + REACT-PACKER-8 (context wiring, singleton fallback when no provider, `MessageResolver` propagation). - `src/error-codes/error-codes.test.ts` — covers REACT-PACKER-7 (code registry, `ReactPackerError.is(...)` brand check). ## Migration impact None — additive only. `@suaveplan/packer-browser` is not modified. Consumers that previously imported hooks from upstream `packer-browser` (none on genesis — packer-browser shipped without hooks per its own proposal) migrate by adding `@suaveplan/react-packer` as a dep and importing from there. ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/react-packer` to enumerate. This is a new package with zero existing dependents on genesis: - No dependents (verified by `grep -rln "@suaveplan/react-packer" packages/ apps/ 2>/dev/null` returning no matches at proposal time).