Files
2026-05-27 23:29:53 +01:00

6.4 KiB

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 <PackedImage> / <PackedVideo> / <PackedAudio> 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 <PackedResourceProvider> 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).