diff --git a/.changeset/config.json b/.changeset/config.json
index e4e693c..0bbe107 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -1,6 +1,6 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.1.1/schema.json",
- "changelog": "@changesets/cli/changelog",
+ "changelog": "@changesets/changelog-git",
"commit": true,
"fixed": [],
"linked": [],
diff --git a/.storybook/preview.ts b/.storybook/preview.ts
index 3250370..29c8b99 100644
--- a/.storybook/preview.ts
+++ b/.storybook/preview.ts
@@ -1,18 +1,19 @@
-import type { Preview } from '@storybook/react-vite'
+import type { Preview } from '@storybook/react-vite';
+
import { PreviewDecorator } from './previewDecorator';
const preview: Preview = {
+ decorators: [PreviewDecorator],
parameters: {
+ actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
- color: /(background|color)$/i,
- date: /Date$/i,
+ color: /(background|color)$/i,
+ date: /Date$/i,
},
},
+ layout: 'fullscreen',
},
- decorators: [
- PreviewDecorator,
- ],
};
export default preview;
\ No newline at end of file
diff --git a/.storybook/previewDecorator.tsx b/.storybook/previewDecorator.tsx
index 36e0979..671d85b 100644
--- a/.storybook/previewDecorator.tsx
+++ b/.storybook/previewDecorator.tsx
@@ -1,16 +1,40 @@
-import { MantineProvider } from '@mantine/core';
-import { ModalsProvider } from '@mantine/modals';
import '@mantine/core/styles.css';
-export function PreviewDecorator(Story: any, { parameters }: any) {
- console.log('Rendering decorator', parameters);
+import type { Decorator } from '@storybook/react-vite';
+
+import { MantineProvider } from '@mantine/core';
+import { ModalsProvider } from '@mantine/modals';
+
+import { GlobalStateStoreProvider } from '../src/GlobalStateStore';
+
+export const PreviewDecorator: Decorator = (Story, context) => {
+ const { parameters } = context;
+
+ // Allow stories to opt-out of GlobalStateStore provider
+ const useGlobalStore = parameters.globalStore !== false;
+
+ // Use 100% for fullscreen layout, 100vh otherwise
+ const isFullscreen = parameters.layout === 'fullscreen';
+ const containerStyle = {
+ height: isFullscreen ? '100%' : '100vh',
+ width: isFullscreen ? '100%' : '100vw',
+ };
+
return (
-
-
-
+ {useGlobalStore ? (
+
+
+
+
+
+ ) : (
+
+
+
+ )}
);
-}
+};
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b9ecc2c..09995a4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,11 @@
# @warkypublic/zustandsyncstore
+## 0.0.32
+
+### Patch Changes
+
+- 53e6b7b: Newest release
+
## 0.0.31
### Patch Changes
diff --git a/README.md b/README.md
index 07da8a7..2482229 100644
--- a/README.md
+++ b/README.md
@@ -8,14 +8,38 @@ Oranguru is a comprehensive component library that extends Mantine's component e
Currently featuring advanced menu components, Oranguru is designed to grow into a full suite of enhanced Mantine components that offer more flexibility and power than their standard counterparts.
-## Features
+## Components
-### Current Components
-- **Enhanced Context Menus**: Better menu positioning and visibility control
-- **Custom Rendering**: Support for custom menu item renderers and complete menu rendering
-- **Async Actions**: Built-in support for async menu item actions with loading states
+### MantineBetterMenu
+
+Enhanced context menus with better positioning and visibility control
+
+### Gridler
+
+Powerful data grid component with sorting, filtering, and pagination
+
+### Former
+
+Form component with React Hook Form integration and validation
+
+### FormerControllers
+
+Pre-built form input controls for use with Former
+
+### Boxer
+
+Advanced combobox/select with virtualization and server-side data support
+
+### ErrorBoundary
+
+React error boundary components for graceful error handling
+
+### GlobalStateStore
+
+Zustand-based global state management with automatic persistence
+
+## Core Features
-### Core Features
- **State Management**: Zustand-based store for component state management
- **TypeScript Support**: Full TypeScript definitions included
- **Portal-based Rendering**: Proper z-index handling through React portals
@@ -37,133 +61,266 @@ npm install react@">= 19.0.0" zustand@">= 5.0.0" @mantine/core@"^8.3.1" @mantine
## Usage
-### Basic Setup
+### MantineBetterMenu
```tsx
-import { MantineBetterMenusProvider } from '@warkypublic/oranguru';
-import { MantineProvider } from '@mantine/core';
+import { MantineBetterMenusProvider, useMantineBetterMenus } from '@warkypublic/oranguru';
-function App() {
- return (
-
-
- {/* Your app content */}
-
-
- );
-}
-```
+// Wrap app with provider
+
+
+
-### Using the Menu Hook
-
-```tsx
-import { useMantineBetterMenus } from '@warkypublic/oranguru';
-
-function MyComponent() {
- const { show, hide } = useMantineBetterMenus();
-
- const handleContextMenu = (e: React.MouseEvent) => {
- e.preventDefault();
- show('my-menu', {
- x: e.clientX,
- y: e.clientY,
- items: [
- {
- label: 'Edit',
- onClick: () => console.log('Edit clicked')
- },
- {
- label: 'Delete',
- onClick: () => console.log('Delete clicked')
- },
- {
- isDivider: true
- },
- {
- label: 'Async Action',
- onClickAsync: async () => {
- await new Promise(resolve => setTimeout(resolve, 2000));
- console.log('Async action completed');
- }
- }
- ]
- });
- };
-
- return (
-
- Right-click me for a context menu
-
- );
-}
-```
-
-### Custom Menu Items
-
-```tsx
-const customMenuItem = {
- renderer: ({ loading }: any) => (
-
- {loading ? 'Loading...' : 'Custom Item'}
-
- )
-};
-
-show('custom-menu', {
+// Use in components
+const { show, hide } = useMantineBetterMenus();
+show('menu-id', {
x: e.clientX,
y: e.clientY,
- items: [customMenuItem]
+ items: [
+ { label: 'Edit', onClick: () => {} },
+ { isDivider: true },
+ { label: 'Async', onClickAsync: async () => {} }
+ ]
});
```
+### Gridler
+
+```tsx
+import { Gridler } from '@warkypublic/oranguru';
+
+// Local data
+
+
+
+
+// API data
+
+
+
+
+// With inline editing form
+
+
+ {
+ setFormProps({ opened: true, request, values: data });
+ }}
+ />
+
+
+ setFormProps({ opened: false })}
+>
+
+
+
+
+// Columns definition
+const columns = [
+ { id: 'name', title: 'Name', width: 200 },
+ { id: 'email', title: 'Email', width: 250 }
+];
+```
+
+### Former
+
+```tsx
+import { Former, FormerDialog } from '@warkypublic/oranguru';
+
+const formRef = useRef(null);
+
+ { /* save logic */ }}
+ primeData={{ name: '', email: '' }}
+ wrapper={FormerDialog}
+>
+ {/* Form content */}
+
+
+// Methods: formRef.current.show(), .save(), .reset()
+```
+
+### FormerControllers
+
+```tsx
+import {
+ TextInputCtrl,
+ PasswordInputCtrl,
+ NativeSelectCtrl,
+ TextAreaCtrl,
+ SwitchCtrl,
+ ButtonCtrl
+} from '@warkypublic/oranguru';
+
+
+
+
+
+
+ Save
+
+```
+
+### Boxer
+
+```tsx
+import { Boxer } from '@warkypublic/oranguru';
+
+// Local data
+
+
+// Server-side data
+ ({
+ data: [...],
+ total: 100
+ })}
+ value={value}
+ onChange={setValue}
+/>
+
+// Multi-select
+
+```
+
+### ErrorBoundary
+
+```tsx
+import { ReactErrorBoundary, ReactBasicErrorBoundary } from '@warkypublic/oranguru';
+
+// Full-featured error boundary
+ {}}
+>
+
+
+
+// Basic error boundary
+
+
+
+```
+
+### GlobalStateStore
+
+```tsx
+import {
+ GlobalStateStoreProvider,
+ useGlobalStateStore,
+ GlobalStateStore
+} from '@warkypublic/oranguru';
+
+// Wrap app
+
+
+
+
+// Use in components
+const { program, session, user, layout } = useGlobalStateStore();
+const { refetch } = useGlobalStateStoreContext();
+
+// Outside React
+GlobalStateStore.getState().setAuthToken('token');
+const apiURL = GlobalStateStore.getState().session.apiURL;
+```
+
## API Reference
-### MantineBetterMenusProvider
+**MantineBetterMenu**
-The main provider component that wraps your application.
+- Provider: `MantineBetterMenusProvider`
+- Hook: `useMantineBetterMenus()` returns `{ show, hide, menus, setInstanceState }`
+- Key Props: `items[]`, `x`, `y`, `visible`, `menuProps`, `renderer`
-**Props:**
-- `providerID?`: Optional unique identifier for the provider instance
+**Gridler**
-### useMantineBetterMenus
+- Main Component: `Gridler`
+- Adaptors: `LocalDataAdaptor`, `APIAdaptorForGoLangv2`, `FormAdaptor`
+- Store Hook: `useGridlerStore()`
+- Key Props: `uniqueid`, `columns[]`, `data`
-Hook to access menu functionality.
+**Former**
-**Returns:**
-- `show(id: string, options?: Partial)`: Show a menu
-- `hide(id: string)`: Hide a menu
-- `menus`: Array of current menu instances
-- `setInstanceState`: Update specific menu instance properties
+- Main Component: `Former`
+- Wrappers: `FormerDialog`, `FormerModel`, `FormerPopover`
+- Ref Methods: `show()`, `close()`, `save()`, `reset()`, `validate()`
+- Key Props: `primeData`, `onSave`, `wrapper`
-### MantineBetterMenuInstance
+**FormerControllers**
-Interface for menu instances:
+- Controls: `TextInputCtrl`, `PasswordInputCtrl`, `TextAreaCtrl`, `NativeSelectCtrl`, `SwitchCtrl`, `ButtonCtrl`, `IconButtonCtrl`
+- Common Props: `name` (required), `label`, `disabled`
-```typescript
-interface MantineBetterMenuInstance {
- id: string;
- items?: Array;
- menuProps?: MenuProps;
- renderer?: ReactNode;
- visible: boolean;
- x: number;
- y: number;
+**Boxer**
+
+- Provider: `BoxerProvider`
+- Store Hook: `useBoxerStore()`
+- Data Sources: `local`, `server`
+- Key Props: `data`, `dataSource`, `onAPICall`, `multiSelect`, `searchable`, `clearable`
+
+**ErrorBoundary**
+
+- Components: `ReactErrorBoundary`, `ReactBasicErrorBoundary`
+- Key Props: `namespace`, `reportAPI`, `onResetClick`, `onRetryClick`
+
+**GlobalStateStore**
+
+- Provider: `GlobalStateStoreProvider`
+- Hook: `useGlobalStateStore()` returns `{ program, session, owner, user, layout, navigation, app }`
+- Store Methods: `setAuthToken()`, `setApiURL()`, `fetchData()`, `login()`, `logout()`
+- Key Props: `apiURL`, `autoFetch`, `fetchOnMount`, `throttleMs`
+
+## MCP Server
+
+Oranguru includes a Model Context Protocol (MCP) server for AI-assisted development.
+
+**Configuration:**
+
+Add to `~/.claude/mcp_settings.json`:
+
+```json
+{
+ "mcpServers": {
+ "oranguru-docs": {
+ "command": "npx",
+ "args": ["-y", "@warkypublic/oranguru", "mcp"]
+ }
+ }
}
```
-### MantineBetterMenuInstanceItem
+**Tools:**
-Interface for menu items:
+- `list_components` - List all components
+- `get_component_docs` - Get component documentation
+- `get_component_example` - Get code examples
-```typescript
-interface MantineBetterMenuInstanceItem extends Partial {
- isDivider?: boolean;
- label?: string;
- onClick?: (e?: React.MouseEvent) => void;
- onClickAsync?: () => Promise;
- renderer?: ((props: MantineBetterMenuInstanceItem & Record) => ReactNode) | ReactNode;
-}
-```
+**Resources:**
+
+- `oranguru://docs/readme` - Full documentation
+- `oranguru://docs/components` - Component list
+
+See `mcp/README.md` for details.
## Development
@@ -174,6 +331,7 @@ interface MantineBetterMenuInstanceItem extends Partial {
- `pnpm lint`: Run ESLint
- `pnpm typecheck`: Run TypeScript type checking
- `pnpm clean`: Clean node_modules and dist folders
+- `pnpm mcp`: Run MCP server
### Building
@@ -189,9 +347,10 @@ See [LICENSE](LICENSE) file for details.
## About the Name
-Oranguru is named after the Orangutan Pokémon (オランガ Oranga), a Normal/Psychic-type Pokémon introduced in Generation VII. Known as the "Sage Pokémon," Oranguru is characterized by its wisdom, intelligence, and ability to use tools strategically.
+Oranguru is named after the Orangutan Pokémon (オランガ Oranga), a Normal/Psychic-type Pokémon introduced in Generation VII. Known as the "Sage Pokémon," Oranguru is characterized by its wisdom, intelligence, and ability to use tools strategically.
In the Pokémon world, Oranguru is known for:
+
- Its exceptional intelligence and strategic thinking
- Living deep in forests and rarely showing itself to humans
- Using its psychic powers to control other Pokémon with its fan
@@ -201,4 +360,4 @@ Just as Oranguru the Pokémon enhances and controls its environment with wisdom
## Author
-**Warky Devs**
\ No newline at end of file
+Warky Devs
diff --git a/mcp-server.json b/mcp-server.json
new file mode 100644
index 0000000..ca37d4e
--- /dev/null
+++ b/mcp-server.json
@@ -0,0 +1,86 @@
+{
+ "name": "@warkypublic/oranguru-mcp",
+ "version": "0.0.31",
+ "description": "MCP server for Oranguru component library documentation and code generation",
+ "mcpServers": {
+ "oranguru-docs": {
+ "command": "node",
+ "args": ["mcp/server.js"],
+ "env": {}
+ }
+ },
+ "tools": [
+ {
+ "name": "get_component_docs",
+ "description": "Get documentation for a specific Oranguru component",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "component": {
+ "type": "string",
+ "enum": [
+ "MantineBetterMenu",
+ "Gridler",
+ "Former",
+ "FormerControllers",
+ "Boxer",
+ "ErrorBoundary",
+ "GlobalStateStore"
+ ],
+ "description": "The component name"
+ }
+ },
+ "required": ["component"]
+ }
+ },
+ {
+ "name": "get_component_example",
+ "description": "Generate code example for a specific Oranguru component",
+ "inputSchema": {
+ "type": "object",
+ "properties": {
+ "component": {
+ "type": "string",
+ "enum": [
+ "MantineBetterMenu",
+ "Gridler",
+ "Former",
+ "FormerControllers",
+ "Boxer",
+ "ErrorBoundary",
+ "GlobalStateStore"
+ ],
+ "description": "The component name"
+ },
+ "variant": {
+ "type": "string",
+ "description": "Example variant (e.g., 'basic', 'advanced', 'with-api')"
+ }
+ },
+ "required": ["component"]
+ }
+ },
+ {
+ "name": "list_components",
+ "description": "List all available Oranguru components",
+ "inputSchema": {
+ "type": "object",
+ "properties": {}
+ }
+ }
+ ],
+ "resources": [
+ {
+ "uri": "oranguru://docs/readme",
+ "name": "Oranguru Documentation",
+ "description": "Main documentation for the Oranguru library",
+ "mimeType": "text/markdown"
+ },
+ {
+ "uri": "oranguru://docs/components",
+ "name": "Component List",
+ "description": "List of all available components",
+ "mimeType": "application/json"
+ }
+ ]
+}
diff --git a/mcp/README.md b/mcp/README.md
new file mode 100644
index 0000000..1549536
--- /dev/null
+++ b/mcp/README.md
@@ -0,0 +1,102 @@
+# Oranguru MCP Server
+
+Model Context Protocol server for Oranguru component library documentation and code generation.
+
+## Installation
+
+```bash
+npm install @warkypublic/oranguru
+```
+
+## Configuration
+
+Add to your Claude Code MCP settings (`~/.claude/mcp_settings.json`):
+
+```json
+{
+ "mcpServers": {
+ "oranguru-docs": {
+ "command": "node",
+ "args": ["./node_modules/@warkypublic/oranguru/mcp/server.js"]
+ }
+ }
+}
+```
+
+Or use npx:
+
+```json
+{
+ "mcpServers": {
+ "oranguru-docs": {
+ "command": "npx",
+ "args": ["-y", "@warkypublic/oranguru", "mcp"]
+ }
+ }
+}
+```
+
+## Available Tools
+
+### `list_components`
+List all available Oranguru components
+
+**Returns:** JSON array of components with name, description, and exports
+
+### `get_component_docs`
+Get detailed documentation for a specific component
+
+**Parameters:**
+- `component` (required): Component name (MantineBetterMenu, Gridler, Former, etc.)
+
+**Returns:** JSON object with component details, exports, and usage information
+
+### `get_component_example`
+Get code examples for a specific component
+
+**Parameters:**
+- `component` (required): Component name
+- `variant` (optional): Example variant ('basic', 'local', 'server', etc.)
+
+**Returns:** Code example string
+
+## Available Resources
+
+### `oranguru://docs/readme`
+Full README documentation
+
+**MIME Type:** text/markdown
+
+### `oranguru://docs/components`
+Component list in JSON format
+
+**MIME Type:** application/json
+
+## Components
+
+- **MantineBetterMenu** - Enhanced context menus
+- **Gridler** - Data grid component
+- **Former** - Form component with React Hook Form
+- **FormerControllers** - Form input controls
+- **Boxer** - Advanced combobox/select
+- **ErrorBoundary** - Error boundary components
+- **GlobalStateStore** - Global state management
+
+## Usage in Claude Code
+
+Once configured, you can ask Claude Code:
+
+- "Show me examples of the Gridler component"
+- "Get documentation for the Former component"
+- "List all Oranguru components"
+- "Generate a code example for Boxer with server-side data"
+
+## Running Locally
+
+```bash
+npm run mcp
+```
+
+## License
+
+See main package LICENSE file
diff --git a/mcp/server.js b/mcp/server.js
new file mode 100755
index 0000000..2bb57e0
--- /dev/null
+++ b/mcp/server.js
@@ -0,0 +1,953 @@
+#!/usr/bin/env node
+
+/**
+ * Oranguru MCP Server
+ * Provides documentation and code generation for Oranguru components
+ */
+
+import { Server } from '@modelcontextprotocol/sdk/server/index.js';
+import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
+import {
+ CallToolRequestSchema,
+ ListResourcesRequestSchema,
+ ListToolsRequestSchema,
+ ReadResourceRequestSchema,
+} from '@modelcontextprotocol/sdk/types.js';
+import { readFileSync } from 'fs';
+import { dirname, join } from 'path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = dirname(__filename);
+
+// Component documentation data
+const COMPONENTS = {
+ Boxer: {
+ component: 'Boxer',
+ description: 'Advanced combobox/select with virtualization and server-side data support',
+ examples: {
+ basic: `import { Boxer } from '@warkypublic/oranguru';
+import { useState } from 'react';
+
+const sampleData = [
+ { label: 'Apple', value: 'apple' },
+ { label: 'Banana', value: 'banana' },
+ { label: 'Cherry', value: 'cherry' }
+];
+
+function MyComponent() {
+ const [value, setValue] = useState(null);
+
+ return (
+
+ );
+}`,
+ multiSelect: `import { Boxer } from '@warkypublic/oranguru';
+import { useState } from 'react';
+
+function MultiSelectExample() {
+ const [values, setValues] = useState([]);
+
+ return (
+
+ );
+}`,
+ server: `import { Boxer } from '@warkypublic/oranguru';
+
+function ServerSideExample() {
+ const [value, setValue] = useState(null);
+
+ const handleAPICall = async ({ page, pageSize, search }) => {
+ const response = await fetch(\`/api/items?page=\${page}&size=\${pageSize}&search=\${search}\`);
+ const result = await response.json();
+
+ return {
+ data: result.items,
+ total: result.total
+ };
+ };
+
+ return (
+
+ );
+}`
+ },
+ exports: ['Boxer', 'BoxerProvider', 'useBoxerStore'],
+ hook: 'useBoxerStore()',
+ name: 'Boxer',
+ provider: 'BoxerProvider'
+ },
+ ErrorBoundary: {
+ components: ['ReactErrorBoundary', 'ReactBasicErrorBoundary'],
+ description: 'React error boundary components for graceful error handling',
+ examples: {
+ basic: `import { ReactBasicErrorBoundary } from '@warkypublic/oranguru';
+
+function App() {
+ return (
+
+
+
+ );
+}`,
+ full: `import { ReactErrorBoundary } from '@warkypublic/oranguru';
+
+function App() {
+ const handleReportError = () => {
+ console.log('Report error to support');
+ };
+
+ const handleReset = () => {
+ console.log('Reset application state');
+ window.location.reload();
+ };
+
+ const handleRetry = () => {
+ console.log('Retry failed operation');
+ };
+
+ return (
+
+
+
+ );
+}`,
+ nested: `import { ReactErrorBoundary } from '@warkypublic/oranguru';
+
+// Multiple error boundaries for granular error handling
+function App() {
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}`,
+ globalConfig: `import { SetErrorBoundaryOptions } from '@warkypublic/oranguru';
+
+// Configure error boundary globally
+SetErrorBoundaryOptions({
+ disabled: false, // Set to true to pass through errors (dev mode)
+ onError: (error, errorInfo) => {
+ console.error('Global error handler:', error);
+ // Send to analytics service
+ analytics.trackError(error, errorInfo);
+ }
+});`
+ },
+ exports: ['ReactErrorBoundary', 'ReactBasicErrorBoundary', 'SetErrorBoundaryOptions', 'GetErrorBoundaryOptions'],
+ name: 'ErrorBoundary'
+ },
+ Former: {
+ component: 'Former',
+ description: 'Form component with React Hook Form integration and validation',
+ examples: {
+ basic: `import { Former } from '@warkypublic/oranguru';
+import { useRef } from 'react';
+import { Controller } from 'react-hook-form';
+
+function BasicForm() {
+ const formRef = useRef(null);
+
+ return (
+ {
+ console.log('Saving:', data);
+ await fetch('/api/save', {
+ method: 'POST',
+ body: JSON.stringify(data)
+ });
+ }}
+ >
+ (
+
+ )}
+ rules={{ required: 'Name is required' }}
+ />
+ (
+
+ )}
+ rules={{ required: 'Email is required' }}
+ />
+ Save
+
+ );
+}`,
+ withWrapper: `import { Former, FormerModel } from '@warkypublic/oranguru';
+import { useState } from 'react';
+
+function ModalForm() {
+ const [opened, setOpened] = useState(false);
+
+ return (
+ <>
+ setOpened(true)}>Open Form
+
+ setOpened(false)}
+ >
+ }
+ />
+
+ >
+ );
+}`,
+ withAPI: `import { Former, FormerRestHeadSpecAPI } from '@warkypublic/oranguru';
+
+function APIForm() {
+ return (
+
+ {/* Form fields */}
+
+ );
+}`,
+ customLayout: `import { Former } from '@warkypublic/oranguru';
+
+function CustomLayoutForm() {
+ return (
+
+ {/* Form fields */}
+
+ );
+}`,
+ refMethods: `import { Former } from '@warkypublic/oranguru';
+import { useRef } from 'react';
+
+function FormWithRef() {
+ const formRef = useRef(null);
+
+ const handleValidate = async () => {
+ const isValid = await formRef.current?.validate();
+ console.log('Form valid:', isValid);
+ };
+
+ const handleSave = async () => {
+ const result = await formRef.current?.save();
+ console.log('Save result:', result);
+ };
+
+ const handleReset = () => {
+ formRef.current?.reset();
+ };
+
+ return (
+
+
+ {/* Form fields */}
+
+ Validate
+ Save
+ Reset
+
+ );
+}`
+ },
+ exports: ['Former', 'FormerDialog', 'FormerModel', 'FormerPopover', 'FormerRestHeadSpecAPI'],
+ name: 'Former',
+ wrappers: ['FormerDialog', 'FormerModel', 'FormerPopover']
+ },
+ FormerControllers: {
+ controls: ['TextInputCtrl', 'PasswordInputCtrl', 'NativeSelectCtrl', 'TextAreaCtrl', 'SwitchCtrl', 'ButtonCtrl', 'IconButtonCtrl'],
+ description: 'Pre-built form input controls for use with Former',
+ examples: {
+ basic: `import { TextInputCtrl, PasswordInputCtrl, NativeSelectCtrl, ButtonCtrl } from '@warkypublic/oranguru';
+
+
+
+
+
+ Save
+ `
+ },
+ exports: ['TextInputCtrl', 'PasswordInputCtrl', 'NativeSelectCtrl', 'TextAreaCtrl', 'SwitchCtrl', 'ButtonCtrl', 'IconButtonCtrl'],
+ name: 'FormerControllers'
+ },
+ GlobalStateStore: {
+ description: 'Zustand-based global state management with automatic persistence',
+ examples: {
+ basic: `import { useGlobalStateStore } from '@warkypublic/oranguru';
+
+function MyComponent() {
+ const state = useGlobalStateStore();
+
+ return (
+
+
{state.program.name}
+
User: {state.user.username}
+
Email: {state.user.email}
+
Connected: {state.session.connected ? 'Yes' : 'No'}
+
+ );
+}`,
+ provider: `import { GlobalStateStoreProvider } from '@warkypublic/oranguru';
+
+function App() {
+ return (
+
+
+
+ );
+}`,
+ stateUpdates: `import { useGlobalStateStore } from '@warkypublic/oranguru';
+
+function StateControls() {
+ const state = useGlobalStateStore();
+
+ const handleUpdateProgram = () => {
+ state.setProgram({
+ name: 'My App',
+ slug: 'my-app',
+ description: 'A great application'
+ });
+ };
+
+ const handleUpdateUser = () => {
+ state.setUser({
+ username: 'john_doe',
+ email: 'john@example.com',
+ fullNames: 'John Doe'
+ });
+ };
+
+ const handleToggleTheme = () => {
+ state.setUser({
+ theme: {
+ ...state.user.theme,
+ darkMode: !state.user.theme?.darkMode
+ }
+ });
+ };
+
+ return (
+
+ Update Program
+ Update User
+ Toggle Dark Mode
+
+ );
+}`,
+ layout: `import { useGlobalStateStore } from '@warkypublic/oranguru';
+
+function LayoutControls() {
+ const state = useGlobalStateStore();
+
+ return (
+
+ state.setLeftBar({ open: !state.layout.leftBar.open })}>
+ Toggle Left Bar
+
+ state.setRightBar({ open: !state.layout.rightBar.open })}>
+ Toggle Right Bar
+
+ state.setLeftBar({ pinned: true, size: 250 })}>
+ Pin & Resize Left Bar
+
+
+ );
+}`,
+ outsideReact: `import { GlobalStateStore } from '@warkypublic/oranguru';
+
+// Access state outside React components
+const currentState = GlobalStateStore.getState();
+console.log('API URL:', currentState.session.apiURL);
+
+// Update state outside React
+GlobalStateStore.getState().setAuthToken('new-token');
+GlobalStateStore.getState().setApiURL('https://new-api.com');
+
+// Subscribe to changes
+const unsubscribe = GlobalStateStore.subscribe(
+ (state) => state.session.connected,
+ (connected) => console.log('Connected:', connected)
+);`
+ },
+ exports: ['GlobalStateStore', 'GlobalStateStoreProvider', 'useGlobalStateStore', 'useGlobalStateStoreContext'],
+ hook: 'useGlobalStateStore()',
+ name: 'GlobalStateStore',
+ provider: 'GlobalStateStoreProvider',
+ store: 'GlobalStateStore'
+ },
+ Gridler: {
+ adaptors: ['LocalDataAdaptor', 'APIAdaptorForGoLangv2', 'FormAdaptor'],
+ component: 'Gridler',
+ description: 'Powerful data grid component with sorting, filtering, and pagination',
+ examples: {
+ basic: `import { Gridler } from '@warkypublic/oranguru';
+
+const columns = [
+ { id: 'name', title: 'Name', width: 200 },
+ { id: 'email', title: 'Email', width: 250 },
+ { id: 'role', title: 'Role', width: 150 }
+];
+
+const data = [
+ { name: 'John Doe', email: 'john@example.com', role: 'Admin' },
+ { name: 'Jane Smith', email: 'jane@example.com', role: 'User' }
+];
+
+function GridExample() {
+ return (
+
+
+
+ );
+}`,
+ customCell: `// Custom cell rendering with icons
+const columns = [
+ {
+ id: 'status',
+ title: 'Status',
+ width: 100,
+ Cell: (row) => {
+ const icon = row?.active ? '✅' : '❌';
+ return {
+ data: \`\${icon} \${row?.status}\`,
+ displayData: \`\${icon} \${row?.status}\`
+ };
+ }
+ },
+ { id: 'name', title: 'Name', width: 200 }
+];`,
+ api: `import { Gridler, GlidlerAPIAdaptorForGoLangv2 } from '@warkypublic/oranguru';
+import { useRef } from 'react';
+
+function APIGridExample() {
+ const gridRef = useRef(null);
+
+ const columns = [
+ { id: 'id', title: 'ID', width: 100 },
+ { id: 'name', title: 'Name', width: 200 },
+ { id: 'status', title: 'Status', width: 150 }
+ ];
+
+ return (
+ console.log('Selected:', values)}
+ >
+
+
+ );
+}`,
+ withForm: `// Gridler with Former integration for inline editing
+import { Gridler, GlidlerAPIAdaptorForGoLangv2 } from '@warkypublic/oranguru';
+import { FormerDialog } from '@warkypublic/oranguru';
+import { TextInputCtrl, NativeSelectCtrl } from '@warkypublic/oranguru';
+import { useState, useRef } from 'react';
+
+function EditableGrid() {
+ const gridRef = useRef(null);
+ const [formProps, setFormProps] = useState({
+ opened: false,
+ request: null,
+ values: null,
+ onClose: () => setFormProps(prev => ({
+ ...prev,
+ opened: false,
+ request: null,
+ values: null
+ })),
+ onChange: (request, data) => {
+ gridRef.current?.refresh({ value: data });
+ }
+ });
+
+ const columns = [
+ { id: 'id', title: 'ID', width: 100 },
+ { id: 'name', title: 'Name', width: 200 },
+ { id: 'type', title: 'Type', width: 150 }
+ ];
+
+ return (
+ <>
+
+
+ {
+ setFormProps(prev => ({
+ ...prev,
+ opened: true,
+ request: request,
+ values: data
+ }));
+ }}
+ />
+
+
+
+
+
+
+
+ >
+ );
+}`,
+ refMethods: `// Using Gridler ref methods for programmatic control
+import { Gridler } from '@warkypublic/oranguru';
+import { useRef } from 'react';
+
+function GridWithControls() {
+ const gridRef = useRef(null);
+
+ return (
+ <>
+
+
+
+
+
+ gridRef.current?.refresh()}>
+ Refresh Grid
+
+ gridRef.current?.selectRow(123)}>
+ Select Row 123
+
+ gridRef.current?.scrollToRow(456)}>
+ Scroll to Row 456
+
+ gridRef.current?.reloadRow(789)}>
+ Reload Row 789
+
+
+ >
+ );
+}`,
+ sections: `// Gridler with custom side sections
+import { Gridler } from '@warkypublic/oranguru';
+import { useState } from 'react';
+
+function GridWithSections() {
+ const [sections, setSections] = useState({
+ top: Top Bar
,
+ bottom: Bottom Bar
,
+ left: L
,
+ right: R
+ });
+
+ return (
+
+
+
+ );
+}`
+ },
+ exports: ['Gridler', 'GlidlerLocalDataAdaptor', 'GlidlerAPIAdaptorForGoLangv2', 'GlidlerFormAdaptor'],
+ hook: 'useGridlerStore()',
+ name: 'Gridler'
+ },
+ MantineBetterMenu: {
+ description: 'Enhanced context menus with better positioning and visibility control',
+ examples: {
+ provider: `import { MantineBetterMenusProvider } from '@warkypublic/oranguru';
+import { MantineProvider } from '@mantine/core';
+
+function App() {
+ return (
+
+
+
+
+
+ );
+}`,
+ contextMenu: `import { useMantineBetterMenus } from '@warkypublic/oranguru';
+
+function MyComponent() {
+ const { show, hide } = useMantineBetterMenus();
+
+ const handleContextMenu = (e) => {
+ e.preventDefault();
+
+ show('context-menu', {
+ x: e.clientX,
+ y: e.clientY,
+ items: [
+ {
+ label: 'Edit',
+ onClick: () => console.log('Edit clicked')
+ },
+ {
+ label: 'Copy',
+ onClick: () => console.log('Copy clicked')
+ },
+ {
+ isDivider: true
+ },
+ {
+ label: 'Delete',
+ onClick: () => console.log('Delete clicked'),
+ color: 'red'
+ }
+ ]
+ });
+ };
+
+ return (
+
+ Right-click me for a context menu
+
+ );
+}`,
+ asyncActions: `import { useMantineBetterMenus } from '@warkypublic/oranguru';
+
+function AsyncMenuExample() {
+ const { show } = useMantineBetterMenus();
+
+ const handleClick = (e) => {
+ show('async-menu', {
+ x: e.clientX,
+ y: e.clientY,
+ items: [
+ {
+ label: 'Save',
+ onClickAsync: async () => {
+ await fetch('/api/save', { method: 'POST' });
+ console.log('Saved successfully');
+ }
+ },
+ {
+ label: 'Load Data',
+ onClickAsync: async () => {
+ const data = await fetch('/api/data').then(r => r.json());
+ console.log('Data loaded:', data);
+ }
+ }
+ ]
+ });
+ };
+
+ return Show Menu ;
+}`,
+ customRenderer: `import { useMantineBetterMenus } from '@warkypublic/oranguru';
+
+function CustomMenuExample() {
+ const { show } = useMantineBetterMenus();
+
+ const handleClick = (e) => {
+ show('custom-menu', {
+ x: e.clientX,
+ y: e.clientY,
+ items: [
+ {
+ renderer: ({ loading }) => (
+
+ {loading ? 'Processing...' : 'Custom Item'}
+
+ )
+ }
+ ]
+ });
+ };
+
+ return Custom Menu ;
+}`
+ },
+ exports: ['MantineBetterMenusProvider', 'useMantineBetterMenus'],
+ hook: 'useMantineBetterMenus()',
+ name: 'MantineBetterMenu',
+ provider: 'MantineBetterMenusProvider'
+ }
+};
+
+class OranguruMCPServer {
+ constructor() {
+ this.server = new Server(
+ {
+ name: 'oranguru-docs',
+ version: '0.0.31',
+ },
+ {
+ capabilities: {
+ resources: {},
+ tools: {},
+ },
+ }
+ );
+
+ this.setupHandlers();
+ this.server.onerror = (error) => console.error('[MCP Error]', error);
+ process.on('SIGINT', async () => {
+ await this.server.close();
+ process.exit(0);
+ });
+ }
+
+ async run() {
+ const transport = new StdioServerTransport();
+ await this.server.connect(transport);
+ console.error('Oranguru MCP server running on stdio');
+ }
+
+ setupHandlers() {
+ // List available tools
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
+ tools: [
+ {
+ description: 'Get documentation for a specific Oranguru component',
+ inputSchema: {
+ properties: {
+ component: {
+ description: 'The component name',
+ enum: Object.keys(COMPONENTS),
+ type: 'string',
+ },
+ },
+ required: ['component'],
+ type: 'object',
+ },
+ name: 'get_component_docs',
+ },
+ {
+ description: 'Generate code example for a specific Oranguru component',
+ inputSchema: {
+ properties: {
+ component: {
+ description: 'The component name',
+ enum: Object.keys(COMPONENTS),
+ type: 'string',
+ },
+ variant: {
+ description: "Example variant (e.g., 'basic', 'local', 'server')",
+ type: 'string',
+ },
+ },
+ required: ['component'],
+ type: 'object',
+ },
+ name: 'get_component_example',
+ },
+ {
+ description: 'List all available Oranguru components',
+ inputSchema: {
+ properties: {},
+ type: 'object',
+ },
+ name: 'list_components',
+ },
+ ],
+ }));
+
+ // Handle tool calls
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
+ const { arguments: args, name } = request.params;
+
+ switch (name) {
+ case 'get_component_docs':
+ if (!args.component || !COMPONENTS[args.component]) {
+ throw new Error(`Component ${args.component} not found`);
+ }
+ return {
+ content: [
+ {
+ text: JSON.stringify(COMPONENTS[args.component], null, 2),
+ type: 'text',
+ },
+ ],
+ };
+
+ case 'get_component_example':
+ if (!args.component || !COMPONENTS[args.component]) {
+ throw new Error(`Component ${args.component} not found`);
+ }
+ const component = COMPONENTS[args.component];
+ const variant = args.variant || Object.keys(component.examples)[0];
+ const example = component.examples[variant];
+
+ if (!example) {
+ throw new Error(`Variant ${variant} not found for ${args.component}`);
+ }
+
+ return {
+ content: [
+ {
+ text: example,
+ type: 'text',
+ },
+ ],
+ };
+
+ case 'list_components':
+ return {
+ content: [
+ {
+ text: JSON.stringify(
+ Object.entries(COMPONENTS).map(([key, comp]) => ({
+ description: comp.description,
+ exports: comp.exports,
+ name: key,
+ })),
+ null,
+ 2
+ ),
+ type: 'text',
+ },
+ ],
+ };
+
+ default:
+ throw new Error(`Unknown tool: ${name}`);
+ }
+ });
+
+ // List resources
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => ({
+ resources: [
+ {
+ description: 'Main documentation for the Oranguru library',
+ mimeType: 'text/markdown',
+ name: 'Oranguru Documentation',
+ uri: 'oranguru://docs/readme',
+ },
+ {
+ description: 'List of all available components',
+ mimeType: 'application/json',
+ name: 'Component List',
+ uri: 'oranguru://docs/components',
+ },
+ ],
+ }));
+
+ // Read resources
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
+ const { uri } = request.params;
+
+ if (uri === 'oranguru://docs/readme') {
+ const readmePath = join(__dirname, '..', 'README.md');
+ const readme = readFileSync(readmePath, 'utf-8');
+ return {
+ contents: [
+ {
+ mimeType: 'text/markdown',
+ text: readme,
+ uri,
+ },
+ ],
+ };
+ }
+
+ if (uri === 'oranguru://docs/components') {
+ return {
+ contents: [
+ {
+ mimeType: 'application/json',
+ text: JSON.stringify(
+ Object.entries(COMPONENTS).map(([key, comp]) => ({
+ description: comp.description,
+ exports: comp.exports,
+ name: key,
+ })),
+ null,
+ 2
+ ),
+ uri,
+ },
+ ],
+ };
+ }
+
+ throw new Error(`Resource not found: ${uri}`);
+ });
+ }
+}
+
+const server = new OranguruMCPServer();
+server.run().catch(console.error);
diff --git a/package.json b/package.json
index da96a3b..4b45944 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "@warkypublic/oranguru",
"author": "Warky Devs",
- "version": "0.0.31",
+ "version": "0.0.32",
"type": "module",
"types": "./dist/lib.d.ts",
"main": "./dist/lib.cjs.js",
@@ -13,13 +13,20 @@
"require": "./dist/lib.cjs.js"
},
"./oranguru.css": "./dist/oranguru.css",
- "./package.json": "./package.json"
+ "./package.json": "./package.json",
+ "./mcp": "./mcp-server.json"
+ },
+ "mcp": {
+ "server": "./mcp/server.js",
+ "config": "./mcp-server.json"
},
"files": [
"dist/**",
"assets/**",
"public/**",
- "global.d.ts"
+ "global.d.ts",
+ "mcp/**",
+ "mcp-server.json"
],
"scripts": {
"dev": "vite",
@@ -33,13 +40,16 @@
"clean": "rm -rf node_modules && rm -rf dist ",
"preview": "vite preview",
"storybook": "storybook dev -p 6006",
- "build-storybook": "storybook build"
+ "build-storybook": "storybook build",
+ "mcp": "node mcp/server.js"
},
"dependencies": {
+ "@modelcontextprotocol/sdk": "^1.0.4",
"@tanstack/react-virtual": "^3.13.18",
"moment": "^2.30.1"
},
"devDependencies": {
+ "@changesets/changelog-git": "^0.2.1",
"@changesets/cli": "^2.29.8",
"@eslint/js": "^9.39.2",
"@microsoft/api-extractor": "^7.56.0",
@@ -98,4 +108,4 @@
"use-sync-external-store": ">= 1.4.0",
"zustand": ">= 5.0.0"
}
-}
+}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6692369..bd9e8d9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -23,6 +23,9 @@ importers:
'@mantine/notifications':
specifier: ^8.3.5
version: 8.3.5(@mantine/core@8.3.1(@mantine/hooks@8.3.1(react@19.2.4))(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(@mantine/hooks@8.3.1(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@modelcontextprotocol/sdk':
+ specifier: ^1.0.4
+ version: 1.26.0(zod@4.1.12)
'@tabler/icons-react':
specifier: ^3.35.0
version: 3.35.0(react@19.2.4)
@@ -57,6 +60,9 @@ importers:
specifier: '>= 5.0.0'
version: 5.0.8(@types/react@19.2.10)(immer@10.1.3)(react@19.2.4)(use-sync-external-store@1.5.0(react@19.2.4))
devDependencies:
+ '@changesets/changelog-git':
+ specifier: ^0.2.1
+ version: 0.2.1
'@changesets/cli':
specifier: ^2.29.8
version: 2.29.8(@types/node@25.2.0)
@@ -623,6 +629,12 @@ packages:
react-dom: ^16.12.0 || 17.x || 18.x
react-responsive-carousel: ^3.2.7
+ '@hono/node-server@1.19.9':
+ resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==}
+ engines: {node: '>=18.14.1'}
+ peerDependencies:
+ hono: ^4
+
'@humanfs/core@0.19.1':
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
engines: {node: '>=18.18.0'}
@@ -772,6 +784,16 @@ packages:
'@microsoft/tsdoc@0.16.0':
resolution: {integrity: sha512-xgAyonlVVS+q7Vc7qLW0UrJU7rSFcETRWsqdXZtjzRU8dF+6CkozTK4V4y1LwOX7j8r/vHphjDeMeGI4tNGeGA==}
+ '@modelcontextprotocol/sdk@1.26.0':
+ resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@cfworker/json-schema': ^4.1.1
+ zod: ^3.25 || ^4.0
+ peerDependenciesMeta:
+ '@cfworker/json-schema':
+ optional: true
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -830,56 +852,67 @@ packages:
resolution: {integrity: sha512-9VlPY/BN3AgbukfVHAB8zNFWB/lKEuvzRo1NKev0Po8sYFKx0i+AQlCYftgEjcL43F2h9Ui1ZSdVBc4En/sP2w==}
cpu: [arm]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.50.2':
resolution: {integrity: sha512-+GdKWOvsifaYNlIVf07QYan1J5F141+vGm5/Y8b9uCZnG/nxoGqgCmR24mv0koIWWuqvFYnbURRqw1lv7IBINw==}
cpu: [arm]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.50.2':
resolution: {integrity: sha512-df0Eou14ojtUdLQdPFnymEQteENwSJAdLf5KCDrmZNsy1c3YaCNaJvYsEUHnrg+/DLBH612/R0xd3dD03uz2dg==}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.50.2':
resolution: {integrity: sha512-iPeouV0UIDtz8j1YFR4OJ/zf7evjauqv7jQ/EFs0ClIyL+by++hiaDAfFipjOgyz6y6xbDvJuiU4HwpVMpRFDQ==}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.50.2':
resolution: {integrity: sha512-OL6KaNvBopLlj5fTa5D5bau4W82f+1TyTZRr2BdnfsrnQnmdxh4okMxR2DcDkJuh4KeoQZVuvHvzuD/lyLn2Kw==}
cpu: [loong64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-ppc64-gnu@4.50.2':
resolution: {integrity: sha512-I21VJl1w6z/K5OTRl6aS9DDsqezEZ/yKpbqlvfHbW0CEF5IL8ATBMuUx6/mp683rKTK8thjs/0BaNrZLXetLag==}
cpu: [ppc64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.50.2':
resolution: {integrity: sha512-Hq6aQJT/qFFHrYMjS20nV+9SKrXL2lvFBENZoKfoTH2kKDOJqff5OSJr4x72ZaG/uUn+XmBnGhfr4lwMRrmqCQ==}
cpu: [riscv64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.50.2':
resolution: {integrity: sha512-82rBSEXRv5qtKyr0xZ/YMF531oj2AIpLZkeNYxmKNN6I2sVE9PGegN99tYDLK2fYHJITL1P2Lgb4ZXnv0PjQvw==}
cpu: [riscv64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.50.2':
resolution: {integrity: sha512-4Q3S3Hy7pC6uaRo9gtXUTJ+EKo9AKs3BXKc2jYypEcMQ49gDPFU2P1ariX9SEtBzE5egIX6fSUmbmGazwBVF9w==}
cpu: [s390x]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.50.2':
resolution: {integrity: sha512-9Jie/At6qk70dNIcopcL4p+1UirusEtznpNtcq/u/C5cC4HBX7qSGsYIcG6bdxj15EYWhHiu02YvmdPzylIZlA==}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.50.2':
resolution: {integrity: sha512-HPNJwxPL3EmhzeAnsWQCM3DcoqOz3/IC6de9rWfGR8ZCuEHETi9km66bH/wG3YH0V3nyzyFEGUZeL5PKyy4xvw==}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@rollup/rollup-openharmony-arm64@4.50.2':
resolution: {integrity: sha512-nMKvq6FRHSzYfKLHZ+cChowlEkR2lj/V0jYj9JnGUVPL2/mIeFGmVM2mLaFeNa5Jev7W7TovXqXIG2d39y1KYA==}
@@ -1019,24 +1052,28 @@ packages:
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
+ libc: [glibc]
'@swc/core-linux-arm64-musl@1.15.11':
resolution: {integrity: sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==}
engines: {node: '>=10'}
cpu: [arm64]
os: [linux]
+ libc: [musl]
'@swc/core-linux-x64-gnu@1.15.11':
resolution: {integrity: sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
+ libc: [glibc]
'@swc/core-linux-x64-musl@1.15.11':
resolution: {integrity: sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==}
engines: {node: '>=10'}
cpu: [x64]
os: [linux]
+ libc: [musl]
'@swc/core-win32-arm64-msvc@1.15.11':
resolution: {integrity: sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==}
@@ -1364,6 +1401,10 @@ packages:
use-sync-external-store: '>= 1.4.0'
zustand: '>= 5.0.0'
+ accepts@2.0.0:
+ resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==}
+ engines: {node: '>= 0.6'}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -1403,6 +1444,9 @@ packages:
ajv@8.13.0:
resolution: {integrity: sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==}
+ ajv@8.17.1:
+ resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==}
+
alien-signals@0.4.14:
resolution: {integrity: sha512-itUAVzhczTmP2U5yX67xVpsbbOiquusbWVyA9N+sy6+r6YVbFkahXvNCeEPWEOMhwDYwbVbGHFkVL03N9I5g+Q==}
@@ -1530,6 +1574,10 @@ packages:
bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
+ body-parser@2.2.2:
+ resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==}
+ engines: {node: '>=18'}
+
brace-expansion@1.1.12:
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
@@ -1549,6 +1597,10 @@ packages:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
+ bytes@3.1.2:
+ resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
+ engines: {node: '>= 0.8'}
+
call-bind-apply-helpers@1.0.2:
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
engines: {node: '>= 0.4'}
@@ -1632,9 +1684,29 @@ packages:
confbox@0.2.2:
resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==}
+ content-disposition@1.0.1:
+ resolution: {integrity: sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==}
+ engines: {node: '>=18'}
+
+ content-type@1.0.5:
+ resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
+ engines: {node: '>= 0.6'}
+
convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ cookie-signature@1.2.2:
+ resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
+ engines: {node: '>=6.6.0'}
+
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
+ cors@2.8.6:
+ resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
+ engines: {node: '>= 0.10'}
+
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -1723,6 +1795,10 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
+ depd@2.0.0:
+ resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==}
+ engines: {node: '>= 0.8'}
+
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -1772,6 +1848,9 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
+ ee-first@1.1.1:
+ resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
+
electron-to-chromium@1.5.222:
resolution: {integrity: sha512-gA7psSwSwQRE60CEoLz6JBCQPIxNeuzB2nL8vE03GK/OHxlvykbLyeiumQy1iH5C2f3YbRAZpGCMT12a/9ih9w==}
@@ -1785,6 +1864,10 @@ packages:
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
engines: {node: '>=14'}
+ encodeurl@2.0.0:
+ resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==}
+ engines: {node: '>= 0.8'}
+
enquirer@2.4.1:
resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==}
engines: {node: '>=8.6'}
@@ -1841,6 +1924,9 @@ packages:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
+ escape-html@1.0.3:
+ resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==}
+
escape-string-regexp@1.0.5:
resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
engines: {node: '>=0.8.0'}
@@ -1960,10 +2046,32 @@ packages:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'}
+ etag@1.8.1:
+ resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
+ engines: {node: '>= 0.6'}
+
+ eventsource-parser@3.0.6:
+ resolution: {integrity: sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==}
+ engines: {node: '>=18.0.0'}
+
+ eventsource@3.0.7:
+ resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==}
+ engines: {node: '>=18.0.0'}
+
expect-type@1.2.2:
resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==}
engines: {node: '>=12.0.0'}
+ express-rate-limit@8.2.1:
+ resolution: {integrity: sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==}
+ engines: {node: '>= 16'}
+ peerDependencies:
+ express: '>= 4.11'
+
+ express@5.2.1:
+ resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==}
+ engines: {node: '>= 18'}
+
exsolve@1.0.7:
resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==}
@@ -1983,6 +2091,9 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+ fast-uri@3.1.0:
+ resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
@@ -2007,6 +2118,10 @@ packages:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
+ finalhandler@2.1.1:
+ resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==}
+ engines: {node: '>= 18.0.0'}
+
find-up@4.1.0:
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
engines: {node: '>=8'}
@@ -2034,6 +2149,14 @@ packages:
resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==}
engines: {node: '>=14'}
+ forwarded@0.2.0:
+ resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
+ engines: {node: '>= 0.6'}
+
+ fresh@2.0.0:
+ resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==}
+ engines: {node: '>= 0.8'}
+
fs-extra@11.3.1:
resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==}
engines: {node: '>=14.14'}
@@ -2178,6 +2301,10 @@ packages:
hermes-parser@0.25.1:
resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==}
+ hono@4.11.8:
+ resolution: {integrity: sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==}
+ engines: {node: '>=16.9.0'}
+
html-element-attributes@1.3.1:
resolution: {integrity: sha512-UrRKgp5sQmRnDy4TEwAUsu14XBUlzKB8U3hjIYDjcZ3Hbp86Jtftzxfgrv6E/ii/h78tsaZwAnAE8HwnHr0dPA==}
@@ -2185,6 +2312,10 @@ packages:
resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+ http-errors@2.0.1:
+ resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==}
+ engines: {node: '>= 0.8'}
+
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
@@ -2242,6 +2373,14 @@ packages:
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
engines: {node: '>= 0.4'}
+ ip-address@10.0.1:
+ resolution: {integrity: sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==}
+ engines: {node: '>= 12'}
+
+ ipaddr.js@1.9.1:
+ resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
+ engines: {node: '>= 0.10'}
+
is-array-buffer@3.0.5:
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
engines: {node: '>= 0.4'}
@@ -2335,6 +2474,9 @@ packages:
is-potential-custom-element-name@1.0.1:
resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+ is-promise@4.0.0:
+ resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==}
+
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
@@ -2408,6 +2550,9 @@ packages:
jju@1.4.0:
resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==}
+ jose@6.1.3:
+ resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
+
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@@ -2442,6 +2587,9 @@ packages:
json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+ json-schema-typed@8.0.2:
+ resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
+
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
@@ -2542,6 +2690,14 @@ packages:
mdn-data@2.12.2:
resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==}
+ media-typer@1.1.0:
+ resolution: {integrity: sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==}
+ engines: {node: '>= 0.8'}
+
+ merge-descriptors@2.0.0:
+ resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==}
+ engines: {node: '>=18'}
+
merge2@1.4.1:
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
engines: {node: '>= 8'}
@@ -2550,6 +2706,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.54.0:
+ resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@3.0.2:
+ resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
+ engines: {node: '>=18'}
+
min-document@2.19.2:
resolution: {integrity: sha512-8S5I8db/uZN8r9HSLFVWPdJCvYOejMcEC82VIzNUc6Zkklf/d1gg2psfE79/vyhWOj4+J8MtwmoOz3TmvaGu5A==}
@@ -2611,6 +2775,10 @@ packages:
resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==}
engines: {node: '>=18'}
+ negotiator@1.0.0:
+ resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
+ engines: {node: '>= 0.6'}
+
node-releases@2.0.21:
resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==}
@@ -2649,6 +2817,10 @@ packages:
obug@2.1.1:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==}
+ on-finished@2.4.1:
+ resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
+ engines: {node: '>= 0.8'}
+
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
@@ -2711,6 +2883,10 @@ packages:
parse5@8.0.0:
resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==}
+ parseurl@1.3.3:
+ resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
+ engines: {node: '>= 0.8'}
+
path-browserify@1.0.1:
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
@@ -2733,6 +2909,9 @@ packages:
resolution: {integrity: sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==}
engines: {node: 20 || >=22}
+ path-to-regexp@8.3.0:
+ resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==}
+
path-type@4.0.0:
resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
engines: {node: '>=8'}
@@ -2759,6 +2938,10 @@ packages:
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
engines: {node: '>=6'}
+ pkce-challenge@5.0.1:
+ resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
+ engines: {node: '>=16.20.0'}
+
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
@@ -2847,16 +3030,32 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ proxy-addr@2.0.7:
+ resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==}
+ engines: {node: '>= 0.10'}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ qs@6.14.1:
+ resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==}
+ engines: {node: '>=0.6'}
+
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ range-parser@1.2.1:
+ resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
+ engines: {node: '>= 0.6'}
+
+ raw-body@3.0.2:
+ resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==}
+ engines: {node: '>= 0.10'}
+
react-docgen-typescript@2.4.0:
resolution: {integrity: sha512-ZtAp5XTO5HRzQctjPU0ybY0RRCQO19X/8fxn3w7y2VVTUbGHDKULPTL4ky3vB05euSgG5NpALhEhDPvQ56wvXg==}
peerDependencies:
@@ -3006,6 +3205,10 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true
+ router@2.2.0:
+ resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==}
+ engines: {node: '>= 18'}
+
run-applescript@7.1.0:
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
@@ -3049,6 +3252,14 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ send@1.2.1:
+ resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==}
+ engines: {node: '>= 18'}
+
+ serve-static@2.2.1:
+ resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==}
+ engines: {node: '>= 18'}
+
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -3061,6 +3272,9 @@ packages:
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
engines: {node: '>= 0.4'}
+ setprototypeof@1.2.0:
+ resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
+
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@@ -3113,6 +3327,10 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
+ statuses@2.0.2:
+ resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
+ engines: {node: '>= 0.8'}
+
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
@@ -3264,6 +3482,10 @@ packages:
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
engines: {node: '>=8.0'}
+ toidentifier@1.0.1:
+ resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
+ engines: {node: '>=0.6'}
+
tough-cookie@6.0.0:
resolution: {integrity: sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==}
engines: {node: '>=16'}
@@ -3321,6 +3543,10 @@ packages:
resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
engines: {node: '>=16'}
+ type-is@2.0.1:
+ resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==}
+ engines: {node: '>= 0.6'}
+
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -3376,6 +3602,10 @@ packages:
resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
engines: {node: '>= 10.0.0'}
+ unpipe@1.0.0:
+ resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==}
+ engines: {node: '>= 0.8'}
+
unplugin@2.3.11:
resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==}
engines: {node: '>=18.12.0'}
@@ -3448,6 +3678,10 @@ packages:
resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
hasBin: true
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
vite-plugin-dts@4.5.4:
resolution: {integrity: sha512-d4sOM8M/8z7vRXHHq/ebbblfaxENjogAAekcfcDCCwAyvGqnPrc7f4NZbvItS+g4WTgerW0xDwSz5qz11JT3vg==}
peerDependencies:
@@ -3638,6 +3872,11 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+ zod-to-json-schema@3.25.1:
+ resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
+ peerDependencies:
+ zod: ^3.25 || ^4
+
zod-validation-error@4.0.2:
resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==}
engines: {node: '>=18.0.0'}
@@ -4174,6 +4413,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@hono/node-server@1.19.9(hono@4.11.8)':
+ dependencies:
+ hono: 4.11.8
+
'@humanfs/core@0.19.1': {}
'@humanfs/node@0.16.7':
@@ -4391,6 +4634,28 @@ snapshots:
'@microsoft/tsdoc@0.16.0': {}
+ '@modelcontextprotocol/sdk@1.26.0(zod@4.1.12)':
+ dependencies:
+ '@hono/node-server': 1.19.9(hono@4.11.8)
+ ajv: 8.17.1
+ ajv-formats: 3.0.1(ajv@8.17.1)
+ content-type: 1.0.5
+ cors: 2.8.6
+ cross-spawn: 7.0.6
+ eventsource: 3.0.7
+ eventsource-parser: 3.0.6
+ express: 5.2.1
+ express-rate-limit: 8.2.1(express@5.2.1)
+ hono: 4.11.8
+ jose: 6.1.3
+ json-schema-typed: 8.0.2
+ pkce-challenge: 5.0.1
+ raw-body: 3.0.2
+ zod: 4.1.12
+ zod-to-json-schema: 3.25.1(zod@4.1.12)
+ transitivePeerDependencies:
+ - supports-color
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -5017,6 +5282,11 @@ snapshots:
use-sync-external-store: 1.5.0(react@19.2.4)
zustand: 5.0.8(@types/react@19.2.10)(immer@10.1.3)(react@19.2.4)(use-sync-external-store@1.5.0(react@19.2.4))
+ accepts@2.0.0:
+ dependencies:
+ mime-types: 3.0.2
+ negotiator: 1.0.0
+
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0
@@ -5033,6 +5303,10 @@ snapshots:
optionalDependencies:
ajv: 8.13.0
+ ajv-formats@3.0.1(ajv@8.17.1):
+ optionalDependencies:
+ ajv: 8.17.1
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -5054,6 +5328,13 @@ snapshots:
require-from-string: 2.0.2
uri-js: 4.4.1
+ ajv@8.17.1:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.0
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
alien-signals@0.4.14: {}
ansi-colors@4.1.3: {}
@@ -5181,6 +5462,20 @@ snapshots:
dependencies:
require-from-string: 2.0.2
+ body-parser@2.2.2:
+ dependencies:
+ bytes: 3.1.2
+ content-type: 1.0.5
+ debug: 4.4.3
+ http-errors: 2.0.1
+ iconv-lite: 0.7.0
+ on-finished: 2.4.1
+ qs: 6.14.1
+ raw-body: 3.0.2
+ type-is: 2.0.1
+ transitivePeerDependencies:
+ - supports-color
+
brace-expansion@1.1.12:
dependencies:
balanced-match: 1.0.2
@@ -5206,6 +5501,8 @@ snapshots:
dependencies:
run-applescript: 7.1.0
+ bytes@3.1.2: {}
+
call-bind-apply-helpers@1.0.2:
dependencies:
es-errors: 1.3.0
@@ -5280,8 +5577,21 @@ snapshots:
confbox@0.2.2: {}
+ content-disposition@1.0.1: {}
+
+ content-type@1.0.5: {}
+
convert-source-map@2.0.0: {}
+ cookie-signature@1.2.2: {}
+
+ cookie@0.7.2: {}
+
+ cors@2.8.6:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -5368,6 +5678,8 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
+ depd@2.0.0: {}
+
dequal@2.0.3: {}
detect-indent@6.1.0: {}
@@ -5409,6 +5721,8 @@ snapshots:
eastasianwidth@0.2.0: {}
+ ee-first@1.1.1: {}
+
electron-to-chromium@1.5.222: {}
emoji-regex@8.0.0: {}
@@ -5417,6 +5731,8 @@ snapshots:
empathic@2.0.0: {}
+ encodeurl@2.0.0: {}
+
enquirer@2.4.1:
dependencies:
ansi-colors: 4.1.3
@@ -5560,6 +5876,8 @@ snapshots:
escalade@3.2.0: {}
+ escape-html@1.0.3: {}
+
escape-string-regexp@1.0.5: {}
escape-string-regexp@4.0.0: {}
@@ -5776,8 +6094,54 @@ snapshots:
esutils@2.0.3: {}
+ etag@1.8.1: {}
+
+ eventsource-parser@3.0.6: {}
+
+ eventsource@3.0.7:
+ dependencies:
+ eventsource-parser: 3.0.6
+
expect-type@1.2.2: {}
+ express-rate-limit@8.2.1(express@5.2.1):
+ dependencies:
+ express: 5.2.1
+ ip-address: 10.0.1
+
+ express@5.2.1:
+ dependencies:
+ accepts: 2.0.0
+ body-parser: 2.2.2
+ content-disposition: 1.0.1
+ content-type: 1.0.5
+ cookie: 0.7.2
+ cookie-signature: 1.2.2
+ debug: 4.4.3
+ depd: 2.0.0
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ finalhandler: 2.1.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ merge-descriptors: 2.0.0
+ mime-types: 3.0.2
+ on-finished: 2.4.1
+ once: 1.4.0
+ parseurl: 1.3.3
+ proxy-addr: 2.0.7
+ qs: 6.14.1
+ range-parser: 1.2.1
+ router: 2.2.0
+ send: 1.2.1
+ serve-static: 2.2.1
+ statuses: 2.0.2
+ type-is: 2.0.1
+ vary: 1.1.2
+ transitivePeerDependencies:
+ - supports-color
+
exsolve@1.0.7: {}
extendable-error@0.1.7: {}
@@ -5796,6 +6160,8 @@ snapshots:
fast-levenshtein@2.0.6: {}
+ fast-uri@3.1.0: {}
+
fastq@1.19.1:
dependencies:
reusify: 1.1.0
@@ -5816,6 +6182,17 @@ snapshots:
dependencies:
to-regex-range: 5.0.1
+ finalhandler@2.1.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ on-finished: 2.4.1
+ parseurl: 1.3.3
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
find-up@4.1.0:
dependencies:
locate-path: 5.0.0
@@ -5848,6 +6225,10 @@ snapshots:
cross-spawn: 7.0.6
signal-exit: 4.1.0
+ forwarded@0.2.0: {}
+
+ fresh@2.0.0: {}
+
fs-extra@11.3.1:
dependencies:
graceful-fs: 4.2.11
@@ -6007,6 +6388,8 @@ snapshots:
dependencies:
hermes-estree: 0.25.1
+ hono@4.11.8: {}
+
html-element-attributes@1.3.1: {}
html-encoding-sniffer@6.0.0:
@@ -6015,6 +6398,14 @@ snapshots:
transitivePeerDependencies:
- '@noble/hashes'
+ http-errors@2.0.1:
+ dependencies:
+ depd: 2.0.0
+ inherits: 2.0.4
+ setprototypeof: 1.2.0
+ statuses: 2.0.2
+ toidentifier: 1.0.1
+
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
@@ -6067,6 +6458,10 @@ snapshots:
hasown: 2.0.2
side-channel: 1.1.0
+ ip-address@10.0.1: {}
+
+ ipaddr.js@1.9.1: {}
+
is-array-buffer@3.0.5:
dependencies:
call-bind: 1.0.8
@@ -6155,6 +6550,8 @@ snapshots:
is-potential-custom-element-name@1.0.1: {}
+ is-promise@4.0.0: {}
+
is-regex@1.2.1:
dependencies:
call-bound: 1.0.4
@@ -6227,6 +6624,8 @@ snapshots:
jju@1.4.0: {}
+ jose@6.1.3: {}
+
js-tokens@4.0.0: {}
js-yaml@3.14.1:
@@ -6272,6 +6671,8 @@ snapshots:
json-schema-traverse@1.0.0: {}
+ json-schema-typed@8.0.2: {}
+
json-stable-stringify-without-jsonify@1.0.1: {}
json5@2.2.3: {}
@@ -6365,6 +6766,10 @@ snapshots:
mdn-data@2.12.2: {}
+ media-typer@1.1.0: {}
+
+ merge-descriptors@2.0.0: {}
+
merge2@1.4.1: {}
micromatch@4.0.8:
@@ -6372,6 +6777,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.54.0: {}
+
+ mime-types@3.0.2:
+ dependencies:
+ mime-db: 1.54.0
+
min-document@2.19.2:
dependencies:
dom-walk: 0.1.2
@@ -6423,6 +6834,8 @@ snapshots:
natural-orderby@5.0.0: {}
+ negotiator@1.0.0: {}
+
node-releases@2.0.21: {}
object-assign@4.1.1: {}
@@ -6467,6 +6880,10 @@ snapshots:
obug@2.1.1: {}
+ on-finished@2.4.1:
+ dependencies:
+ ee-first: 1.1.1
+
once@1.4.0:
dependencies:
wrappy: 1.0.2
@@ -6537,6 +6954,8 @@ snapshots:
dependencies:
entities: 6.0.1
+ parseurl@1.3.3: {}
+
path-browserify@1.0.1: {}
path-exists@4.0.0: {}
@@ -6552,6 +6971,8 @@ snapshots:
lru-cache: 11.2.5
minipass: 7.1.2
+ path-to-regexp@8.3.0: {}
+
path-type@4.0.0: {}
pathe@2.0.3: {}
@@ -6566,6 +6987,8 @@ snapshots:
pify@4.0.1: {}
+ pkce-challenge@5.0.1: {}
+
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
@@ -6663,12 +7086,30 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ proxy-addr@2.0.7:
+ dependencies:
+ forwarded: 0.2.0
+ ipaddr.js: 1.9.1
+
punycode@2.3.1: {}
+ qs@6.14.1:
+ dependencies:
+ side-channel: 1.1.0
+
quansync@0.2.11: {}
queue-microtask@1.2.3: {}
+ range-parser@1.2.1: {}
+
+ raw-body@3.0.2:
+ dependencies:
+ bytes: 3.1.2
+ http-errors: 2.0.1
+ iconv-lite: 0.7.0
+ unpipe: 1.0.0
+
react-docgen-typescript@2.4.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
@@ -6862,6 +7303,16 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.50.2
fsevents: 2.3.3
+ router@2.2.0:
+ dependencies:
+ debug: 4.4.3
+ depd: 2.0.0
+ is-promise: 4.0.0
+ parseurl: 1.3.3
+ path-to-regexp: 8.3.0
+ transitivePeerDependencies:
+ - supports-color
+
run-applescript@7.1.0: {}
run-parallel@1.2.0:
@@ -6903,6 +7354,31 @@ snapshots:
semver@7.7.3: {}
+ send@1.2.1:
+ dependencies:
+ debug: 4.4.3
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ etag: 1.8.1
+ fresh: 2.0.0
+ http-errors: 2.0.1
+ mime-types: 3.0.2
+ ms: 2.1.3
+ on-finished: 2.4.1
+ range-parser: 1.2.1
+ statuses: 2.0.2
+ transitivePeerDependencies:
+ - supports-color
+
+ serve-static@2.2.1:
+ dependencies:
+ encodeurl: 2.0.0
+ escape-html: 1.0.3
+ parseurl: 1.3.3
+ send: 1.2.1
+ transitivePeerDependencies:
+ - supports-color
+
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -6925,6 +7401,8 @@ snapshots:
es-errors: 1.3.0
es-object-atoms: 1.1.1
+ setprototypeof@1.2.0: {}
+
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@@ -6978,6 +7456,8 @@ snapshots:
stackback@0.0.2: {}
+ statuses@2.0.2: {}
+
std-env@3.10.0: {}
stop-iteration-iterator@1.1.0:
@@ -7145,6 +7625,8 @@ snapshots:
dependencies:
is-number: 7.0.0
+ toidentifier@1.0.1: {}
+
tough-cookie@6.0.0:
dependencies:
tldts: 7.0.17
@@ -7187,6 +7669,12 @@ snapshots:
type-fest@4.41.0: {}
+ type-is@2.0.1:
+ dependencies:
+ content-type: 1.0.5
+ media-typer: 1.1.0
+ mime-types: 3.0.2
+
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -7252,6 +7740,8 @@ snapshots:
universalify@2.0.1: {}
+ unpipe@1.0.0: {}
+
unplugin@2.3.11:
dependencies:
'@jridgewell/remapping': 2.3.5
@@ -7311,6 +7801,8 @@ snapshots:
uuid@11.1.0: {}
+ vary@1.1.2: {}
+
vite-plugin-dts@4.5.4(@types/node@25.2.0)(rollup@4.50.2)(typescript@5.9.3)(vite@7.3.1(@types/node@25.2.0)(jiti@2.6.1)(sugarss@5.0.1(postcss@8.5.6))):
dependencies:
'@microsoft/api-extractor': 7.56.0(@types/node@25.2.0)
@@ -7507,6 +7999,10 @@ snapshots:
yocto-queue@0.1.0: {}
+ zod-to-json-schema@3.25.1(zod@4.1.12):
+ dependencies:
+ zod: 4.1.12
+
zod-validation-error@4.0.2(zod@4.1.12):
dependencies:
zod: 4.1.12
diff --git a/src/Boxer/hooks/useBoxerOptions.tsx b/src/Boxer/hooks/useBoxerOptions.tsx
index 7028ecf..7de38bb 100644
--- a/src/Boxer/hooks/useBoxerOptions.tsx
+++ b/src/Boxer/hooks/useBoxerOptions.tsx
@@ -1,17 +1,17 @@
-import { Combobox, Checkbox } from '@mantine/core';
+import { Checkbox, Combobox } from '@mantine/core';
import { useMemo } from 'react';
import type { BoxerItem } from '../Boxer.types';
interface UseBoxerOptionsProps {
boxerData: Array;
- value?: any | Array;
multiSelect?: boolean;
onOptionSubmit: (index: number) => void;
+ value?: any | Array;
}
const useBoxerOptions = (props: UseBoxerOptionsProps) => {
- const { boxerData, value, multiSelect, onOptionSubmit } = props;
+ const { boxerData, multiSelect, onOptionSubmit, value } = props;
const options = useMemo(() => {
return boxerData.map((item, index) => {
@@ -21,15 +21,15 @@ const useBoxerOptions = (props: UseBoxerOptionsProps) => {
return (
{
onOptionSubmit(index);
}}
+ value={String(index)}
>
{multiSelect ? (
-
+
{}} tabIndex={-1} />
{item.label}
diff --git a/src/Former/Former.tsx b/src/Former/Former.tsx
index 1960cb2..189bf36 100644
--- a/src/Former/Former.tsx
+++ b/src/Former/Former.tsx
@@ -97,7 +97,7 @@ const FormerInner = forwardRef
, Partial> & Props
return (
{typeof wrapper === 'function' ? (
- wrapper({props.children} , opened, onClose, onOpen, getState)
+ wrapper({props.children} , opened ??false, onClose ?? (() => {setState('opened', false)}), onOpen ?? (() => {setState('opened', true)}), getState)
) : (
{props.children || null}
)}
diff --git a/src/Former/Former.types.ts b/src/Former/Former.types.ts
index c5107f2..f895f97 100644
--- a/src/Former/Former.types.ts
+++ b/src/Former/Former.types.ts
@@ -62,9 +62,9 @@ export interface FormerRef {
export type FormerSectionRender = (
children: React.ReactNode,
- opened: boolean | undefined,
- onClose: ((data?: T) => void) | undefined,
- onOpen: ((data?: T) => void) | undefined,
+ opened: boolean ,
+ onClose: ((data?: T) => void),
+ onOpen: ((data?: T) => void) ,
getState: FormerState['getState']
) => React.ReactNode;
diff --git a/src/Former/FormerLayoutBottom.tsx b/src/Former/FormerLayoutBottom.tsx
index c07aa7d..35c6fb4 100644
--- a/src/Former/FormerLayoutBottom.tsx
+++ b/src/Former/FormerLayoutBottom.tsx
@@ -2,19 +2,20 @@ import { useFormerStore } from './Former.store';
import { FormerButtonArea } from './FormerButtonArea';
export const FormerLayoutBottom = () => {
- const { buttonArea, getState, opened, renderBottom } = useFormerStore((state) => ({
+ const { buttonArea, getState, opened, renderBottom ,setState} = useFormerStore((state) => ({
buttonArea: state.layout?.buttonArea,
getState: state.getState,
opened: state.opened,
renderBottom: state.layout?.renderBottom,
+ setState: state.setState,
}));
if (renderBottom) {
return renderBottom(
,
- opened,
- getState('onClose'),
- getState('onOpen'),
+ opened ?? false,
+ getState('onClose') ?? (() => {setState('opened', false)}),
+ getState('onOpen') ?? (() => {setState('opened', true)}),
getState
);
}
diff --git a/src/Former/FormerLayoutTop.tsx b/src/Former/FormerLayoutTop.tsx
index 5bc3369..32cb685 100644
--- a/src/Former/FormerLayoutTop.tsx
+++ b/src/Former/FormerLayoutTop.tsx
@@ -2,19 +2,20 @@ import { useFormerStore } from './Former.store';
import { FormerButtonArea } from './FormerButtonArea';
export const FormerLayoutTop = () => {
- const { buttonArea, getState, opened, renderTop } = useFormerStore((state) => ({
+ const { buttonArea, getState, opened, renderTop,setState } = useFormerStore((state) => ({
buttonArea: state.layout?.buttonArea,
getState: state.getState,
opened: state.opened,
renderTop: state.layout?.renderTop,
+ setState: state.setState,
}));
if (renderTop) {
return renderTop(
,
- opened,
- getState('onClose'),
- getState('onOpen'),
+ opened ?? false,
+ getState('onClose') ?? (() => {setState('opened', false)}),
+ getState('onOpen') ?? (() => {setState('opened', true)}),
getState
);
}
diff --git a/src/GlobalStateStore/GlobalStateStore.stories.tsx b/src/GlobalStateStore/GlobalStateStore.stories.tsx
new file mode 100644
index 0000000..ecaeb11
--- /dev/null
+++ b/src/GlobalStateStore/GlobalStateStore.stories.tsx
@@ -0,0 +1,411 @@
+import type { Meta, StoryObj } from '@storybook/react-vite';
+
+import { Button, Card, Group, Stack, Switch, Text, TextInput, Title } from '@mantine/core';
+import { useEffect, useState } from 'react';
+
+import {
+ GlobalStateStore,
+ GlobalStateStoreProvider,
+ useGlobalStateStore,
+ useGlobalStateStoreContext,
+} from './';
+
+// Basic State Display Component
+const StateDisplay = () => {
+ const state = useGlobalStateStore();
+
+ return (
+
+
+ Current State
+
+
+ Program:
+ Name: {state.program.name || '(empty)'}
+ Slug: {state.program.slug || '(empty)'}
+
+
+
+ Session:
+ API URL: {state.session.apiURL || '(empty)'}
+ Connected: {state.session.connected ? 'Yes' : 'No'}
+ Auth Token: {state.session.authToken || '(empty)'}
+
+
+
+ Owner:
+ Name: {state.owner.name || '(empty)'}
+ ID: {state.owner.id}
+ Theme: {state.owner.theme?.name || 'none'}
+ Dark Mode: {state.owner.theme?.darkMode ? 'Yes' : 'No'}
+
+
+
+ User:
+ Username: {state.user.username || '(empty)'}
+ Email: {state.user.email || '(empty)'}
+ Theme: {state.user.theme?.name || 'none'}
+ Dark Mode: {state.user.theme?.darkMode ? 'Yes' : 'No'}
+
+
+
+ Layout:
+ Left Bar: {state.layout.leftBar.open ? 'Open' : 'Closed'}
+ Right Bar: {state.layout.rightBar.open ? 'Open' : 'Closed'}
+ Top Bar: {state.layout.topBar.open ? 'Open' : 'Closed'}
+ Bottom Bar: {state.layout.bottomBar.open ? 'Open' : 'Closed'}
+
+
+
+ );
+};
+
+// Interactive Controls Component
+const InteractiveControls = () => {
+ const state = useGlobalStateStore();
+ const [programName, setProgramName] = useState('');
+ const [username, setUsername] = useState('');
+ const [email, setEmail] = useState('');
+
+ return (
+
+
+ Controls
+
+
+ Program
+
+ setProgramName(e.currentTarget.value)}
+ placeholder="Program name"
+ value={programName}
+ />
+ state.setProgram({ name: programName })}>
+ Set Program Name
+
+
+
+
+
+ User
+
+
+ setUsername(e.currentTarget.value)}
+ placeholder="Username"
+ value={username}
+ />
+ setEmail(e.currentTarget.value)}
+ placeholder="Email"
+ value={email}
+ />
+ state.setUser({ email, username })}>
+ Set User Info
+
+
+
+
+
+
+ Theme
+
+
+ state.setUser({
+ theme: { ...state.user.theme, darkMode: e.currentTarget.checked },
+ })
+ }
+ />
+
+ state.setOwner({
+ theme: { ...state.owner.theme, darkMode: e.currentTarget.checked },
+ })
+ }
+ />
+
+
+
+
+ Layout
+
+ state.setLeftBar({ open: e.currentTarget.checked })}
+ />
+ state.setRightBar({ open: e.currentTarget.checked })}
+ />
+ state.setTopBar({ open: e.currentTarget.checked })}
+ />
+ state.setBottomBar({ open: e.currentTarget.checked })}
+ />
+
+
+
+
+ Actions
+
+ {
+ state.setProgram({ name: '', slug: '' });
+ state.setUser({ email: '', username: '' });
+ state.setOwner({ id: 0, name: '' });
+ }}
+ >
+ Reset State
+
+
+
+
+
+ );
+};
+
+// Provider Context Example
+const ProviderExample = () => {
+ const { refetch } = useGlobalStateStoreContext();
+ const state = useGlobalStateStore();
+
+ return (
+
+
+ Provider Context
+ API URL: {state.session.apiURL}
+ Loading: {state.session.loading ? 'Yes' : 'No'}
+ Connected: {state.session.connected ? 'Yes' : 'No'}
+ Refetch Data
+
+
+ );
+};
+
+// Main Story Component
+const BasicStory = () => {
+ useEffect(() => {
+ // Set initial state for demo
+ GlobalStateStore.getState().setProgram({
+ description: 'A demonstration application',
+ name: 'Demo App',
+ slug: 'demo-app',
+ });
+ GlobalStateStore.getState().setOwner({
+ id: 1,
+ name: 'Demo Organization',
+ theme: { darkMode: false, name: 'light' },
+ });
+ GlobalStateStore.getState().setUser({
+ email: 'demo@example.com',
+ theme: { darkMode: false, name: 'light' },
+ username: 'demo-user',
+ });
+ }, []);
+
+ return (
+
+
+
+
+ );
+};
+
+// Provider Story Component
+const ProviderStory = () => {
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+// Layout Controls Story
+const LayoutStory = () => {
+ const state = useGlobalStateStore();
+
+ return (
+
+
+ Layout Controls
+
+
+
+ Left Sidebar
+ state.setLeftBar({ open: e.currentTarget.checked })}
+ />
+ state.setLeftBar({ pinned: e.currentTarget.checked })}
+ />
+ state.setLeftBar({ collapsed: e.currentTarget.checked })}
+ />
+
+ state.setLeftBar({ size: parseInt(e.currentTarget.value) || 0 })
+ }
+ type="number"
+ value={state.layout.leftBar.size || 0}
+ />
+
+
+
+ Right Sidebar
+ state.setRightBar({ open: e.currentTarget.checked })}
+ />
+ state.setRightBar({ pinned: e.currentTarget.checked })}
+ />
+
+
+
+
+
+
+ );
+};
+
+// Theme Story
+const ThemeStory = () => {
+ const state = useGlobalStateStore();
+
+ useEffect(() => {
+ GlobalStateStore.getState().setOwner({
+ id: 1,
+ name: 'Acme Corp',
+ theme: { darkMode: false, name: 'corporate' },
+ });
+ }, []);
+
+ return (
+
+
+ Theme Settings
+
+
+ Owner Theme (Organization Default)
+
+
+ state.setOwner({
+ theme: { ...state.owner.theme, name: e.currentTarget.value },
+ })
+ }
+ value={state.owner.theme?.name || ''}
+ />
+
+ state.setOwner({
+ theme: { ...state.owner.theme, darkMode: e.currentTarget.checked },
+ })
+ }
+ />
+
+
+
+
+ User Theme (Personal Override)
+
+
+ state.setUser({
+ theme: { ...state.user.theme, name: e.currentTarget.value },
+ })
+ }
+ value={state.user.theme?.name || ''}
+ />
+
+ state.setUser({
+ theme: { ...state.user.theme, darkMode: e.currentTarget.checked },
+ })
+ }
+ />
+
+
+
+
+ Effective Theme
+
+ Name: {state.user.theme?.name || state.owner.theme?.name || 'default'}
+
+
+ Dark Mode:{' '}
+ {(state.user.theme?.darkMode ?? state.owner.theme?.darkMode) ? 'Yes' : 'No'}
+
+
+
+
+
+
+ );
+};
+
+const meta = {
+ component: BasicStory,
+ parameters: {
+ layout: 'fullscreen',
+ },
+ tags: ['autodocs'],
+ title: 'State/GlobalStateStore',
+} satisfies Meta;
+
+export default meta;
+
+type Story = StoryObj;
+
+export const Basic: Story = {
+ render: () => ,
+};
+
+export const WithProvider: Story = {
+ render: () => ,
+};
+
+export const LayoutControls: Story = {
+ render: () => ,
+};
+
+export const ThemeControls: Story = {
+ render: () => ,
+};
diff --git a/src/GlobalStateStore/GlobalStateStore.ts b/src/GlobalStateStore/GlobalStateStore.ts
new file mode 100644
index 0000000..e423344
--- /dev/null
+++ b/src/GlobalStateStore/GlobalStateStore.ts
@@ -0,0 +1,278 @@
+import type { StoreApi } from 'zustand';
+
+import { shallow } from 'zustand/shallow';
+import { useStoreWithEqualityFn } from 'zustand/traditional';
+import { createStore } from 'zustand/vanilla';
+
+import type {
+ AppState,
+ BarState,
+ ExtractState,
+ GlobalState,
+ GlobalStateStoreType,
+ LayoutState,
+ NavigationState,
+ OwnerState,
+ ProgramState,
+ SessionState,
+ UserState,
+} from './GlobalStateStore.types';
+
+import { loadStorage, saveStorage } from './GlobalStateStore.utils';
+
+const initialState: GlobalState = {
+ app: {
+ controls: {},
+ environment: 'production',
+ },
+ layout: {
+ bottomBar: { open: false },
+ leftBar: { open: false },
+ rightBar: { open: false },
+ topBar: { open: false },
+ },
+ navigation: {
+ menu: [],
+ },
+ owner: {
+ id: 0,
+ name: '',
+ },
+ program: {
+ name: '',
+ slug: '',
+ },
+ session: {
+ apiURL: '',
+ authToken: '',
+ connected: true,
+ loading: false,
+ },
+ user: {
+ username: '',
+ },
+};
+
+type GetState = () => GlobalStateStoreType;
+type SetState = (
+ partial: ((state: GlobalState) => Partial) | Partial
+) => void;
+
+const createProgramSlice = (set: SetState) => ({
+ setProgram: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ program: { ...state.program, ...updates },
+ })),
+});
+
+const createSessionSlice = (set: SetState) => ({
+ setApiURL: (url: string) =>
+ set((state: GlobalState) => ({
+ session: { ...state.session, apiURL: url },
+ })),
+
+ setAuthToken: (token: string) =>
+ set((state: GlobalState) => ({
+ session: { ...state.session, authToken: token },
+ })),
+
+ setSession: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ session: { ...state.session, ...updates },
+ })),
+});
+
+const createOwnerSlice = (set: SetState) => ({
+ setOwner: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ owner: { ...state.owner, ...updates },
+ })),
+});
+
+const createUserSlice = (set: SetState) => ({
+ setUser: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ user: { ...state.user, ...updates },
+ })),
+});
+
+const createLayoutSlice = (set: SetState) => ({
+ setBottomBar: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ layout: { ...state.layout, bottomBar: { ...state.layout.bottomBar, ...updates } },
+ })),
+
+ setLayout: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ layout: { ...state.layout, ...updates },
+ })),
+
+ setLeftBar: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ layout: { ...state.layout, leftBar: { ...state.layout.leftBar, ...updates } },
+ })),
+
+ setRightBar: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ layout: { ...state.layout, rightBar: { ...state.layout.rightBar, ...updates } },
+ })),
+
+ setTopBar: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ layout: { ...state.layout, topBar: { ...state.layout.topBar, ...updates } },
+ })),
+});
+
+const createNavigationSlice = (set: SetState) => ({
+ setCurrentPage: (page: NavigationState['currentPage']) =>
+ set((state: GlobalState) => ({
+ navigation: { ...state.navigation, currentPage: page },
+ })),
+
+ setMenu: (menu: NavigationState['menu']) =>
+ set((state: GlobalState) => ({
+ navigation: { ...state.navigation, menu },
+ })),
+
+ setNavigation: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ navigation: { ...state.navigation, ...updates },
+ })),
+});
+
+const createAppSlice = (set: SetState) => ({
+ setApp: (updates: Partial) =>
+ set((state: GlobalState) => ({
+ app: { ...state.app, ...updates },
+ })),
+});
+
+const createComplexActions = (set: SetState, get: GetState) => ({
+ fetchData: async (url?: string) => {
+ try {
+ set((state: GlobalState) => ({
+ session: {
+ ...state.session,
+ apiURL: url ?? state.session.apiURL,
+ loading: true,
+ },
+ }));
+
+ const currentState = get();
+ const result = await currentState.onFetchSession?.(currentState);
+
+ set((state: GlobalState) => ({
+ ...state,
+ ...result,
+ app: {
+ ...state.app,
+ ...result?.app,
+ updatedAt: new Date().toISOString(),
+ },
+ session: {
+ ...state.session,
+ ...result?.session,
+ connected: true,
+ loading: false,
+ },
+ }));
+ } catch (e) {
+ set((state: GlobalState) => ({
+ session: {
+ ...state.session,
+ connected: false,
+ error: `Load Exception: ${String(e)}`,
+ loading: false,
+ },
+ }));
+ }
+ },
+
+ login: async (authToken?: string) => {
+ set((state: GlobalState) => ({
+ session: {
+ ...state.session,
+ authToken: authToken ?? '',
+ },
+ }));
+ await get().fetchData();
+ },
+
+ logout: async () => {
+ set((state: GlobalState) => ({
+ ...initialState,
+ session: {
+ ...initialState.session,
+ apiURL: state.session.apiURL,
+ },
+ }));
+ await get().fetchData();
+ },
+});
+
+const GlobalStateStore = createStore((set, get) => ({
+ ...initialState,
+ ...createProgramSlice(set),
+ ...createSessionSlice(set),
+ ...createOwnerSlice(set),
+ ...createUserSlice(set),
+ ...createLayoutSlice(set),
+ ...createNavigationSlice(set),
+ ...createAppSlice(set),
+ ...createComplexActions(set, get),
+}));
+
+loadStorage()
+ .then((state) => {
+ GlobalStateStore.setState((current) => ({
+ ...current,
+ ...state,
+ session: {
+ ...current.session,
+ ...state.session,
+ connected: true,
+ loading: false,
+ },
+ }));
+ })
+ .catch((e) => {
+ console.error('Error loading storage:', e);
+ });
+
+GlobalStateStore.subscribe((state) => {
+ saveStorage(state).catch((e) => {
+ console.error('Error saving storage:', e);
+ });
+});
+
+const createTypeBoundedUseStore = ((store) => (selector) =>
+ useStoreWithEqualityFn(store, selector, shallow)) as >(
+ store: S
+) => {
+ (): ExtractState;
+ (selector: (state: ExtractState) => T): T;
+};
+
+const useGlobalStateStore = createTypeBoundedUseStore(GlobalStateStore);
+
+const setApiURL = (url: string) => {
+ GlobalStateStore.getState().setApiURL(url);
+};
+
+const getApiURL = (): string => {
+ return GlobalStateStore.getState().session.apiURL;
+};
+
+const getAuthToken = (): string => {
+ return GlobalStateStore.getState().session.authToken;
+};
+
+const setAuthToken = (token: string) => {
+ GlobalStateStore.getState().setAuthToken(token);
+};
+
+const GetGlobalState = (): GlobalStateStoreType => {
+ return GlobalStateStore.getState();
+}
+
+export { getApiURL, getAuthToken, GetGlobalState, GlobalStateStore, setApiURL, setAuthToken, useGlobalStateStore };
diff --git a/src/GlobalStateStore/GlobalStateStore.types.ts b/src/GlobalStateStore/GlobalStateStore.types.ts
new file mode 100644
index 0000000..67210d5
--- /dev/null
+++ b/src/GlobalStateStore/GlobalStateStore.types.ts
@@ -0,0 +1,188 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+interface AppState {
+ controls?: Record;
+ environment: 'development' | 'production';
+ globals?: Record;
+ updatedAt?: string;
+}
+
+interface BarState {
+ collapsed?: boolean;
+ menuItems?: MenuItem[];
+ meta?: Record;
+ open: boolean;
+ pinned?: boolean;
+ render?: () => React.ReactNode;
+ size?: number;
+
+}
+
+type DatabaseDetail = {
+ name?: string;
+ version?: string;
+};
+
+type ExtractState = S extends { getState: () => infer X } ? X : never;
+
+interface GlobalState {
+ app: AppState;
+ layout: LayoutState;
+ navigation: NavigationState;
+ owner: OwnerState;
+ program: ProgramState;
+ session: SessionState;
+ user: UserState;
+}
+
+interface GlobalStateActions {
+ // Complex actions
+ fetchData: (url?: string) => Promise;
+
+ login: (authToken?: string) => Promise;
+ logout: () => Promise;
+ // Callback for custom fetch logic
+ onFetchSession?: (state: GlobalState) => Promise>;
+ setApiURL: (url: string) => void;
+
+ // App actions
+ setApp: (updates: Partial) => void;
+
+ setAuthToken: (token: string) => void;
+ setBottomBar: (updates: Partial) => void;
+ setCurrentPage: (page: PageInfo) => void;
+
+ // Layout actions
+ setLayout: (updates: Partial) => void;
+ setLeftBar: (updates: Partial) => void;
+
+ setMenu: (menu: MenuItem[]) => void;
+
+ // Navigation actions
+ setNavigation: (updates: Partial) => void;
+
+ // Owner actions
+ setOwner: (updates: Partial) => void;
+
+ // Program actions
+ setProgram: (updates: Partial) => void;
+
+ setRightBar: (updates: Partial) => void;
+
+ // Session actions
+ setSession: (updates: Partial) => void;
+
+ setTopBar: (updates: Partial) => void;
+
+ // User actions
+ setUser: (updates: Partial) => void;
+}
+
+interface GlobalStateStoreType extends GlobalState, GlobalStateActions {}
+
+interface LayoutState {
+ bottomBar: BarState;
+ leftBar: BarState;
+ rightBar: BarState;
+ topBar: BarState;
+}
+
+type MenuItem = {
+ [key: string]: any;
+ children?: MenuItem[];
+ icon?: string;
+ id?: number | string;
+ label: string;
+ path?: string;
+};
+
+interface NavigationState {
+ currentPage?: PageInfo;
+ menu: MenuItem[];
+}
+
+interface OwnerState {
+ id: number;
+ logo?: string;
+ name: string;
+ settings?: Record;
+ theme?: ThemeSettings;
+}
+
+type PageInfo = {
+ breadcrumbs?: string[];
+ meta?: Record;
+ path?: string;
+ title?: string;
+};
+
+interface ProgramState {
+ backendVersion?: string;
+ bigLogo?: string;
+ database?: DatabaseDetail;
+ databaseVersion?: string;
+ description?: string;
+ logo?: string;
+ meta?: Record;
+ name: string;
+ slug: string;
+ tags?: string[];
+ version?: string;
+}
+
+interface ProgramWrapperProps {
+ apiURL?: string;
+ children: React.ReactNode | React.ReactNode[];
+ debugMode?: boolean;
+ fallback?: React.ReactNode | React.ReactNode[];
+ renderFallback?: boolean;
+ testMode?: boolean;
+ version?: string;
+}
+
+interface SessionState {
+ apiURL: string;
+ authToken: string;
+ connected: boolean;
+ error?: string;
+ isSecurity?: boolean;
+ loading: boolean;
+ meta?: Record;
+ parameters?: Record;
+}
+
+interface ThemeSettings {
+ darkMode?: boolean;
+ name?: string;
+}
+
+interface UserState {
+ avatarUrl?: string;
+ email?: string;
+ fullNames?: string;
+ guid?: string;
+ isAdmin?: boolean;
+ noticeMsg?: string;
+ parameters?: Record;
+ rid?: number;
+ theme?: ThemeSettings;
+ username: string;
+}
+
+export type {
+ AppState,
+ BarState,
+ ExtractState,
+ GlobalState,
+ GlobalStateActions,
+ GlobalStateStoreType,
+ LayoutState,
+ MenuItem,
+ NavigationState,
+ OwnerState,
+ PageInfo,
+ ProgramState,
+ ProgramWrapperProps,
+ SessionState,
+ ThemeSettings,
+ UserState,
+};
diff --git a/src/GlobalStateStore/GlobalStateStore.utils.ts b/src/GlobalStateStore/GlobalStateStore.utils.ts
new file mode 100644
index 0000000..3046516
--- /dev/null
+++ b/src/GlobalStateStore/GlobalStateStore.utils.ts
@@ -0,0 +1,93 @@
+import { get, set } from 'idb-keyval';
+
+import type { GlobalState } from './GlobalStateStore.types';
+
+const STORAGE_KEY = 'app-data';
+
+const SKIP_PATHS = new Set([
+ 'app.controls',
+ 'session.connected',
+ 'session.error',
+ 'session.loading',
+]);
+
+const shouldSkipPath = (path: string): boolean => {
+ return SKIP_PATHS.has(path);
+};
+
+const filterState = (state: unknown, prefix = ''): unknown => {
+ if (typeof state === 'function') {
+ return undefined;
+ }
+
+ if (state === null || typeof state !== 'object') {
+ return state;
+ }
+
+ if (Array.isArray(state)) {
+ return state.map((item, idx) => filterState(item, `${prefix}[${idx}]`));
+ }
+
+ const filtered: Record = {};
+ for (const [key, value] of Object.entries(state)) {
+ const path = prefix ? `${prefix}.${key}` : key;
+
+ if (shouldSkipPath(path) || typeof value === 'function') {
+ continue;
+ }
+
+ filtered[key] = filterState(value, path);
+ }
+
+ return filtered;
+};
+
+async function loadStorage(): Promise> {
+ try {
+ if (typeof indexedDB !== 'undefined') {
+ const data = await get(STORAGE_KEY);
+ if (data) {
+ return JSON.parse(data) as Partial;
+ }
+ }
+ } catch (e) {
+ console.error('Failed to load from IndexedDB, falling back to localStorage:', e);
+ }
+
+ try {
+ if (typeof localStorage !== 'undefined') {
+ const data = localStorage.getItem(STORAGE_KEY);
+ if (data) {
+ return JSON.parse(data) as Partial;
+ }
+ }
+ } catch (e) {
+ console.error('Failed to load from localStorage:', e);
+ }
+
+ return {};
+}
+
+async function saveStorage(state: GlobalState): Promise {
+ const filtered = filterState(state);
+ const serialized = JSON.stringify(filtered);
+
+ try {
+ if (typeof indexedDB !== 'undefined') {
+ await set(STORAGE_KEY, serialized);
+ return;
+ }
+ } catch (e) {
+ console.error('Failed to save to IndexedDB, falling back to localStorage:', e);
+ }
+
+ try {
+ if (typeof localStorage !== 'undefined') {
+ localStorage.setItem(STORAGE_KEY, serialized);
+ }
+ } catch (e) {
+ console.error('Failed to save to localStorage:', e);
+ }
+}
+
+export { loadStorage, saveStorage };
diff --git a/src/GlobalStateStore/GlobalStateStoreWrapper.tsx b/src/GlobalStateStore/GlobalStateStoreWrapper.tsx
new file mode 100644
index 0000000..c98313e
--- /dev/null
+++ b/src/GlobalStateStore/GlobalStateStoreWrapper.tsx
@@ -0,0 +1,107 @@
+import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef } from 'react';
+
+import type { GlobalStateStoreType } from './GlobalStateStore.types';
+
+import { GetGlobalState, GlobalStateStore } from './GlobalStateStore';
+
+
+interface GlobalStateStoreContextValue {
+ fetchData: (url?: string) => Promise;
+ getState: () => GlobalStateStoreType;
+ refetch: () => Promise;
+}
+
+const GlobalStateStoreContext = createContext(null);
+
+
+interface GlobalStateStoreProviderProps {
+ apiURL?: string;
+ autoFetch?: boolean;
+ children: ReactNode;
+ fetchOnMount?: boolean;
+ throttleMs?: number;
+}
+
+export function GlobalStateStoreProvider({
+ apiURL,
+ autoFetch = true,
+ children,
+ fetchOnMount = true,
+ throttleMs = 0,
+}: GlobalStateStoreProviderProps) {
+ const lastFetchTime = useRef(0);
+ const fetchInProgress = useRef(false);
+ const mounted = useRef(false);
+
+
+ const throttledFetch = useCallback(
+ async (url?: string) => {
+ const now = Date.now();
+ const timeSinceLastFetch = now - lastFetchTime.current;
+
+ if (fetchInProgress.current) {
+ return;
+ }
+
+ if (throttleMs > 0 && timeSinceLastFetch < throttleMs) {
+ return;
+ }
+
+ try {
+ fetchInProgress.current = true;
+ lastFetchTime.current = now;
+ await GlobalStateStore.getState().fetchData(url);
+ } finally {
+ fetchInProgress.current = false;
+ }
+ },
+ [throttleMs]
+ );
+
+ const refetch = useCallback(async () => {
+ await throttledFetch();
+ }, [throttledFetch]);
+
+ useEffect(() => {
+ if (apiURL) {
+ GlobalStateStore.getState().setApiURL(apiURL);
+ }
+ }, [apiURL]);
+
+ useEffect(() => {
+ if (!mounted.current) {
+ mounted.current = true;
+
+ if (autoFetch && fetchOnMount) {
+ throttledFetch(apiURL).catch((e) => {
+ console.error('Failed to fetch on mount:', e);
+ });
+ }
+ }
+ }, [apiURL, autoFetch, fetchOnMount, throttledFetch]);
+
+ const context = useMemo(() => {
+ return {
+ fetchData: throttledFetch,
+ getState: GetGlobalState,
+ refetch,
+ };
+ }, [throttledFetch, refetch]);
+
+
+
+ return (
+
+ {children}
+
+ );
+}
+
+// eslint-disable-next-line react-refresh/only-export-components
+export function useGlobalStateStoreContext(): GlobalStateStoreContextValue {
+ const context = useContext(GlobalStateStoreContext);
+ if (!context) {
+ throw new Error('useGlobalStateStoreContext must be used within GlobalStateStoreProvider');
+ }
+ return context;
+}
diff --git a/src/GlobalStateStore/README.md b/src/GlobalStateStore/README.md
new file mode 100644
index 0000000..34f74e7
--- /dev/null
+++ b/src/GlobalStateStore/README.md
@@ -0,0 +1,105 @@
+# GlobalStateStore
+
+Zustand-based global state management with automatic persistence.
+
+## Quick Start
+
+```tsx
+import {
+ GlobalStateStoreProvider,
+ useGlobalStateStore,
+ useGlobalStateStoreContext
+} from './GlobalStateStore';
+
+// Wrap app with provider
+function App() {
+ return (
+
+
+
+ );
+}
+
+// Use in components
+function MyComponent() {
+ const { program, session, user } = useGlobalStateStore();
+ const { refetch } = useGlobalStateStoreContext();
+
+ return (
+
+ {program.name}
+ Refresh
+
+ );
+}
+
+// Outside React
+const apiURL = GlobalStateStore.getState().session.apiURL;
+GlobalStateStore.getState().setAuthToken('token');
+```
+
+## Provider Props
+
+- **apiURL** - Initial API URL (optional)
+- **autoFetch** - Enable automatic fetching (default: `true`)
+- **fetchOnMount** - Fetch data when provider mounts (default: `true`)
+- **throttleMs** - Minimum time between fetch calls in milliseconds (default: `0`)
+
+## Context Hook
+
+`useGlobalStateStoreContext()` returns:
+- **fetchData(url?)** - Throttled fetch function
+- **refetch()** - Refetch with current URL
+
+## State Slices
+
+- **program** - name, logo, description, tags, version
+- **session** - apiURL, authToken, connected, loading, error, parameters, meta
+- **owner** - id, name, logo, settings, theme (darkMode, name)
+- **user** - username, email, fullNames, isAdmin, avatarUrl, parameters, theme (darkMode, name)
+- **layout** - leftBar, rightBar, topBar, bottomBar (each: open, collapsed, pinned, size, menuItems)
+- **navigation** - menu, currentPage
+- **app** - environment, updatedAt, controls, globals
+
+## Actions
+
+**Program:** `setProgram(updates)`
+**Session:** `setSession(updates)`, `setAuthToken(token)`, `setApiURL(url)`
+**Owner:** `setOwner(updates)`
+**User:** `setUser(updates)`
+**Layout:** `setLayout(updates)`, `setLeftBar(updates)`, `setRightBar(updates)`, `setTopBar(updates)`, `setBottomBar(updates)`
+**Navigation:** `setNavigation(updates)`, `setMenu(items)`, `setCurrentPage(page)`
+**App:** `setApp(updates)`
+**Complex:** `fetchData(url?)`, `login(token?)`, `logout()`
+
+## Custom Fetch
+
+```ts
+GlobalStateStore.getState().onFetchSession = async (state) => {
+ const response = await fetch(`${state.session.apiURL}/session`, {
+ headers: { Authorization: `Bearer ${state.session.authToken}` }
+ });
+ const data = await response.json();
+
+ return {
+ program: { name: data.appName, ... },
+ user: { id: data.userId, name: data.userName, ... },
+ navigation: { menu: data.menu },
+ };
+};
+```
+
+## Persistence
+
+Auto-saves to IndexedDB (localStorage fallback).
+Auto-loads on initialization.
+Skips transient data: loading states, errors, controls.
+
+## TypeScript
+
+Fully typed with exported types:
+`GlobalState`, `ProgramState`, `SessionState`, `OwnerState`, `UserState`, `ThemeSettings`, `LayoutState`, `BarState`, `NavigationState`, `AppState`
diff --git a/src/GlobalStateStore/index.ts b/src/GlobalStateStore/index.ts
new file mode 100644
index 0000000..92a9f3c
--- /dev/null
+++ b/src/GlobalStateStore/index.ts
@@ -0,0 +1,16 @@
+export {
+ getApiURL,
+ getAuthToken,
+ GetGlobalState,
+ GlobalStateStore,
+ setApiURL,
+ setAuthToken,
+ useGlobalStateStore
+} from './GlobalStateStore';
+
+export type * from './GlobalStateStore.types';
+
+export {
+ GlobalStateStoreProvider,
+ useGlobalStateStoreContext,
+} from './GlobalStateStoreWrapper';
diff --git a/src/Gridler/stories/Examples.goapi.tsx b/src/Gridler/stories/Examples.goapi.tsx
index 65dc1d3..2afd441 100644
--- a/src/Gridler/stories/Examples.goapi.tsx
+++ b/src/Gridler/stories/Examples.goapi.tsx
@@ -4,6 +4,10 @@ import { useRef, useState } from 'react';
import type { GridlerColumns } from '../components/Column';
+import { FormerDialog } from '../../Former';
+import { NativeSelectCtrl, TextInputCtrl } from '../../FormerControllers';
+import { InlineWrapper } from '../../FormerControllers/Inputs/InlineWrapper';
+import NumberInputCtrl from '../../FormerControllers/Inputs/NumberInputCtrl';
import { GlidlerAPIAdaptorForGoLangv2 } from '../components/adaptors';
import { type GridlerRef } from '../components/GridlerStore';
import { Gridler } from '../Gridler';
@@ -18,12 +22,18 @@ export const GridlerGoAPIExampleEventlog = () => {
const [selectRow, setSelectRow] = useState('');
const [values, setValues] = useState>>([]);
const [search, setSearch] = useState('');
+ const [formProps, setFormProps] = useState<{ onChange?: any; onClose?: any; opened: boolean; request: any; title?: string; values: any; } | null>({
+ onChange: (_request: string, data: any) => { ref.current?.refresh({ value: data }); },
+ onClose: () => { setFormProps((cv) => ({ ...cv, opened: false, request: null, values: null })) },
+ opened: false,
+ request: null,
+ values: null,
+ });
const [sections, setSections] = useState | undefined>(undefined);
const columns: GridlerColumns = [
{
Cell: (row) => {
- const process = `${
- row?.cql2?.length > 0
+ const process = `${row?.cql2?.length > 0
? '🔖'
: row?.cql1?.length > 0
? '📕'
@@ -32,7 +42,7 @@ export const GridlerGoAPIExampleEventlog = () => {
: row?.status === 2
? '🔒'
: '⚙️'
- } ${String(row?.id_process ?? '0')}`;
+ } ${String(row?.id_process ?? '0')}`;
return {
data: process,
@@ -129,10 +139,29 @@ export const GridlerGoAPIExampleEventlog = () => {
changeOnActiveClick={true}
descriptionField={'process'}
onRequestForm={(request, data) => {
- console.log('Form requested', request, data);
+ setFormProps((cv)=> {
+ return {...cv, opened: true, request: request as any, values: data as any}
+ })
}}
/>
+
+
+
+
+
+
+
+
+
{
Goto 2050
-
+
);
};
diff --git a/src/lib.ts b/src/lib.ts
index 61bee52..ff1eed0 100644
--- a/src/lib.ts
+++ b/src/lib.ts
@@ -2,6 +2,7 @@ export * from './Boxer';
export * from './ErrorBoundary';
export * from './Former';
export * from './FormerControllers';
+export * from './GlobalStateStore';
export * from './Gridler';
export {