diff --git a/ROADMAP.md b/ROADMAP.md index 3d456a89da..164659b8d9 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -184,6 +184,20 @@ All 4 phases complete across 5 designers (Page, View, DataModel, Process, Report - [ ] Column reorder/resize persistence for grid - [ ] Drag-to-reschedule calendar events (move between dates) +#### P1.4 View Tab Management UX (Airtable/Salesforce-style) +- [x] `ViewTabBarConfig` type in `@object-ui/types` — configures tab bar UX (showAddButton, contextMenu, overflow, indicators) +- [x] `ViewTabBar` reusable component in `plugin-view` — extracted from console inline tabs +- [x] Inline "+" Add View button on tab bar (configurable via `showAddButton`) +- [x] Right-click context menu on view tabs: Rename, Duplicate, Delete, Set as Default, Share +- [x] Tab overflow → "More" dropdown when `maxVisibleTabs` exceeded +- [x] Filter/sort indicator badge (dot) on view tabs (`showIndicators`) +- [x] "Save as View" indicator + button when user has unsaved filter/sort changes +- [x] Double-click inline rename on view tabs (`inlineRename`) +- [ ] Drag-reorder view tabs (`reorderable` — needs `@dnd-kit/sortable`) +- [ ] Pin/favorite views +- [ ] View type quick-switch palette (Notion-style layout menu) +- [ ] Personal vs. shared views grouping in tab bar + ### P2. Component & Plugin Excellence 🧩 (Completed Foundation) **Goal:** Every component is polished, consistent, well-tested, and delightful to use. diff --git a/apps/console/src/components/ObjectView.tsx b/apps/console/src/components/ObjectView.tsx index 596a167fdd..0c76fb007a 100644 --- a/apps/console/src/components/ObjectView.tsx +++ b/apps/console/src/components/ObjectView.tsx @@ -14,7 +14,8 @@ import { useParams, useSearchParams, useNavigate } from 'react-router-dom'; import { ObjectChart } from '@object-ui/plugin-charts'; import { ListView } from '@object-ui/plugin-list'; import { DetailView } from '@object-ui/plugin-detail'; -import { ObjectView as PluginObjectView } from '@object-ui/plugin-view'; +import { ObjectView as PluginObjectView, ViewTabBar } from '@object-ui/plugin-view'; +import type { ViewTabItem } from '@object-ui/plugin-view'; // Import plugins for side-effects (registration) import '@object-ui/plugin-grid'; import '@object-ui/plugin-kanban'; @@ -358,30 +359,39 @@ export function ObjectView({ dataSource, objects, onEdit, onRowClick }: any) { - {/* View Tabs — Airtable-style named-view switcher */} + {/* View Tabs — Airtable-style named-view switcher with management UX */} {views.length > 1 && (
-
- {views.map((view: { id: string; label: string; type: string }) => { - const isActive = view.id === activeViewId; - const ViewIcon = VIEW_TYPE_ICONS[view.type] || TableIcon; - return ( - - ); - })} -
+ ({ + id: view.id, + label: view.label, + type: view.type, + hasActiveFilters: Array.isArray((view as any).filter) && (view as any).filter.length > 0, + hasActiveSort: Array.isArray((view as any).sort) && (view as any).sort.length > 0, + } as ViewTabItem))} + activeViewId={activeViewId} + onViewChange={handleViewChange} + viewTypeIcons={VIEW_TYPE_ICONS} + config={objectDef.viewTabBar} + onAddView={() => navigate(viewId ? '../../views/new' : 'views/new')} + onRenameView={(id, newName) => { + // Rename is wired for future backend integration + console.info('[ViewTabBar] Rename view:', id, newName); + }} + onDuplicateView={(id) => { + console.info('[ViewTabBar] Duplicate view:', id); + }} + onDeleteView={(id) => { + console.info('[ViewTabBar] Delete view:', id); + }} + onSetDefaultView={(id) => { + console.info('[ViewTabBar] Set default view:', id); + }} + onShareView={(id) => { + console.info('[ViewTabBar] Share view:', id); + }} + />
)} diff --git a/packages/plugin-view/src/ViewTabBar.tsx b/packages/plugin-view/src/ViewTabBar.tsx new file mode 100644 index 0000000000..ce2ffc6e56 --- /dev/null +++ b/packages/plugin-view/src/ViewTabBar.tsx @@ -0,0 +1,376 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/** + * ViewTabBar Component + * + * A reusable view tab bar with: + * - Inline "+" Add View button + * - Right-click context menu (rename, duplicate, delete, set as default, share) + * - Overflow → "More" dropdown when maxVisibleTabs exceeded + * - Filter/sort indicator badges on tabs + * - "Save as View" button when user filters differ from saved state + * - Double-click to rename view tab inline + */ + +import React, { useState, useRef, useEffect, useCallback, type ComponentType } from 'react'; +import { + cn, + ContextMenu, + ContextMenuTrigger, + ContextMenuContent, + ContextMenuItem, + ContextMenuSeparator, + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + Input, + Button, + Tooltip, + TooltipTrigger, + TooltipContent, + TooltipProvider, +} from '@object-ui/components'; +import { + Plus, + MoreHorizontal, + Pencil, + Copy, + Trash2, + Star, + Share2, + Save, + Table as TableIcon, +} from 'lucide-react'; +import type { ViewTabBarConfig } from '@object-ui/types'; + +/** A single view definition for the tab bar */ +export interface ViewTabItem { + /** Unique view identifier */ + id: string; + /** Display label */ + label: string; + /** View type (grid, kanban, calendar, etc.) */ + type: string; + /** Whether this view has active filters */ + hasActiveFilters?: boolean; + /** Whether this view has active sort */ + hasActiveSort?: boolean; + /** Whether this is the default view */ + isDefault?: boolean; +} + +export interface ViewTabBarProps { + /** Views to render as tabs */ + views: ViewTabItem[]; + /** Currently active view ID */ + activeViewId: string; + /** Callback when a view tab is clicked */ + onViewChange: (viewId: string) => void; + /** Icon map: view type → React component */ + viewTypeIcons?: Record>; + /** Configuration for the tab bar UX */ + config?: ViewTabBarConfig; + + // --- Action callbacks --- + /** Called when "+" button is clicked to add a new view */ + onAddView?: () => void; + /** Called when a view is renamed via context menu or double-click */ + onRenameView?: (viewId: string, newName: string) => void; + /** Called when a view is duplicated */ + onDuplicateView?: (viewId: string) => void; + /** Called when a view is deleted */ + onDeleteView?: (viewId: string) => void; + /** Called when a view is set as default */ + onSetDefaultView?: (viewId: string) => void; + /** Called when a view is shared */ + onShareView?: (viewId: string) => void; + /** Called when "Save as View" is clicked */ + onSaveAsView?: () => void; + + /** Whether user has unsaved filter/sort changes (shows "Save as View" indicator) */ + hasUnsavedChanges?: boolean; + /** Called when user clicks "Reset" to discard changes */ + onResetChanges?: () => void; + /** Additional CSS class */ + className?: string; +} + +/** + * ViewTabBar — Airtable/Salesforce-style view tab bar with management UX. + */ +export const ViewTabBar: React.FC = ({ + views, + activeViewId, + onViewChange, + viewTypeIcons = {}, + config = {}, + onAddView, + onRenameView, + onDuplicateView, + onDeleteView, + onSetDefaultView, + onShareView, + onSaveAsView, + hasUnsavedChanges = false, + onResetChanges, + className, +}) => { + const { + showAddButton = true, + inlineRename = true, + contextMenu: enableContextMenu = true, + maxVisibleTabs = 6, + showIndicators = true, + showSaveAsView = true, + } = config; + + // --- Inline rename state --- + const [renamingViewId, setRenamingViewId] = useState(null); + const [renameValue, setRenameValue] = useState(''); + const renameInputRef = useRef(null); + + useEffect(() => { + if (renamingViewId && renameInputRef.current) { + renameInputRef.current.focus(); + renameInputRef.current.select(); + } + }, [renamingViewId]); + + const startRename = useCallback((viewId: string) => { + if (!inlineRename || !onRenameView) return; + const view = views.find(v => v.id === viewId); + if (!view) return; + setRenamingViewId(viewId); + setRenameValue(view.label); + }, [inlineRename, onRenameView, views]); + + const commitRename = useCallback(() => { + if (renamingViewId && renameValue.trim() && onRenameView) { + onRenameView(renamingViewId, renameValue.trim()); + } + setRenamingViewId(null); + setRenameValue(''); + }, [renamingViewId, renameValue, onRenameView]); + + const cancelRename = useCallback(() => { + setRenamingViewId(null); + setRenameValue(''); + }, []); + + // --- Overflow --- + const visibleViews = views.slice(0, maxVisibleTabs); + const overflowViews = views.slice(maxVisibleTabs); + + const DefaultIcon = TableIcon; + + // --- Render a single tab --- + const renderTab = (view: ViewTabItem) => { + const isActive = view.id === activeViewId; + const ViewIcon = viewTypeIcons[view.type] || DefaultIcon; + const isRenaming = renamingViewId === view.id; + const hasIndicator = showIndicators && (view.hasActiveFilters || view.hasActiveSort); + + const tabContent = ( + + ); + + // Wrap with context menu if enabled + if (enableContextMenu && !isRenaming) { + return ( + + + {tabContent} + + + {onRenameView && ( + startRename(view.id)} + > + Rename + + )} + {onDuplicateView && ( + onDuplicateView(view.id)} + > + Duplicate View + + )} + {onShareView && ( + onShareView(view.id)} + > + Share View + + )} + {onSetDefaultView && ( + onSetDefaultView(view.id)} + > + Set as Default + + )} + {onDeleteView && ( + <> + + onDeleteView(view.id)} + className="text-destructive focus:text-destructive" + > + Delete View + + + )} + + + ); + } + + return {tabContent}; + }; + + return ( + +
+ {/* Visible tabs */} + {visibleViews.map(renderTab)} + + {/* Overflow "More" dropdown */} + {overflowViews.length > 0 && ( + + + + + + {overflowViews.map((view) => { + const ViewIcon = viewTypeIcons[view.type] || DefaultIcon; + return ( + onViewChange(view.id)} + > + + {view.label} + {showIndicators && (view.hasActiveFilters || view.hasActiveSort) && ( + + )} + + ); + })} + + + )} + + {/* Inline "+" Add View button */} + {showAddButton && onAddView && ( + + + + + Add View + + )} + + {/* "Save as View" indicator */} + {showSaveAsView && hasUnsavedChanges && ( +
+ + Unsaved changes + {onSaveAsView && ( + + )} + {onResetChanges && ( + + )} +
+ )} +
+
+ ); +}; diff --git a/packages/plugin-view/src/__tests__/ViewTabBar.test.tsx b/packages/plugin-view/src/__tests__/ViewTabBar.test.tsx new file mode 100644 index 0000000000..9bf17a4671 --- /dev/null +++ b/packages/plugin-view/src/__tests__/ViewTabBar.test.tsx @@ -0,0 +1,363 @@ +/** + * ObjectUI + * Copyright (c) 2024-present ObjectStack Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { render, screen, fireEvent } from '@testing-library/react'; +import { ViewTabBar, type ViewTabItem, type ViewTabBarProps } from '../ViewTabBar'; + +const createViews = (count: number): ViewTabItem[] => + Array.from({ length: count }, (_, i) => ({ + id: `view-${i}`, + label: `View ${i}`, + type: i === 0 ? 'grid' : i === 1 ? 'kanban' : 'calendar', + })); + +const defaultProps: ViewTabBarProps = { + views: createViews(3), + activeViewId: 'view-0', + onViewChange: vi.fn(), +}; + +describe('ViewTabBar', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + // ============================ + // Basic Rendering + // ============================ + describe('Basic Rendering', () => { + it('should render all view tabs', () => { + render(); + expect(screen.getByTestId('view-tab-bar')).toBeDefined(); + expect(screen.getByTestId('view-tab-view-0')).toBeDefined(); + expect(screen.getByTestId('view-tab-view-1')).toBeDefined(); + expect(screen.getByTestId('view-tab-view-2')).toBeDefined(); + }); + + it('should render view labels', () => { + render(); + expect(screen.getByText('View 0')).toBeDefined(); + expect(screen.getByText('View 1')).toBeDefined(); + expect(screen.getByText('View 2')).toBeDefined(); + }); + + it('should highlight active tab', () => { + render(); + const activeTab = screen.getByTestId('view-tab-view-0'); + expect(activeTab.className).toContain('border-primary'); + }); + + it('should call onViewChange when tab is clicked', () => { + const onViewChange = vi.fn(); + render(); + fireEvent.click(screen.getByTestId('view-tab-view-1')); + expect(onViewChange).toHaveBeenCalledWith('view-1'); + }); + }); + + // ============================ + // Inline "+" Add View Button + // ============================ + describe('Add View Button', () => { + it('should render "+" button when onAddView is provided', () => { + const onAddView = vi.fn(); + render(); + expect(screen.getByTestId('view-tab-add')).toBeDefined(); + }); + + it('should not render "+" button when showAddButton is false', () => { + render( + + ); + expect(screen.queryByTestId('view-tab-add')).toBeNull(); + }); + + it('should not render "+" button when onAddView is not provided', () => { + render(); + expect(screen.queryByTestId('view-tab-add')).toBeNull(); + }); + + it('should call onAddView when "+" button is clicked', () => { + const onAddView = vi.fn(); + render(); + fireEvent.click(screen.getByTestId('view-tab-add')); + expect(onAddView).toHaveBeenCalledOnce(); + }); + }); + + // ============================ + // Tab Overflow ("More" Dropdown) + // ============================ + describe('Tab Overflow', () => { + it('should show overflow button when views exceed maxVisibleTabs', () => { + render( + + ); + expect(screen.getByTestId('view-tab-overflow')).toBeDefined(); + expect(screen.getByText('2 more')).toBeDefined(); + }); + + it('should not show overflow when all views fit', () => { + render( + + ); + expect(screen.queryByTestId('view-tab-overflow')).toBeNull(); + }); + + it('should only render maxVisibleTabs tabs directly', () => { + render( + + ); + // First 4 should be visible as direct tabs + expect(screen.getByTestId('view-tab-view-0')).toBeDefined(); + expect(screen.getByTestId('view-tab-view-3')).toBeDefined(); + // 5th should NOT be a direct tab + expect(screen.queryByTestId('view-tab-view-4')).toBeNull(); + // But should appear in overflow + expect(screen.getByText('4 more')).toBeDefined(); + }); + }); + + // ============================ + // Filter/Sort Indicator Badges + // ============================ + describe('Filter/Sort Indicators', () => { + it('should show indicator dot when view has active filters', () => { + const views: ViewTabItem[] = [ + { id: 'v1', label: 'Active', type: 'grid', hasActiveFilters: true }, + { id: 'v2', label: 'All', type: 'grid' }, + ]; + render(); + expect(screen.getByTestId('view-tab-indicator-v1')).toBeDefined(); + expect(screen.queryByTestId('view-tab-indicator-v2')).toBeNull(); + }); + + it('should show indicator dot when view has active sort', () => { + const views: ViewTabItem[] = [ + { id: 'v1', label: 'Sorted', type: 'grid', hasActiveSort: true }, + { id: 'v2', label: 'All', type: 'grid' }, + ]; + render(); + expect(screen.getByTestId('view-tab-indicator-v1')).toBeDefined(); + }); + + it('should not show indicators when showIndicators is false', () => { + const views: ViewTabItem[] = [ + { id: 'v1', label: 'Active', type: 'grid', hasActiveFilters: true }, + ]; + render( + + ); + expect(screen.queryByTestId('view-tab-indicator-v1')).toBeNull(); + }); + }); + + // ============================ + // Context Menu + // ============================ + describe('Context Menu', () => { + it('should show context menu items when right-clicking a tab', () => { + render( + + ); + // Context menu is rendered but hidden by default via Radix + // Verify the tab is rendered as a trigger + const tab = screen.getByTestId('view-tab-view-0'); + expect(tab).toBeDefined(); + }); + + it('should not wrap with context menu when contextMenu is false', () => { + const { container } = render( + + ); + // When contextMenu is disabled, no context menu wrapper + expect(container.querySelector('[data-testid="context-menu-rename-view-0"]')).toBeNull(); + }); + }); + + // ============================ + // Save as View + // ============================ + describe('Save as View', () => { + it('should show save-as-view indicator when hasUnsavedChanges is true', () => { + render( + + ); + expect(screen.getByTestId('view-tab-save-as')).toBeDefined(); + }); + + it('should not show save-as-view indicator when hasUnsavedChanges is false', () => { + render( + + ); + expect(screen.queryByTestId('view-tab-save-as')).toBeNull(); + }); + + it('should call onSaveAsView when Save button is clicked', () => { + const onSaveAsView = vi.fn(); + render( + + ); + fireEvent.click(screen.getByTestId('view-tab-save-as-btn')); + expect(onSaveAsView).toHaveBeenCalledOnce(); + }); + + it('should call onResetChanges when Reset button is clicked', () => { + const onResetChanges = vi.fn(); + render( + + ); + fireEvent.click(screen.getByTestId('view-tab-reset-btn')); + expect(onResetChanges).toHaveBeenCalledOnce(); + }); + + it('should not show save-as-view when showSaveAsView is false', () => { + render( + + ); + expect(screen.queryByTestId('view-tab-save-as')).toBeNull(); + }); + }); + + // ============================ + // Inline Rename + // ============================ + describe('Inline Rename', () => { + it('should enter rename mode on double-click', () => { + render( + + ); + const tab = screen.getByTestId('view-tab-view-0'); + fireEvent.doubleClick(tab); + expect(screen.getByTestId('view-tab-rename-input-view-0')).toBeDefined(); + }); + + it('should call onRenameView on Enter key', () => { + const onRenameView = vi.fn(); + render( + + ); + const tab = screen.getByTestId('view-tab-view-0'); + fireEvent.doubleClick(tab); + + const input = screen.getByTestId('view-tab-rename-input-view-0'); + fireEvent.change(input, { target: { value: 'New Name' } }); + fireEvent.keyDown(input, { key: 'Enter' }); + + expect(onRenameView).toHaveBeenCalledWith('view-0', 'New Name'); + }); + + it('should cancel rename on Escape key', () => { + render( + + ); + const tab = screen.getByTestId('view-tab-view-0'); + fireEvent.doubleClick(tab); + + const input = screen.getByTestId('view-tab-rename-input-view-0'); + fireEvent.keyDown(input, { key: 'Escape' }); + + // Should exit rename mode + expect(screen.queryByTestId('view-tab-rename-input-view-0')).toBeNull(); + }); + + it('should not enter rename mode when inlineRename is false', () => { + render( + + ); + const tab = screen.getByTestId('view-tab-view-0'); + fireEvent.doubleClick(tab); + expect(screen.queryByTestId('view-tab-rename-input-view-0')).toBeNull(); + }); + }); + + // ============================ + // Default View Indicator + // ============================ + describe('Default View Indicator', () => { + it('should show star icon for default view', () => { + const views: ViewTabItem[] = [ + { id: 'v1', label: 'Default', type: 'grid', isDefault: true }, + { id: 'v2', label: 'Other', type: 'grid' }, + ]; + const { container } = render(); + // The default view tab should contain a star icon + const defaultTab = screen.getByTestId('view-tab-v1'); + // Star icon rendered via lucide-react SVG + expect(defaultTab.querySelector('svg.text-amber-500')).toBeDefined(); + }); + }); +}); diff --git a/packages/plugin-view/src/index.tsx b/packages/plugin-view/src/index.tsx index da899e49b3..ba166d773a 100644 --- a/packages/plugin-view/src/index.tsx +++ b/packages/plugin-view/src/index.tsx @@ -15,8 +15,10 @@ import { SortUI } from './SortUI'; import { SharedViewLink } from './SharedViewLink'; export { ObjectView, ViewSwitcher, FilterUI, SortUI, SharedViewLink }; +export { ViewTabBar } from './ViewTabBar'; export type { ObjectViewProps } from './ObjectView'; export type { ViewSwitcherProps } from './ViewSwitcher'; +export type { ViewTabBarProps, ViewTabItem } from './ViewTabBar'; export type { FilterUIProps } from './FilterUI'; export type { SortUIProps } from './SortUI'; export type { SharedViewLinkProps } from './SharedViewLink'; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index f39e4ed9b4..0bb103832f 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -302,6 +302,7 @@ export type { ObjectViewSchema, NamedListView, ViewNavigationConfig, + ViewTabBarConfig, ObjectQLComponentSchema, } from './objectql'; diff --git a/packages/types/src/objectql.ts b/packages/types/src/objectql.ts index 1f5b468d86..06b73a9eb8 100644 --- a/packages/types/src/objectql.ts +++ b/packages/types/src/objectql.ts @@ -896,6 +896,32 @@ export interface ObjectViewSchema extends BaseSchema { * Custom CSS class */ className?: string; + + /** + * View tab bar UX configuration (inline add, context menu, overflow, indicators). + */ + viewTabBar?: ViewTabBarConfig; +} + +/** + * View Tab Bar Configuration + * Controls the UX of the view tab bar (inline add, context menu, overflow, indicators). + */ +export interface ViewTabBarConfig { + /** Show inline "+" button to create new views @default true */ + showAddButton?: boolean; + /** Allow inline renaming by double-clicking tab @default true */ + inlineRename?: boolean; + /** Show context menu on right-click @default true */ + contextMenu?: boolean; + /** Allow drag-reorder of view tabs @default false */ + reorderable?: boolean; + /** Max visible tabs before overflow → "More" dropdown @default 6 */ + maxVisibleTabs?: number; + /** Show filter/sort indicator badges on tabs @default true */ + showIndicators?: boolean; + /** Show "Save as View" when filters differ from saved @default true */ + showSaveAsView?: boolean; } /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96305f4217..2c6c0762ea 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -573,273 +573,6 @@ importers: specifier: 19.2.4 version: 19.2.4(react@19.2.4) - examples/hotcrm/packages/ai: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - axios: - specifier: ^1.13.5 - version: 1.13.5 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/analytics: - dependencies: - '@hotcrm/ai': - specifier: workspace:* - version: link:../ai - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/community: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/core: - dependencies: - '@objectstack/spec': - specifier: ^3.0.2 - version: 3.0.7 - zod: - specifier: ^4.3.6 - version: 4.3.6 - - examples/hotcrm/packages/crm: - dependencies: - '@hotcrm/ai': - specifier: workspace:* - version: link:../ai - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/education: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/finance: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/financial-services: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/healthcare: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/hr: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/integration: - dependencies: - '@hotcrm/ai': - specifier: workspace:* - version: link:../ai - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/marketing: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/products: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/real-estate: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - zod: - specifier: ^4.3.6 - version: 4.3.6 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/server: - dependencies: - '@objectstack/cli': - specifier: ^3.0.2 - version: 3.0.7(@objectstack/core@3.0.7(pino@10.3.1))(esbuild@0.27.3)(pino@10.3.1) - '@objectstack/core': - specifier: ^3.0.2 - version: 3.0.7(pino@10.3.1) - '@objectstack/metadata': - specifier: ^3.0.2 - version: 3.0.7(pino@10.3.1) - '@objectstack/objectql': - specifier: ^3.0.2 - version: 3.0.7(pino@10.3.1) - '@objectstack/plugin-hono-server': - specifier: ^3.0.2 - version: 3.0.7(pino@10.3.1) - '@objectstack/runtime': - specifier: ^3.0.2 - version: 3.0.7(pino@10.3.1) - '@objectstack/spec': - specifier: ^3.0.2 - version: 3.0.7 - pino: - specifier: ^10.3.1 - version: 10.3.1 - devDependencies: - '@types/js-yaml': - specifier: ^4.0.9 - version: 4.0.9 - ts-node: - specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.15.11)(@types/node@25.2.3)(typescript@5.9.3) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - - examples/hotcrm/packages/support: - dependencies: - '@objectstack/spec': - specifier: ^3.0.3 - version: 3.0.7 - devDependencies: - '@objectstack/cli': - specifier: ^3.0.3 - version: 3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0) - typescript: - specifier: ^5.9.3 - version: 5.9.3 - examples/kitchen-sink: dependencies: '@objectstack/spec': @@ -4287,9 +4020,6 @@ packages: resolution: {integrity: sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA==} engines: {node: '>= 20.0.0'} - '@pinojs/redact@0.4.0': - resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==} - '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -9920,13 +9650,6 @@ packages: pino-std-serializers@6.2.2: resolution: {integrity: sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA==} - pino-std-serializers@7.1.0: - resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==} - - pino@10.3.1: - resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} - hasBin: true - pino@8.21.0: resolution: {integrity: sha512-ip4qdzjkAyDDZklUaZkcRFb2iA118H9SgRh8yzTkSQK8HilsOJF7rSY8HoW5+I0M46AZgX/pxbprf2vvzQCE0Q==} hasBin: true @@ -10060,9 +9783,6 @@ packages: process-warning@3.0.0: resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==} - process-warning@5.0.0: - resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - process@0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} @@ -11022,10 +10742,6 @@ packages: thread-stream@2.7.0: resolution: {integrity: sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==} - thread-stream@4.0.0: - resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} - engines: {node: '>=20'} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -12499,6 +12215,7 @@ snapshots: '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 + optional: true '@csstools/color-helpers@6.0.1': {} @@ -13311,6 +13028,7 @@ snapshots: dependencies: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 + optional: true '@manypkg/find-root@1.1.0': dependencies: @@ -13590,24 +13308,6 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 - '@objectstack/cli@3.0.7(@objectstack/core@3.0.7(pino@10.3.1))(esbuild@0.27.3)(pino@10.3.1)': - dependencies: - '@objectstack/core': 3.0.7(pino@10.3.1) - '@objectstack/driver-memory': 3.0.7(pino@10.3.1) - '@objectstack/objectql': 3.0.7(pino@10.3.1) - '@objectstack/plugin-hono-server': 3.0.7(pino@10.3.1) - '@objectstack/rest': 3.0.7(pino@10.3.1) - '@objectstack/runtime': 3.0.7(pino@10.3.1) - '@objectstack/spec': 3.0.7 - '@oclif/core': 4.8.0 - bundle-require: 5.1.0(esbuild@0.27.3) - chalk: 5.6.2 - tsx: 4.21.0 - zod: 4.3.6 - transitivePeerDependencies: - - esbuild - - pino - '@objectstack/cli@3.0.7(@objectstack/core@3.0.7(pino@8.21.0))(esbuild@0.27.3)(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13642,14 +13342,6 @@ snapshots: transitivePeerDependencies: - pino - '@objectstack/core@3.0.7(pino@10.3.1)': - dependencies: - '@objectstack/spec': 3.0.7 - pino-pretty: 13.1.3 - zod: 4.3.6 - optionalDependencies: - pino: 10.3.1 - '@objectstack/core@3.0.7(pino@8.21.0)': dependencies: '@objectstack/spec': 3.0.7 @@ -13658,14 +13350,6 @@ snapshots: optionalDependencies: pino: 8.21.0 - '@objectstack/driver-memory@3.0.7(pino@10.3.1)': - dependencies: - '@objectstack/core': 3.0.7(pino@10.3.1) - '@objectstack/spec': 3.0.7 - mingo: 7.2.0 - transitivePeerDependencies: - - pino - '@objectstack/driver-memory@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13674,18 +13358,6 @@ snapshots: transitivePeerDependencies: - pino - '@objectstack/metadata@3.0.7(pino@10.3.1)': - dependencies: - '@objectstack/core': 3.0.7(pino@10.3.1) - '@objectstack/spec': 3.0.7 - '@objectstack/types': 3.0.7 - chokidar: 5.0.0 - glob: 13.0.5 - js-yaml: 4.1.1 - zod: 4.3.6 - transitivePeerDependencies: - - pino - '@objectstack/metadata@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13698,14 +13370,6 @@ snapshots: transitivePeerDependencies: - pino - '@objectstack/objectql@3.0.7(pino@10.3.1)': - dependencies: - '@objectstack/core': 3.0.7(pino@10.3.1) - '@objectstack/spec': 3.0.7 - '@objectstack/types': 3.0.7 - transitivePeerDependencies: - - pino - '@objectstack/objectql@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13741,15 +13405,6 @@ snapshots: - vitest - vue - '@objectstack/plugin-hono-server@3.0.7(pino@10.3.1)': - dependencies: - '@hono/node-server': 1.19.9(hono@4.11.9) - '@objectstack/core': 3.0.7(pino@10.3.1) - '@objectstack/spec': 3.0.7 - hono: 4.11.9 - transitivePeerDependencies: - - pino - '@objectstack/plugin-hono-server@3.0.7(pino@8.21.0)': dependencies: '@hono/node-server': 1.19.9(hono@4.11.9) @@ -13772,14 +13427,6 @@ snapshots: - pino - typescript - '@objectstack/rest@3.0.7(pino@10.3.1)': - dependencies: - '@objectstack/core': 3.0.7(pino@10.3.1) - '@objectstack/spec': 3.0.7 - zod: 4.3.6 - transitivePeerDependencies: - - pino - '@objectstack/rest@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13788,16 +13435,6 @@ snapshots: transitivePeerDependencies: - pino - '@objectstack/runtime@3.0.7(pino@10.3.1)': - dependencies: - '@objectstack/core': 3.0.7(pino@10.3.1) - '@objectstack/rest': 3.0.7(pino@10.3.1) - '@objectstack/spec': 3.0.7 - '@objectstack/types': 3.0.7 - zod: 4.3.6 - transitivePeerDependencies: - - pino - '@objectstack/runtime@3.0.7(pino@8.21.0)': dependencies: '@objectstack/core': 3.0.7(pino@8.21.0) @@ -13887,8 +13524,6 @@ snapshots: '@orama/orama@3.1.18': {} - '@pinojs/redact@0.4.0': {} - '@pkgjs/parseargs@0.11.0': optional: true @@ -15411,13 +15046,17 @@ snapshots: minimatch: 10.2.1 path-browserify: 1.0.1 - '@tsconfig/node10@1.0.12': {} + '@tsconfig/node10@1.0.12': + optional: true - '@tsconfig/node12@1.0.11': {} + '@tsconfig/node12@1.0.11': + optional: true - '@tsconfig/node14@1.0.3': {} + '@tsconfig/node14@1.0.3': + optional: true - '@tsconfig/node16@1.0.4': {} + '@tsconfig/node16@1.0.4': + optional: true '@tybys/wasm-util@0.10.1': dependencies: @@ -16279,7 +15918,8 @@ snapshots: archy@1.0.0: {} - arg@4.1.3: {} + arg@4.1.3: + optional: true argparse@1.0.10: dependencies: @@ -16858,7 +16498,8 @@ snapshots: coverage-v8@0.0.1-security: {} - create-require@1.1.1: {} + create-require@1.1.1: + optional: true cross-spawn@7.0.6: dependencies: @@ -17039,7 +16680,8 @@ snapshots: diff-sequences@29.6.3: {} - diff@4.0.4: {} + diff@4.0.4: + optional: true diff@8.0.3: {} @@ -19321,7 +18963,8 @@ snapshots: dependencies: semver: 7.7.4 - make-error@1.3.6: {} + make-error@1.3.6: + optional: true makeerror@1.0.12: dependencies: @@ -20373,22 +20016,6 @@ snapshots: pino-std-serializers@6.2.2: {} - pino-std-serializers@7.1.0: {} - - pino@10.3.1: - dependencies: - '@pinojs/redact': 0.4.0 - atomic-sleep: 1.0.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 3.0.0 - pino-std-serializers: 7.1.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.1 - thread-stream: 4.0.0 - pino@8.21.0: dependencies: atomic-sleep: 1.0.0 @@ -20525,8 +20152,6 @@ snapshots: process-warning@3.0.0: {} - process-warning@5.0.0: {} - process@0.11.10: {} prompts@2.4.2: @@ -21736,10 +21361,6 @@ snapshots: dependencies: real-require: 0.2.0 - thread-stream@4.0.0: - dependencies: - real-require: 0.2.0 - tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -21829,6 +21450,7 @@ snapshots: yn: 3.1.1 optionalDependencies: '@swc/core': 1.15.11 + optional: true tsconfig-paths@4.2.0: dependencies: @@ -22131,7 +21753,8 @@ snapshots: uuid@9.0.1: {} - v8-compile-cache-lib@3.0.1: {} + v8-compile-cache-lib@3.0.1: + optional: true v8-to-istanbul@9.3.0: dependencies: @@ -22621,7 +22244,8 @@ snapshots: dependencies: buffer-crc32: 0.2.13 - yn@3.1.1: {} + yn@3.1.1: + optional: true yocto-queue@0.1.0: {}