diff --git a/examples/ui/msw-react-crud/.gitignore b/examples/ui/msw-react-crud/.gitignore
new file mode 100644
index 000000000..5aa73bd33
--- /dev/null
+++ b/examples/ui/msw-react-crud/.gitignore
@@ -0,0 +1,31 @@
+# Dependencies
+node_modules
+pnpm-lock.yaml
+
+# Build outputs
+dist
+build
+*.tsbuildinfo
+
+# Development
+.vite
+.cache
+
+# Environment
+.env
+.env.local
+
+# Editor
+.vscode/*
+!.vscode/extensions.json
+.idea
+*.swp
+*.swo
+*~
+
+# OS
+.DS_Store
+Thumbs.db
+
+# MSW
+public/mockServiceWorker.js
diff --git a/examples/ui/msw-react-crud/CHANGELOG.md b/examples/ui/msw-react-crud/CHANGELOG.md
new file mode 100644
index 000000000..619b660bf
--- /dev/null
+++ b/examples/ui/msw-react-crud/CHANGELOG.md
@@ -0,0 +1,18 @@
+# @objectstack/example-msw-react-crud
+
+## 1.0.0
+
+### Major Features
+
+- Initial release of MSW + React CRUD example
+- Complete CRUD operations (Create, Read, Update, Delete) for tasks
+- Integration with @objectstack/client for all API calls
+- MSW browser worker setup for API mocking
+- React components demonstrating best practices:
+ - TaskList component for displaying and managing tasks
+ - TaskForm component for creating and editing tasks
+ - TaskItem component for individual task display
+- Comprehensive README with setup instructions and usage examples
+- Full TypeScript support with proper type definitions
+- Vite development server and build configuration
+- Styled UI with modern CSS
diff --git a/examples/ui/msw-react-crud/QUICKSTART.md b/examples/ui/msw-react-crud/QUICKSTART.md
new file mode 100644
index 000000000..da4d802b9
--- /dev/null
+++ b/examples/ui/msw-react-crud/QUICKSTART.md
@@ -0,0 +1,171 @@
+# Quick Start Guide - Simplified MSW Integration
+
+This is the **simplified** version using `@objectstack/plugin-msw` that auto-mocks all API endpoints. No manual handler code required!
+
+## Prerequisites
+
+- Node.js 18 or later
+- pnpm package manager
+
+## Step 1: Install Dependencies
+
+From the repository root:
+
+```bash
+pnpm install
+```
+
+## Step 2: Build Required Packages
+
+Build the core packages:
+
+```bash
+# Build all required packages
+pnpm --filter @objectstack/spec build
+pnpm --filter @objectstack/runtime build
+pnpm --filter @objectstack/plugin-msw build
+pnpm --filter @objectstack/client build
+```
+
+## Step 3: Initialize MSW
+
+The MSW service worker file should already be initialized:
+
+```bash
+cd examples/ui/msw-react-crud
+npx msw init public/ --save
+```
+
+## Step 4: Start the Development Server
+
+```bash
+cd examples/ui/msw-react-crud
+pnpm dev
+```
+
+The application will start on `http://localhost:3000`
+
+## ✨ What's Different?
+
+### Before (Manual Approach) ❌
+
+You had to manually write 145+ lines of MSW handlers:
+
+```typescript
+// src/mocks/browser.ts - OLD WAY
+const handlers = [
+ http.get('/api/v1/data/task', ({ request }) => {
+ // Manual pagination, filtering, sorting...
+ }),
+ http.post('/api/v1/data/task', async ({ request }) => {
+ // Manual ID generation, validation...
+ }),
+ // ... more manual handlers
+];
+const worker = setupWorker(...handlers);
+await worker.start();
+```
+
+### After (Plugin Approach) ✅
+
+Now just **3 lines** with auto-mocking:
+
+```typescript
+// src/mocks/browser.ts - NEW WAY
+const mswPlugin = new MSWPlugin({ baseUrl: '/api/v1' });
+const runtime = new ObjectStackKernel([appConfig, mswPlugin]);
+await runtime.start(); // Auto-mocks ALL endpoints!
+```
+
+## 📦 How It Works
+
+1. **Define Your Data Model** in `objectstack.config.ts`:
+ ```typescript
+ const TaskObject = ObjectSchema.create({
+ name: 'task',
+ fields: {
+ subject: Field.text({ required: true }),
+ priority: Field.number()
+ }
+ });
+ ```
+
+2. **Auto-Mock Everything**: The MSW plugin automatically mocks:
+ - ✅ Discovery endpoints
+ - ✅ Metadata endpoints
+ - ✅ All CRUD operations
+ - ✅ Query operations
+ - ✅ Pagination, sorting, filtering
+
+3. **Use ObjectStack Client** normally:
+ ```typescript
+ const client = new ObjectStackClient({ baseUrl: '/api/v1' });
+ await client.data.create('task', { subject: 'New task' });
+ ```
+
+## 🎯 Test CRUD Operations
+
+Once running, you can:
+
+1. **Create** tasks using the form
+2. **Read** tasks in the list
+3. **Update** tasks by clicking "Edit"
+4. **Delete** tasks by clicking "Delete"
+5. **Toggle completion** status
+
+## 🔍 What Gets Auto-Mocked?
+
+The plugin automatically handles:
+
+| Endpoint | Description |
+|----------|-------------|
+| `GET /api/v1` | Discovery |
+| `GET /api/v1/meta/*` | All metadata |
+| `GET /api/v1/data/:object` | Find records |
+| `GET /api/v1/data/:object/:id` | Get by ID |
+| `POST /api/v1/data/:object` | Create |
+| `PATCH /api/v1/data/:object/:id` | Update |
+| `DELETE /api/v1/data/:object/:id` | Delete |
+
+**Zero manual code!** 🎉
+
+## 🔧 Advanced: Custom Handlers
+
+Need custom logic? Easy:
+
+```typescript
+const customHandlers = [
+ http.get('/api/custom/hello', () =>
+ HttpResponse.json({ message: 'Hello!' })
+ )
+];
+
+const mswPlugin = new MSWPlugin({
+ customHandlers, // Add your custom handlers
+ baseUrl: '/api/v1'
+});
+```
+
+## Troubleshooting
+
+**MSW worker not starting?**
+```bash
+npx msw init public/ --save
+```
+
+**TypeScript errors?**
+```bash
+pnpm --filter @objectstack/spec build
+pnpm --filter @objectstack/runtime build
+pnpm --filter @objectstack/plugin-msw build
+```
+
+**404 errors?**
+Check browser console for MSW startup message
+
+## 📚 Learn More
+
+- Full documentation: [README.md](./README.md)
+- MSW Plugin: `packages/plugin-msw/README.md`
+- Runtime: `packages/runtime/README.md`
+- Client API: `packages/client/README.md`
diff --git a/examples/ui/msw-react-crud/README.md b/examples/ui/msw-react-crud/README.md
new file mode 100644
index 000000000..f06ba7ee0
--- /dev/null
+++ b/examples/ui/msw-react-crud/README.md
@@ -0,0 +1,239 @@
+# MSW + React CRUD Example
+
+This example demonstrates complete CRUD operations in a React application using **Mock Service Worker (MSW)** for API mocking and the **@objectstack/client** package for all data operations.
+
+## 🎯 Features
+
+- ✅ **Complete CRUD Operations**: Create, Read, Update, Delete tasks
+- ✅ **ObjectStack Client Integration**: Uses official `@objectstack/client` for all API calls
+- ✅ **MSW API Mocking**: All API requests are intercepted and mocked in the browser
+- ✅ **React + TypeScript**: Modern React with full TypeScript support
+- ✅ **Vite**: Fast development server and build tool
+- ✅ **Best Practices**: Follows ObjectStack conventions and patterns
+
+## 📁 Project Structure
+
+```
+src/
+├── components/
+│ ├── TaskForm.tsx # Create/Update form component
+│ ├── TaskItem.tsx # Single task display component
+│ └── TaskList.tsx # Task list with read operations
+├── mocks/
+│ └── browser.ts # MSW handlers and mock database
+├── App.tsx # Main application component
+├── App.css # Application styles
+├── main.tsx # Entry point with MSW initialization
+└── types.ts # TypeScript type definitions
+```
+
+## 🚀 Getting Started
+
+### Prerequisites
+
+- Node.js 18+
+- pnpm (package manager)
+
+### Installation
+
+```bash
+# Install dependencies
+pnpm install
+
+# Initialize MSW service worker (required for browser mode)
+pnpm dlx msw init public/ --save
+```
+
+### Running the Application
+
+```bash
+# Start development server
+pnpm dev
+```
+
+The application will be available at `http://localhost:3000`
+
+### Building for Production
+
+```bash
+# Build the application
+pnpm build
+
+# Preview the production build
+pnpm preview
+```
+
+## 📖 How It Works
+
+### 1. MSW Setup (`src/mocks/browser.ts`)
+
+MSW intercepts HTTP requests in the browser and returns mock data:
+
+```typescript
+import { setupWorker } from 'msw/browser';
+import { http, HttpResponse } from 'msw';
+
+// Define handlers matching ObjectStack API
+const handlers = [
+ http.get('/api/v1/data/task', () => {
+ return HttpResponse.json({ value: tasks, count: tasks.length });
+ }),
+
+ http.post('/api/v1/data/task', async ({ request }) => {
+ const body = await request.json();
+ const newTask = { id: generateId(), ...body };
+ return HttpResponse.json(newTask, { status: 201 });
+ }),
+
+ // ... more handlers
+];
+
+export const worker = setupWorker(...handlers);
+```
+
+### 2. ObjectStack Client Usage
+
+All components use the official `@objectstack/client` package:
+
+```typescript
+import { ObjectStackClient } from '@objectstack/client';
+
+// Initialize client
+const client = new ObjectStackClient({ baseUrl: '/api/v1' });
+await client.connect();
+
+// READ - Find all tasks
+const result = await client.data.find('task', {
+ top: 100,
+ sort: ['priority']
+});
+
+// CREATE - Create new task
+const newTask = await client.data.create('task', {
+ subject: 'New task',
+ priority: 1
+});
+
+// UPDATE - Update existing task
+await client.data.update('task', taskId, {
+ isCompleted: true
+});
+
+// DELETE - Delete task
+await client.data.delete('task', taskId);
+```
+
+### 3. React Components
+
+**TaskList Component** (`src/components/TaskList.tsx`)
+- Fetches and displays all tasks
+- Demonstrates READ operations
+- Handles task deletion and status toggling
+
+**TaskForm Component** (`src/components/TaskForm.tsx`)
+- Form for creating new tasks
+- Form for editing existing tasks
+- Demonstrates CREATE and UPDATE operations
+
+**TaskItem Component** (`src/components/TaskItem.tsx`)
+- Displays individual task
+- Provides edit and delete actions
+- Shows task metadata (priority, completion status)
+
+## 🔌 API Endpoints Mocked
+
+The example mocks the following ObjectStack API endpoints:
+
+| Method | Endpoint | Description |
+|--------|----------|-------------|
+| `GET` | `/api/v1` | Discovery endpoint |
+| `GET` | `/api/v1/meta/object/task` | Get task object metadata |
+| `GET` | `/api/v1/data/task` | Find/list all tasks |
+| `GET` | `/api/v1/data/task/:id` | Get single task by ID |
+| `POST` | `/api/v1/data/task` | Create new task |
+| `PATCH` | `/api/v1/data/task/:id` | Update existing task |
+| `DELETE` | `/api/v1/data/task/:id` | Delete task |
+
+## 🎨 UI Features
+
+- **Priority Indicators**: Color-coded priority levels (1-5)
+- **Completion Status**: Checkbox to mark tasks as complete
+- **Real-time Updates**: Automatic list refresh after CRUD operations
+- **Responsive Design**: Works on desktop and mobile devices
+- **Loading States**: Shows loading indicators during async operations
+- **Error Handling**: Displays error messages for failed operations
+
+## 📚 Key Concepts
+
+### MSW (Mock Service Worker)
+
+MSW intercepts requests at the network level, making it ideal for:
+- Development without a backend
+- Testing components in isolation
+- Demos and prototypes
+- Offline development
+
+### ObjectStack Client
+
+The `@objectstack/client` provides a type-safe, consistent API for:
+- Auto-discovery of server capabilities
+- Metadata operations
+- Data CRUD operations
+- Query operations with filters, sorting, and pagination
+
+### Best Practices Demonstrated
+
+1. **Single Source of Truth**: All API calls go through ObjectStack Client
+2. **Type Safety**: Full TypeScript support with proper interfaces
+3. **Component Separation**: Clear separation between data fetching and presentation
+4. **Error Handling**: Proper error handling and user feedback
+5. **Loading States**: Visual feedback during async operations
+
+## 🔧 Customization
+
+### Adding New Fields
+
+1. Update the `Task` interface in `src/types.ts`
+2. Update mock handlers in `src/mocks/browser.ts`
+3. Update components to display/edit new fields
+
+### Changing Mock Data
+
+Edit the initial data in `src/mocks/browser.ts`:
+
+```typescript
+const mockTasks = new Map([
+ ['1', { id: '1', subject: 'Your task', priority: 1, ... }],
+ // Add more tasks...
+]);
+```
+
+### Styling
+
+All styles are in `src/App.css`. The design uses CSS custom properties (variables) for easy theming.
+
+## 📦 Dependencies
+
+- **@objectstack/client** - Official ObjectStack client SDK
+- **@objectstack/plugin-msw** - MSW integration for ObjectStack
+- **react** - UI library
+- **msw** - Mock Service Worker for API mocking
+- **vite** - Build tool and dev server
+- **typescript** - Type safety
+
+## 🤝 Related Examples
+
+- [`/examples/msw-demo`](../../../msw-demo) - MSW plugin integration examples
+- [`/examples/todo`](../../../todo) - Todo app with ObjectStack Client
+- [`/examples/ui/react-renderer`](../react-renderer) - React metadata renderer
+
+## 📖 Further Reading
+
+- [MSW Documentation](https://mswjs.io/)
+- [ObjectStack Client API](../../packages/client)
+- [ObjectStack Protocol Specification](../../packages/spec)
+- [React Documentation](https://react.dev/)
+
+## 📝 License
+
+Apache-2.0
diff --git a/examples/ui/msw-react-crud/index.html b/examples/ui/msw-react-crud/index.html
new file mode 100644
index 000000000..75d8f22ee
--- /dev/null
+++ b/examples/ui/msw-react-crud/index.html
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+ ObjectStack MSW + React CRUD Example
+
+
+
+
+
+
diff --git a/examples/ui/msw-react-crud/objectstack.config.ts b/examples/ui/msw-react-crud/objectstack.config.ts
new file mode 100644
index 000000000..898c03053
--- /dev/null
+++ b/examples/ui/msw-react-crud/objectstack.config.ts
@@ -0,0 +1,57 @@
+/**
+ * Task Object Definition
+ */
+export const TaskObject = {
+ name: 'task',
+ label: 'Task',
+ description: 'Task management object',
+ icon: 'check-square',
+ titleFormat: '{subject}',
+ enable: {
+ apiEnabled: true,
+ trackHistory: false,
+ feeds: false,
+ activities: false,
+ mru: true,
+ },
+ fields: {
+ id: { name: 'id', label: 'ID', type: 'text', required: true },
+ subject: { name: 'subject', label: 'Subject', type: 'text', required: true },
+ priority: { name: 'priority', label: 'Priority', type: 'number', defaultValue: 5 },
+ isCompleted: { name: 'isCompleted', label: 'Completed', type: 'boolean', defaultValue: false },
+ createdAt: { name: 'createdAt', label: 'Created At', type: 'datetime' }
+ }
+};
+
+/**
+ * App Configuration
+ */
+export default {
+ name: 'task_app',
+ label: 'Task Management',
+ description: 'MSW + React CRUD Example with ObjectStack',
+ version: '1.0.0',
+ icon: 'check-square',
+ branding: {
+ primaryColor: '#3b82f6',
+ logo: '/assets/logo.png',
+ },
+ objects: [
+ TaskObject
+ ],
+ navigation: [
+ {
+ id: 'group_tasks',
+ type: 'group',
+ label: 'Tasks',
+ children: [
+ {
+ id: 'nav_tasks',
+ type: 'object',
+ objectName: 'task',
+ label: 'My Tasks'
+ }
+ ]
+ }
+ ]
+};
diff --git a/examples/ui/msw-react-crud/package.json b/examples/ui/msw-react-crud/package.json
new file mode 100644
index 000000000..e85f86231
--- /dev/null
+++ b/examples/ui/msw-react-crud/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "@objectstack/example-msw-react-crud",
+ "version": "1.0.0",
+ "description": "Complete MSW integration example with React CRUD components using ObjectStack Client",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@objectstack/client": "workspace:*",
+ "@objectstack/spec": "workspace:*",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.1",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.4",
+ "msw": "^2.0.0",
+ "typescript": "^5.0.0",
+ "vite": "^5.4.11"
+ },
+ "msw": {
+ "workerDirectory": [
+ "public"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/examples/ui/msw-react-crud/src/App.css b/examples/ui/msw-react-crud/src/App.css
new file mode 100644
index 000000000..506e411e1
--- /dev/null
+++ b/examples/ui/msw-react-crud/src/App.css
@@ -0,0 +1,367 @@
+/**
+ * App Styles
+ */
+
+:root {
+ --primary-color: #2563eb;
+ --primary-hover: #1d4ed8;
+ --success-color: #10b981;
+ --danger-color: #ef4444;
+ --warning-color: #f59e0b;
+ --border-color: #e5e7eb;
+ --bg-color: #f9fafb;
+ --text-color: #111827;
+ --text-muted: #6b7280;
+ --shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1);
+}
+
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
+ background-color: var(--bg-color);
+ color: var(--text-color);
+ line-height: 1.6;
+}
+
+code {
+ background-color: #f1f5f9;
+ padding: 2px 6px;
+ border-radius: 4px;
+ font-family: 'Courier New', monospace;
+ font-size: 0.9em;
+}
+
+/* App Container */
+.app-container {
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 2rem;
+}
+
+/* Header */
+.app-header {
+ text-align: center;
+ margin-bottom: 3rem;
+ padding-bottom: 2rem;
+ border-bottom: 2px solid var(--border-color);
+}
+
+.app-header h1 {
+ font-size: 2.5rem;
+ margin-bottom: 0.5rem;
+ color: var(--primary-color);
+}
+
+.subtitle {
+ font-size: 1.1rem;
+ color: var(--text-muted);
+ margin-bottom: 1rem;
+}
+
+.status-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ background-color: var(--success-color);
+ color: white;
+ padding: 0.5rem 1rem;
+ border-radius: 20px;
+ font-size: 0.9rem;
+ font-weight: 500;
+}
+
+.status-indicator {
+ width: 8px;
+ height: 8px;
+ background-color: white;
+ border-radius: 50%;
+ animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.5; }
+}
+
+/* Main Layout */
+.app-main {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 2rem;
+ margin-bottom: 3rem;
+}
+
+@media (max-width: 768px) {
+ .app-main {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* Form Section */
+.form-section, .list-section {
+ background: white;
+ padding: 2rem;
+ border-radius: 8px;
+ box-shadow: var(--shadow);
+}
+
+.task-form h2 {
+ margin-bottom: 1.5rem;
+ color: var(--primary-color);
+ font-size: 1.5rem;
+}
+
+.form-group {
+ margin-bottom: 1.5rem;
+}
+
+.form-group label {
+ display: block;
+ margin-bottom: 0.5rem;
+ font-weight: 500;
+ color: var(--text-color);
+}
+
+.form-group input[type="text"],
+.form-group select {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid var(--border-color);
+ border-radius: 6px;
+ font-size: 1rem;
+ transition: border-color 0.2s;
+}
+
+.form-group input[type="text"]:focus,
+.form-group select:focus {
+ outline: none;
+ border-color: var(--primary-color);
+ box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
+}
+
+.checkbox-group label {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ cursor: pointer;
+}
+
+.checkbox-group input[type="checkbox"] {
+ width: 18px;
+ height: 18px;
+ cursor: pointer;
+}
+
+.form-error {
+ background-color: #fee2e2;
+ color: var(--danger-color);
+ padding: 0.75rem;
+ border-radius: 6px;
+ margin-bottom: 1rem;
+ border-left: 4px solid var(--danger-color);
+}
+
+.form-actions {
+ display: flex;
+ gap: 1rem;
+ margin-top: 1.5rem;
+}
+
+/* Buttons */
+button {
+ padding: 0.75rem 1.5rem;
+ border: none;
+ border-radius: 6px;
+ font-size: 1rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.btn-primary {
+ background-color: var(--primary-color);
+ color: white;
+}
+
+.btn-primary:hover:not(:disabled) {
+ background-color: var(--primary-hover);
+ transform: translateY(-1px);
+ box-shadow: var(--shadow);
+}
+
+.btn-secondary {
+ background-color: var(--border-color);
+ color: var(--text-color);
+}
+
+.btn-secondary:hover:not(:disabled) {
+ background-color: #d1d5db;
+}
+
+button:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+}
+
+.btn-edit {
+ background-color: var(--primary-color);
+ color: white;
+ padding: 0.5rem 1rem;
+ font-size: 0.9rem;
+}
+
+.btn-edit:hover {
+ background-color: var(--primary-hover);
+}
+
+.btn-delete {
+ background-color: var(--danger-color);
+ color: white;
+ padding: 0.5rem 1rem;
+ font-size: 0.9rem;
+}
+
+.btn-delete:hover {
+ background-color: #dc2626;
+}
+
+/* Task List */
+.task-list h2 {
+ margin-bottom: 1.5rem;
+ color: var(--primary-color);
+ font-size: 1.5rem;
+}
+
+.tasks {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.task-item {
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+ padding: 1rem;
+ background-color: white;
+ transition: all 0.2s;
+}
+
+.task-item:hover {
+ box-shadow: var(--shadow);
+ transform: translateY(-2px);
+}
+
+.task-item.completed {
+ opacity: 0.7;
+ background-color: #f9fafb;
+}
+
+.task-item.completed .task-subject {
+ text-decoration: line-through;
+ color: var(--text-muted);
+}
+
+.task-content {
+ display: flex;
+ align-items: flex-start;
+ gap: 1rem;
+ margin-bottom: 1rem;
+}
+
+.task-checkbox {
+ width: 20px;
+ height: 20px;
+ margin-top: 0.25rem;
+ cursor: pointer;
+}
+
+.task-info {
+ flex: 1;
+}
+
+.task-subject {
+ font-size: 1.1rem;
+ margin-bottom: 0.5rem;
+ color: var(--text-color);
+}
+
+.task-meta {
+ display: flex;
+ gap: 1rem;
+ font-size: 0.9rem;
+ color: var(--text-muted);
+}
+
+.task-priority {
+ padding: 0.25rem 0.75rem;
+ border-radius: 12px;
+ font-weight: 500;
+}
+
+.priority-1 {
+ background-color: #fee2e2;
+ color: #991b1b;
+}
+
+.priority-2 {
+ background-color: #fed7aa;
+ color: #9a3412;
+}
+
+.priority-3 {
+ background-color: #fef3c7;
+ color: #92400e;
+}
+
+.priority-4 {
+ background-color: #dbeafe;
+ color: #1e40af;
+}
+
+.priority-5 {
+ background-color: #e0e7ff;
+ color: #3730a3;
+}
+
+.task-actions {
+ display: flex;
+ gap: 0.5rem;
+ justify-content: flex-end;
+}
+
+/* Loading & Empty States */
+.loading, .empty-state, .error {
+ text-align: center;
+ padding: 2rem;
+ color: var(--text-muted);
+}
+
+.error {
+ color: var(--danger-color);
+}
+
+.loading-container, .error-container {
+ text-align: center;
+ padding: 4rem 2rem;
+}
+
+/* Footer */
+.app-footer {
+ text-align: center;
+ padding-top: 2rem;
+ border-top: 1px solid var(--border-color);
+ color: var(--text-muted);
+ font-size: 0.9rem;
+}
+
+.tech-stack {
+ margin-top: 0.5rem;
+ font-size: 0.85rem;
+ color: var(--text-muted);
+}
diff --git a/examples/ui/msw-react-crud/src/App.tsx b/examples/ui/msw-react-crud/src/App.tsx
new file mode 100644
index 000000000..017b534f2
--- /dev/null
+++ b/examples/ui/msw-react-crud/src/App.tsx
@@ -0,0 +1,127 @@
+/**
+ * App Component
+ *
+ * Main application component that demonstrates complete CRUD operations
+ * using ObjectStack Client with MSW for API mocking.
+ */
+
+import { useState, useEffect } from 'react';
+import { ObjectStackClient } from '@objectstack/client';
+import type { Task } from './types';
+import { TaskForm } from './components/TaskForm';
+import { TaskList } from './components/TaskList';
+import './App.css';
+
+export function App() {
+ const [client, setClient] = useState(null);
+ const [editingTask, setEditingTask] = useState(null);
+ const [refreshTrigger, setRefreshTrigger] = useState(0);
+ const [connected, setConnected] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ initializeClient();
+ }, []);
+
+ async function initializeClient() {
+ try {
+ // Initialize ObjectStack Client pointing to our mocked API
+ const stackClient = new ObjectStackClient({
+ baseUrl: '/api/v1'
+ });
+
+ // Connect to the server (will be intercepted by MSW)
+ await stackClient.connect();
+
+ setClient(stackClient);
+ setConnected(true);
+ console.log('✅ ObjectStack Client connected (via MSW)');
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to initialize client');
+ console.error('Failed to initialize client:', err);
+ }
+ }
+
+ function handleFormSuccess() {
+ setEditingTask(null);
+ // Trigger refresh of task list
+ setRefreshTrigger(prev => prev + 1);
+ }
+
+ function handleEditTask(task: Task) {
+ setEditingTask(task);
+ // Scroll to form
+ window.scrollTo({ top: 0, behavior: 'smooth' });
+ }
+
+ function handleCancelEdit() {
+ setEditingTask(null);
+ }
+
+ if (error) {
+ return (
+
+
+
Connection Error
+
{error}
+
+
+
+ );
+ }
+
+ if (!connected || !client) {
+ return (
+
+
+
Connecting to ObjectStack...
+
Initializing MSW and ObjectStack Client...
+
+
+ );
+ }
+
+ return (
+
+
+ 📋 ObjectStack MSW + React CRUD Example
+
+ Complete CRUD operations using @objectstack/client with Mock Service Worker
+
+
+
+ MSW Active - All API calls are mocked
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/ui/msw-react-crud/src/components/TaskForm.tsx b/examples/ui/msw-react-crud/src/components/TaskForm.tsx
new file mode 100644
index 000000000..88974aa04
--- /dev/null
+++ b/examples/ui/msw-react-crud/src/components/TaskForm.tsx
@@ -0,0 +1,164 @@
+/**
+ * TaskForm Component
+ *
+ * Form for creating and updating tasks.
+ * Demonstrates CREATE and UPDATE operations using @objectstack/client.
+ */
+
+import { useState, useEffect } from 'react';
+import { ObjectStackClient } from '@objectstack/client';
+import type { Task, CreateTaskInput } from '../types';
+
+interface TaskFormProps {
+ client: ObjectStackClient;
+ editingTask: Task | null;
+ onSuccess: () => void;
+ onCancel: () => void;
+}
+
+export function TaskForm({ client, editingTask, onSuccess, onCancel }: TaskFormProps) {
+ const [subject, setSubject] = useState('');
+ const [priority, setPriority] = useState(3);
+ const [isCompleted, setIsCompleted] = useState(false);
+ const [submitting, setSubmitting] = useState(false);
+ const [error, setError] = useState(null);
+
+ // Populate form when editing
+ useEffect(() => {
+ if (editingTask) {
+ setSubject(editingTask.subject);
+ setPriority(editingTask.priority);
+ setIsCompleted(editingTask.isCompleted);
+ } else {
+ resetForm();
+ }
+ }, [editingTask]);
+
+ function resetForm() {
+ setSubject('');
+ setPriority(3);
+ setIsCompleted(false);
+ setError(null);
+ }
+
+ async function handleSubmit(e: React.FormEvent) {
+ e.preventDefault();
+
+ if (!subject.trim()) {
+ setError('Subject is required');
+ return;
+ }
+
+ setSubmitting(true);
+ setError(null);
+
+ try {
+ if (editingTask) {
+ // UPDATE operation using ObjectStack Client
+ await client.data.update('task', editingTask.id, {
+ subject: subject.trim(),
+ priority,
+ isCompleted
+ });
+ } else {
+ // CREATE operation using ObjectStack Client
+ const taskData: CreateTaskInput = {
+ subject: subject.trim(),
+ priority,
+ isCompleted
+ };
+
+ await client.data.create('task', taskData);
+ }
+
+ resetForm();
+ onSuccess();
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to save task');
+ console.error('Error saving task:', err);
+ } finally {
+ setSubmitting(false);
+ }
+ }
+
+ function handleCancel() {
+ resetForm();
+ onCancel();
+ }
+
+ return (
+
+
{editingTask ? 'Edit Task' : 'Create New Task'}
+
+
+
+ );
+}
diff --git a/examples/ui/msw-react-crud/src/components/TaskItem.tsx b/examples/ui/msw-react-crud/src/components/TaskItem.tsx
new file mode 100644
index 000000000..5945d8180
--- /dev/null
+++ b/examples/ui/msw-react-crud/src/components/TaskItem.tsx
@@ -0,0 +1,52 @@
+/**
+ * TaskItem Component
+ *
+ * Displays a single task with actions (edit, delete, toggle complete).
+ */
+
+import type { Task } from '../types';
+
+interface TaskItemProps {
+ task: Task;
+ onEdit: () => void;
+ onDelete: () => void;
+ onToggleComplete: () => void;
+}
+
+export function TaskItem({ task, onEdit, onDelete, onToggleComplete }: TaskItemProps) {
+ const priorityLabel = ['Critical', 'High', 'Medium', 'Low', 'Lowest'][task.priority - 1] || 'Unknown';
+ const priorityClass = `priority-${task.priority}`;
+
+ return (
+
+
+
+
+
{task.subject}
+
+
+ Priority: {priorityLabel} ({task.priority})
+
+
+ Created: {new Date(task.createdAt).toLocaleDateString()}
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/examples/ui/msw-react-crud/src/components/TaskList.tsx b/examples/ui/msw-react-crud/src/components/TaskList.tsx
new file mode 100644
index 000000000..c23c2dbd3
--- /dev/null
+++ b/examples/ui/msw-react-crud/src/components/TaskList.tsx
@@ -0,0 +1,112 @@
+/**
+ * TaskList Component
+ *
+ * Displays a list of tasks fetched from the ObjectStack API.
+ * Demonstrates READ operation using @objectstack/client.
+ */
+
+import { useEffect, useState } from 'react';
+import { ObjectStackClient } from '@objectstack/client';
+import type { Task } from '../types';
+import { TaskItem } from './TaskItem';
+
+interface TaskListProps {
+ client: ObjectStackClient;
+ onEdit: (task: Task) => void;
+ refreshTrigger: number;
+}
+
+export function TaskList({ client, onEdit, refreshTrigger }: TaskListProps) {
+ const [tasks, setTasks] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ loadTasks();
+ }, [refreshTrigger]);
+
+ async function loadTasks() {
+ try {
+ setLoading(true);
+ setError(null);
+
+ // Use ObjectStack Client to fetch tasks
+ const result = await client.data.find('task', {
+ top: 100,
+ sort: ['priority', '-createdAt']
+ });
+
+ setTasks(result.value as Task[]);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to load tasks');
+ console.error('Error loading tasks:', err);
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ async function handleDelete(id: string) {
+ try {
+ // Use ObjectStack Client to delete task
+ await client.data.delete('task', id);
+
+ // Refresh the list
+ await loadTasks();
+ } catch (err) {
+ alert('Failed to delete task: ' + (err instanceof Error ? err.message : 'Unknown error'));
+ }
+ }
+
+ async function handleToggleComplete(task: Task) {
+ try {
+ // Use ObjectStack Client to update task
+ await client.data.update('task', task.id, {
+ isCompleted: !task.isCompleted
+ });
+
+ // Refresh the list
+ await loadTasks();
+ } catch (err) {
+ alert('Failed to update task: ' + (err instanceof Error ? err.message : 'Unknown error'));
+ }
+ }
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
Error: {error}
+
+
+ );
+ }
+
+ return (
+
+
Tasks ({tasks.length})
+
+ {tasks.length === 0 ? (
+
No tasks yet. Create one to get started!
+ ) : (
+
+ {tasks.map((task) => (
+ onEdit(task)}
+ onDelete={() => handleDelete(task.id)}
+ onToggleComplete={() => handleToggleComplete(task)}
+ />
+ ))}
+
+ )}
+
+ );
+}
diff --git a/examples/ui/msw-react-crud/src/main.tsx b/examples/ui/msw-react-crud/src/main.tsx
new file mode 100644
index 000000000..10bc318b3
--- /dev/null
+++ b/examples/ui/msw-react-crud/src/main.tsx
@@ -0,0 +1,25 @@
+/**
+ * Main Entry Point
+ *
+ * Initializes MSW and renders the React application.
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import { App } from './App';
+import { startMockServer } from './mocks/browser';
+
+// Start MSW before rendering the app
+async function bootstrap() {
+ // Initialize Mock Service Worker
+ await startMockServer();
+
+ // Render the React app
+ ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+ );
+}
+
+bootstrap();
diff --git a/examples/ui/msw-react-crud/src/mocks/browser.ts b/examples/ui/msw-react-crud/src/mocks/browser.ts
new file mode 100644
index 000000000..16382dd4d
--- /dev/null
+++ b/examples/ui/msw-react-crud/src/mocks/browser.ts
@@ -0,0 +1,68 @@
+/**
+ * MSW Browser Worker Setup
+ *
+ * Simplified setup using auto-generated handlers from objectstack.config.ts
+ * No runtime overhead - just pure MSW handlers generated from your config!
+ */
+
+import { setupWorker } from 'msw/browser';
+import { createMockHandlers, seedData } from './createMockHandlers';
+import appConfig from '../../objectstack.config';
+
+/**
+ * Start the MSW worker with auto-generated handlers
+ *
+ * This function:
+ * 1. Seeds initial data
+ * 2. Generates MSW handlers automatically from your config
+ * 3. Starts the MSW worker
+ */
+export async function startMockServer() {
+ // Seed initial task data
+ seedData('task', [
+ {
+ id: '1',
+ subject: 'Complete MSW integration example',
+ priority: 1,
+ isCompleted: false,
+ createdAt: new Date().toISOString()
+ },
+ {
+ id: '2',
+ subject: 'Test CRUD operations with React',
+ priority: 2,
+ isCompleted: false,
+ createdAt: new Date().toISOString()
+ },
+ {
+ id: '3',
+ subject: 'Write documentation',
+ priority: 3,
+ isCompleted: true,
+ createdAt: new Date().toISOString()
+ }
+ ]);
+
+ // Create metadata from config
+ const metadata = {
+ objects: (appConfig.objects || []).reduce((acc: any, obj: any) => {
+ acc[obj.name] = obj;
+ return acc;
+ }, {})
+ };
+
+ // Create handlers from config
+ const handlers = createMockHandlers('/api/v1', metadata);
+
+ // Start MSW worker
+ const worker = setupWorker(...handlers);
+ await worker.start({
+ onUnhandledRequest: 'bypass',
+ });
+
+ console.log('[MSW] Auto-mocked API ready! All endpoints generated from objectstack.config.ts');
+ console.log('[MSW] Objects:', Object.keys(metadata.objects));
+
+ return worker;
+}
+
diff --git a/examples/ui/msw-react-crud/src/mocks/createMockHandlers.ts b/examples/ui/msw-react-crud/src/mocks/createMockHandlers.ts
new file mode 100644
index 000000000..4793912be
--- /dev/null
+++ b/examples/ui/msw-react-crud/src/mocks/createMockHandlers.ts
@@ -0,0 +1,199 @@
+/**
+ * Auto-generate MSW handlers from ObjectStack configuration
+ *
+ * This helper creates all necessary MSW handlers based on your
+ * objectstack.config.ts without requiring the full runtime.
+ */
+
+import { http, HttpResponse } from 'msw';
+
+// Simple in-memory store
+const stores = new Map>();
+
+let idCounters = new Map();
+
+function getStore(objectName: string): Map {
+ if (!stores.has(objectName)) {
+ stores.set(objectName, new Map());
+ }
+ return stores.get(objectName)!;
+}
+
+function getNextId(objectName: string): string {
+ const current = idCounters.get(objectName) || 0;
+ const next = current + 1;
+ idCounters.set(objectName, next);
+ return String(next);
+}
+
+/**
+ * Seed initial data for an object
+ */
+export function seedData(objectName: string, records: any[]) {
+ const store = getStore(objectName);
+ records.forEach((record) => {
+ const id = record.id || getNextId(objectName);
+ store.set(id, { ...record, id });
+ });
+
+ // Initialize ID counter
+ const maxId = Math.max(
+ 0,
+ ...Array.from(store.values()).map(r => parseInt(r.id) || 0)
+ );
+ idCounters.set(objectName, maxId);
+}
+
+/**
+ * Create auto-mocked MSW handlers for ObjectStack API
+ */
+export function createMockHandlers(baseUrl: string = '/api/v1', metadata: any = {}) {
+ const discoveryResponse = {
+ name: 'ObjectStack Mock Server',
+ version: '1.0.0',
+ environment: 'development',
+ routes: {
+ discovery: `${baseUrl}`,
+ metadata: `${baseUrl}/meta`,
+ data: `${baseUrl}/data`,
+ ui: `${baseUrl}/ui`
+ },
+ capabilities: {
+ search: true,
+ files: false
+ }
+ };
+
+ // Generate handlers for both correct paths and doubled paths (client compatibility)
+ const paths = [baseUrl, `${baseUrl}/api/v1`];
+
+ const handlers = [];
+
+ for (const path of paths) {
+ handlers.push(
+ // Discovery endpoint
+ http.get(path, () => {
+ return HttpResponse.json(discoveryResponse);
+ }),
+
+ // Meta endpoints
+ http.get(`${path}/meta`, () => {
+ return HttpResponse.json({
+ data: [
+ { type: 'object', href: `${baseUrl}/meta/objects`, count: Object.keys(metadata.objects || {}).length }
+ ]
+ });
+ }),
+
+ http.get(`${path}/meta/objects`, () => {
+ const objects = metadata.objects || {};
+ return HttpResponse.json({
+ data: Object.values(objects).map((obj: any) => ({
+ name: obj.name,
+ label: obj.label,
+ description: obj.description,
+ path: `${baseUrl}/data/${obj.name}`,
+ self: `${baseUrl}/meta/object/${obj.name}`
+ }))
+ });
+ }),
+
+ http.get(`${path}/meta/object/:name`, ({ params }) => {
+ const objectName = params.name as string;
+ const obj = metadata.objects?.[objectName];
+
+ if (!obj) {
+ return HttpResponse.json({ error: 'Object not found' }, { status: 404 });
+ }
+
+ return HttpResponse.json(obj);
+ }),
+
+ // Data: Find/List
+ http.get(`${path}/data/:object`, ({ params, request }) => {
+ const objectName = params.object as string;
+ const store = getStore(objectName);
+ const url = new URL(request.url);
+
+ const top = parseInt(url.searchParams.get('top') || '100');
+ const skip = parseInt(url.searchParams.get('$skip') || '0');
+
+ const records = Array.from(store.values());
+ const paginatedRecords = records.slice(skip, skip + top);
+
+ return HttpResponse.json({
+ '@odata.context': `${baseUrl}/data/$metadata#${objectName}`,
+ value: paginatedRecords,
+ count: records.length
+ });
+ }),
+
+ // Data: Get by ID
+ http.get(`${path}/data/:object/:id`, ({ params }) => {
+ const objectName = params.object as string;
+ const id = params.id as string;
+ const store = getStore(objectName);
+ const record = store.get(id);
+
+ if (!record) {
+ return HttpResponse.json({ error: 'Record not found' }, { status: 404 });
+ }
+
+ return HttpResponse.json(record);
+ }),
+
+ // Data: Create
+ http.post(`${path}/data/:object`, async ({ params, request }) => {
+ const objectName = params.object as string;
+ const body = await request.json() as any;
+ const store = getStore(objectName);
+
+ const id = body.id || getNextId(objectName);
+ const newRecord = {
+ ...body,
+ id,
+ createdAt: body.createdAt || new Date().toISOString()
+ };
+
+ store.set(id, newRecord);
+
+ return HttpResponse.json(newRecord, { status: 201 });
+ }),
+
+ // Data: Update
+ http.patch(`${path}/data/:object/:id`, async ({ params, request }) => {
+ const objectName = params.object as string;
+ const id = params.id as string;
+ const updates = await request.json() as any;
+ const store = getStore(objectName);
+ const record = store.get(id);
+
+ if (!record) {
+ return HttpResponse.json({ error: 'Record not found' }, { status: 404 });
+ }
+
+ const updatedRecord = { ...record, ...updates };
+ store.set(id, updatedRecord);
+
+ return HttpResponse.json(updatedRecord);
+ }),
+
+ // Data: Delete
+ http.delete(`${path}/data/:object/:id`, ({ params }) => {
+ const objectName = params.object as string;
+ const id = params.id as string;
+ const store = getStore(objectName);
+
+ if (!store.has(id)) {
+ return HttpResponse.json({ error: 'Record not found' }, { status: 404 });
+ }
+
+ store.delete(id);
+
+ return HttpResponse.json({ success: true }, { status: 204 });
+ })
+ );
+ }
+
+ return handlers;
+}
diff --git a/examples/ui/msw-react-crud/src/types.ts b/examples/ui/msw-react-crud/src/types.ts
new file mode 100644
index 000000000..789ed9aa1
--- /dev/null
+++ b/examples/ui/msw-react-crud/src/types.ts
@@ -0,0 +1,22 @@
+/**
+ * Task type definition
+ */
+export interface Task {
+ id: string;
+ subject: string;
+ priority: number;
+ isCompleted: boolean;
+ createdAt: string;
+}
+
+export interface CreateTaskInput {
+ subject: string;
+ priority?: number;
+ isCompleted?: boolean;
+}
+
+export interface UpdateTaskInput {
+ subject?: string;
+ priority?: number;
+ isCompleted?: boolean;
+}
diff --git a/examples/ui/msw-react-crud/tsconfig.json b/examples/ui/msw-react-crud/tsconfig.json
new file mode 100644
index 000000000..a7fc6fbf2
--- /dev/null
+++ b/examples/ui/msw-react-crud/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/examples/ui/msw-react-crud/tsconfig.node.json b/examples/ui/msw-react-crud/tsconfig.node.json
new file mode 100644
index 000000000..42872c59f
--- /dev/null
+++ b/examples/ui/msw-react-crud/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/examples/ui/msw-react-crud/vite.config.ts b/examples/ui/msw-react-crud/vite.config.ts
new file mode 100644
index 000000000..e7cace28c
--- /dev/null
+++ b/examples/ui/msw-react-crud/vite.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react()],
+ server: {
+ port: 3000,
+ },
+ optimizeDeps: {
+ include: ['msw', 'msw/browser']
+ }
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4b4024875..c80479e88 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -227,6 +227,115 @@ importers:
specifier: ^5.0.0
version: 5.9.3
+ examples/ui/custom-components:
+ dependencies:
+ '@objectstack/spec':
+ specifier: workspace:*
+ version: link:../../../packages/spec
+ lucide-react:
+ specifier: ^0.562.0
+ version: 0.562.0(react@18.3.1)
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ devDependencies:
+ '@types/react':
+ specifier: ^18.3.1
+ version: 18.3.27
+ '@types/react-dom':
+ specifier: ^18.3.0
+ version: 18.3.7(@types/react@18.3.27)
+ '@vitejs/plugin-react':
+ specifier: ^4.3.4
+ version: 4.7.0(vite@5.4.21(@types/node@20.19.30)(lightningcss@1.30.2))
+ typescript:
+ specifier: ^5.0.0
+ version: 5.9.3
+ vite:
+ specifier: ^5.4.11
+ version: 5.4.21(@types/node@20.19.30)(lightningcss@1.30.2)
+ vitest:
+ specifier: ^2.1.8
+ version: 2.1.9(@types/node@20.19.30)(lightningcss@1.30.2)(msw@2.12.7(@types/node@20.19.30)(typescript@5.9.3))
+
+ examples/ui/metadata-examples:
+ dependencies:
+ '@objectstack/spec':
+ specifier: workspace:*
+ version: link:../../../packages/spec
+ devDependencies:
+ typescript:
+ specifier: ^5.0.0
+ version: 5.9.3
+
+ examples/ui/msw-react-crud:
+ dependencies:
+ '@objectstack/client':
+ specifier: workspace:*
+ version: link:../../../packages/client
+ '@objectstack/spec':
+ specifier: workspace:*
+ version: link:../../../packages/spec
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ devDependencies:
+ '@types/react':
+ specifier: ^18.3.1
+ version: 18.3.27
+ '@types/react-dom':
+ specifier: ^18.3.0
+ version: 18.3.7(@types/react@18.3.27)
+ '@vitejs/plugin-react':
+ specifier: ^4.3.4
+ version: 4.7.0(vite@5.4.21(@types/node@20.19.30)(lightningcss@1.30.2))
+ msw:
+ specifier: ^2.0.0
+ version: 2.12.7(@types/node@20.19.30)(typescript@5.9.3)
+ typescript:
+ specifier: ^5.0.0
+ version: 5.9.3
+ vite:
+ specifier: ^5.4.11
+ version: 5.4.21(@types/node@20.19.30)(lightningcss@1.30.2)
+
+ examples/ui/react-renderer:
+ dependencies:
+ '@objectstack/spec':
+ specifier: workspace:*
+ version: link:../../../packages/spec
+ react:
+ specifier: ^18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: ^18.3.1
+ version: 18.3.1(react@18.3.1)
+ devDependencies:
+ '@types/react':
+ specifier: ^18.3.1
+ version: 18.3.27
+ '@types/react-dom':
+ specifier: ^18.3.0
+ version: 18.3.7(@types/react@18.3.27)
+ '@vitejs/plugin-react':
+ specifier: ^4.3.4
+ version: 4.7.0(vite@5.4.21(@types/node@20.19.30)(lightningcss@1.30.2))
+ typescript:
+ specifier: ^5.0.0
+ version: 5.9.3
+ vite:
+ specifier: ^5.4.11
+ version: 5.4.21(@types/node@20.19.30)(lightningcss@1.30.2)
+ vitest:
+ specifier: ^2.1.8
+ version: 2.1.9(@types/node@20.19.30)(lightningcss@1.30.2)(msw@2.12.7(@types/node@20.19.30)(typescript@5.9.3))
+
packages/client:
dependencies:
'@objectstack/spec':
@@ -368,6 +477,44 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
+ '@babel/code-frame@7.28.6':
+ resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/compat-data@7.28.6':
+ resolution: {integrity: sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/core@7.28.6':
+ resolution: {integrity: sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.28.6':
+ resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-compilation-targets@7.28.6':
+ resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-imports@7.28.6':
+ resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-module-transforms@7.28.6':
+ resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+
+ '@babel/helper-plugin-utils@7.28.6':
+ resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
@@ -376,15 +523,43 @@ packages:
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
engines: {node: '>=6.9.0'}
+ '@babel/helper-validator-option@7.27.1':
+ resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helpers@7.28.6':
+ resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/parser@7.28.6':
resolution: {integrity: sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==}
engines: {node: '>=6.0.0'}
hasBin: true
+ '@babel/plugin-transform-react-jsx-self@7.27.1':
+ resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1':
+ resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+
'@babel/runtime@7.28.6':
resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==}
engines: {node: '>=6.9.0'}
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.28.6':
+ resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==}
+ engines: {node: '>=6.9.0'}
+
'@babel/types@7.28.6':
resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==}
engines: {node: '>=6.9.0'}
@@ -1462,6 +1637,9 @@ packages:
'@radix-ui/rect@1.1.1':
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
+ '@rolldown/pluginutils@1.0.0-beta.27':
+ resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==}
+
'@rollup/rollup-android-arm-eabi@4.55.2':
resolution: {integrity: sha512-21J6xzayjy3O6NdnlO6aXi/urvSRjm6nCI6+nF6ra2YofKruGixN9kfT+dt55HVNwfDmpDHJcaS3JuP/boNnlA==}
cpu: [arm]
@@ -1728,6 +1906,18 @@ packages:
'@tsconfig/node16@1.0.4':
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+ '@types/babel__core@7.20.5':
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+
+ '@types/babel__generator@7.27.0':
+ resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==}
+
+ '@types/babel__template@7.4.4':
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+
+ '@types/babel__traverse@7.28.0':
+ resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==}
+
'@types/debug@4.1.12':
resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
@@ -1758,11 +1948,22 @@ packages:
'@types/node@20.19.30':
resolution: {integrity: sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==}
+ '@types/prop-types@15.7.15':
+ resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==}
+
+ '@types/react-dom@18.3.7':
+ resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==}
+ peerDependencies:
+ '@types/react': ^18.0.0
+
'@types/react-dom@19.2.3':
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
peerDependencies:
'@types/react': ^19.2.0
+ '@types/react@18.3.27':
+ resolution: {integrity: sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==}
+
'@types/react@19.2.8':
resolution: {integrity: sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==}
@@ -1778,6 +1979,12 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+ '@vitejs/plugin-react@4.7.0':
+ resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0
+
'@vitest/coverage-v8@2.1.9':
resolution: {integrity: sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==}
peerDependencies:
@@ -2015,6 +2222,9 @@ packages:
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
+ convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+
cookie@1.1.1:
resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==}
engines: {node: '>=18'}
@@ -2301,6 +2511,10 @@ packages:
tailwindcss:
optional: true
+ gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+
get-caller-file@2.0.5:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*}
@@ -2470,6 +2684,9 @@ packages:
resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==}
hasBin: true
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
js-tokens@9.0.1:
resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==}
@@ -2481,6 +2698,16 @@ packages:
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
hasBin: true
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
@@ -2568,6 +2795,10 @@ packages:
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+ loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+
loupe@2.3.7:
resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
@@ -2577,6 +2808,9 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+ lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+
lucide-react@0.562.0:
resolution: {integrity: sha512-82hOAu7y0dbVuFfmO4bYF1XEwYk/mEbM5E+b1jgci/udUBEE/R7LF5Ip0CCEmXe8AybRM8L+04eP+LGZeDvkiw==}
peerDependencies:
@@ -2992,6 +3226,11 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ react-dom@18.3.1:
+ resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==}
+ peerDependencies:
+ react: ^18.3.1
+
react-dom@19.2.3:
resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==}
peerDependencies:
@@ -3006,6 +3245,10 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-refresh@0.17.0:
+ resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==}
+ engines: {node: '>=0.10.0'}
+
react-remove-scroll-bar@2.3.8:
resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==}
engines: {node: '>=10'}
@@ -3036,6 +3279,10 @@ packages:
'@types/react':
optional: true
+ react@18.3.1:
+ resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
+ engines: {node: '>=0.10.0'}
+
react@19.2.3:
resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==}
engines: {node: '>=0.10.0'}
@@ -3121,12 +3368,19 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ scheduler@0.23.2:
+ resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
+
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
scroll-into-view-if-needed@3.1.0:
resolution: {integrity: sha512-49oNpRjWRvnU8NyGVmUaYG4jtTkNonFZI86MmGRDqBphEK2EXT9gdEUoQPZhuBM8yWHxCWbobltqYO5M4XrUvQ==}
+ semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+
semver@7.7.3:
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
engines: {node: '>=10'}
@@ -3548,6 +3802,9 @@ packages:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'}
+ yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+
yargs-parser@21.1.1:
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
engines: {node: '>=12'}
@@ -3591,16 +3848,115 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/trace-mapping': 0.3.31
+ '@babel/code-frame@7.28.6':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/compat-data@7.28.6': {}
+
+ '@babel/core@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/generator': 7.28.6
+ '@babel/helper-compilation-targets': 7.28.6
+ '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.6)
+ '@babel/helpers': 7.28.6
+ '@babel/parser': 7.28.6
+ '@babel/template': 7.28.6
+ '@babel/traverse': 7.28.6
+ '@babel/types': 7.28.6
+ '@jridgewell/remapping': 2.3.5
+ convert-source-map: 2.0.0
+ debug: 4.4.3
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/generator@7.28.6':
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ '@jridgewell/gen-mapping': 0.3.13
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-compilation-targets@7.28.6':
+ dependencies:
+ '@babel/compat-data': 7.28.6
+ '@babel/helper-validator-option': 7.27.1
+ browserslist: 4.28.1
+ lru-cache: 5.1.1
+ semver: 6.3.1
+
+ '@babel/helper-globals@7.28.0': {}
+
+ '@babel/helper-module-imports@7.28.6':
+ dependencies:
+ '@babel/traverse': 7.28.6
+ '@babel/types': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.6)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-module-imports': 7.28.6
+ '@babel/helper-validator-identifier': 7.28.5
+ '@babel/traverse': 7.28.6
+ transitivePeerDependencies:
+ - supports-color
+
+ '@babel/helper-plugin-utils@7.28.6': {}
+
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.28.5': {}
+ '@babel/helper-validator-option@7.27.1': {}
+
+ '@babel/helpers@7.28.6':
+ dependencies:
+ '@babel/template': 7.28.6
+ '@babel/types': 7.28.6
+
'@babel/parser@7.28.6':
dependencies:
'@babel/types': 7.28.6
+ '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.6)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+
+ '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.6)':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/helper-plugin-utils': 7.28.6
+
'@babel/runtime@7.28.6': {}
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+
+ '@babel/traverse@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.28.6
+ '@babel/generator': 7.28.6
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.28.6
+ '@babel/template': 7.28.6
+ '@babel/types': 7.28.6
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/types@7.28.6':
dependencies:
'@babel/helper-string-parser': 7.27.1
@@ -4585,6 +4941,8 @@ snapshots:
'@radix-ui/rect@1.1.1': {}
+ '@rolldown/pluginutils@1.0.0-beta.27': {}
+
'@rollup/rollup-android-arm-eabi@4.55.2':
optional: true
@@ -4797,6 +5155,27 @@ snapshots:
'@tsconfig/node16@1.0.4': {}
+ '@types/babel__core@7.20.5':
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+ '@types/babel__generator': 7.27.0
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.28.0
+
+ '@types/babel__generator@7.27.0':
+ dependencies:
+ '@babel/types': 7.28.6
+
+ '@types/babel__template@7.4.4':
+ dependencies:
+ '@babel/parser': 7.28.6
+ '@babel/types': 7.28.6
+
+ '@types/babel__traverse@7.28.0':
+ dependencies:
+ '@babel/types': 7.28.6
+
'@types/debug@4.1.12':
dependencies:
'@types/ms': 2.1.0
@@ -4827,10 +5206,21 @@ snapshots:
dependencies:
undici-types: 6.21.0
+ '@types/prop-types@15.7.15': {}
+
+ '@types/react-dom@18.3.7(@types/react@18.3.27)':
+ dependencies:
+ '@types/react': 18.3.27
+
'@types/react-dom@19.2.3(@types/react@19.2.8)':
dependencies:
'@types/react': 19.2.8
+ '@types/react@18.3.27':
+ dependencies:
+ '@types/prop-types': 15.7.15
+ csstype: 3.2.3
+
'@types/react@19.2.8':
dependencies:
csstype: 3.2.3
@@ -4843,6 +5233,18 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
+ '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@20.19.30)(lightningcss@1.30.2))':
+ dependencies:
+ '@babel/core': 7.28.6
+ '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.6)
+ '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.6)
+ '@rolldown/pluginutils': 1.0.0-beta.27
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.17.0
+ vite: 5.4.21(@types/node@20.19.30)(lightningcss@1.30.2)
+ transitivePeerDependencies:
+ - supports-color
+
'@vitest/coverage-v8@2.1.9(vitest@2.1.9(@types/node@20.19.30)(lightningcss@1.30.2)(msw@2.12.7(@types/node@20.19.30)(typescript@5.9.3)))':
dependencies:
'@ampproject/remapping': 2.3.0
@@ -5086,6 +5488,8 @@ snapshots:
confbox@0.1.8: {}
+ convert-source-map@2.0.0: {}
+
cookie@1.1.1: {}
create-require@1.1.1: {}
@@ -5417,6 +5821,8 @@ snapshots:
transitivePeerDependencies:
- '@types/react-dom'
+ gensync@1.0.0-beta.2: {}
+
get-caller-file@2.0.5: {}
get-func-name@2.0.2: {}
@@ -5608,6 +6014,8 @@ snapshots:
jiti@2.6.1: {}
+ js-tokens@4.0.0: {}
+
js-tokens@9.0.1: {}
js-yaml@3.14.2:
@@ -5619,6 +6027,10 @@ snapshots:
dependencies:
argparse: 2.0.1
+ jsesc@3.1.0: {}
+
+ json5@2.2.3: {}
+
jsonfile@4.0.0:
optionalDependencies:
graceful-fs: 4.2.11
@@ -5685,6 +6097,10 @@ snapshots:
longest-streak@3.1.0: {}
+ loose-envify@1.4.0:
+ dependencies:
+ js-tokens: 4.0.0
+
loupe@2.3.7:
dependencies:
get-func-name: 2.0.2
@@ -5693,6 +6109,14 @@ snapshots:
lru-cache@10.4.3: {}
+ lru-cache@5.1.1:
+ dependencies:
+ yallist: 3.1.1
+
+ lucide-react@0.562.0(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+
lucide-react@0.562.0(react@19.2.3):
dependencies:
react: 19.2.3
@@ -6369,6 +6793,12 @@ snapshots:
queue-microtask@1.2.3: {}
+ react-dom@18.3.1(react@18.3.1):
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.3.1
+ scheduler: 0.23.2
+
react-dom@19.2.3(react@19.2.3):
dependencies:
react: 19.2.3
@@ -6381,6 +6811,8 @@ snapshots:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
+ react-refresh@0.17.0: {}
+
react-remove-scroll-bar@2.3.8(@types/react@19.2.8)(react@19.2.3):
dependencies:
react: 19.2.3
@@ -6408,6 +6840,10 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.8
+ react@18.3.1:
+ dependencies:
+ loose-envify: 1.4.0
+
react@19.2.3: {}
read-yaml-file@1.1.0:
@@ -6563,12 +6999,18 @@ snapshots:
safer-buffer@2.1.2: {}
+ scheduler@0.23.2:
+ dependencies:
+ loose-envify: 1.4.0
+
scheduler@0.27.0: {}
scroll-into-view-if-needed@3.1.0:
dependencies:
compute-scroll-into-view: 3.1.1
+ semver@6.3.1: {}
+
semver@7.7.3: {}
server-only@0.0.1: {}
@@ -7018,6 +7460,8 @@ snapshots:
y18n@5.0.8: {}
+ yallist@3.1.1: {}
+
yargs-parser@21.1.1: {}
yargs@17.7.2:
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index f7b1a786a..78b146d8a 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -2,7 +2,9 @@ packages:
- packages/*
- apps/*
- examples/*
+ - examples/ui/*
onlyBuiltDependencies:
- esbuild
+ - msw
- sharp