<!-- source-hash: d56fe778d5cc9e99389a419b59c5dcc9 -->
Environment-aware lazy loader shim that defaults to `React.lazy` for non-Next.js hosts (Vite, esbuild, CRA) while supporting opt-in delegation to the real `next/dynamic` in Next.js environments.

## Key Components

| Export | Type | Description |
|--------|------|-------------|
| `dynamic` | Default export | Drop-in lazy loader; delegates to `React.lazy` or registered `next/dynamic` |
| `registerDynamic` | Named export | One-time registration to swap in the real `next/dynamic` at app init |
| `DynamicOptions` | Interface | Subset of `next/dynamic` options (`loading`, `ssr`, `suspense`) |
| `DynamicLoader<P>` | Type | Loader function returning `{ default: ComponentType }` or bare component |

## Usage Example

**Non-Next.js host (Vite/CRA) — no setup required:**

```typescript
import dynamic from '@flamingo-stack/openframe-frontend-core/next-dynamic'

// Callers must be wrapped in a <Suspense> boundary
const HeavyWidget = dynamic(() => import('./HeavyWidget'))
```

**Next.js host — register once at app init:**

```typescript
// lib/embed-shim-registration.ts
import nextDynamic from 'next/dynamic'
import { registerDynamic } from '@flamingo-stack/openframe-frontend-core/embed-shims'

registerDynamic(nextDynamic)
```

**With options (Next.js only — ignored on React.lazy fallback):**

```typescript
const Chart = dynamic(() => import('./Chart'), {
  ssr: false,
  loading: ({ isLoading }) => isLoading ? <Spinner /> : null,
})
```

> **Note:** `ssr` and `loading` options are silently ignored when running on the `React.lazy` fallback. Consumers relying on those options must call `registerDynamic` first. All call sites must have a `<Suspense>` boundary in the component tree.