Initial Commit
This commit is contained in:
128
openspec/archive/2026-05-19-add-react-packer/proposal.md
Normal file
128
openspec/archive/2026-05-19-add-react-packer/proposal.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# 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).
|
||||
Reference in New Issue
Block a user