first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
# `pack-context` — React context plumbing for the pack registry
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
- [pack-registry-provider](./pack-registry-provider/pack-registry-provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_ - [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_- [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_ - [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_ - [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_- [pack-context](./pack-context.md) — <!-- begin:auto-docs --> _(no submodules)_- pack-registry-provider — <!-- begin:auto-docs --> _(…
- [pack-registry-provider](./pack-registry-provider/pack-registry-provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_- [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_- [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_- [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_- [pack-context](./pack-context.md) — <!-- begin:auto-docs --> _(no submodules)_- [pack-registry-provider](./pack-registry-provider/pack-registry-provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_- [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_- [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_- [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_- [pack-context](./pack-context.md) — <!-- begin:auto-docs --> _(no submodules)_- pack-registry-provider — <!-…
- [pack-registry-provider](./pack-registry-provider/pack-registry-provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_- [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_- [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_- [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_- [pack-context](./pack-context.md) — <!-- begin:auto-docs --> _(no submodules)_- pack-registry-provider — <!-- begin:auto-docs --> _…
- [pack-registry-provider](./pack-registry-provider/pack-registry-provider.md) — <!-- begin:auto-docs --> _(no submodules)_- [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_ - [runtime-override](./runtime-override/runtime-override.md) — <!-- begin:auto-docs --> _(no submodules)_- [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_ - [use-pack-component](./use-pack-component/use-pack-component.md) — <!-- begin:auto-docs --> _(no submodules)_- [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_ - [version-check](./version-check/version-check.md) — <!-- begin:auto-docs --> _(no submodules)_- [pack-context](./pack-context.md) — <!-- begin:auto-docs --> _(no submodules)_
## Purpose
Owns the React `Context` `<PackRegistryProvider>` writes into and the
`usePackComponent` hook reads from. The context carries two slices: a
`packs` map (raw `ComponentPack` references keyed by pack name) and a
`resolved` map (the merged `(packName -> componentName -> Component)`
view that already accounts for runtime overrides). Splitting raw and
resolved lets debugging surfaces inspect either layer.
## Features
- Single source-of-truth context for the chassis pack system
- `null` discriminator drives provider-missing detection in every
consumer hook
- `displayName` set so React DevTools surfaces the context as
`ReactUiPackRegistryContext` rather than the generic
`Context.Provider`
- `resolved` map is computed inside the Provider's `useMemo` so a
re-render does not rebuild the merged maps unless `packs` or the
override prop change
## Usage: Basic example
```ts
import { useContext } from "react";
import { PackRegistryContext } from "@suaveplan/react-ui/pack";
function PackInspector() {
const ctx = useContext(PackRegistryContext);
if (!ctx) return null;
return <pre>{[...ctx.packs.keys()].join(", ")}</pre>;
}
```
## Usage: Advanced example
```ts
import { useContext } from "react";
import { PackRegistryContext } from "@suaveplan/react-ui/pack";
function ResolvedView() {
const ctx = useContext(PackRegistryContext);
if (!ctx) return null;
const rows = [];
for (const [pack, components] of ctx.resolved) {
for (const componentName of components.keys()) {
rows.push(`${pack}/${componentName}`);
}
}
return <ul>{rows.map((r) => <li key={r}>{r}</li>)}</ul>;
}
```
## API Reference
- `PackRegistryContext: React.Context<PackRegistryContextValue | null>`
- `interface PackRegistryContextValue`
- `packs: ReadonlyMap<string, ComponentPack>`
- `resolved: ReadonlyMap<string, ReadonlyMap<string, ComponentType<unknown>>>`
## Implementation notes
The two-map split allows the consumer-facing `usePackComponent` hook
to read directly from `resolved` (a flat lookup) without walking the
raw pack array. Test fixtures and DevTools surfaces that need to see
the original pack lifecycle hooks consult `packs` instead. The
context default `null` makes provider-missing failures surface at the
hook call site rather than producing a confusing read-from-empty-map
scenario.

View File

@@ -0,0 +1,86 @@
# `<PackRegistryProvider>` — chassis pack registry
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Mounts the active pack registry, validates every pack against
`ComponentPackSchema` plus the no-collision invariant, runs each pack's
`onRegister(ctx)` hook in declared order on mount, runs
`onUnregister(ctx)` in reverse order on unmount, forwards each pack's
translation bundles into the active i18n catalog under the pack's name
as the namespace, and emits the OTel signals defined in the proposal.
## Features
- Validates packs synchronously at mount; throws
`ReactUiError(REACT_UI.PACK_NAME_COLLISION)` when two packs share a
`name`
- Registration in declared order; teardown in reverse order
- Each pack receives a `PackContext` containing the active i18n
instance, active theme, a `PackLoggerLike` (defaulting to a
console-backed shim), and an `AbortSignal` scoped to the pack's
mount lifetime
- Pack translations auto-registered via `i18n.catalog.set(locale,
packName, bundle)` so consumers reading `t('packName:key')` see the
registered values
- `peerRange` mismatch logs a structured warning AND increments
`react_ui.pack.version_mismatch_total` — the pack still registers
(degrades gracefully, never throws)
- Runtime override map (`<ReactUiProvider components={...}>`) merges
on top of pack defaults at registration time
- OTel: counters `react_ui.pack.register_total`,
`react_ui.pack.unregister_total`,
`react_ui.pack.version_mismatch_total`; span
`react_ui.pack.register{name}` around each `onRegister`
## Usage: Basic example
```tsx
import { PackRegistryProvider } from "@suaveplan/react-ui/pack/pack-registry-provider";
<PackRegistryProvider i18n={i18n} packs={[authPack]}>
<App />
</PackRegistryProvider>;
```
## Usage: Advanced example
```tsx
import { PackRegistryProvider } from "@suaveplan/react-ui/pack/pack-registry-provider";
<PackRegistryProvider
i18n={i18n}
packs={[authPack, socialPack, billingPack]}
components={{
auth: { LoginForm: AcmeLoginForm },
social: { ShareButton: AcmeShareButton },
}}
logger={{ warn: log.warn, info: log.info }}
>
<App />
</PackRegistryProvider>;
```
## API Reference
- `<PackRegistryProvider>` — props per `PackRegistryProviderProps`:
- `i18n: I18nInstance` — required; pack translations register here
- `packs: ReadonlyArray<ComponentPack>` — required
- `components?: RuntimeComponentOverride` — optional override map
- `logger?: PackLoggerLike` — optional; defaults to a console-backed
shim
- `children: ReactNode`
## Implementation notes
The provider enforces TWO validation passes at mount time: the
collision check (synchronous, throws on duplicate name) and the schema
check (synchronous, throws on bad shape). Both run BEFORE any
`onRegister` hook fires so a partially-registered registry never
exists. `onRegister` runs inside an OTel span so failures inside a
pack's hook are attributable to the pack name in trace storage. The
teardown loop iterates in reverse so packs that depend on earlier
packs (e.g. a `billing` pack assuming `auth` is mounted) tear down in
the safe order.

View File

@@ -0,0 +1,76 @@
# `runtime-override` — pack-component override merger
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Pure helper that merges a `ComponentPack`'s default components with the
`<ReactUiProvider components={...}>` runtime override map. The result
is a flat `Map<componentName, ComponentType<unknown>>` the chassis
stores in the `PackRegistryContext`'s `resolved` slot. White-label and
runtime-swap scenarios drive the override mechanism — consumers swap a
pack's `LoginForm` for their bespoke implementation without forking the
pack package.
## Features
- Pure / synchronous merge of pack defaults with overrides
- Skips `undefined` override entries so consumers can spread a partial
override map without wiping a pack default
- Skips overrides for unrelated packs so multiple packs share a single
override map without cross-talk
- Returns a frozen-style `ReadonlyMap` so consumers cannot mutate
the registry post-construction
## Usage: Basic example
```ts
import { mergePackComponents } from "@suaveplan/react-ui/pack";
const merged = mergePackComponents({
pack: authPack,
overrides: {
auth: { LoginForm: AcmeLoginForm },
},
});
```
## Usage: Advanced example
```ts
import {
mergePackComponents,
type RuntimeComponentOverride,
} from "@suaveplan/react-ui/pack";
const overrides: RuntimeComponentOverride = {
auth: {
LoginForm: AcmeLoginForm,
RegisterForm: AcmeRegisterForm,
},
social: {
ShareButton: AcmeShareButton,
},
};
const merged = mergePackComponents({ pack: authPack, overrides });
const Login = merged.get("LoginForm"); // → AcmeLoginForm
```
## API Reference
- `mergePackComponents({ pack, overrides? })` →
`ReadonlyMap<string, ComponentType<unknown>>`
- `type RuntimeComponentOverride = Partial<Record<string,
Partial<Record<string, ComponentType<unknown>>>>>`
## Implementation notes
The override semantics are intentionally simple: the override entry,
if present and not `undefined`, replaces the pack's default for that
component name. There is no deep-merge or composition step because
React components are atomic units — swapping `LoginForm` is the
intent, not "augment the existing component with override props". A
consumer that wants compositional swapping wraps their override
component around the imported pack default explicitly.

View File

@@ -0,0 +1,76 @@
# `usePackComponent()` — typed pack-component lookup
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
The primary read-side hook for the chassis pack registry. Returns the
`ComponentType` registered under `(packName, componentName)`, applying
runtime overrides automatically. Throws structured `ReactUiError`
instances so misuses fail fast instead of rendering `undefined`.
## Features
- Generic-typed signature so consumers narrowing on the pack name AND
component name (`usePackComponent('auth', 'LoginForm')`) get the
exact `ComponentType` declared in the pack's `PackComponents`
augmentation
- Throws `ReactUiError(REACT_UI.NO_PROVIDER)` outside the registry
- Throws `ReactUiError(REACT_UI.PACK_NOT_FOUND)` for an unknown pack
with `error.context.packName` populated
- Throws `ReactUiError(REACT_UI.PACK_COMPONENT_NOT_FOUND)` for a known
pack but unknown component, with both `packName` and `componentName`
in context
- Reads from the resolved `(packName -> componentName ->
ComponentType)` map populated by `<PackRegistryProvider>` so runtime
overrides flow through automatically
## Usage: Basic example
```tsx
import { usePackComponent } from "@suaveplan/react-ui/pack/use-pack-component";
function LoginScreen() {
const LoginForm = usePackComponent("auth", "LoginForm");
return <LoginForm />;
}
```
## Usage: Advanced example
```tsx
import { usePackComponent } from "@suaveplan/react-ui/pack/use-pack-component";
declare module "@suaveplan/react-ui/pack" {
interface PackComponents {
readonly auth: {
readonly LoginForm: ComponentType<{ redirectTo?: string }>;
};
}
}
function LoginRoute({ redirectTo }: { redirectTo?: string }) {
const LoginForm = usePackComponent("auth", "LoginForm");
return <LoginForm redirectTo={redirectTo} />;
}
```
## API Reference
- `usePackComponent<TPack, TKey>(packName: TPack, componentName: TKey)`
- Returns `PackComponents[TPack][TKey]` when the pack has been
augmented, otherwise `ComponentType<unknown>`
- Throws on missing Provider, missing pack, or missing component
## Implementation notes
The lookup walks the `PackRegistryContext.resolved` map directly so
overrides supplied via `<ReactUiProvider components={...}>` win over
pack defaults without an extra merge step at the call site. The
generic narrowing is intentionally permissive — string-typed
`packName` / `componentName` arguments still typecheck so consumers
that compose pack lookups dynamically (e.g. inside an
admin-configurable route table) can use the hook without casting. The
runtime contract is the source of truth; the type narrowing is a
convenience layer for the common static-key call site.

View File

@@ -0,0 +1,66 @@
# `version-check` — pack peerRange satisfier
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Pure helper that asks `semver.satisfies(reactUiVersion, peerRange)` so
the chassis can decide whether a pack's declared `peerRange` is
satisfied by the running `@suaveplan/react-ui` version. The function
catches semver parse failures and returns `false` so a malformed range
is treated as a mismatch (the pack is still registered but a warning
fires).
## Features
- Uses `semver.satisfies` with `includePrerelease: true` so an
in-flight `0.0.0-canary.1` build still matches `^0.1.0`-shaped
ranges
- Returns `false` on parser failure — never throws
- Pure / synchronous; safe to call in any context
## Usage: Basic example
```ts
import { satisfiesPeerRange } from "@suaveplan/react-ui/pack";
const ok = satisfiesPeerRange({
reactUiVersion: "0.1.0",
peerRange: "^0.1.0",
});
```
## Usage: Advanced example
```ts
import { satisfiesPeerRange } from "@suaveplan/react-ui/pack";
import { REACT_UI_VERSION } from "@suaveplan/react-ui/version";
function gate(packPeerRange: string): "ok" | "warn" {
return satisfiesPeerRange({
reactUiVersion: REACT_UI_VERSION,
peerRange: packPeerRange,
})
? "ok"
: "warn";
}
```
## API Reference
- `satisfiesPeerRange(input: { reactUiVersion: string; peerRange: string }): boolean`
- Returns `true` when the version satisfies the range
- Returns `false` when the version does not satisfy or the range
fails to parse
## Implementation notes
The helper deliberately swallows parse errors rather than re-throwing
because the caller (the chassis pack-registry provider) needs a binary
"is this satisfied?" answer at register time. The
`includePrerelease: true` flag keeps in-flight chassis dev builds
working with realistic peer ranges; production releases pinned to
exact versions still satisfy a `^X.Y.Z` range. The helper imports the
`semver` library statically so tree-shaking does not strip it from the
bundle.