diff --git a/README.md b/README.md index 1c32bac..0b7277d 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,7 @@ # Contection +Contection is a state management library that extends React Context API with fine-grained subscriptions and computed values. -A state management library that extends React Context API with fine-grained subscriptions and computed values. Built on React hooks and `useSyncExternalStore` to provide efficient, granular state updates. - -[npm](https://www.npmjs.com/package/contection) • [demo](https://www.contection.dev/) - -## Features - -- **React-Context-like API** - Extends the standard React Context pattern with hooks and components -- **Granular Subscriptions** - Built on `useSyncExternalStore` for efficient, per-key subscription updates -- **Selective Re-renders** - Subscribe to specific store keys to minimize component re-renders -- **Computed Values** - Transform and derive state with mutation functions -- **Additional Modules** - Extended functionality through specialized modules like [viewport management](https://github.com/alexdln/contection/tree/main/modules/viewport) and [top-layer management](https://github.com/alexdln/contection/tree/main/modules/top-layer) -- **Storage adapters** - Automatic state persistence with optional validation and selective key persistence via [storage adapter](https://github.com/alexdln/contection/tree/main/adapters/storage) (localStorage/sessionStorage) and [next-cookie adapter](https://github.com/alexdln/contection/tree/main/adapters/next-cookie) (cookies with SSR support) +[GitHub](https://github.com/alexdln/contection) • [NPM](https://www.npmjs.com/package/contection) • [Documentation](https://www.contection.dev/) ## Installation @@ -23,6 +13,11 @@ yarn add contection pnpm add contection ``` +## Getting Started + +1. [Getting Started](./docs/01-getting-started/README.md) - Installation and features +2. [Quick Start](./docs/01-getting-started/quick-start.md) - Get up and running in minutes + ## Quick Start ### 1. Create a Store @@ -160,608 +155,12 @@ function UserProfile() { } ``` -## Advanced Usage - -### Updating the Store - -Use `useStoreReducer` to get the store state and setStore function. Unlike `useStore`, the store returned from `useStoreReducer` does not trigger re-renders when it changes, making it useful for reading values without subscribing to updates: - -```tsx -import { useStoreReducer } from "contection"; - -function Counter() { - const [store, setStore] = useStoreReducer(AppStore); - - return ( -
- - - -
- ); -} -``` - -### Selective Subscriptions - -Subscribe to specific store keys to limit re-render scope: - -```tsx -// Component re-renders only when 'count' key changes -const { count } = useStore(AppStore, { keys: ["count"] }); - -// Component re-renders only when 'user' or 'theme' keys change -const data = useStore(AppStore, { keys: ["user", "theme"] }); -``` - -### Conditional Subscriptions - -Use the `enabled` option to conditionally enable or disable subscriptions. This is useful for tracking changes only under specific conditions, such as user roles, value ranges, or page contexts. When the `enabled` value changes, the hook will automatically resubscribe. - -The `enabled` option accepts: - -- `"always"` (default) - Subscription is always active -- `"never"` - Subscription is never active -- `"after-hydration"` - Subscription is active only after the component has mounted (useful for SSR/hydration scenarios) -- A function `(store: Store) => boolean` - Dynamically determines if the subscription should be active based on the current store state - -```tsx -// Track account changes only if user is an admin -const { account } = useStore(AppStore, { - keys: ["account"], - enabled: (store) => store.user.role === "admin", -}); - -// Track numbers only when their values are less than 10 -const { count } = useStore(AppStore, { - keys: ["count"], - enabled: (store) => store.count < 10, -}); - -// Disable subscription completely -const { notifications } = useStore(AppStore, { - keys: ["notifications"], - enabled: "never", -}); - -// Enable subscription only after hydration (useful for SSR) -const { user } = useStore(AppStore, { - keys: ["user"], - enabled: "after-hydration", -}); -``` - -### Computed Values - -Derive computed state from store values using mutation functions: - -```tsx -// Mutation calls only when 'user' key change -// Component re-renders only when mutation result change -const userInitials = useStore(AppStore, { - keys: ["user"], - mutation: (user) => { - const names = user.name.split(" "); - return names - .map((n) => n[0]) - .join("") - .toUpperCase(); - }, -}); - -return userInitials; // JD -``` - -#### Mutation Function Parameters - -The mutation function receives three parameters: - -1. **`newStore`** - The current store state (or selected keys if `keys` option is used) -2. **`prevStore`** - The previous store state (or selected keys). `undefined` on the first call -3. **`prevMutatedStore`** - The previous result of the mutation function. `undefined` on the first call - -Use `prevStore` and `prevMutatedStore` to implement incremental updates, compare values, or optimize computations: - -```tsx -// Track count changes and compute differences -const countChange = useStore(AppStore, { - keys: ["count"], - mutation: (newStore, prevStore, prevMutatedStore) => { - if (!prevStore) { - return { current: newStore.count, change: 0 }; - } - return { - current: newStore.count, - change: newStore.count - prevStore.count, - }; - }, -}); - -// Incremental list updates using previous computed value -const filteredItems = useStore(AppStore, { - keys: ["items", "filter"], - mutation: (newStore, prevStore, prevMutatedStore) => { - // Reuse previous result if filter hasn't changed - if (prevMutatedStore && prevStore?.filter === newStore.filter) { - return prevMutatedStore; - } - return newStore.items.filter((item) => item.includes(newStore.filter)); - }, -}); -``` - -### Full Store Access - -Access the entire store when needed with full re-render cycle: - -```tsx -const store = useStore(AppStore); - -// Or with Consumer - - {(store) => ( -
-

User: {store.user.name}

-

Count: {store.count}

-

Theme: {store.theme}

-
- )} -
; -``` - -### Imperative Subscriptions - -Use `subscribe` and `unsubscribe` for imperative subscriptions outside React's render cycle. Useful for side effects, logging, or external system integrations: - -```tsx -import { useStoreReducer } from "contection"; -import { useEffect } from "react"; - -function AnalyticsTracker() { - const [store, setStore, subscribe, unsubscribe] = useStoreReducer(AppStore); - - useEffect(() => { - const unsubscribeUser = subscribe("user", (user) => { - analytics.track("user_updated", { userId: user.email }); - }); - - const unsubscribeTheme = subscribe("theme", (theme) => { - document.documentElement.setAttribute("data-theme", theme); - }); - - // Cleanup subscriptions on unmount - return () => { - unsubscribeUser(); - unsubscribeTheme(); - }; - }, [subscribe]); - - return null; -} -``` - -You can also use `subscribe` in a `ref` callback to set up subscriptions when you have direct access to a DOM node. This pattern is useful for imperative DOM manipulation that needs to react to store changes: +## Documentation -```tsx -const Header = () => { - const [store, , subscribe] = useStoreReducer(AppStore); - return ( -
- {/* ... */} - -
- ); -}; -``` - -### Lifecycle Hooks - -Lifecycle hooks allow you to perform initialization and cleanup operations at different stages of the store's lifecycle. They are passed as options to `createStore`: - -```tsx -const AppStore = createStore( - { - user: { name: "", email: "" }, - count: 0, - theme: "light", - }, - { - lifecycleHooks: { - storeWillMount: (store, setStore, subscribe, unsubscribe) => { - // Initialization logic - // Return cleanup function if needed - }, - storeDidMount: (store, setStore, subscribe, unsubscribe) => { - // Post-mount logic - // Return cleanup function if needed - }, - storeWillUnmount: (store) => { - // Synchronous cleanup before unmount - }, - storeWillUnmountAsync: (store) => { - // Asynchronous cleanup during unmount - }, - }, - } -); -``` - -You can also pass `options` to individual Provider instances to customize lifecycle hooks per instance. Provider options completely override options passed to `createStore`, allowing you to disable or customize settings for specific Provider instances. See [Provider-Level Lifecycle Hooks](#provider-level-lifecycle-hooks) for details. - -#### `storeWillMount` - -**Recommended for:** Single Page Applications (SPA), background key detection or subscriptions. - -Runs synchronously during render, **before** the store is fully initialized. This hook is ideal for: - -- Setting up background subscriptions that won't cause hydration errors -- Initializing client-only state (e.g., localStorage, sessionStorage) in SPA -- Detecting and subscribing to keys for custom logic - -**Important:** In React Strict Mode (development), `storeWillMount` is called **twice**. Return a cleanup function to properly handle subscriptions and prevent memory leaks: - -```tsx -const AppStore = createStore( - { - user: { name: "", email: "" }, - count: 0, - theme: "light", - lastVisit: null as Date | null, - }, - { - lifecycleHooks: { - storeWillMount: (store, setStore, subscribe) => { - const savedTheme = localStorage.getItem("theme"); - if (savedTheme) { - setStore({ theme: savedTheme as "light" | "dark" }); - } - const unsubscribe = subscribe("count", (count) => { - console.log("Count changed:", count); - }); - return unsubscribe; - }, - }, - } -); -``` - -#### `storeDidMount` - -**Recommended for:** Fullstack solutions (Next.js, Remix, etc.) to avoid hydration errors. - -Runs asynchronously **after** the component mounts, making it safe for operations that might differ between server and client. This hook is ideal for: - -- Initializing state that depends on browser APIs -- Fetching data that should only happen on the client -- Setting up subscriptions that need to match server-rendered content - -```tsx -const AppStore = createStore( - { - user: { name: "", email: "" }, - count: 0, - theme: "light", - windowWidth: 0, - }, - { - lifecycleHooks: { - storeDidMount: (store, setStore, subscribe) => { - setStore({ windowWidth: window.innerWidth }); - - const handleResize = () => { - setStore({ windowWidth: window.innerWidth }); - }; - window.addEventListener("resize", handleResize); - - // Return cleanup function - return () => { - window.removeEventListener("resize", handleResize); - }; - }, - }, - } -); -``` - -#### `storeWillUnmount` - -**Recommended for:** Synchronous cleanup operations that must complete before the component unmounts. - -Runs synchronously in `useLayoutEffect` cleanup, **before** the component unmounts. This hook is ideal for: - -- Synchronous cleanup that must happen before unmount -- Cleanup operations that should block unmounting - -**Note:** This hook runs synchronously and should not perform heavy operations that could block the UI. - -```tsx -const AppStore = createStore( - { - user: { name: "", email: "" }, - count: 0, - theme: "light", - }, - { - lifecycleHooks: { - storeWillUnmount: (store) => { - if (store.count > 0) { - localStorage.setItem("lastCount", String(store.count)); - } - }, - }, - } -); -``` - -#### `storeWillUnmountAsync` - -**Recommended for:** Asynchronous cleanup operations that can run during unmount. - -Runs asynchronously in `useEffect` cleanup, **during** component unmount. This hook is ideal for: - -- Asynchronous cleanup operations (API calls, timers, etc.) -- Cleanup that doesn't need to block unmounting -- Final data synchronization that can happen asynchronously - -**Execution Order:** This hook runs after `storeDidMount` cleanup (if provided) and after `storeWillMount` cleanup (if provided). - -```tsx -const AppStore = createStore( - { - user: { name: "", email: "" }, - count: 0, - theme: "light", - }, - { - lifecycleHooks: { - storeDidMount: (store, setStore, subscribe, unsubscribe) => { - const ws = new WebSocket("wss://example.com"); - - return () => { - ws.close(); - }; - }, - storeWillUnmountAsync: (store) => { - fetch("https://example.com/api/sync-state", { - method: "POST", - body: JSON.stringify(store), - }).catch(console.error); - }, - }, - } -); -``` - -### Lifecycle Execution Order - -1. **Mount Phase:** - -- `storeWillMount` (synchronous, during render) - called twice in React Strict Mode; -- `storeDidMount` (asynchronous, after mount); - -2. **Unmount Phase:** - -- `storeWillUnmount` (synchronous, before unmount); -- `storeWillMount` cleanup (if returned) - called an additional time in React Strict Mode between `storeWillMount` calls; -- `storeDidMount` cleanup (if returned); -- `storeWillUnmountAsync` (asynchronous, during unmount). - -### Provider-Level Lifecycle Hooks - -While lifecycle hooks can be passed to `createStore`, they are shared across all Provider instances and initialized outside React's scope. For per-instance customization, you can pass `options` directly to individual Provider components. - -**Provider options completely override options from `createStore`**, allowing you to: - -- Disable lifecycle hooks for specific instances -- Customize hooks per Provider instance -- Use React state/props in lifecycle hooks (since they're initialized within React scope) - -```tsx -const sharedOptions = { - lifecycleHooks: { - storeDidMount: (store, setStore) => { - console.log("Shared initialization"); - }, - }, -}; - -const AppStore = createStore( - { - user: { name: "", email: "" }, - count: 0, - theme: "light", - }, - sharedOptions -); - -function App() { - return ( - <> - {/* Uses shared options from createStore */} - - - - {/* Overrides with Provider-specific options */} - { - setStore({ count: 100 }); - }, - }, - }} - > - - - {/* Disables lifecycle hooks */} - - - - - ); -} -``` - -### Store Validation - -The `validate` option allows you to validate store data before it's applied. This is useful for ensuring data integrity and preventing invalid state updates. - -The validation function receives the store data (or partial update) and should return a truthy value if valid, or a falsy value if invalid: - -- **Invalid initial data** - Throws an error when the Provider is created -- **Invalid updates** - Silently rejected (the update is not applied) - -```tsx -import { createStore, useStoreReducer } from "contection"; -import { z } from "zod"; - -const schema = z.object({ - user: z.object({ - name: z.string().min(1), - email: z.string().email(), - }), - count: z.number().int().min(0), -}); - -const AppStore = createStore( - { - user: { name: "John", email: "john@example.com" }, - count: 0, - }, - { - validate: (data) => { - const partialSchema = schema.pick( - Object.fromEntries(Object.keys(data).map((k) => [k, true])) - ); - const result = partialSchema.safeParse(data); - return result.success ? result.data : false; - }, - } -); - - - {/* Error: Invalid initial store data */} -; - -// Invalid updates are silently rejected -function Counter() { - const [store, setStore] = useStoreReducer(AppStore); - - // This update will be rejected silently - setStore({ count: -1 }); - - // This update will be applied - setStore({ count: 1 }); -} -``` - -## API Reference - -### `createStore(initialData: Store, options?)` - -Creates a new store instance with Provider and Consumer components. - -**Parameters:** - -- `initialData: Store` - Initial state for the store -- `options?: CreateStoreOptions` (optional): - - `lifecycleHooks?: { storeWillMount?, storeDidMount?, storeWillUnmount?, storeWillUnmountAsync? }` - Lifecycle hooks for store initialization and cleanup - - `validate?: (data: any) => boolean | null | never | undefined` - Validation function that validates store data. Returns a truthy value if valid, falsy if invalid. Invalid initial data throws an error, invalid updates are silently rejected. - -**Returns:** - -- `Provider` - React component to wrap scope -- `Consumer` - React component for render props pattern -- `_context` - The underlying React Context. In some cases, you can use it with the `use` hook to access the useStoreReducer data -- `_initial` - The initial store data - -### `useStore(instance, options?)` - -Hook that subscribes to store state with optional key listening and computed value derivation. - -**Parameters:** - -- `instance` - Store instance returned from `createStore` -- `options` (optional): - - `keys?: string[]` - Array of store keys to subscribe to. If omitted, subscribes to all keys. - - `mutation?: (newStore, prevStore?, prevMutatedStore?) => T` - Function to compute derived value from subscribed state. Receives: - - `newStore` - Current store state (or selected keys if `keys` is provided) - - `prevStore` - Previous store state (or selected keys). `undefined` on first call - - `prevMutatedStore` - Previous result of the mutation function. `undefined` on first call - - `enabled?: "always" | "never" | "after-hydration" | ((store: Store) => boolean)` - Condition to enable or disable the subscription. Accepts `"always"` (default), `"never"`, `"after-hydration"`, or a function `(store: Store) => boolean`. When this value changes, the hook will automatically resubscribe. - -**Returns:** Subscribed store data or computed value if mutation function is provided - -### `useStoreReducer(instance)` - -Hook that returns a tuple containing the store state and setStore functions. - -**Returns:** `[store, setStore, subscribe, unsubscribe]` tuple where: - -- `store` - Current store state object -- `setStore` - Function to update store state: `(partial: Partial | (prev: Store) => Partial) => void` -- `subscribe` - Function to subscribe to store key changes: `(key: K, listener: (value: Store[K]) => void) => () => void`. Returns an unsubscribe function. -- `unsubscribe` - Function to unsubscribe from store key changes: `(key: K, listener: (value: Store[K]) => void) => void` - -### `Provider` - -Component that provides a scoped store instance to child components. Each Provider instance creates its own isolated store scope, similar to React Context.Provider. Components within a Provider can only access the store state from that Provider's scope. - -**Props:** - -- `children: React.ReactNode` -- `value?: Store` - Optional initial value for this Provider's scope (defaults to store's initial data from `createStore`) -- `options?: CreateStoreOptions` (optional): - - `lifecycleHooks?: { storeWillMount?, storeDidMount?, storeWillUnmount?, storeWillUnmountAsync? }` - lifecycle hooks configuration. **Completely overrides** options passed to `createStore`, allowing per-instance customization. See [Lifecycle Hooks](#lifecycle-hooks) for available hooks. - - `validate?: (data: any) => boolean | null | never | undefined` - Validation function that validates store data. Returns a truthy value if valid, falsy if invalid. Invalid initial data throws an error, invalid updates are silently rejected. - -**Scoping Behavior:** - -- `` (same as ``) instance creates a completely isolated store -- Multiple Providers of the same store type do not share state -- Nested Providers create nested scopes (inner Provider overrides outer Provider for its children) - -### `Consumer` - -Component that consumes the store using render props pattern. - -**Props:** - -- `children: (data) => React.ReactNode` - Render function -- `options?: { keys?: string[], mutation?: Function, enabled?: boolean | Function }`: - - `keys?: string[]` - Array of store keys to subscribe to. If omitted, subscribes to all keys. - - `mutation?: (newStore, prevStore?, prevMutatedStore?) => T` - Function to compute derived value from subscribed state. Receives: - - `newStore` - Current store state (or selected keys if `keys` is provided) - - `prevStore` - Previous store state (or selected keys). `undefined` on first call - - `prevMutatedStore` - Previous result of the mutation function. `undefined` on first call - - `enabled?: "always" | "never" | "after-hydration" | ((store: Store) => boolean)` - Condition to enable or disable the subscription. Accepts `"always"` (default), `"never"`, `"after-hydration"`, or a function `(store: Store) => boolean`. When this value changes, the consumer will automatically resubscribe. - -## Contection modules - -### [contection-viewport](https://github.com/alexdln/contection/tree/main/modules/viewport) - -A performance-based viewport management module built on top of Contection. Provides efficient screen size tracking with granular subscriptions, memoization, and a single global resize listener. - -### [contection-top-layer](https://github.com/alexdln/contection/tree/main/modules/top-layer) - -A layer management module built on top of Contection. Provides efficient management of dialogs and upper layers with granular subscriptions, type safety, and support for isolated layers. +- [Core Topics](./docs/02-core/README.md) - Stores, providers, hooks +- [Advanced](./docs/03-advanced/README.md) - Lifecycle, validation, API reference, guides +- [Modules and Adapters](./docs/04-modules/README.md) - Viewport, top-layer, storage, cookies +- [Examples](./docs/05-examples/README.md) - Code examples and live demos ## Contection adapters @@ -777,10 +176,8 @@ A cookie-based persistence adapter for Contection designed for Next.js applicati The repository includes example applications demonstrating Contection's capabilities: -- **[demo](examples/demo)** - Demonstrates fine-grained subscriptions with various optimization strategies, storage adapters for state persistence, and integration with `contection-viewport` and `contection-top-layer` modules. [Preview](https://www.contection.dev/) - +- **[demo](examples/demo)** - Demonstrates fine-grained subscriptions with various optimization strategies, storage adapters for state persistence, and integration with `contection-viewport` and `contection-top-layer` modules. [Preview](https://contection.dev/demo/) - **[nextjs-bsky](examples/nextjs-bsky)** - Showcases performance improvements in Next.js applications using `cacheComponents` and a combined client-server architecture with next-cookie adapter and storage adapter for state persistence. [Preview](https://router-bsky.contection.dev/) - - **[react-routerjs-bsky](examples/react-routerjs-bsky)** - Showcases performance improvements in Next.js applications using `cacheComponents` and a combined client-server architecture with react-router-cookie adapter and storage adapter for state persistence. [Preview](https://router-bsky.contection.dev/) ## License diff --git a/adapters/storage/README.md b/adapters/storage/README.md index 64c65ea..1f40d46 100644 --- a/adapters/storage/README.md +++ b/adapters/storage/README.md @@ -2,7 +2,7 @@ A persistent storage adapter for [contection](https://github.com/alexdln/contection) that automatically saves and restores state to browser storage (localStorage or sessionStorage). -[npm](https://www.npmjs.com/package/contection-storage-adapter) • [demo](https://www.contection.dev/) +[npm](https://www.npmjs.com/package/contection-storage-adapter) ## Overview @@ -150,8 +150,6 @@ The adapter automatically detects storage availability and gracefully degrades i The repository includes example applications demonstrating storage adapter capabilities: -- **[demo](examples/demo)** - Demonstrates fine-grained subscriptions with various optimization strategies, storage adapters for state persistence, and integration with `contection-viewport` and `contection-top-layer` modules. - - **[nextjs-bsky](examples/nextjs-bsky)** - Showcases performance improvements in Next.js applications using `cacheComponents` and a combined client-server architecture with next-cookie adapter and storage adapter for state persistence. ## License diff --git a/docs/01-getting-started/README.md b/docs/01-getting-started/README.md new file mode 100644 index 0000000..54d11c0 --- /dev/null +++ b/docs/01-getting-started/README.md @@ -0,0 +1,35 @@ +# Getting Started + +Contection extends React Context API with fine-grained subscriptions and computed values. + +## Installation + +```bash switcher tab="npm" +npm install contection +``` + +```bash switcher tab="pnpm" +pnpm add contection +``` + +```bash switcher tab="yarn" +yarn add contection +``` + +```bash switcher tab="bun" +bun add contection +``` + +**Requirements:** React 18+ (React 19 recommended), TypeScript 4.5+ (optional, types included) + +## Features + +- **Granular Subscriptions** - Subscribe to specific store keys +- **Selective Re-renders** - Components update only when subscribed keys change +- **Computed Values** - Derive state with mutation functions +- **Type-Safe** - Full TypeScript support +- **SSR Compatible** - Works with Next.js and other SSR frameworks + +## Next + +- [Quick Start](./quick-start.md) - Get up and running in minutes diff --git a/docs/01-getting-started/quick-start.md b/docs/01-getting-started/quick-start.md new file mode 100644 index 0000000..f7f60e9 --- /dev/null +++ b/docs/01-getting-started/quick-start.md @@ -0,0 +1,143 @@ +# Quick Start + +## Step 1: Create a Store + +```tsx filename="store.ts" switcher tab="TypeScript" +import { createStore } from "contection"; + +type AppStoreType = { + user: { name: string; email: string }; + count: number; + theme: "light" | "dark"; +}; + +const AppStore = createStore({ + user: { name: "", email: "" }, + count: 0, + theme: "light", +}); + +export { AppStore }; +export type { AppStoreType }; +``` + +```js filename="store.js" switcher tab="JavaScript" +import { createStore } from "contection"; + +const AppStore = createStore({ + user: { name: "", email: "" }, + count: 0, + theme: "light", +}); + +export { AppStore }; +``` + +## Step 2: Provide the Store + +```tsx filename="App.tsx" switcher tab="TypeScript" +import { AppStore } from "./store"; + +function App() { + return ( + + + + ); +} +``` + +```jsx filename="App.jsx" switcher tab="JavaScript" +import { AppStore } from "./store"; + +function App() { + return ( + + + + ); +} +``` + +Each Provider creates an isolated scope, similar to React Context.Provider. + +## Step 3: Use the Store + +### Subscribe to Specific Keys + +```tsx filename="Counter.tsx" switcher tab="TypeScript" +import { useStore } from "contection"; +import { AppStore } from "./store"; + +function Counter() { + // Component re-renders only when 'count' value changes + const { count } = useStore(AppStore, { keys: ["count"] }); + + return ( +
+

Count: {count}

+
+ ); +} +``` + +```jsx filename="Counter.jsx" switcher tab="JavaScript" +import { useStore } from "contection"; +import { AppStore } from "./store"; + +function Counter() { + const { count } = useStore(AppStore, { keys: ["count"] }); + + return ( +
+

Count: {count}

+
+ ); +} +``` + +### Access Nested Values + +```tsx filename="UserEmail.tsx" switcher tab="TypeScript" +import { useStore } from "contection"; +import { AppStore } from "./store"; + +function UserEmail() { + // Component re-renders only when 'user.email' changes + const email = useStore(AppStore, { + keys: ["user"], + mutation: (store) => store.user.email, + }); + + return

E-mail: {email}

; +} +``` + +### Update the Store + +```tsx filename="CounterControls.tsx" switcher tab="TypeScript" +import { useStoreReducer } from "contection"; +import { AppStore } from "./store"; + +function CounterControls() { + // useStoreReducer never triggers re-render + const [store, setStore] = useStoreReducer(AppStore); + + return ( +
+ + + +
+ ); +} +``` + +## Next + +- [Stores and Providers](../02-core/stores-and-providers.md) +- [Hooks](../02-core/hooks.md) diff --git a/docs/02-core/README.md b/docs/02-core/README.md new file mode 100644 index 0000000..cb6c442 --- /dev/null +++ b/docs/02-core/README.md @@ -0,0 +1,10 @@ +# Core Topics + +Contection provides two main primitives: **stores** for state containers and **hooks** for accessing them. + +Stores are created once and scoped via Providers. Unlike global state managers, each Provider maintains its own isolated state - components only see the nearest Provider's data. + +Hooks connect components to stores. `useStore` subscribes to specific keys and triggers re-renders; `useStoreReducer` provides state access and updates without re-renders. + +- [Stores and Providers](./stores-and-providers.md) - Create stores, scope with Providers, handle multiple instances +- [Hooks](./hooks.md) - Subscribe with `useStore`, update with `useStoreReducer`, compute derived values diff --git a/docs/02-core/hooks.md b/docs/02-core/hooks.md new file mode 100644 index 0000000..b2a5606 --- /dev/null +++ b/docs/02-core/hooks.md @@ -0,0 +1,188 @@ +# Hooks + +## useStore + +Subscribes to store state and triggers re-renders when subscribed keys change. + +```tsx +import { useStore } from "contection"; + +function Counter() { + const { count } = useStore(AppStore, { keys: ["count"] }); + return
Count: {count}
; +} +``` + +### Options + +#### `keys?: string[]` + +Keys to subscribe to. Omit for all keys. + +```tsx +// Single key +const { count } = useStore(AppStore, { keys: ["count"] }); + +// Multiple keys - re-renders when 'user' OR 'theme' changes +const { user, theme } = useStore(AppStore, { keys: ["user", "theme"] }); + +// All keys - re-renders when ANY key changes +const store = useStore(AppStore); +``` + +#### `mutation?: (newStore, prevStore?, prevMutatedStore?) => T` + +Compute derived values: + +```tsx +const email = useStore(AppStore, { + keys: ["user"], + mutation: (store) => store.user.email, +}); + +const initials = useStore(AppStore, { + keys: ["user"], + mutation: (store) => store.user.name.split(" ").map((n) => n[0]).join("").toUpperCase(), +}); +``` + +**Memoization** - use previous values to avoid recomputation: + +```tsx +const filtered = useStore(AppStore, { + keys: ["items", "filter"], + mutation: (newStore, prevStore, prevMutatedStore) => { + if (prevMutatedStore && prevStore?.filter === newStore.filter) { + return prevMutatedStore; + } + return newStore.items.filter((item) => item.includes(newStore.filter)); + }, +}); +``` + +#### `enabled?: "always" | "never" | "after-hydration" | (store) => boolean` + +Conditional subscription: + +```tsx +// Role-based +const { adminData } = useStore(AppStore, { + keys: ["adminData"], + enabled: (store) => store.user.role === "admin", +}); + +// SSR - active after mount +const { clientData } = useStore(AppStore, { + keys: ["clientData"], + enabled: "after-hydration", +}); +``` + +## useStoreReducer + +Returns store state and update functions **without triggering re-renders**. + +```tsx +import { useStoreReducer } from "contection"; + +function Counter() { + const [store, setStore] = useStoreReducer(AppStore); + + return ( +
+ + +
+ ); +} +``` + +### Return Value + +```tsx +const [store, setStore, subscribe, unsubscribe] = useStoreReducer(AppStore); +``` + +- `store` - Current state (read-only, no re-renders) +- `setStore` - Update function (object or function) +- `subscribe` - Imperative subscription +- `unsubscribe` - Remove subscription + +### Updating State + +```tsx +// Object update +setStore({ count: 10 }); +setStore({ count: 10, theme: "dark" }); + +// Function update +setStore((prev) => ({ count: prev.count + 1 })); +``` + +### Imperative Subscriptions + +Subscribe to key changes outside React's render cycle: + +```tsx +const [, , subscribe] = useStoreReducer(AppStore); + +useEffect(() => { + const unsubscribe = subscribe("user", (user) => { + analytics.track("user_updated", { userId: user.email }); + }); + return unsubscribe; +}, [subscribe]); +``` + +**Use cases:** + +```tsx +// DOM manipulation +subscribe("theme", (theme) => { + document.documentElement.setAttribute("data-theme", theme); +}); + +// WebSocket sync +const ws = new WebSocket("wss://example.com"); +subscribe("data", (data) => ws.send(JSON.stringify(data))); +``` + +## Consumer Component + +Render props pattern with same options as `useStore`: + +```tsx + + {({ user }) => ( +
+

{user.name}

+

{user.email}

+
+ )} +
+ +// With mutation + store.user.email, + }} +> + {(email) =>

E-mail: {email}

} +
+``` + +Prefer `useStore` for better TypeScript inference. + +## Comparison + +| Feature | `useStore` | `useStoreReducer` | +|---------|-----------|-------------------| +| Re-renders on changes | Yes | No | +| Subscribe to keys | Yes | No | +| Computed values | Yes | No | +| Read state | Yes | Yes | +| Update state | No | Yes | +| Imperative subscriptions | No | Yes | diff --git a/docs/02-core/stores-and-providers.md b/docs/02-core/stores-and-providers.md new file mode 100644 index 0000000..f34c1ad --- /dev/null +++ b/docs/02-core/stores-and-providers.md @@ -0,0 +1,170 @@ +# Stores and Providers + +## Creating a Store + +```tsx filename="store.ts" switcher tab="TypeScript" +import { createStore } from "contection"; + +type AppStoreType = { + user: { name: string; email: string }; + count: number; + theme: "light" | "dark"; +}; + +const AppStore = createStore({ + user: { name: "", email: "" }, + count: 0, + theme: "light", +}); + +export { AppStore }; +export type { AppStoreType }; +``` + +```js filename="store.js" switcher tab="JavaScript" +import { createStore } from "contection"; + +const AppStore = createStore({ + user: { name: "", email: "" }, + count: 0, + theme: "light", +}); + +export { AppStore }; +``` + +### Options + +```tsx +const AppStore = createStore( + { + user: { name: "", email: "" }, + count: 0, + theme: "light", + }, + { + lifecycleHooks: { + storeDidMount: (store, setStore) => { + // Initialization logic + }, + }, + validate: (data) => { + // Validation logic + return true; + }, + } +); +``` + +## Provider Component + +```tsx +function App() { + return ( + + + + ); +} + +// Or explicitly: +function App() { + return ( + + + + ); +} +``` + +## Store Scoping + +Each Provider creates an isolated scope: + +```tsx +function App() { + return ( + <> + {/* First scope with initial data */} + + + + + {/* Second scope with different initial data - completely isolated */} + + + + + ); +} +``` + +Multiple Providers do not share state. Inner Providers override outer ones. + +## Multiple Stores + +```tsx +const UserStore = createStore({ + user: { name: "", email: "" }, +}); + +const ThemeStore = createStore({ + theme: "light", + accent: "blue", +}); + +const CounterStore = createStore({ + count: 0, +}); + +function App() { + return ( + + + + + + + + ); +} +``` + +## Provider Props + +### `value` + +Optional initial value for this scope: + +```tsx + + + +``` + +### `options` + +Provider-level options override `createStore` options. See [Advanced Topics](../03-advanced/README.md). + +## Nested Providers + +```tsx + + {/* sees theme: "light" */} + + {/* sees theme: "dark" */} + + +``` diff --git a/docs/03-advanced/README.md b/docs/03-advanced/README.md new file mode 100644 index 0000000..625cf8d --- /dev/null +++ b/docs/03-advanced/README.md @@ -0,0 +1,17 @@ +# Advanced + +Beyond basic usage, Contection supports initialization logic, data validation, and per-Provider customization. + +**Lifecycle hooks** run code when stores mount/unmount - load from localStorage, set up event listeners, sync with external systems. **Validation** ensures data integrity before state updates. **Provider-level configuration** lets you override these behaviors per Provider instance. + +## Core + +- [Lifecycle and Validation](./lifecycle-and-validation.md) - `storeWillMount`, `storeDidMount`, `validate`, Provider overrides +- [API Reference](./api.md) - Complete function signatures and options + +## Guides + +- [Performance](./performance.md) - Optimize re-renders, memoization patterns +- [TypeScript](./typescript.md) - Type definitions, inference, generic stores +- [Migration](./migration.md) - From Context, Zustand, Redux +- [Troubleshooting](./troubleshooting.md) - Common issues and solutions diff --git a/docs/03-advanced/api.md b/docs/03-advanced/api.md new file mode 100644 index 0000000..9d95f84 --- /dev/null +++ b/docs/03-advanced/api.md @@ -0,0 +1,132 @@ +# API Reference + +Complete function signatures and options for all Contection exports. + +## createStore + +```tsx +function createStore( + initialData: Store, + options?: CreateStoreOptions +): StoreInstance +``` + +### Parameters + +- `initialData` - Initial state for the store +- `options.lifecycleHooks` - Lifecycle callbacks (see [Lifecycle and Validation](./lifecycle-and-validation.md)) +- `options.validate` - Validation function `(data: Partial) => boolean` + +### Returns + +- `Provider` - Component wrapper +- `Consumer` - Render props component +- `_context` - Underlying React Context +- `_initial` - Initial store data + +```tsx +const AppStore = createStore( + { user: { name: "", email: "" }, count: 0 }, + { + lifecycleHooks: { + storeDidMount: (store, setStore) => { + const saved = localStorage.getItem("theme"); + if (saved) setStore({ theme: saved }); + }, + }, + validate: (data) => !("count" in data && data.count < 0), + } +); +``` + +--- + +## useStore + +```tsx +function useStore( + instance: StoreInstance, + options?: UseStoreOptions +): T +``` + +### Options + +- `keys?: string[]` - Keys to subscribe to (omit for all) +- `mutation?: (newStore, prevStore?, prevMutatedStore?) => T` - Compute derived value +- `enabled?: "always" | "never" | "after-hydration" | (store) => boolean` - Conditional subscription + +### Returns + +Subscribed data or computed value. Re-renders when subscribed keys change. + +```tsx +const { count } = useStore(AppStore, { keys: ["count"] }); +const email = useStore(AppStore, { + keys: ["user"], + mutation: (store) => store.user.email, +}); +``` + +--- + +## useStoreReducer + +```tsx +function useStoreReducer( + instance: StoreInstance +): [store, setStore, subscribe, unsubscribe] +``` + +**Does not trigger re-renders.** + +### Returns + +- `store` - Current state (read-only) +- `setStore` - Update function (object or function) +- `subscribe` - Imperative subscription `(key, callback) => unsubscribe` +- `unsubscribe` - Remove subscription `(key, callback) => void` + +```tsx +const [store, setStore] = useStoreReducer(AppStore); +setStore({ count: 10 }); +setStore((prev) => ({ count: prev.count + 1 })); +``` + +--- + +## Provider + +```tsx +> + {children} + +``` + +Each Provider creates isolated scope. + +- `value` - Optional initial value (defaults to `createStore` initial data) +- `options` - Overrides `createStore` options completely + +```tsx + + + + + + true }}> +``` + +--- + +## Consumer + +Render props pattern. Same options as `useStore`. + +```tsx + + {({ count }) =>
{count}
} +
+``` + +Prefer `useStore` for better TypeScript inference. diff --git a/docs/03-advanced/lifecycle-and-validation.md b/docs/03-advanced/lifecycle-and-validation.md new file mode 100644 index 0000000..f828918 --- /dev/null +++ b/docs/03-advanced/lifecycle-and-validation.md @@ -0,0 +1,172 @@ +# Lifecycle and Validation + +Control store initialization, cleanup, and data integrity. + +## Lifecycle Hooks + +Run code when Providers mount/unmount - load persisted data, set up listeners, sync with external systems. + +```tsx +const AppStore = createStore( + { user: { name: "", email: "" }, count: 0, theme: "light" }, + { + lifecycleHooks: { + storeWillMount: (store, setStore, subscribe, unsubscribe) => { + // Sync init (SPA only) - return cleanup if needed + }, + storeDidMount: (store, setStore, subscribe, unsubscribe) => { + // Async init (SSR-safe) - return cleanup if needed + }, + storeWillUnmount: (store) => { + // Sync cleanup (useLayoutEffect) + }, + storeWillUnmountAsync: (store) => { + // Async cleanup (useEffect) + }, + }, + } +); +``` + +### storeWillMount + +Runs synchronously during render, **before** mount. Use for SPA client-only initialization. + +```tsx +storeWillMount: (store, setStore, subscribe) => { + const saved = localStorage.getItem("theme"); + if (saved) setStore({ theme: saved }); + + const unsubscribe = subscribe("count", (count) => console.log(count)); + return unsubscribe; // Cleanup for Strict Mode +} +``` + +### storeDidMount + +Runs asynchronously **after** mount. Use for SSR apps to avoid hydration errors. + +```tsx +storeDidMount: (store, setStore) => { + setStore({ windowWidth: window.innerWidth }); + + const handleResize = () => setStore({ windowWidth: window.innerWidth }); + window.addEventListener("resize", handleResize); + return () => window.removeEventListener("resize", handleResize); +} +``` + +### storeWillUnmount / storeWillUnmountAsync + +```tsx +storeWillUnmount: (store) => { + localStorage.setItem("lastCount", String(store.count)); // Sync +} + +storeWillUnmountAsync: (store) => { + fetch("/api/sync", { method: "POST", body: JSON.stringify(store) }); // Async +} +``` + +### Execution Order + +**Mount:** `storeWillMount` (sync) → `storeDidMount` (async) + +**Unmount:** `storeWillUnmount` (sync) → cleanups → `storeWillUnmountAsync` (async) + +--- + +## Store Validation + +Validate store data before it's applied. + +```tsx +const AppStore = createStore( + { count: 0 }, + { + validate: (data) => { + if ("count" in data && data.count < 0) return false; + return true; + }, + } +); +``` + +- **Truthy** → Update applied +- **Falsy** → Update rejected +- **Invalid initial data** throws error +- **Invalid updates** silently rejected + +### With Zod + +```tsx +import { z } from "zod"; + +const schema = z.object({ + user: z.object({ name: z.string().min(1), email: z.string().email() }), + count: z.number().int().min(0), +}); + +const AppStore = createStore( + { user: { name: "John", email: "john@example.com" }, count: 0 }, + { + validate: (data) => { + const partialSchema = schema.pick( + Object.fromEntries(Object.keys(data).map((k) => [k, true])) + ); + const result = partialSchema.safeParse(data); + return result.success ? result.data : false; + }, + } +); +``` + +--- + +## Provider-Level Configuration + +Provider `options` completely override `createStore` options. + +```tsx + { + // Provider-specific initialization + }, + }, + validate: (data) => true, + }} +> + + +``` + +### With React Props + +Provider-level hooks can access React props: + +```tsx +function App({ userId }: { userId: string }) { + return ( + { + fetchUserData(userId).then((data) => setStore({ user: data })); + }, + }, + }} + > + + + ); +} +``` + +### Disable Features + +```tsx + + +``` diff --git a/docs/03-advanced/migration.md b/docs/03-advanced/migration.md new file mode 100644 index 0000000..8a1605a --- /dev/null +++ b/docs/03-advanced/migration.md @@ -0,0 +1,120 @@ +# Migration Guide + +Contection replaces React Context, Zustand, and Redux with simpler patterns. + +## From React Context + +**Before:** +```tsx +const AppContext = createContext({ count: 0, setCount: () => {} }); + +function Provider({ children }) { + const [count, setCount] = useState(0); + return ( + + {children} + + ); +} + +function Counter() { + const { count, setCount } = useContext(AppContext); + return ; +} +``` + +**After:** +```tsx +const AppStore = createStore({ count: 0 }); + +function App() { + return ( + + + + ); +} + +function Counter() { + const { count } = useStore(AppStore, { keys: ["count"] }); + const [, setStore] = useStoreReducer(AppStore); + return ; +} +``` + +## From Zustand + +**Before:** +```tsx +const useStore = create((set) => ({ + count: 0, + increment: () => set((state) => ({ count: state.count + 1 })), +})); + +function Counter() { + const count = useStore((state) => state.count); + const increment = useStore((state) => state.increment); + return ; +} +``` + +**After:** +```tsx +const AppStore = createStore({ count: 0 }); + +function Counter() { + const { count } = useStore(AppStore, { keys: ["count"] }); + const [, setStore] = useStoreReducer(AppStore); + return ; +} +``` + +## From Redux + +**Before:** +```tsx +// store.ts +const counterSlice = createSlice({ + name: "counter", + initialState: { count: 0 }, + reducers: { + increment: (state) => { state.count += 1; }, + }, +}); + +// Counter.tsx +function Counter() { + const count = useSelector((state) => state.counter.count); + const dispatch = useDispatch(); + return ; +} +``` + +**After:** +```tsx +const AppStore = createStore({ count: 0 }); + +function Counter() { + const { count } = useStore(AppStore, { keys: ["count"] }); + const [, setStore] = useStoreReducer(AppStore); + return ; +} +``` + +## Key Differences + +| Feature | Context | Zustand | Redux | Contection | +|---------|---------|---------|-------|------------| +| Granular subscriptions | Manual | Selector | Selector | Built-in `keys` | +| State updates | setState | set() | dispatch | setStore | +| Provider required | Yes | No | Yes | Yes | +| Provider scoping | Yes | No | No | Yes | +| Boilerplate | Medium | Low | High | Low | + +## Migration Steps + +1. **Create store** with `createStore()` using your existing state shape +2. **Wrap with Provider** at the same level as your current provider +3. **Replace hooks** - `useContext` → `useStore`, selectors → `keys` option +4. **Replace updates** - setState/dispatch → `setStore` from `useStoreReducer` +5. **Add granular subscriptions** - specify `keys` to optimize re-renders diff --git a/docs/03-advanced/performance.md b/docs/03-advanced/performance.md new file mode 100644 index 0000000..c3212a9 --- /dev/null +++ b/docs/03-advanced/performance.md @@ -0,0 +1,78 @@ +# Performance Optimization + +Contection's granular subscriptions already minimize re-renders, but these patterns help further. + +## Subscribe to Specific Keys + +Only subscribe to what you need - components won't re-render for unrelated state changes. + +```tsx +// Only re-renders when count changes +const { count } = useStore(AppStore, { keys: ["count"] }); + +// Re-renders when user OR theme changes +const { user, theme } = useStore(AppStore, { keys: ["user", "theme"] }); +``` + +## Memoize Expensive Computations + +Use mutation's `prevMutatedStore` to skip recomputation when inputs haven't changed. + +```tsx +const filteredItems = useStore(AppStore, { + keys: ["items", "filter"], + mutation: (newStore, prevStore, prevMutatedStore) => { + // Skip if filter hasn't changed + if (prevMutatedStore && prevStore?.filter === newStore.filter) { + return prevMutatedStore; + } + return newStore.items.filter((item) => + item.name.includes(newStore.filter) + ); + }, +}); +``` + +## Use useStoreReducer for No Re-renders + +For event handlers that only read/write state without displaying it. + +```tsx +function SaveButton() { + const [store, setStore] = useStoreReducer(AppStore); + + const handleSave = () => { + // Read current state without subscribing + saveToServer(store.data); + setStore({ lastSaved: Date.now() }); + }; + + return ; +} +``` + +## Split Large Stores + +Instead of one monolithic store, split by domain: + +```tsx +// Instead of one large store +const AppStore = createStore({ user, theme, cart, notifications, ... }); + +// Split into focused stores +const UserStore = createStore({ user }); +const ThemeStore = createStore({ theme }); +const CartStore = createStore({ items, total }); +``` + +## Avoid Subscribing to All Keys + +Omitting `keys` subscribes to everything - the component re-renders on any change. + +```tsx +// Avoid: re-renders on ANY state change +const store = useStore(AppStore); + +// Better: only re-renders when count changes +const { count } = useStore(AppStore, { keys: ["count"] }); +``` diff --git a/docs/04-modules/README.md b/docs/04-modules/README.md new file mode 100644 index 0000000..d52a0af --- /dev/null +++ b/docs/04-modules/README.md @@ -0,0 +1,11 @@ +# Modules and Adapters + +Pre-built Contection stores and adapters for common use cases. + +**Modules** are ready-to-use stores with built-in logic: +- [Viewport](./viewport.md) - Tracks window dimensions with a single resize listener shared across all subscribers +- [Top Layer](./top-layer.md) - Manages stacked dialogs, modals, and overlays with proper z-index ordering + +**Adapters** persist store state to external storage: +- [Storage Adapter](./storage-adapter.md) - Saves/restores state to localStorage or sessionStorage +- [Next.js Cookie Adapter](./next-cookie-adapter.md) - Cookie persistence with SSR support - state available on both server and client diff --git a/docs/04-modules/next-cookie-adapter.md b/docs/04-modules/next-cookie-adapter.md new file mode 100644 index 0000000..7f67053 --- /dev/null +++ b/docs/04-modules/next-cookie-adapter.md @@ -0,0 +1,105 @@ +# Next.js Cookie Adapter + +[GitHub](https://github.com/alexdln/contection/tree/main/adapters/next-cookie) • [Demo](https://nextjs-bsky.contection.dev/) + +SSR-compatible state persistence using cookies. Unlike localStorage, cookies are available on both server and client, enabling true server-side rendering with pre-populated state. + +## Why Use This + +- **SSR support** - State available during server render, no hydration mismatch +- **Automatic hydration** - Client automatically picks up server state +- **Cookie options** - Full control over expiry, security, and scope +- **Validation** - Validate cookie data before restoring + +## Installation + +```bash switcher tab="npm" +npm install contection-next-cookie-adapter +``` + +```bash switcher tab="pnpm" +pnpm add contection-next-cookie-adapter +``` + +```bash switcher tab="yarn" +yarn add contection-next-cookie-adapter +``` + +## Usage + +```tsx +import { createNextCookieAdapter } from "contection-next-cookie-adapter"; + +const cookieAdapter = createNextCookieAdapter({ + keys: ["theme", "user"], + cookieOptions: { + httpOnly: false, + secure: process.env.NODE_ENV === "production", + sameSite: "lax", + maxAge: 60 * 60 * 24 * 7, // 7 days + }, +}); + +cookieAdapter.attach(AppStore); +``` + +### Options + +| Option | Type | Description | +|--------|------|-------------| +| `keys` | `string[]` | Store keys to persist | +| `cookieOptions.httpOnly` | `boolean` | `false` for client access | +| `cookieOptions.secure` | `boolean` | `true` for HTTPS only | +| `cookieOptions.sameSite` | `string` | `"lax"`, `"strict"`, or `"none"` | +| `cookieOptions.maxAge` | `number` | Expiry in seconds | +| `validate` | `(data) => boolean` | Validate before restoring | + +### With Validation + +```tsx +const cookieAdapter = createNextCookieAdapter({ + keys: ["theme", "preferences"], + cookieOptions: { + httpOnly: false, + secure: true, + sameSite: "lax", + maxAge: 60 * 60 * 24 * 30, // 30 days + }, + validate: (data) => { + if (data.theme && !["light", "dark", "system"].includes(data.theme)) { + return false; + } + return true; + }, +}); +``` + +### Server Component Access + +```tsx +// app/layout.tsx +import { cookies } from "next/headers"; + +export default function RootLayout({ children }) { + const theme = cookies().get("theme")?.value || "light"; + + return ( + + + + {children} + + + + ); +} +``` + +## Comparison with Storage Adapter + +| Feature | Storage Adapter | Cookie Adapter | +|---------|-----------------|----------------| +| SSR support | No | Yes | +| Server access | No | Yes | +| Size limit | ~5MB | ~4KB | +| Sent with requests | No | Yes | diff --git a/docs/04-modules/storage-adapter.md b/docs/04-modules/storage-adapter.md new file mode 100644 index 0000000..c4c536c --- /dev/null +++ b/docs/04-modules/storage-adapter.md @@ -0,0 +1,78 @@ +# Storage Adapter + +[GitHub](https://github.com/alexdln/contection/tree/main/adapters/storage) + +Automatically persist and restore store state to browser storage. State survives page refreshes and browser sessions. + +## Why Use This + +- **Automatic sync** - Saves on every state change, restores on mount +- **Selective persistence** - Choose which keys to persist +- **Validation** - Validate stored data before restoring +- **Storage choice** - Use localStorage (persistent) or sessionStorage (tab-scoped) + +## Installation + +```bash switcher tab="npm" +npm install contection-storage-adapter +``` + +```bash switcher tab="pnpm" +pnpm add contection-storage-adapter +``` + +```bash switcher tab="yarn" +yarn add contection-storage-adapter +``` + +## Usage + +```tsx +import { createStorageAdapter } from "contection-storage-adapter"; + +const storageAdapter = createStorageAdapter({ + storage: localStorage, + keys: ["theme", "user"], +}); + +storageAdapter.attach(AppStore); +``` + +### Options + +| Option | Type | Description | +|--------|------|-------------| +| `storage` | `Storage` | `localStorage` or `sessionStorage` | +| `keys` | `string[]` | Store keys to persist | +| `validate` | `(data) => boolean` | Validate before restoring | +| `storageKey` | `string` | Custom storage key name | + +### With Validation + +```tsx +const storageAdapter = createStorageAdapter({ + storage: localStorage, + keys: ["theme", "settings"], + validate: (data) => { + if (data.theme && !["light", "dark"].includes(data.theme)) { + return false; + } + return true; + }, +}); +``` + +### Session Storage + +```tsx +// Data cleared when tab closes +const sessionAdapter = createStorageAdapter({ + storage: sessionStorage, + keys: ["formData"], +}); +``` + +## Examples + +- [nextjs-bsky](https://nextjs-bsky.contection.dev/) - Combined with cookie adapter +- [react-routerjs-bsky](https://router-bsky.contection.dev/) - React Router integration diff --git a/docs/04-modules/top-layer.md b/docs/04-modules/top-layer.md new file mode 100644 index 0000000..69be606 --- /dev/null +++ b/docs/04-modules/top-layer.md @@ -0,0 +1,103 @@ +# Top Layer Module + +[GitHub](https://github.com/alexdln/contection/tree/main/modules/top-layer) + +Manage stacked UI layers like dialogs, modals, drawers, and tooltips. Handles z-index ordering, focus trapping, and escape key dismissal. + +## Why Use This + +- **Stack management** - Automatic z-index ordering for nested layers +- **Focus handling** - Traps focus within active layer +- **Keyboard support** - Escape key closes top layer +- **Portal rendering** - Renders layers at document root + +## Installation + +```bash switcher tab="npm" +npm install contection-top-layer +``` + +```bash switcher tab="pnpm" +pnpm add contection-top-layer +``` + +```bash switcher tab="yarn" +yarn add contection-top-layer +``` + +## Usage + +```tsx +import { createTopLayerStore } from "contection-top-layer"; +import { useStore, useStoreReducer } from "contection"; + +const TopLayerStore = createTopLayerStore(); + +function App() { + return ( + + + + + ); +} +``` + +### Open a Layer + +```tsx +function DialogTrigger() { + const [, setStore] = useStoreReducer(TopLayerStore); + + const openDialog = () => { + setStore((prev) => ({ + layers: [...prev.layers, { + id: "dialog-1", + component: MyDialog, + props: { title: "Hello" } + }], + })); + }; + + return ; +} +``` + +### Close a Layer + +```tsx +function MyDialog({ id, title }) { + const [, setStore] = useStoreReducer(TopLayerStore); + + const close = () => { + setStore((prev) => ({ + layers: prev.layers.filter((l) => l.id !== id), + })); + }; + + return ( +
+

{title}

+ +
+ ); +} +``` + +### Render Layers + +```tsx +function LayerRenderer() { + const { layers } = useStore(TopLayerStore, { keys: ["layers"] }); + + return ( + <> + {layers.map((layer, index) => ( +
+ +
+ ))} + + ); +} +``` diff --git a/docs/04-modules/viewport.md b/docs/04-modules/viewport.md new file mode 100644 index 0000000..d273c11 --- /dev/null +++ b/docs/04-modules/viewport.md @@ -0,0 +1,74 @@ +# Viewport Module + +[GitHub](https://github.com/alexdln/contection/tree/main/modules/viewport) + +Reactive viewport dimensions with a single shared resize listener. Components subscribe to specific properties and only re-render when those values change. + +## Why Use This + +- **Single listener** - One `resize` event handler shared across all subscribers +- **Granular updates** - Subscribe to `width`, `height`, or breakpoints independently +- **SSR-safe** - Proper hydration handling for server-rendered apps + +## Installation + +```bash switcher tab="npm" +npm install contection-viewport +``` + +```bash switcher tab="pnpm" +pnpm add contection-viewport +``` + +```bash switcher tab="yarn" +yarn add contection-viewport +``` + +## Usage + +```tsx +import { createViewportStore } from "contection-viewport"; +import { useStore } from "contection"; + +const ViewportStore = createViewportStore(); + +function App() { + return ( + + + + ); +} +``` + +### Subscribe to Dimensions + +```tsx +function WindowSize() { + const { width, height } = useStore(ViewportStore, { + keys: ["width", "height"], + }); + return

{width} x {height}

; +} +``` + +### Subscribe to Breakpoints + +```tsx +function ResponsiveComponent() { + const { isMobile } = useStore(ViewportStore, { + keys: ["isMobile"], + }); + return isMobile ? : ; +} +``` + +### Available Store Keys + +| Key | Type | Description | +|-----|------|-------------| +| `width` | `number` | Viewport width in pixels | +| `height` | `number` | Viewport height in pixels | +| `isMobile` | `boolean` | `true` if width < 768px | +| `isTablet` | `boolean` | `true` if width >= 768px and < 1024px | +| `isDesktop` | `boolean` | `true` if width >= 1024px | diff --git a/docs/05-examples/README.md b/docs/05-examples/README.md new file mode 100644 index 0000000..acad449 --- /dev/null +++ b/docs/05-examples/README.md @@ -0,0 +1,136 @@ +# Examples + +Working examples demonstrating Contection patterns and integrations. + +## Live Demos + +| Example | Description | Preview | +|---------|-------------|---------| +| **demo** | Fine-grained subscriptions, storage adapters, viewport/top-layer modules | [contection.dev](https://contection.dev/demo) | +| **nextjs-bsky** | Next.js SSR with cookie adapter, combined server/client architecture | [nextjs-bsky.contection.dev](https://nextjs-bsky.contection.dev/) | +| **react-routerjs-bsky** | React Router integration with storage persistence | [router-bsky.contection.dev](https://router-bsky.contection.dev/) | + +## Basic Patterns + +### Counter + +```tsx +const CounterStore = createStore({ count: 0 }); + +function Counter() { + const { count } = useStore(CounterStore, { keys: ["count"] }); + const [, setStore] = useStoreReducer(CounterStore); + + return ( +
+

Count: {count}

+ + +
+ ); +} +``` + +### Theme Toggle + +```tsx +const ThemeStore = createStore({ theme: "light" as "light" | "dark" }); + +function ThemeToggle() { + const { theme } = useStore(ThemeStore, { keys: ["theme"] }); + const [, setStore] = useStoreReducer(ThemeStore); + + return ( + + ); +} +``` + +--- + +## Advanced Patterns + +### Form with Validation + +```tsx +const FormStore = createStore({ + fields: { name: "", email: "" }, + errors: {} as Record, +}); + +const handleChange = (field: string, value: string) => { + const error = field === "email" && !value.includes("@") ? "Invalid email" : ""; + setStore({ + fields: { ...fields, [field]: value }, + errors: { ...errors, [field]: error }, + }); +}; +``` + +### WebSocket Sync + +```tsx +useEffect(() => { + const ws = new WebSocket("wss://example.com"); + ws.onmessage = (e) => setStore({ data: JSON.parse(e.data) }); + + const unsubscribe = subscribe("data", (data) => { + if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(data)); + }); + + return () => { unsubscribe(); ws.close(); }; +}, []); +``` + +--- + +## Framework Integration + +### Next.js + +```tsx +// app/layout.tsx +export default function RootLayout({ children }) { + return ( + + {children} + + ); +} +``` + +### React Router + +```tsx +function App() { + return ( + + + + } /> + + + + ); +} +``` + +### With Adapters + +```tsx +// Storage adapter - persists to localStorage +const storageAdapter = createStorageAdapter({ + storage: localStorage, + keys: ["theme"], +}); +storageAdapter.attach(AppStore); + +// Cookie adapter - SSR-compatible +const cookieAdapter = createNextCookieAdapter({ + keys: ["theme"], + cookieOptions: { httpOnly: false, secure: true }, +}); +cookieAdapter.attach(AppStore); +``` diff --git a/examples/demo/.gitignore b/examples/demo/.gitignore index 5940145..7ac8ea1 100644 --- a/examples/demo/.gitignore +++ b/examples/demo/.gitignore @@ -2,4 +2,3 @@ node_modules dist .DS_Store *.log - diff --git a/examples/demo/src/components/demos/contection/store-controls.tsx b/examples/demo/src/components/demos/contection/store-controls.tsx index 272d225..6d33946 100644 --- a/examples/demo/src/components/demos/contection/store-controls.tsx +++ b/examples/demo/src/components/demos/contection/store-controls.tsx @@ -20,7 +20,6 @@ export const StoreControls: React.FC = () => { }; for (let i = 0; i < 200; i++) { const nextTimezone = `UTC${Math.floor(i / 20)}`; - await new Promise((resolve) => setTimeout(resolve, 20)); const random = Math.random(); if (random < 0.25) { summary.counter++; diff --git a/examples/demo/src/components/ui/layout/styles.scss b/examples/demo/src/components/ui/layout/styles.scss index 1e4fbeb..0790d3f 100644 --- a/examples/demo/src/components/ui/layout/styles.scss +++ b/examples/demo/src/components/ui/layout/styles.scss @@ -37,5 +37,4 @@ @media (max-width: 768px) { padding: 12px; } -} - +} \ No newline at end of file diff --git a/modules/top-layer/README.md b/modules/top-layer/README.md index 94ab34b..e006d93 100644 --- a/modules/top-layer/README.md +++ b/modules/top-layer/README.md @@ -2,7 +2,7 @@ A layer management module built on top of [contection](https://github.com/alexdln/contection) - a performance-focused state management package. Provides efficient management of dialogs and upper layers with granular subscriptions, type safety, and support for isolated layers. -[npm](https://www.npmjs.com/package/contection-top-layer) • [demo](https://www.contection.dev/?tab=top-layer) +[npm](https://www.npmjs.com/package/contection-top-layer) ## Features diff --git a/modules/viewport/README.md b/modules/viewport/README.md index 573d0a5..7d97e73 100644 --- a/modules/viewport/README.md +++ b/modules/viewport/README.md @@ -2,7 +2,7 @@ A performance-based viewport management module built on top of [contection](https://github.com/alexdln/contection) - a performance-focused state management package. Provides efficient screen size tracking with granular subscriptions, memoization, and a single global resize subscribeer that triggers re-renders only where needed. -[npm](https://www.npmjs.com/package/contection-viewport) • [demo](https://www.contection.dev/?tab=viewport) +[npm](https://www.npmjs.com/package/contection-viewport) ## Features diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 356496d..a604b98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,7 +48,7 @@ importers: version: link:../../package next: specifier: ^16.0.4 - version: 16.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.3) + version: 16.0.4(react-dom@19.2.3(react@19.2.0))(react@19.2.0)(sass@1.97.2) typescript: specifier: 5.9.3 version: 5.9.3 @@ -67,7 +67,7 @@ importers: version: link:../../package react-router: specifier: 7.9.6 - version: 7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 7.9.6(react-dom@19.2.3(react@19.2.0))(react@19.2.0) typescript: specifier: 5.9.3 version: 5.9.3 @@ -120,7 +120,7 @@ importers: version: 19.2.2(@types/react@19.2.2) '@vitejs/plugin-react': specifier: 5.1.1 - version: 5.1.1(vite@7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.93.3)) + version: 5.1.1(vite@7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)) sass-embedded: specifier: ^1.93.3 version: 1.93.3 @@ -129,7 +129,7 @@ importers: version: 5.9.3 vite: specifier: 7.2.2 - version: 7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.93.3) + version: 7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) examples/nextjs-bsky: dependencies: @@ -162,7 +162,7 @@ importers: version: 11.2.2 next: specifier: 16.0.7 - version: 16.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.3) + version: 16.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.97.2) react: specifier: 19.2.0 version: 19.2.0 @@ -242,7 +242,7 @@ importers: devDependencies: '@react-router/dev': specifier: 7.9.6 - version: 7.9.6(@react-router/serve@7.9.6(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(typescript@5.9.3))(@types/node@22.10.1)(@vitejs/plugin-rsc@0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)))(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(sass-embedded@1.93.3)(sass@1.93.3)(typescript@5.9.3)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)) + version: 7.9.6(@react-router/serve@7.9.6(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(typescript@5.9.3))(@types/node@22.10.1)(@vitejs/plugin-rsc@0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)))(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)(typescript@5.9.3)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)) '@types/node': specifier: 22.10.1 version: 22.10.1 @@ -254,19 +254,19 @@ importers: version: 19.2.2(@types/react@19.2.2) '@vitejs/plugin-rsc': specifier: 0.5.2 - version: 0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)) + version: 0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)) typescript: specifier: 5.9.3 version: 5.9.3 vite: specifier: 7.2.6 - version: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3) + version: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) vite-plugin-devtools-json: specifier: 1.0.0 - version: 1.0.0(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)) + version: 1.0.0(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)) vite-tsconfig-paths: specifier: 5.1.4 - version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)) + version: 5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)) modules/top-layer: dependencies: @@ -313,6 +313,49 @@ importers: specifier: 5.9.3 version: 5.9.3 + site: + dependencies: + '@robindoc/minisearch': + specifier: 3.7.2 + version: 3.7.2(tsx@4.21.0) + '@robindoc/next': + specifier: 3.7.2 + version: 3.7.2(next@16.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.97.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(robindoc@3.7.2(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + contection: + specifier: latest + version: 2.3.0(react@19.2.3) + next: + specifier: 16.1.4 + version: 16.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.97.2) + react: + specifier: 19.2.3 + version: 19.2.3 + react-dom: + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) + robindoc: + specifier: 3.7.2 + version: 3.7.2(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + sass: + specifier: 1.97.2 + version: 1.97.2 + devDependencies: + '@types/node': + specifier: 25.0.8 + version: 25.0.8 + '@types/react': + specifier: 19.2.8 + version: 19.2.8 + '@types/react-dom': + specifier: 19.2.3 + version: 19.2.3(@types/react@19.2.8) + tsx: + specifier: 4.21.0 + version: 4.21.0 + typescript: + specifier: 5.9.3 + version: 5.9.3 + tests: dependencies: contection: @@ -1083,156 +1126,312 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.25.12': resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.25.12': resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.25.12': resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.25.12': resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.25.12': resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.25.12': resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.12': resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.25.12': resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.25.12': resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.25.12': resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.25.12': resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.25.12': resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.25.12': resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.25.12': resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.25.12': resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.25.12': resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} engines: {node: '>=18'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + '@esbuild/netbsd-arm64@0.25.12': resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.12': resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + '@esbuild/openbsd-arm64@0.25.12': resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.12': resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/openharmony-arm64@0.25.12': resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + '@esbuild/sunos-x64@0.25.12': resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.25.12': resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.25.12': resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.25.12': resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} engines: {node: '>=18'} cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.9.0': resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1559,6 +1758,9 @@ packages: '@next/env@16.0.7': resolution: {integrity: sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==} + '@next/env@16.1.4': + resolution: {integrity: sha512-gkrXnZyxPUy0Gg6SrPQPccbNVLSP3vmW8LU5dwEttEEC1RwDivk8w4O+sZIjFvPrSICXyhQDCG+y3VmjlJf+9A==} + '@next/swc-darwin-arm64@16.0.4': resolution: {integrity: sha512-TN0cfB4HT2YyEio9fLwZY33J+s+vMIgC84gQCOLZOYusW7ptgjIn8RwxQt0BUpoo9XRRVVWEHLld0uhyux1ZcA==} engines: {node: '>= 10'} @@ -1571,6 +1773,12 @@ packages: cpu: [arm64] os: [darwin] + '@next/swc-darwin-arm64@16.1.4': + resolution: {integrity: sha512-T8atLKuvk13XQUdVLCv1ZzMPgLPW0+DWWbHSQXs0/3TjPrKNxTmUIhOEaoEyl3Z82k8h/gEtqyuoZGv6+Ugawg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + '@next/swc-darwin-x64@16.0.4': resolution: {integrity: sha512-XsfI23jvimCaA7e+9f3yMCoVjrny2D11G6H8NCcgv+Ina/TQhKPXB9P4q0WjTuEoyZmcNvPdrZ+XtTh3uPfH7Q==} engines: {node: '>= 10'} @@ -1583,6 +1791,12 @@ packages: cpu: [x64] os: [darwin] + '@next/swc-darwin-x64@16.1.4': + resolution: {integrity: sha512-AKC/qVjUGUQDSPI6gESTx0xOnOPQ5gttogNS3o6bA83yiaSZJek0Am5yXy82F1KcZCx3DdOwdGPZpQCluonuxg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + '@next/swc-linux-arm64-gnu@16.0.4': resolution: {integrity: sha512-uo8X7qHDy4YdJUhaoJDMAbL8VT5Ed3lijip2DdBHIB4tfKAvB1XBih6INH2L4qIi4jA0Qq1J0ErxcOocBmUSwg==} engines: {node: '>= 10'} @@ -1595,6 +1809,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-gnu@16.1.4': + resolution: {integrity: sha512-POQ65+pnYOkZNdngWfMEt7r53bzWiKkVNbjpmCt1Zb3V6lxJNXSsjwRuTQ8P/kguxDC8LRkqaL3vvsFrce4dMQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-arm64-musl@16.0.4': resolution: {integrity: sha512-pvR/AjNIAxsIz0PCNcZYpH+WmNIKNLcL4XYEfo+ArDi7GsxKWFO5BvVBLXbhti8Coyv3DE983NsitzUsGH5yTw==} engines: {node: '>= 10'} @@ -1607,6 +1827,12 @@ packages: cpu: [arm64] os: [linux] + '@next/swc-linux-arm64-musl@16.1.4': + resolution: {integrity: sha512-3Wm0zGYVCs6qDFAiSSDL+Z+r46EdtCv/2l+UlIdMbAq9hPJBvGu/rZOeuvCaIUjbArkmXac8HnTyQPJFzFWA0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + '@next/swc-linux-x64-gnu@16.0.4': resolution: {integrity: sha512-2hebpsd5MRRtgqmT7Jj/Wze+wG+ZEXUK2KFFL4IlZ0amEEFADo4ywsifJNeFTQGsamH3/aXkKWymDvgEi+pc2Q==} engines: {node: '>= 10'} @@ -1619,6 +1845,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-gnu@16.1.4': + resolution: {integrity: sha512-lWAYAezFinaJiD5Gv8HDidtsZdT3CDaCeqoPoJjeB57OqzvMajpIhlZFce5sCAH6VuX4mdkxCRqecCJFwfm2nQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-linux-x64-musl@16.0.4': resolution: {integrity: sha512-pzRXf0LZZ8zMljH78j8SeLncg9ifIOp3ugAFka+Bq8qMzw6hPXOc7wydY7ardIELlczzzreahyTpwsim/WL3Sg==} engines: {node: '>= 10'} @@ -1631,6 +1863,12 @@ packages: cpu: [x64] os: [linux] + '@next/swc-linux-x64-musl@16.1.4': + resolution: {integrity: sha512-fHaIpT7x4gA6VQbdEpYUXRGyge/YbRrkG6DXM60XiBqDM2g2NcrsQaIuj375egnGFkJow4RHacgBOEsHfGbiUw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + '@next/swc-win32-arm64-msvc@16.0.4': resolution: {integrity: sha512-7G/yJVzum52B5HOqqbQYX9bJHkN+c4YyZ2AIvEssMHQlbAWOn3iIJjD4sM6ihWsBxuljiTKJovEYlD1K8lCUHw==} engines: {node: '>= 10'} @@ -1643,6 +1881,12 @@ packages: cpu: [arm64] os: [win32] + '@next/swc-win32-arm64-msvc@16.1.4': + resolution: {integrity: sha512-MCrXxrTSE7jPN1NyXJr39E+aNFBrQZtO154LoCz7n99FuKqJDekgxipoodLNWdQP7/DZ5tKMc/efybx1l159hw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + '@next/swc-win32-x64-msvc@16.0.4': resolution: {integrity: sha512-0Vy4g8SSeVkuU89g2OFHqGKM4rxsQtihGfenjx2tRckPrge5+gtFnRWGAAwvGXr0ty3twQvcnYjEyOrLHJ4JWA==} engines: {node: '>= 10'} @@ -1655,6 +1899,12 @@ packages: cpu: [x64] os: [win32] + '@next/swc-win32-x64-msvc@16.1.4': + resolution: {integrity: sha512-JSVlm9MDhmTXw/sO2PE/MRj+G6XOSMZB+BcZ0a7d6KwVFZVpkHcb2okyoYFBaco6LeiL53BBklRlOrDDbOeE5w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + '@nimpl/cache-redis@0.4.0': resolution: {integrity: sha512-+e/wWC5zfuyV++skOUE8Mk6t+LQzX1U6GOq0ei7YbZTqBOUmli+TaTkRVm911OvyXGE9SPrElA/VMimcFX9a7Q==} @@ -1863,6 +2113,23 @@ packages: '@remix-run/node-fetch-server@0.9.0': resolution: {integrity: sha512-SoLMv7dbH+njWzXnOY6fI08dFMI5+/dQ+vY3n8RnnbdG7MdJEgiP28Xj/xWlnRnED/aB6SFw56Zop+LbmaaKqA==} + '@robindoc/minisearch@3.7.2': + resolution: {integrity: sha512-nzMtWIhjWU94RQUL8DFG98WtvQeC6iQCc+YCDVho1FftkWtIRBU/thMoPfoG8HxUF7Jqc9PXQvPFgYPoEYLvdg==} + hasBin: true + peerDependencies: + tsx: '>= 1.0.0' + peerDependenciesMeta: + tsx: + optional: true + + '@robindoc/next@3.7.2': + resolution: {integrity: sha512-PhFYOAQX++cCzAvM74jf+g49Hp8cSTIDickcVKzDwQyaytmvY6XeGLO6o1o63jrGLnhGLMivl5nhKowUANemRA==} + peerDependencies: + next: '>= 14.0.0' + react: '>= 18.3.0' + react-dom: '>= 18.3.0' + robindoc: '>= 3.0.0' + '@rolldown/pluginutils@1.0.0-beta.47': resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} @@ -1976,6 +2243,27 @@ packages: cpu: [x64] os: [win32] + '@shikijs/core@3.21.0': + resolution: {integrity: sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==} + + '@shikijs/engine-javascript@3.21.0': + resolution: {integrity: sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==} + + '@shikijs/engine-oniguruma@3.21.0': + resolution: {integrity: sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==} + + '@shikijs/langs@3.21.0': + resolution: {integrity: sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==} + + '@shikijs/themes@3.21.0': + resolution: {integrity: sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==} + + '@shikijs/types@3.21.0': + resolution: {integrity: sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + '@sinclair/typebox@0.34.41': resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} @@ -2040,6 +2328,9 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -2058,6 +2349,9 @@ packages: '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/node@22.10.1': resolution: {integrity: sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==} @@ -2067,20 +2361,34 @@ packages: '@types/node@24.10.0': resolution: {integrity: sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==} + '@types/node@25.0.8': + resolution: {integrity: sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==} + '@types/react-dom@19.2.2': resolution: {integrity: sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==} peerDependencies: '@types/react': ^19.2.0 + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + '@types/react@19.2.2': resolution: {integrity: sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==} + '@types/react@19.2.8': + resolution: {integrity: sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==} + '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} '@types/tough-cookie@4.0.5': resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} + '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} @@ -2445,6 +2753,9 @@ packages: caniuse-lite@1.0.30001757: resolution: {integrity: sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2453,6 +2764,12 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -2471,6 +2788,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + cluster-key-slot@1.1.2: resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} engines: {node: '>=0.10.0'} @@ -2492,6 +2813,9 @@ packages: colorjs.io@0.5.2: resolution: {integrity: sha512-twmVoizEW7ylZSN32OgKdXRmo1qg+wT5/6C3xu5b9QsWzSFAhHLn2xd8ro0diCsKfCj1RdaTP/nrcW+vAoQPIw==} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + compressible@2.0.18: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} @@ -2503,6 +2827,11 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + contection@2.3.0: + resolution: {integrity: sha512-SvgdLv4Ynb3Abpmf53a8WqYMlCSrG6JHwJvxA5FZqfKYaWSP2aGzFjQ/q8Tyvb/Hbbt8JT7bgl9cyhTGPNmH2w==} + peerDependencies: + react: '>= 19.0.0' + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -2615,12 +2944,32 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dot-prop@10.1.0: + resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==} + engines: {node: '>=20'} + dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} @@ -2652,6 +3001,10 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} @@ -2682,6 +3035,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -2791,6 +3149,10 @@ packages: resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -2900,6 +3262,12 @@ packages: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -2933,6 +3301,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -2945,10 +3317,19 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hosted-git-info@6.1.3: resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + html-dom-parser@5.1.2: + resolution: {integrity: sha512-9nD3Rj3/FuQt83AgIa1Y3ruzspwFFA54AJbQnohXN+K6fL1/bhcDQJJY5Ne4L4A163ADQFVESd/0TLyNoV0mfg==} + html-encoding-sniffer@4.0.0: resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} engines: {node: '>=18'} @@ -2956,6 +3337,21 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + html-react-parser@5.2.11: + resolution: {integrity: sha512-WnSQVn/D1UTj64nSz5y8MriL+MrbsZH80Ytr1oqKqs8DGZnphWY1R1pl3t7TY3rpqTSu+FHA21P80lrsmrdNBA==} + peerDependencies: + '@types/react': 0.14 || 15 || 16 || 17 || 18 || 19 + react: 0.14 || 15 || 16 || 17 || 18 || 19 + peerDependenciesMeta: + '@types/react': + optional: true + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + htmlparser2@10.0.0: + resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -3024,6 +3420,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + ioredis@5.8.2: resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} engines: {node: '>=12.22.0'} @@ -3039,6 +3438,10 @@ packages: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -3296,6 +3699,10 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} @@ -3362,10 +3769,18 @@ packages: makeerror@1.0.12: resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + marked@17.0.1: + resolution: {integrity: sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==} + engines: {node: '>= 20'} + hasBin: true + math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} @@ -3384,6 +3799,21 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + micromatch@4.0.8: resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} @@ -3424,6 +3854,9 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minisearch@7.2.0: + resolution: {integrity: sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==} + morgan@1.10.1: resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} engines: {node: '>= 0.8.0'} @@ -3501,6 +3934,27 @@ packages: sass: optional: true + next@16.1.4: + resolution: {integrity: sha512-gKSecROqisnV7Buen5BfjmXAm7Xlpx9o2ueVQRo5DxQcjC8d330dOM1xiGWc2k3Dcnz0In3VybyRPOsudwgiqQ==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + node-addon-api@7.1.1: resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} @@ -3564,6 +4018,12 @@ packages: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.4: + resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -3705,6 +4165,9 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -3736,12 +4199,20 @@ packages: peerDependencies: react: ^19.2.0 + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + peerDependencies: + react: ^19.2.3 + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-property@2.0.2: + resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==} + react-refresh@0.14.2: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} @@ -3764,6 +4235,10 @@ packages: resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} engines: {node: '>=0.10.0'} + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + engines: {node: '>=0.10.0'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -3791,6 +4266,15 @@ packages: regenerate@1.4.2: resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + regexpu-core@6.4.0: resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} engines: {node: '>=4'} @@ -3818,6 +4302,9 @@ packages: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} @@ -3831,6 +4318,12 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + robindoc@3.7.2: + resolution: {integrity: sha512-D2ZzYd7goA2ryXVIoH2r7re453naflxPFi5PYprPwAh350DoxTaohiQqrdyN3wSTObrSKNDX6M4/hCDc2Dya2w==} + peerDependencies: + react: '>= 18.3.0' + react-dom: '>= 18.3.0' + rollup@4.53.3: resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -3968,6 +4461,11 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + sass@1.97.2: + resolution: {integrity: sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==} + engines: {node: '>=14.0.0'} + hasBin: true + saxes@6.0.0: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} @@ -3975,6 +4473,10 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -4014,6 +4516,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shiki@3.21.0: + resolution: {integrity: sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -4055,6 +4560,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -4097,6 +4605,9 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -4105,6 +4616,10 @@ packages: resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} engines: {node: '>=12'} + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + strip-bom@4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} @@ -4124,6 +4639,12 @@ packages: strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + styled-jsx@5.1.6: resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} engines: {node: '>= 12.0.0'} @@ -4164,6 +4685,10 @@ packages: resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} engines: {node: ^14.18.0 || >=16.0.0} + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -4202,6 +4727,9 @@ packages: resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} engines: {node: '>=18'} + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + ts-api-utils@2.1.0: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} @@ -4221,6 +4749,11 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + turbo-stream@3.1.0: resolution: {integrity: sha512-tVI25WEXl4fckNEmrq70xU1XumxUwEx/FZD5AgEcV8ri7Wvrg2o7GEq8U7htrNx3CajciGm+kDyhRf5JB6t7/A==} @@ -4236,6 +4769,10 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} + type-fest@5.4.1: + resolution: {integrity: sha512-xygQcmneDyzsEuKZrFbRMne5HDqMs++aFzefrJTgEIKjQ3rekM+RPfFCVq2Gp1VIDqddoYeppCj4Pcb+RZW0GQ==} + engines: {node: '>=20'} + type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -4283,6 +4820,21 @@ packages: unicode-segmenter@0.14.0: resolution: {integrity: sha512-AH4lhPCJANUnSLEKnM4byboctePJzltF4xj8b+NbNiYeAkAXGh7px2K/4NANFp7dnr6+zB3e6HLu8Jj8SKyvYg==} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.1.0: + resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -4333,6 +4885,12 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -4535,6 +5093,9 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + snapshots: '@adobe/css-tools@4.4.4': {} @@ -5444,81 +6005,159 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true + '@esbuild/aix-ppc64@0.27.2': + optional: true + '@esbuild/android-arm64@0.25.12': optional: true + '@esbuild/android-arm64@0.27.2': + optional: true + '@esbuild/android-arm@0.25.12': optional: true + '@esbuild/android-arm@0.27.2': + optional: true + '@esbuild/android-x64@0.25.12': optional: true + '@esbuild/android-x64@0.27.2': + optional: true + '@esbuild/darwin-arm64@0.25.12': optional: true + '@esbuild/darwin-arm64@0.27.2': + optional: true + '@esbuild/darwin-x64@0.25.12': optional: true + '@esbuild/darwin-x64@0.27.2': + optional: true + '@esbuild/freebsd-arm64@0.25.12': optional: true + '@esbuild/freebsd-arm64@0.27.2': + optional: true + '@esbuild/freebsd-x64@0.25.12': optional: true + '@esbuild/freebsd-x64@0.27.2': + optional: true + '@esbuild/linux-arm64@0.25.12': optional: true + '@esbuild/linux-arm64@0.27.2': + optional: true + '@esbuild/linux-arm@0.25.12': optional: true + '@esbuild/linux-arm@0.27.2': + optional: true + '@esbuild/linux-ia32@0.25.12': optional: true + '@esbuild/linux-ia32@0.27.2': + optional: true + '@esbuild/linux-loong64@0.25.12': optional: true + '@esbuild/linux-loong64@0.27.2': + optional: true + '@esbuild/linux-mips64el@0.25.12': optional: true + '@esbuild/linux-mips64el@0.27.2': + optional: true + '@esbuild/linux-ppc64@0.25.12': optional: true + '@esbuild/linux-ppc64@0.27.2': + optional: true + '@esbuild/linux-riscv64@0.25.12': optional: true + '@esbuild/linux-riscv64@0.27.2': + optional: true + '@esbuild/linux-s390x@0.25.12': optional: true + '@esbuild/linux-s390x@0.27.2': + optional: true + '@esbuild/linux-x64@0.25.12': optional: true + '@esbuild/linux-x64@0.27.2': + optional: true + '@esbuild/netbsd-arm64@0.25.12': optional: true + '@esbuild/netbsd-arm64@0.27.2': + optional: true + '@esbuild/netbsd-x64@0.25.12': optional: true + '@esbuild/netbsd-x64@0.27.2': + optional: true + '@esbuild/openbsd-arm64@0.25.12': optional: true + '@esbuild/openbsd-arm64@0.27.2': + optional: true + '@esbuild/openbsd-x64@0.25.12': optional: true + '@esbuild/openbsd-x64@0.27.2': + optional: true + '@esbuild/openharmony-arm64@0.25.12': optional: true + '@esbuild/openharmony-arm64@0.27.2': + optional: true + '@esbuild/sunos-x64@0.25.12': optional: true + '@esbuild/sunos-x64@0.27.2': + optional: true + '@esbuild/win32-arm64@0.25.12': optional: true + '@esbuild/win32-arm64@0.27.2': + optional: true + '@esbuild/win32-ia32@0.25.12': optional: true + '@esbuild/win32-ia32@0.27.2': + optional: true + '@esbuild/win32-x64@0.25.12': optional: true + '@esbuild/win32-x64@0.27.2': + optional: true + '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': dependencies: eslint: 9.39.1 @@ -5916,54 +6555,80 @@ snapshots: '@next/env@16.0.7': {} + '@next/env@16.1.4': {} + '@next/swc-darwin-arm64@16.0.4': optional: true '@next/swc-darwin-arm64@16.0.7': optional: true + '@next/swc-darwin-arm64@16.1.4': + optional: true + '@next/swc-darwin-x64@16.0.4': optional: true '@next/swc-darwin-x64@16.0.7': optional: true + '@next/swc-darwin-x64@16.1.4': + optional: true + '@next/swc-linux-arm64-gnu@16.0.4': optional: true '@next/swc-linux-arm64-gnu@16.0.7': optional: true + '@next/swc-linux-arm64-gnu@16.1.4': + optional: true + '@next/swc-linux-arm64-musl@16.0.4': optional: true '@next/swc-linux-arm64-musl@16.0.7': optional: true + '@next/swc-linux-arm64-musl@16.1.4': + optional: true + '@next/swc-linux-x64-gnu@16.0.4': optional: true '@next/swc-linux-x64-gnu@16.0.7': optional: true + '@next/swc-linux-x64-gnu@16.1.4': + optional: true + '@next/swc-linux-x64-musl@16.0.4': optional: true '@next/swc-linux-x64-musl@16.0.7': optional: true + '@next/swc-linux-x64-musl@16.1.4': + optional: true + '@next/swc-win32-arm64-msvc@16.0.4': optional: true '@next/swc-win32-arm64-msvc@16.0.7': optional: true + '@next/swc-win32-arm64-msvc@16.1.4': + optional: true + '@next/swc-win32-x64-msvc@16.0.4': optional: true '@next/swc-win32-x64-msvc@16.0.7': optional: true + '@next/swc-win32-x64-msvc@16.1.4': + optional: true + '@nimpl/cache-redis@0.4.0': dependencies: chalk: 4.1.2 @@ -6085,7 +6750,7 @@ snapshots: '@pkgr/core@0.2.9': {} - '@react-router/dev@7.9.6(@react-router/serve@7.9.6(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(typescript@5.9.3))(@types/node@22.10.1)(@vitejs/plugin-rsc@0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)))(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(sass-embedded@1.93.3)(sass@1.93.3)(typescript@5.9.3)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3))': + '@react-router/dev@7.9.6(@react-router/serve@7.9.6(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(typescript@5.9.3))(@types/node@22.10.1)(@vitejs/plugin-rsc@0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)))(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)(typescript@5.9.3)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0))': dependencies: '@babel/core': 7.28.5 '@babel/generator': 7.28.5 @@ -6115,11 +6780,11 @@ snapshots: semver: 7.7.3 tinyglobby: 0.2.15 valibot: 1.2.0(typescript@5.9.3) - vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3) - vite-node: 3.2.4(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3) + vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) + vite-node: 3.2.4(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) optionalDependencies: '@react-router/serve': 7.9.6(react-router@7.9.6(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(typescript@5.9.3) - '@vitejs/plugin-rsc': 0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)) + '@vitejs/plugin-rsc': 0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)) typescript: 5.9.3 transitivePeerDependencies: - '@types/node' @@ -6191,6 +6856,21 @@ snapshots: '@remix-run/node-fetch-server@0.9.0': {} + '@robindoc/minisearch@3.7.2(tsx@4.21.0)': + dependencies: + gray-matter: 4.0.3 + marked: 17.0.1 + minisearch: 7.2.0 + optionalDependencies: + tsx: 4.21.0 + + '@robindoc/next@3.7.2(next@16.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.97.2))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(robindoc@3.7.2(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))': + dependencies: + next: 16.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.97.2) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + robindoc: 3.7.2(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@rolldown/pluginutils@1.0.0-beta.47': {} '@rollup/rollup-android-arm-eabi@4.53.3': @@ -6259,6 +6939,39 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true + '@shikijs/core@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.4 + + '@shikijs/engine-oniguruma@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + + '@shikijs/themes@3.21.0': + dependencies: + '@shikijs/types': 3.21.0 + + '@shikijs/types@3.21.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + '@sinclair/typebox@0.34.41': {} '@sinonjs/commons@3.0.1': @@ -6340,6 +7053,10 @@ snapshots: '@types/estree@1.0.8': {} + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/istanbul-lib-coverage@2.0.6': {} '@types/istanbul-lib-report@3.0.3': @@ -6363,6 +7080,10 @@ snapshots: '@types/json-schema@7.0.15': {} + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + '@types/node@22.10.1': dependencies: undici-types: 6.20.0 @@ -6375,18 +7096,32 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/node@25.0.8': + dependencies: + undici-types: 7.16.0 + '@types/react-dom@19.2.2(@types/react@19.2.2)': dependencies: '@types/react': 19.2.2 + '@types/react-dom@19.2.3(@types/react@19.2.8)': + dependencies: + '@types/react': 19.2.8 + '@types/react@19.2.2': dependencies: csstype: 3.2.3 + '@types/react@19.2.8': + dependencies: + csstype: 3.2.3 + '@types/stack-utils@2.0.3': {} '@types/tough-cookie@4.0.5': {} + '@types/unist@3.0.3': {} + '@types/yargs-parser@21.0.3': {} '@types/yargs@17.0.35': @@ -6547,7 +7282,7 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vitejs/plugin-react@5.1.1(vite@7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.93.3))': + '@vitejs/plugin-react@5.1.1(vite@7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0))': dependencies: '@babel/core': 7.28.5 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) @@ -6555,11 +7290,11 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.47 '@types/babel__core': 7.20.5 react-refresh: 0.18.0 - vite: 7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.93.3) + vite: 7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) transitivePeerDependencies: - supports-color - '@vitejs/plugin-rsc@0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3))': + '@vitejs/plugin-rsc@0.5.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0))': dependencies: '@remix-run/node-fetch-server': 0.12.0 es-module-lexer: 1.7.0 @@ -6570,8 +7305,8 @@ snapshots: react-dom: 19.2.0(react@19.2.0) strip-literal: 3.1.0 turbo-stream: 3.1.0 - vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3) - vitefu: 1.1.1(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)) + vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) + vitefu: 1.1.1(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)) accepts@1.3.8: dependencies: @@ -6793,6 +7528,8 @@ snapshots: caniuse-lite@1.0.30001757: {} + ccount@2.0.1: {} + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -6800,6 +7537,10 @@ snapshots: char-regex@1.0.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -6816,6 +7557,8 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + clsx@2.1.1: {} + cluster-key-slot@1.1.2: {} co@4.6.0: {} @@ -6830,6 +7573,8 @@ snapshots: colorjs.io@0.5.2: {} + comma-separated-tokens@2.0.3: {} + compressible@2.0.18: dependencies: mime-db: 1.54.0 @@ -6848,6 +7593,10 @@ snapshots: concat-map@0.0.1: {} + contection@2.3.0(react@19.2.3): + dependencies: + react: 19.2.3 + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -6923,10 +7672,36 @@ snapshots: detect-newline@3.1.0: {} + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dot-prop@10.1.0: + dependencies: + type-fest: 5.4.1 + dunder-proto@1.0.1: dependencies: call-bind-apply-helpers: 1.0.2 @@ -6949,6 +7724,8 @@ snapshots: encodeurl@2.0.0: {} + entities@4.5.0: {} + entities@6.0.1: {} err-code@2.0.3: {} @@ -6996,6 +7773,35 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -7152,6 +7958,10 @@ snapshots: transitivePeerDependencies: - supports-color + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -7261,6 +8071,12 @@ snapshots: get-stream@6.0.1: {} + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-slugger@2.0.0: {} + glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -7297,6 +8113,13 @@ snapshots: graphemer@1.4.0: {} + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.2 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + has-flag@4.0.0: {} has-symbols@1.1.0: {} @@ -7305,16 +8128,58 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-whitespace@3.0.0: + dependencies: + '@types/hast': 3.0.4 + hosted-git-info@6.1.3: dependencies: lru-cache: 7.18.3 + html-dom-parser@5.1.2: + dependencies: + domhandler: 5.0.3 + htmlparser2: 10.0.0 + html-encoding-sniffer@4.0.0: dependencies: whatwg-encoding: 3.1.1 html-escaper@2.0.2: {} + html-react-parser@5.2.11(@types/react@19.2.8)(react@19.2.3): + dependencies: + domhandler: 5.0.3 + html-dom-parser: 5.1.2 + react: 19.2.3 + react-property: 2.0.2 + style-to-js: 1.1.21 + optionalDependencies: + '@types/react': 19.2.8 + + html-void-elements@3.0.0: {} + + htmlparser2@10.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.2 + entities: 6.0.1 + http-errors@2.0.0: dependencies: depd: 2.0.0 @@ -7384,6 +8249,8 @@ snapshots: inherits@2.0.4: {} + inline-style-parser@0.2.7: {} + ioredis@5.8.2: dependencies: '@ioredis/commands': 1.4.0 @@ -7406,6 +8273,8 @@ snapshots: dependencies: hasown: 2.0.2 + is-extendable@0.1.1: {} + is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -7884,6 +8753,8 @@ snapshots: dependencies: json-buffer: 3.0.1 + kind-of@6.0.3: {} + leven@3.1.0: {} levn@0.4.1: @@ -7937,8 +8808,22 @@ snapshots: dependencies: tmpl: 1.0.5 + marked@17.0.1: {} + math-intrinsics@1.1.0: {} + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + media-typer@0.3.0: {} merge-descriptors@1.0.3: {} @@ -7949,6 +8834,23 @@ snapshots: methods@1.1.2: {} + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-encode@2.0.1: {} + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + micromatch@4.0.8: dependencies: braces: 3.0.3 @@ -7978,6 +8880,8 @@ snapshots: minipass@7.1.2: {} + minisearch@7.2.0: {} + morgan@1.10.1: dependencies: basic-auth: 2.0.1 @@ -8004,14 +8908,14 @@ snapshots: negotiator@0.6.4: {} - next@16.0.4(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.3): + next@16.0.4(react-dom@19.2.3(react@19.2.0))(react@19.2.0)(sass@1.97.2): dependencies: '@next/env': 16.0.4 '@swc/helpers': 0.5.15 caniuse-lite: 1.0.30001757 postcss: 8.4.31 react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) + react-dom: 19.2.3(react@19.2.0) styled-jsx: 5.1.6(react@19.2.0) optionalDependencies: '@next/swc-darwin-arm64': 16.0.4 @@ -8022,13 +8926,13 @@ snapshots: '@next/swc-linux-x64-musl': 16.0.4 '@next/swc-win32-arm64-msvc': 16.0.4 '@next/swc-win32-x64-msvc': 16.0.4 - sass: 1.93.3 + sass: 1.97.2 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - next@16.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.93.3): + next@16.0.7(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sass@1.97.2): dependencies: '@next/env': 16.0.7 '@swc/helpers': 0.5.15 @@ -8046,7 +8950,32 @@ snapshots: '@next/swc-linux-x64-musl': 16.0.7 '@next/swc-win32-arm64-msvc': 16.0.7 '@next/swc-win32-x64-msvc': 16.0.7 - sass: 1.93.3 + sass: 1.97.2 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + next@16.1.4(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(sass@1.97.2): + dependencies: + '@next/env': 16.1.4 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.8.31 + caniuse-lite: 1.0.30001757 + postcss: 8.4.31 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(react@19.2.3) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.4 + '@next/swc-darwin-x64': 16.1.4 + '@next/swc-linux-arm64-gnu': 16.1.4 + '@next/swc-linux-arm64-musl': 16.1.4 + '@next/swc-linux-x64-gnu': 16.1.4 + '@next/swc-linux-x64-musl': 16.1.4 + '@next/swc-win32-arm64-msvc': 16.1.4 + '@next/swc-win32-x64-msvc': 16.1.4 + sass: 1.97.2 sharp: 0.34.5 transitivePeerDependencies: - '@babel/core' @@ -8114,6 +9043,14 @@ snapshots: dependencies: mimic-fn: 2.1.0 + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.4: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.1.0 + regex-recursion: 6.0.2 + optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -8240,6 +9177,8 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + property-information@7.1.0: {} + proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -8269,10 +9208,22 @@ snapshots: react: 19.2.0 scheduler: 0.27.0 + react-dom@19.2.3(react@19.2.0): + dependencies: + react: 19.2.0 + scheduler: 0.27.0 + + react-dom@19.2.3(react@19.2.3): + dependencies: + react: 19.2.3 + scheduler: 0.27.0 + react-is@17.0.2: {} react-is@18.3.1: {} + react-property@2.0.2: {} + react-refresh@0.14.2: {} react-refresh@0.18.0: {} @@ -8285,8 +9236,18 @@ snapshots: optionalDependencies: react-dom: 19.2.0(react@19.2.0) + react-router@7.9.6(react-dom@19.2.3(react@19.2.0))(react@19.2.0): + dependencies: + cookie: 1.1.1 + react: 19.2.0 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.3(react@19.2.0) + react@19.2.0: {} + react@19.2.3: {} + readdirp@4.1.2: {} redent@3.0.0: @@ -8314,6 +9275,16 @@ snapshots: regenerate@1.4.2: {} + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + regexpu-core@6.4.0: dependencies: regenerate: 1.4.2 @@ -8339,6 +9310,8 @@ snapshots: resolve-from@5.0.0: {} + resolve-pkg-maps@1.0.0: {} + resolve@1.22.11: dependencies: is-core-module: 2.16.1 @@ -8349,6 +9322,21 @@ snapshots: reusify@1.1.0: {} + robindoc@3.7.2(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + clsx: 2.1.1 + dot-prop: 10.1.0 + github-slugger: 2.0.0 + gray-matter: 4.0.3 + html-react-parser: 5.2.11(@types/react@19.2.8)(react@19.2.3) + marked: 17.0.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + shiki: 3.21.0 + tinyglobby: 0.2.15 + transitivePeerDependencies: + - '@types/react' + rollup@4.53.3: dependencies: '@types/estree': 1.0.8 @@ -8490,12 +9478,25 @@ snapshots: '@parcel/watcher': 2.5.1 optional: true + sass@1.97.2: + dependencies: + chokidar: 4.0.3 + immutable: 5.1.4 + source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 + saxes@6.0.0: dependencies: xmlchars: 2.2.0 scheduler@0.27.0: {} + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + semver@6.3.1: {} semver@7.7.3: {} @@ -8587,6 +9588,17 @@ snapshots: shebang-regex@3.0.0: {} + shiki@3.21.0: + dependencies: + '@shikijs/core': 3.21.0 + '@shikijs/engine-javascript': 3.21.0 + '@shikijs/engine-oniguruma': 3.21.0 + '@shikijs/langs': 3.21.0 + '@shikijs/themes': 3.21.0 + '@shikijs/types': 3.21.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -8635,6 +9647,8 @@ snapshots: source-map@0.6.1: {} + space-separated-tokens@2.0.2: {} + spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 @@ -8678,6 +9692,11 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.2 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -8686,6 +9705,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom-string@1.0.0: {} + strip-bom@4.0.0: {} strip-final-newline@2.0.0: {} @@ -8700,11 +9721,24 @@ snapshots: dependencies: js-tokens: 9.0.1 + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + styled-jsx@5.1.6(react@19.2.0): dependencies: client-only: 0.0.1 react: 19.2.0 + styled-jsx@5.1.6(react@19.2.3): + dependencies: + client-only: 0.0.1 + react: 19.2.3 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -8727,6 +9761,8 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 + tagged-tag@1.0.0: {} + test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 @@ -8762,6 +9798,8 @@ snapshots: dependencies: punycode: 2.3.1 + trim-lines@3.0.1: {} + ts-api-utils@2.1.0(typescript@5.9.3): dependencies: typescript: 5.9.3 @@ -8772,6 +9810,13 @@ snapshots: tslib@2.8.1: {} + tsx@4.21.0: + dependencies: + esbuild: 0.27.2 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 + turbo-stream@3.1.0: {} type-check@0.4.0: @@ -8782,6 +9827,10 @@ snapshots: type-fest@0.21.3: {} + type-fest@5.4.1: + dependencies: + tagged-tag: 1.0.0 + type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -8823,6 +9872,29 @@ snapshots: unicode-segmenter@0.14.0: {} + unist-util-is@6.0.1: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.1.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + unpipe@1.0.0: {} unrs-resolver@1.11.1: @@ -8884,13 +9956,23 @@ snapshots: vary@1.1.2: {} - vite-node@3.2.4(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3): + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite-node@3.2.4(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3) + vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) transitivePeerDependencies: - '@types/node' - jiti @@ -8905,23 +9987,23 @@ snapshots: - tsx - yaml - vite-plugin-devtools-json@1.0.0(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)): + vite-plugin-devtools-json@1.0.0(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)): dependencies: uuid: 11.1.0 - vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3) + vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) - vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)): + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)): dependencies: debug: 4.4.3 globrex: 0.1.2 tsconfck: 3.1.6(typescript@5.9.3) optionalDependencies: - vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3) + vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) transitivePeerDependencies: - supports-color - typescript - vite@7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.93.3): + vite@7.2.2(@types/node@22.19.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -8932,10 +10014,11 @@ snapshots: optionalDependencies: '@types/node': 22.19.1 fsevents: 2.3.3 - sass: 1.93.3 + sass: 1.97.2 sass-embedded: 1.93.3 + tsx: 4.21.0 - vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3): + vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -8946,12 +10029,13 @@ snapshots: optionalDependencies: '@types/node': 22.10.1 fsevents: 2.3.3 - sass: 1.93.3 + sass: 1.97.2 sass-embedded: 1.93.3 + tsx: 4.21.0 - vitefu@1.1.1(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3)): + vitefu@1.1.1(vite@7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0)): optionalDependencies: - vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.93.3) + vite: 7.2.6(@types/node@22.10.1)(sass-embedded@1.93.3)(sass@1.97.2)(tsx@4.21.0) w3c-xmlserializer@5.0.0: dependencies: @@ -9030,3 +10114,5 @@ snapshots: zimmerframe@1.1.4: {} zod@3.25.76: {} + + zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3959455..9f87b57 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,7 @@ packages: - "package" - "tests" + - "site" - "examples/*" - "modules/*" - "adapters/*" diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..a408881 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,40 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo + +# generated +/public/sitemap* +/public/preview +/public/search-index.json diff --git a/site/next.config.mjs b/site/next.config.mjs new file mode 100644 index 0000000..4205403 --- /dev/null +++ b/site/next.config.mjs @@ -0,0 +1,4 @@ +/** @type {import("next").NextConfig} */ +const nextConfig = {}; + +export default nextConfig; diff --git a/site/package.json b/site/package.json new file mode 100644 index 0000000..29179b7 --- /dev/null +++ b/site/package.json @@ -0,0 +1,27 @@ +{ + "name": "robindoc-app", + "private": true, + "scripts": { + "dev": "pnpm run prebuild && next dev", + "prebuild": "robindoc-minisearch --template src/app/docs/robindoc.ts", + "build": "next build", + "start": "next start" + }, + "dependencies": { + "next": "16.1.4", + "react": "19.2.3", + "react-dom": "19.2.3", + "robindoc": "3.7.2", + "@robindoc/minisearch": "3.7.2", + "@robindoc/next": "3.7.2", + "sass": "1.97.2", + "contection": "latest" + }, + "devDependencies": { + "@types/node": "25.0.8", + "@types/react": "19.2.8", + "@types/react-dom": "19.2.3", + "typescript": "5.9.3", + "tsx": "4.21.0" + } +} \ No newline at end of file diff --git a/site/src/app/demo/components/computed-subscriber/index.tsx b/site/src/app/demo/components/computed-subscriber/index.tsx new file mode 100644 index 0000000..a5f8f98 --- /dev/null +++ b/site/src/app/demo/components/computed-subscriber/index.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useStore } from "contection"; + +import { DemoStore } from "../../store"; +import { RenderTracker } from "../render-tracker"; + +export function ComputedSubscriber() { + const counterTens = useStore(DemoStore, { + keys: ["counter"], + mutation: (store) => Math.floor(store.counter / 10), + }); + + return ( + +

Only when counter / 10 changes

+
{counterTens}
+
+ ); +} diff --git a/site/src/app/demo/components/conditional-subscriber/index.tsx b/site/src/app/demo/components/conditional-subscriber/index.tsx new file mode 100644 index 0000000..a9d0220 --- /dev/null +++ b/site/src/app/demo/components/conditional-subscriber/index.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useStore } from "contection"; + +import { DemoStore } from "../../store"; +import { RenderTracker } from "../render-tracker"; + +export function ConditionalSubscriber() { + const { counter } = useStore(DemoStore, { + keys: ["counter"], + enabled: (store) => store.theme === "dark", + }); + + return ( + +

Counter only when theme is dark

+
{counter}
+
+ ); +} diff --git a/site/src/app/demo/components/consumer-demo/index.tsx b/site/src/app/demo/components/consumer-demo/index.tsx new file mode 100644 index 0000000..b6ff324 --- /dev/null +++ b/site/src/app/demo/components/consumer-demo/index.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { DemoStore } from "../../store"; +import { RenderTracker } from "../render-tracker"; + +export function ConsumerDemo() { + return ( + +

Render-prop pattern

+ + {({ counter, theme }) => ( +
+ counter: {counter} + theme: {theme} +
+ )} +
+
+ ); +} diff --git a/site/src/app/demo/components/counter-subscriber/index.tsx b/site/src/app/demo/components/counter-subscriber/index.tsx new file mode 100644 index 0000000..1c27d5e --- /dev/null +++ b/site/src/app/demo/components/counter-subscriber/index.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { useStore } from "contection"; + +import { DemoStore } from "../../store"; +import { RenderTracker } from "../render-tracker"; + +export function CounterSubscriber() { + const { counter } = useStore(DemoStore, { keys: ["counter"] }); + + return ( + +

Only when counter changes

+
{counter}
+
+ ); +} diff --git a/site/src/app/demo/components/full-store-subscriber/index.tsx b/site/src/app/demo/components/full-store-subscriber/index.tsx new file mode 100644 index 0000000..5b90748 --- /dev/null +++ b/site/src/app/demo/components/full-store-subscriber/index.tsx @@ -0,0 +1,21 @@ +"use client"; + +import { useStore } from "contection"; + +import { DemoStore } from "../../store"; +import { RenderTracker } from "../render-tracker"; + +export function FullStoreSubscriber() { + const store = useStore(DemoStore); + + return ( + +

Re-renders on ANY change

+
+ counter: {store.counter} + theme: {store.theme} + user: {store.user.name} +
+
+ ); +} diff --git a/site/src/app/demo/components/index.tsx b/site/src/app/demo/components/index.tsx new file mode 100644 index 0000000..d86e27f --- /dev/null +++ b/site/src/app/demo/components/index.tsx @@ -0,0 +1,11 @@ +export { RenderTracker } from "./render-tracker"; +export { FullStoreSubscriber } from "./full-store-subscriber"; +export { CounterSubscriber } from "./counter-subscriber"; +export { UserSubscriber } from "./user-subscriber"; +export { ThemeSubscriber } from "./theme-subscriber"; +export { ComputedSubscriber } from "./computed-subscriber"; +export { ConditionalSubscriber } from "./conditional-subscriber"; +export { NoRenderControls } from "./no-render-controls"; +export { ConsumerDemo } from "./consumer-demo"; +export { StoreControls } from "./store-controls"; +export { Level1 } from "./nested-components"; diff --git a/site/src/app/demo/components/nested-components/index.tsx b/site/src/app/demo/components/nested-components/index.tsx new file mode 100644 index 0000000..5f64823 --- /dev/null +++ b/site/src/app/demo/components/nested-components/index.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { useStore } from "contection"; + +import { DemoStore } from "../../store"; +import { RenderTracker } from "../render-tracker"; + +import "./styles.scss"; + +function Level3() { + const { theme } = useStore(DemoStore, { keys: ["theme"] }); + return ( + + {theme} + + ); +} + +function Level2() { + const email = useStore(DemoStore, { + keys: ["user"], + mutation: (store) => store.user.email, + }); + return ( + + {email} + + + ); +} + +export function Level1() { + return ( + + + {({ counter }) => {counter}} + + + + ); +} diff --git a/site/src/app/demo/components/nested-components/styles.scss b/site/src/app/demo/components/nested-components/styles.scss new file mode 100644 index 0000000..6cf5f6d --- /dev/null +++ b/site/src/app/demo/components/nested-components/styles.scss @@ -0,0 +1,6 @@ +.nested-value { + display: block; + font-family: monospace; + color: var(--r-main-700); + margin-bottom: 12px; +} \ No newline at end of file diff --git a/site/src/app/demo/components/no-render-controls/index.tsx b/site/src/app/demo/components/no-render-controls/index.tsx new file mode 100644 index 0000000..0889215 --- /dev/null +++ b/site/src/app/demo/components/no-render-controls/index.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { useStoreReducer } from "contection"; + +import { DemoStore } from "../../store"; +import { RenderTracker } from "../render-tracker"; + +export function NoRenderControls() { + const [store, setStore] = useStoreReducer(DemoStore); + + return ( + +

Updates without re-rendering this component

+
+ + +
+
+ ); +} diff --git a/site/src/app/demo/components/render-tracker/index.tsx b/site/src/app/demo/components/render-tracker/index.tsx new file mode 100644 index 0000000..eaa2796 --- /dev/null +++ b/site/src/app/demo/components/render-tracker/index.tsx @@ -0,0 +1,47 @@ +"use client"; + +import { useRef, useEffect, type ReactNode } from "react"; + +import "./styles.scss"; + +interface RenderTrackerProps { + children: ReactNode; + label: string; + color: string; +} + +export function RenderTracker({ children, label, color }: RenderTrackerProps) { + const ref = useRef(null); + const countRef = useRef(0); + const countDisplayRef = useRef(null); + + useEffect(() => { + countRef.current += 1; + if (countDisplayRef.current) { + countDisplayRef.current.textContent = `${countRef.current}`; + } + + if (ref.current) { + ref.current.style.boxShadow = `inset 0 0 0 2px ${color}`; + + const timeout = setTimeout(() => { + if (ref.current) { + ref.current.style.boxShadow = "none"; + } + }, 300); + return () => clearTimeout(timeout); + } + }); + + return ( +
+
+ {label} + + {countRef.current} + +
+ {children} +
+ ); +} diff --git a/site/src/app/demo/components/render-tracker/styles.scss b/site/src/app/demo/components/render-tracker/styles.scss new file mode 100644 index 0000000..7868987 --- /dev/null +++ b/site/src/app/demo/components/render-tracker/styles.scss @@ -0,0 +1,64 @@ +.card { + padding: 16px; + background: var(--r-main-100); + border: 1px solid var(--r-main-200); + border-radius: 8px; + transition: box-shadow 0.3s; +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 12px; +} + +.card-label { + font-weight: 600; + color: var(--r-main-700); +} + +.card-count { + min-width: 24px; + padding: 2px 8px; + font-weight: 700; + font-family: monospace; + font-size: 12px; + color: white; + text-align: center; + border-radius: 100px; +} + +.card-desc { + color: var(--r-main-700); + margin: 0 0 12px; +} + +.card-value { + font-weight: 600; + font-family: monospace; + text-align: center; + padding: 12px; + background: var(--r-main-200); + border-radius: 8px; +} + +.card-values { + display: flex; + flex-direction: column; + gap: 6px; + + code { + font-family: monospace; + color: var(--r-main-700); + padding: 6px 10px; + background: var(--r-main-200); + border-radius: 4px; + } +} + +.card-buttons { + display: flex; + gap: 6px; + margin-top: 12px; +} \ No newline at end of file diff --git a/site/src/app/demo/components/store-controls/index.tsx b/site/src/app/demo/components/store-controls/index.tsx new file mode 100644 index 0000000..b922f20 --- /dev/null +++ b/site/src/app/demo/components/store-controls/index.tsx @@ -0,0 +1,97 @@ +"use client"; + +import { useStoreReducer } from "contection"; + +import { DemoStore, initialState } from "../../store"; + +import "./styles.scss"; + +export function StoreControls() { + const [, setStore] = useStoreReducer(DemoStore); + + const rapidUpdates = async (e: React.MouseEvent) => { + const node = e.currentTarget; + node.disabled = true; + for (let i = 0; i < 500; i++) { + await new Promise((r) => setTimeout(r, 0)); + const random = Math.random(); + if (random < 0.33) { + setStore((prev) => ({ counter: prev.counter + 1 })); + } else if (random < 0.66) { + setStore((prev) => ({ + user: { ...prev.user, email: `${Math.random().toString(36).slice(2, 8)}@demo.com` }, + })); + } else { + setStore((prev) => ({ theme: (prev.theme === "light" ? "dark" : "light") as "light" | "dark" })); + } + } + node.disabled = false; + }; + + return ( +
+
+ Counter +
+ + +
+
+ +
+ User +
+ + +
+
+ +
+ Theme +
+ +
+
+ +
+ + +
+
+ ); +} diff --git a/site/src/app/demo/components/store-controls/styles.scss b/site/src/app/demo/components/store-controls/styles.scss new file mode 100644 index 0000000..9825504 --- /dev/null +++ b/site/src/app/demo/components/store-controls/styles.scss @@ -0,0 +1,81 @@ +.controls { + display: flex; + flex-wrap: wrap; + gap: 24px; + padding: 20px; + background: var(--r-main-100); + border: 1px solid var(--r-main-200); + border-radius: 8px; + + @media (max-width: 600px) { + flex-direction: column; + gap: 16px; + } +} + +.control-group { + display: flex; + align-items: center; + gap: 12px; +} + +.control-group.actions { + margin-left: auto; + + @media (max-width: 600px) { + margin-left: 0; + } +} + +.control-label { + font-weight: 500; + color: var(--r-main-700); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.control-buttons { + display: flex; + gap: 6px; +} + +.btn { + padding: 8px 14px; + font-family: inherit; + font-size: inherit; + font-weight: 500; + border: 1px solid var(--r-main-200); + border-radius: 8px; + background: transparent; + color: var(--r-main-950); + cursor: pointer; + transition: all 0.15s; + + &:hover:not(:disabled) { + border-color: var(--r-main-700); + } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +.btn.primary { + background: var(--r-primary-500); + border-color: var(--r-primary-300); + color: white; + + &:hover:not(:disabled) { + background: var(--r-primary-600); + border-color: var(--r-primary-400); + } +} + +.btn.ghost { + color: var(--r-main-700); +} + +.btn.sm { + padding: 6px 10px; +} \ No newline at end of file diff --git a/site/src/app/demo/components/theme-subscriber/index.tsx b/site/src/app/demo/components/theme-subscriber/index.tsx new file mode 100644 index 0000000..aa6bd33 --- /dev/null +++ b/site/src/app/demo/components/theme-subscriber/index.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { useStore } from "contection"; + +import { DemoStore } from "../../store"; +import { RenderTracker } from "../render-tracker"; + +export function ThemeSubscriber() { + const { theme } = useStore(DemoStore, { keys: ["theme"] }); + + return ( + +

Only when theme changes

+
{theme}
+
+ ); +} diff --git a/site/src/app/demo/components/user-subscriber/index.tsx b/site/src/app/demo/components/user-subscriber/index.tsx new file mode 100644 index 0000000..a2c0b3d --- /dev/null +++ b/site/src/app/demo/components/user-subscriber/index.tsx @@ -0,0 +1,20 @@ +"use client"; + +import { useStore } from "contection"; + +import { DemoStore } from "../../store"; +import { RenderTracker } from "../render-tracker"; + +export function UserSubscriber() { + const { user } = useStore(DemoStore, { keys: ["user"] }); + + return ( + +

Only when user changes

+
+ {user.name} + {user.email} +
+
+ ); +} diff --git a/site/src/app/demo/demo.scss b/site/src/app/demo/demo.scss new file mode 100644 index 0000000..7f26d8d --- /dev/null +++ b/site/src/app/demo/demo.scss @@ -0,0 +1,35 @@ +.main { + font-size: 16px; + padding: 24px; +} + +.section { + margin-bottom: 48px; +} + +.section-desc { + color: var(--r-main-700); + margin: -8px 0 16px; +} + +.grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); + gap: 16px; + + @media (max-width: 600px) { + grid-template-columns: 1fr; + } +} + +.nested { + padding: 16px; + background: var(--r-main-100); + border: 1px solid var(--r-main-200); + border-radius: 8px; + + .card { + margin-top: 12px; + background: color-mix(in srgb, var(--r-main-200) 30%, transparent); + } +} \ No newline at end of file diff --git a/site/src/app/demo/page.tsx b/site/src/app/demo/page.tsx new file mode 100644 index 0000000..c395d17 --- /dev/null +++ b/site/src/app/demo/page.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { DemoStore, initialState } from "./store"; +import { + FullStoreSubscriber, + CounterSubscriber, + UserSubscriber, + ThemeSubscriber, + ComputedSubscriber, + ConditionalSubscriber, + NoRenderControls, + ConsumerDemo, + StoreControls, + Level1, +} from "./components"; + +import "./demo.scss"; + +export default function DemoPage() { + return ( + +
+
+

Controls

+ +
+ +
+

Subscription Patterns

+
+ + + + + + + + +
+
+ +
+

Nested Components

+

Each level subscribes independently

+
+ +
+
+
+
+ ); +} diff --git a/site/src/app/demo/store.ts b/site/src/app/demo/store.ts new file mode 100644 index 0000000..0e1600e --- /dev/null +++ b/site/src/app/demo/store.ts @@ -0,0 +1,31 @@ +"use client"; + +import { createStore } from "contection"; + +export interface DemoStore { + counter: number; + user: { + name: string; + email: string; + }; + theme: "light" | "dark"; + settings: { + notifications: boolean; + language: string; + }; +} + +export const initialState: DemoStore = { + counter: 0, + user: { + name: "Alex", + email: "alex@example.com", + }, + theme: "light", + settings: { + notifications: true, + language: "en", + }, +}; + +export const DemoStore = createStore(initialState); diff --git a/site/src/app/docs/[[...segments]]/page.tsx b/site/src/app/docs/[[...segments]]/page.tsx new file mode 100644 index 0000000..2dee9a6 --- /dev/null +++ b/site/src/app/docs/[[...segments]]/page.tsx @@ -0,0 +1,29 @@ +import { Page, getMetadata, getStaticParams } from "../robindoc"; + +export default async function Docs({ params }: { params: Promise<{ segments?: string[] }> }) { + const { segments } = await params; + const pathname = "/docs/" + (segments?.join("/") || ""); + + return ( + + ); +} + +export const generateMetadata = async ({ params }: { params: Promise<{ segments?: string[] }> }) => { + const { segments } = await params; + const pathname = "/docs/" + (segments?.join("/") || ""); + const metadata = await getMetadata(pathname); + + return metadata; +}; + +export const generateStaticParams = async () => { + const staticParams = await getStaticParams("/docs"); + + return staticParams; +}; diff --git a/site/src/app/docs/layout.tsx b/site/src/app/docs/layout.tsx new file mode 100644 index 0000000..6f80276 --- /dev/null +++ b/site/src/app/docs/layout.tsx @@ -0,0 +1,14 @@ +import type { ReactNode } from "react"; +import { DocsContainer, KeylinkToNavigation } from "robindoc"; + +import { Sidebar } from "./robindoc"; + +export default function DocsLayout({ children }: { children: ReactNode }) { + return ( + + + {children} + + + ); +} diff --git a/site/src/app/docs/robindoc.ts b/site/src/app/docs/robindoc.ts new file mode 100644 index 0000000..f5424b5 --- /dev/null +++ b/site/src/app/docs/robindoc.ts @@ -0,0 +1,30 @@ +import { notFound } from "next/navigation"; +import { initializeRobindoc } from "robindoc"; + +export const { Page, Sidebar, getPageData, getMetadata, getStaticParams, getPageInstruction } = initializeRobindoc( + { + configuration: { + sourceRoot: "../docs", + basePath: "/docs", + }, + items: [ + { + title: "Introduction", + type: "heading", + href: "/", + configuration: { + sourceRoot: "../README.md", + }, + }, + { + type: "separator", + }, + "auto", + ], + }, + { + processError: notFound, + matcher: ["/(?!.*\\..+).*"], + cache: true, + }, +); diff --git a/site/src/app/globals.css b/site/src/app/globals.css new file mode 100644 index 0000000..27bedc6 --- /dev/null +++ b/site/src/app/globals.css @@ -0,0 +1,12 @@ +html, +body { + padding: 0; + margin: 0; + font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} diff --git a/site/src/app/home.scss b/site/src/app/home.scss new file mode 100644 index 0000000..9c8dd80 --- /dev/null +++ b/site/src/app/home.scss @@ -0,0 +1,117 @@ +.home { + margin: 60px auto; +} + +.hero { + text-align: center; + margin-bottom: 64px; +} + +.hero h1 { + font-size: 3rem; + font-weight: 700; + margin: 0 0 12px; + letter-spacing: -0.02em; +} + +.tagline { + font-size: 1.125rem; + color: var(--r-main-700); + margin: 0 0 32px; +} + +.hero-actions { + display: flex; + gap: 12px; + justify-content: center; +} + +.btn-primary, +.btn-secondary { + padding: 10px 20px; + border-radius: 6px; + font-weight: 500; + text-decoration: none; + transition: opacity 0.15s; +} + +.btn-primary { + background: var(--r-primary-500); + color: white; +} + +.btn-secondary { + background: var(--r-main-200); + color: var(--r-main-900); +} + +.btn-primary:hover, +.btn-secondary:hover { + opacity: 0.85; +} + +.features { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 24px; + margin: 80px 0; + + @media (max-width: 600px) { + grid-template-columns: 1fr; + } +} + +.feature { + padding: 16px; + border-radius: 8px; + border: 4px solid var(--r-main-100); +} + +.feature h3 { + font-size: 1rem; + font-weight: 600; + margin: 0 0 6px; +} + +.feature p { + font-size: 0.875rem; + color: var(--r-main-700); + margin: 0; + line-height: 1.5; +} + +.example { + max-width: 600px; + margin: 60px auto; +} + +.example pre { + border-radius: 8px; + border: 4px solid var(--r-main-100); + padding: 16px; + overflow-x: auto; + margin: 0; +} + +.example code { + font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; + font-size: 0.875rem; + line-height: 1.6; + color: var(--r-main-900); +} + +.links { + display: flex; + gap: 24px; + justify-content: center; +} + +.links a { + color: var(--r-main-700); + text-decoration: none; +} + +.links a:hover { + color: var(--r-main-900); + text-decoration: underline; +} \ No newline at end of file diff --git a/site/src/app/layout.tsx b/site/src/app/layout.tsx new file mode 100644 index 0000000..64ca8bf --- /dev/null +++ b/site/src/app/layout.tsx @@ -0,0 +1,32 @@ +import { type ReactNode } from "react"; +import { RobinProvider, Header, Footer } from "robindoc"; +import { NavigationProvider } from "@robindoc/next"; + +import { searchProvider } from "./search-provider"; + +import "robindoc/lib/styles.css"; +import "./globals.css"; + +export default function RootLayout({ children }: { children: ReactNode }) { + return ( + + + + +
Contection} + links={[ + { href: "/docs", title: "Docs" }, + { href: "/demo", title: "Demo" }, + ]} + git="https://github.com/alexdln/contection" + searcher={searchProvider} + /> + {children} +