@object-ui/app-shell 6.0.1 → 6.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # @object-ui/app-shell — Changelog
2
2
 
3
+ ## 6.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - d0e63f1: Migrate AI chat history from localStorage to the server-backed
8
+ `ai_conversations` / `ai_messages` REST API. The studio `AiChatPanel`,
9
+ the console `ConsoleFloatingChatbot`, and any other consumer of the new
10
+ `useChatConversation` hook (in `@object-ui/app-shell`) now resolve a
11
+ durable conversation id per signed-in user, hydrate prior messages on
12
+ mount, and rotate the conversation on reset. The previous
13
+ `objectstack:ai-chat-messages` localStorage entries are no longer read
14
+ or written.
15
+ - @object-ui/types@6.0.2
16
+ - @object-ui/core@6.0.2
17
+ - @object-ui/i18n@6.0.2
18
+ - @object-ui/react@6.0.2
19
+ - @object-ui/components@6.0.2
20
+ - @object-ui/fields@6.0.2
21
+ - @object-ui/layout@6.0.2
22
+ - @object-ui/data-objectstack@6.0.2
23
+ - @object-ui/auth@6.0.2
24
+ - @object-ui/permissions@6.0.2
25
+ - @object-ui/collaboration@6.0.2
26
+ - @object-ui/providers@6.0.2
27
+
3
28
  ## 6.0.1
4
29
 
5
30
  ### Patch Changes
@@ -237,7 +237,7 @@ export function AppContent({ extraRoutes, extraRoutesNoApp } = {}) {
237
237
  const expressionUser = user
238
238
  ? { name: user.name, email: user.email, role: user.role ?? 'user' }
239
239
  : { name: 'Anonymous', email: '', role: 'guest' };
240
- return (_jsxs(ExpressionProvider, { user: expressionUser, app: activeApp, data: {}, children: [_jsx(NavigationSyncEffect, {}), _jsxs(ConsoleLayout, { activeAppName: activeApp.name, activeApp: activeApp, onAppChange: handleAppChange, objects: allObjects, connectionState: connectionState, children: [_jsx(CommandPalette, { apps: apps, activeApp: activeApp, objects: allObjects, onAppChange: handleAppChange, dataSource: dataSource }), _jsx(KeyboardShortcutsDialog, {}), _jsx(OnboardingWalkthrough, {}), _jsx(ErrorBoundary, { children: _jsx(Suspense, { fallback: _jsx(LoadingScreen, {}), children: _jsx(RouteFader, { children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Navigate, { to: resolveLandingRoute(activeApp), replace: true }) }), _jsx(Route, { path: ":objectName", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit, externalRefreshKey: refreshKey }) }), _jsx(Route, { path: ":objectName/new", element: _jsx(RecordFormPage, { mode: "create" }) }), _jsx(Route, { path: ":objectName/view/:viewId", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit, externalRefreshKey: refreshKey }) }), _jsx(Route, { path: ":objectName/record/:recordId", element: _jsx(RecordDetailView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit }, refreshKey) }), _jsx(Route, { path: ":objectName/record/:recordId/edit", element: _jsx(RecordFormPage, { mode: "edit" }) }), _jsx(Route, { path: "dashboard/:dashboardName", element: _jsx(DashboardView, { dataSource: dataSource }) }), _jsx(Route, { path: "report/:reportName", element: _jsx(ReportView, { dataSource: dataSource }) }), _jsx(Route, { path: "page/:pageName", element: _jsx(PageView, {}) }), _jsx(Route, { path: "design/page/:pageName", element: _jsx(PageDesignPage, {}) }), _jsx(Route, { path: "design/dashboard/:dashboardName", element: _jsx(DashboardDesignPage, {}) }), _jsx(Route, { path: "search", element: _jsx(SearchResultsPage, {}) }), _jsx(Route, { path: "create-app", element: _jsx(CreateAppPage, {}) }), _jsx(Route, { path: "edit-app/:editAppName", element: _jsx(EditAppPage, {}) }), _jsx(Route, { path: "system/marketplace", element: _jsx(MarketplacePage, {}) }), _jsx(Route, { path: "system/marketplace/installed", element: _jsx(MarketplaceInstalledPage, {}) }), _jsx(Route, { path: "system/marketplace/:packageId", element: _jsx(MarketplacePackagePage, {}) }), extraRoutes, _jsx(Route, { path: ":objectName/:maybeRecordId", element: _jsx(ShorthandRecordRedirect, {}) }), _jsx(Route, { path: "*", element: _jsx(RouteNotFound, {}) })] }) }) }) }), currentObjectDef && (_jsx(ModalForm, { schema: {
240
+ return (_jsxs(ExpressionProvider, { user: expressionUser, app: activeApp, data: {}, children: [_jsx(NavigationSyncEffect, {}), _jsxs(ConsoleLayout, { activeAppName: activeApp.name, activeApp: activeApp, onAppChange: handleAppChange, objects: allObjects, connectionState: connectionState, userId: user?.id, children: [_jsx(CommandPalette, { apps: apps, activeApp: activeApp, objects: allObjects, onAppChange: handleAppChange, dataSource: dataSource }), _jsx(KeyboardShortcutsDialog, {}), _jsx(OnboardingWalkthrough, {}), _jsx(ErrorBoundary, { children: _jsx(Suspense, { fallback: _jsx(LoadingScreen, {}), children: _jsx(RouteFader, { children: _jsxs(Routes, { children: [_jsx(Route, { path: "/", element: _jsx(Navigate, { to: resolveLandingRoute(activeApp), replace: true }) }), _jsx(Route, { path: ":objectName", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit, externalRefreshKey: refreshKey }) }), _jsx(Route, { path: ":objectName/new", element: _jsx(RecordFormPage, { mode: "create" }) }), _jsx(Route, { path: ":objectName/view/:viewId", element: _jsx(ObjectView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit, externalRefreshKey: refreshKey }) }), _jsx(Route, { path: ":objectName/record/:recordId", element: _jsx(RecordDetailView, { dataSource: dataSource, objects: allObjects, onEdit: handleEdit }, refreshKey) }), _jsx(Route, { path: ":objectName/record/:recordId/edit", element: _jsx(RecordFormPage, { mode: "edit" }) }), _jsx(Route, { path: "dashboard/:dashboardName", element: _jsx(DashboardView, { dataSource: dataSource }) }), _jsx(Route, { path: "report/:reportName", element: _jsx(ReportView, { dataSource: dataSource }) }), _jsx(Route, { path: "page/:pageName", element: _jsx(PageView, {}) }), _jsx(Route, { path: "design/page/:pageName", element: _jsx(PageDesignPage, {}) }), _jsx(Route, { path: "design/dashboard/:dashboardName", element: _jsx(DashboardDesignPage, {}) }), _jsx(Route, { path: "search", element: _jsx(SearchResultsPage, {}) }), _jsx(Route, { path: "create-app", element: _jsx(CreateAppPage, {}) }), _jsx(Route, { path: "edit-app/:editAppName", element: _jsx(EditAppPage, {}) }), _jsx(Route, { path: "system/marketplace", element: _jsx(MarketplacePage, {}) }), _jsx(Route, { path: "system/marketplace/installed", element: _jsx(MarketplaceInstalledPage, {}) }), _jsx(Route, { path: "system/marketplace/:packageId", element: _jsx(MarketplacePackagePage, {}) }), extraRoutes, _jsx(Route, { path: ":objectName/:maybeRecordId", element: _jsx(ShorthandRecordRedirect, {}) }), _jsx(Route, { path: "*", element: _jsx(RouteNotFound, {}) })] }) }) }) }), currentObjectDef && (_jsx(ModalForm, { schema: {
241
241
  type: 'object-form',
242
242
  formType: 'modal',
243
243
  objectName: currentObjectDef.name,
@@ -10,6 +10,11 @@
10
10
  import React from 'react';
11
11
  interface HomeLayoutProps {
12
12
  children: React.ReactNode;
13
+ /**
14
+ * Signed-in user id. Forwarded to the floating chatbot so it can hydrate
15
+ * server-backed conversation history.
16
+ */
17
+ userId?: string;
13
18
  }
14
- export declare function HomeLayout({ children }: HomeLayoutProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function HomeLayout({ children, userId }: HomeLayoutProps): import("react/jsx-runtime").JSX.Element;
15
20
  export {};
@@ -15,7 +15,7 @@ import { useDiscovery } from '@object-ui/react';
15
15
  // Lightweight FAB stub — the heavy chat chunk graph only downloads on
16
16
  // first hover/click. See ../../layout/ConsoleChatbotFab.tsx.
17
17
  import { ConsoleChatbotFab } from '../../layout/ConsoleChatbotFab';
18
- export function HomeLayout({ children }) {
18
+ export function HomeLayout({ children, userId }) {
19
19
  const { setContext } = useNavigationContext();
20
20
  const { isAiEnabled } = useDiscovery();
21
21
  // Render the chatbot whenever AI is reachable. If the developer has explicitly
@@ -26,5 +26,5 @@ export function HomeLayout({ children }) {
26
26
  useEffect(() => {
27
27
  setContext('home');
28
28
  }, [setContext]);
29
- return (_jsxs("div", { className: "flex min-h-svh w-full flex-col bg-background", "data-testid": "home-layout", children: [_jsx("header", { className: "sticky top-0 z-30 flex h-14 w-full shrink-0 items-center gap-2 border-b bg-background px-2 sm:px-4", children: _jsx(AppHeader, { variant: "home" }) }), _jsx("main", { className: "flex-1 min-w-0 overflow-auto pb-20 sm:pb-0", children: children }), showChatbot && _jsx(ConsoleChatbotFab, { appLabel: "Workspace", objects: [] })] }));
29
+ return (_jsxs("div", { className: "flex min-h-svh w-full flex-col bg-background", "data-testid": "home-layout", children: [_jsx("header", { className: "sticky top-0 z-30 flex h-14 w-full shrink-0 items-center gap-2 border-b bg-background px-2 sm:px-4", children: _jsx(AppHeader, { variant: "home" }) }), _jsx("main", { className: "flex-1 min-w-0 overflow-auto pb-20 sm:pb-0", children: children }), showChatbot && _jsx(ConsoleChatbotFab, { appLabel: "Workspace", objects: [], userId: userId })] }));
30
30
  }
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /**
3
3
  * Marketplace Package Detail Page.
4
4
  *
@@ -7,8 +7,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
7
7
  */
8
8
  import { useEffect, useState } from 'react';
9
9
  import { useNavigate, useParams } from 'react-router-dom';
10
- import { Button, Badge, Card, CardContent, CardHeader, CardTitle, Skeleton, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Label, Checkbox, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@object-ui/components';
11
- import { ArrowLeft, ExternalLink, Download, AlertCircle, Package, Trash2 } from 'lucide-react';
10
+ import { Button, Badge, Card, CardContent, CardHeader, CardTitle, Skeleton, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Label, Checkbox, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, } from '@object-ui/components';
11
+ import { ArrowLeft, ExternalLink, Download, AlertCircle, Package, Trash2, MoreHorizontal, CheckCircle2 } from 'lucide-react';
12
12
  import { PackageIcon } from './PackageIcon';
13
13
  import { MarkdownText } from './MarkdownText';
14
14
  import { getMarketplacePackage, installPackage, installLocal, uninstallLocal, listLocalInstalls, listCloudEnvironments, listInstallableOrgIds, cloudInstallDeepLink, } from './marketplaceApi';
@@ -186,18 +186,29 @@ export function MarketplacePackagePage() {
186
186
  }
187
187
  };
188
188
  if (loading) {
189
- return (_jsxs("div", { className: "flex flex-col gap-6 p-4 sm:p-6", children: [_jsx(Skeleton, { className: "h-8 w-32" }), _jsx(Skeleton, { className: "h-16 w-full" }), _jsx(Skeleton, { className: "h-64 w-full" })] }));
189
+ return (_jsxs("div", { className: "mx-auto w-full max-w-6xl flex flex-col gap-6 p-4 sm:p-6", children: [_jsx(Skeleton, { className: "h-8 w-32" }), _jsx(Skeleton, { className: "h-16 w-full" }), _jsx(Skeleton, { className: "h-64 w-full" })] }));
190
190
  }
191
191
  if (error || !data) {
192
- return (_jsxs("div", { className: "flex flex-col gap-6 p-4 sm:p-6", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate(`${basePath}/system/marketplace`), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Back to marketplace"] }), _jsxs("div", { className: "flex items-start gap-3 rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 mt-0.5 text-destructive", "aria-hidden": "true" }), _jsxs("div", { children: [_jsx("div", { className: "font-medium text-destructive", children: "Failed to load package" }), _jsx("div", { className: "text-muted-foreground mt-1", children: error ?? 'Not found.' })] })] })] }));
192
+ return (_jsxs("div", { className: "mx-auto w-full max-w-6xl flex flex-col gap-6 p-4 sm:p-6", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigate(`${basePath}/system/marketplace`), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Back to marketplace"] }), _jsxs("div", { className: "flex items-start gap-3 rounded-md border border-destructive/30 bg-destructive/5 p-4 text-sm", children: [_jsx(AlertCircle, { className: "h-4 w-4 mt-0.5 text-destructive", "aria-hidden": "true" }), _jsxs("div", { children: [_jsx("div", { className: "font-medium text-destructive", children: "Failed to load package" }), _jsx("div", { className: "text-muted-foreground mt-1", children: error ?? 'Not found.' })] })] })] }));
193
193
  }
194
194
  const pkg = data.package;
195
195
  const latestVersion = pkg.latest_version?.version ?? data.versions[0]?.version ?? null;
196
196
  const localInstall = localInstalls.find((i) => i.manifestId === pkg.manifest_id) ?? null;
197
- return (_jsxs("div", { className: "flex flex-col gap-6 p-4 sm:p-6 max-w-5xl", children: [_jsxs(Button, { variant: "ghost", size: "sm", className: "self-start", onClick: () => navigate(`${basePath}/system/marketplace`), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Back to marketplace"] }), _jsxs("div", { className: "flex items-start gap-5 flex-wrap rounded-2xl border bg-gradient-to-br from-primary/5 via-background to-background p-6", children: [_jsx(PackageIcon, { iconUrl: pkg.icon_url, displayName: pkg.display_name, manifestId: pkg.manifest_id, className: "h-20 w-20 rounded-2xl shadow-sm ring-1 ring-border", initialClassName: "text-3xl font-bold" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("h1", { className: "text-3xl font-bold tracking-tight truncate", children: pkg.display_name || pkg.manifest_id }), _jsxs("div", { className: "text-sm text-muted-foreground mt-2 flex flex-wrap items-center gap-2", children: [_jsx("code", { className: "font-mono text-xs px-1.5 py-0.5 rounded bg-muted", children: pkg.manifest_id }), latestVersion && (_jsxs(Badge, { variant: "outline", children: ["v", latestVersion] })), pkg.publisher && pkg.publisher !== 'private' && (_jsx(Badge, { variant: pkg.publisher === 'objectstack' ? 'default' : 'secondary', children: pkg.publisher })), pkg.category && _jsx(Badge, { variant: "outline", children: pkg.category }), pkg.license && _jsx(Badge, { variant: "outline", className: "font-normal", children: pkg.license }), localInstall && (_jsxs(Badge, { variant: "default", className: "bg-green-600 hover:bg-green-600", children: ["Installed \u00B7 v", localInstall.version] }))] }), pkg.description && (_jsx("p", { className: "text-base text-foreground/80 mt-4 max-w-2xl leading-relaxed", children: pkg.description }))] }), _jsxs("div", { className: "flex flex-col gap-2 shrink-0 min-w-[14rem]", children: [getRuntimeConfig().features.installLocal && (_jsxs(_Fragment, { children: [_jsxs(Button, { onClick: doInstallLocal, disabled: !latestVersion || installingLocal, size: "lg", children: [_jsx(Download, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), installingLocal
198
- ? 'Working…'
199
- : localInstall
200
- ? `Reinstall to this runtime`
201
- : 'Install to this runtime'] }), localInstall && (_jsxs(Button, { variant: "outline", onClick: doUninstallLocal, disabled: installingLocal, children: [_jsx(Trash2, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Uninstall from this runtime"] }))] })), _jsxs(Button, { variant: "ghost", onClick: openInstall, disabled: !latestVersion, size: "sm", children: [_jsx(Download, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Install to cloud environment\u2026"] }), pkg.homepage_url && (_jsx("a", { href: pkg.homepage_url, target: "_blank", rel: "noopener noreferrer", children: _jsxs(Button, { variant: "ghost", size: "sm", className: "w-full", children: [_jsx(ExternalLink, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Homepage"] }) })), localResult && (_jsx("div", { className: `rounded-md border p-2 text-xs whitespace-pre-wrap ${localResult.ok ? 'border-green-500/30 bg-green-500/5 text-green-700 dark:text-green-400' : 'border-destructive/30 bg-destructive/5 text-destructive'}`, children: localResult.message }))] })] }), _jsxs("div", { className: "grid gap-4 lg:grid-cols-3", children: [_jsx("div", { className: "lg:col-span-2 space-y-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "About" }) }), _jsx(CardContent, { children: pkg.readme ? (_jsx(MarkdownText, { source: pkg.readme })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "No readme provided." })) })] }) }), _jsx("div", { className: "space-y-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Versions" }) }), _jsx(CardContent, { children: data.versions.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No approved versions." })) : (_jsx("ul", { className: "space-y-2", children: data.versions.map((v) => (_jsxs("li", { className: "flex items-center justify-between gap-2 text-sm", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Package, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-hidden": "true" }), _jsxs("code", { className: "font-mono", children: ["v", v.version] }), v.is_prerelease && _jsx(Badge, { variant: "outline", className: "text-xs", children: "pre" })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: v.published_at ? new Date(v.published_at).toLocaleDateString() : '—' })] }, v.id))) })) })] }) })] }), _jsx(Dialog, { open: installOpen, onOpenChange: (o) => { setInstallOpen(o); if (!o)
197
+ const supportsLocal = getRuntimeConfig().features.installLocal;
198
+ const primaryDisabled = !latestVersion || installingLocal || installing;
199
+ const primaryAction = supportsLocal
200
+ ? {
201
+ label: installingLocal
202
+ ? 'Working…'
203
+ : localInstall
204
+ ? 'Reinstall'
205
+ : 'Install',
206
+ onClick: doInstallLocal,
207
+ }
208
+ : {
209
+ label: installing ? 'Installing…' : 'Install to cloud…',
210
+ onClick: openInstall,
211
+ };
212
+ return (_jsxs("div", { className: "mx-auto w-full max-w-6xl flex flex-col gap-6 p-4 sm:p-6", children: [_jsxs(Button, { variant: "ghost", size: "sm", className: "self-start -ml-2 text-muted-foreground hover:text-foreground", onClick: () => navigate(`${basePath}/system/marketplace`), children: [_jsx(ArrowLeft, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Back to marketplace"] }), _jsxs("div", { className: "flex items-start gap-5 flex-wrap sm:flex-nowrap rounded-2xl border bg-gradient-to-br from-primary/5 via-background to-background p-6 sm:p-8", children: [_jsx(PackageIcon, { iconUrl: pkg.icon_url, displayName: pkg.display_name, manifestId: pkg.manifest_id, className: "h-20 w-20 rounded-2xl shadow-sm ring-1 ring-border shrink-0", initialClassName: "text-3xl font-bold" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "flex items-center gap-2 flex-wrap", children: [_jsx("h1", { className: "text-2xl sm:text-3xl font-bold tracking-tight truncate", children: pkg.display_name || pkg.manifest_id }), pkg.homepage_url && (_jsxs("a", { href: pkg.homepage_url, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground transition-colors", title: "Homepage", children: [_jsx(ExternalLink, { className: "h-3.5 w-3.5", "aria-hidden": "true" }), _jsx("span", { className: "hidden sm:inline", children: "Homepage" })] }))] }), _jsxs("div", { className: "text-sm text-muted-foreground mt-2 flex flex-wrap items-center gap-1.5", children: [_jsx("code", { className: "font-mono text-xs px-1.5 py-0.5 rounded bg-muted", children: pkg.manifest_id }), latestVersion && _jsxs(Badge, { variant: "outline", children: ["v", latestVersion] }), pkg.publisher && pkg.publisher !== 'private' && (_jsx(Badge, { variant: pkg.publisher === 'objectstack' ? 'default' : 'secondary', children: pkg.publisher })), pkg.category && _jsx(Badge, { variant: "outline", children: pkg.category }), pkg.license && _jsx(Badge, { variant: "outline", className: "font-normal", children: pkg.license }), localInstall && (_jsxs(Badge, { variant: "default", className: "bg-green-600 hover:bg-green-600 gap-1", children: [_jsx(CheckCircle2, { className: "h-3 w-3", "aria-hidden": "true" }), "Installed \u00B7 v", localInstall.version] }))] }), pkg.description && (_jsx("p", { className: "text-sm sm:text-base text-foreground/80 mt-3 max-w-2xl leading-relaxed", children: pkg.description }))] }), _jsxs("div", { className: "flex items-center gap-2 shrink-0 self-start", children: [_jsxs(Button, { onClick: primaryAction.onClick, disabled: primaryDisabled, size: "lg", className: "min-w-[8rem]", children: [_jsx(Download, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), primaryAction.label] }), localInstall && (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "outline", size: "lg", className: "px-2.5", "aria-label": "More install options", children: _jsx(MoreHorizontal, { className: "h-4 w-4", "aria-hidden": "true" }) }) }), _jsx(DropdownMenuContent, { align: "end", className: "w-56", children: _jsxs(DropdownMenuItem, { onSelect: doUninstallLocal, disabled: installingLocal, className: "text-destructive focus:text-destructive", children: [_jsx(Trash2, { className: "h-4 w-4 mr-2", "aria-hidden": "true" }), "Uninstall from this runtime"] }) })] }))] })] }), localResult && (_jsxs("div", { role: "status", className: `flex items-start gap-2 rounded-md border p-3 text-sm whitespace-pre-wrap ${localResult.ok ? 'border-green-500/30 bg-green-500/5 text-green-700 dark:text-green-400' : 'border-destructive/30 bg-destructive/5 text-destructive'}`, children: [localResult.ok ? _jsx(CheckCircle2, { className: "h-4 w-4 mt-0.5 shrink-0", "aria-hidden": "true" }) : _jsx(AlertCircle, { className: "h-4 w-4 mt-0.5 shrink-0", "aria-hidden": "true" }), _jsx("div", { className: "flex-1", children: localResult.message }), _jsx("button", { type: "button", className: "text-xs underline opacity-60 hover:opacity-100", onClick: () => setLocalResult(null), children: "Dismiss" })] })), _jsxs("div", { className: "grid gap-4 lg:grid-cols-3", children: [_jsx("div", { className: "lg:col-span-2 space-y-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "About" }) }), _jsx(CardContent, { children: pkg.readme ? (_jsx(MarkdownText, { source: pkg.readme })) : (_jsx("p", { className: "text-sm text-muted-foreground", children: "No readme provided." })) })] }) }), _jsx("div", { className: "space-y-4", children: _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: "Versions" }) }), _jsx(CardContent, { children: data.versions.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No approved versions." })) : (_jsx("ul", { className: "space-y-2", children: data.versions.map((v) => (_jsxs("li", { className: "flex items-center justify-between gap-2 text-sm", children: [_jsxs("span", { className: "flex items-center gap-1.5", children: [_jsx(Package, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-hidden": "true" }), _jsxs("code", { className: "font-mono", children: ["v", v.version] }), v.is_prerelease && _jsx(Badge, { variant: "outline", className: "text-xs", children: "pre" })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: v.published_at ? new Date(v.published_at).toLocaleDateString() : '—' })] }, v.id))) })) })] }) })] }), _jsx(Dialog, { open: installOpen, onOpenChange: (o) => { setInstallOpen(o); if (!o)
202
213
  setInstallResult(null); }, children: _jsxs(DialogContent, { children: [_jsxs(DialogHeader, { children: [_jsxs(DialogTitle, { children: ["Install ", pkg.display_name || pkg.manifest_id] }), _jsx(DialogDescription, { children: "Choose an environment to install this app into. You need to be signed into ObjectStack Cloud." })] }), envsLoading ? (_jsx(Skeleton, { className: "h-10 w-full" })) : envsError ? (_jsxs("div", { className: "rounded-md border border-amber-500/30 bg-amber-500/5 p-3 text-sm space-y-2", children: [_jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertCircle, { className: "h-4 w-4 mt-0.5 text-amber-600", "aria-hidden": "true" }), _jsx("div", { className: "flex-1", children: envsError })] }), _jsx("a", { href: cloudInstallDeepLink(pkg.id), target: "_blank", rel: "noopener noreferrer", children: _jsxs(Button, { variant: "outline", size: "sm", className: "w-full", children: [_jsx(ExternalLink, { className: "h-4 w-4 mr-1.5", "aria-hidden": "true" }), "Open on cloud"] }) })] })) : envs.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "No environments found in your active organization." })) : (_jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Label, { htmlFor: "env-select", children: "Environment" }), _jsxs(Select, { value: selectedEnv, onValueChange: setSelectedEnv, children: [_jsx(SelectTrigger, { id: "env-select", children: _jsx(SelectValue, { placeholder: "Pick an environment" }) }), _jsx(SelectContent, { children: envs.map((e) => (_jsxs(SelectItem, { value: e.id, children: [e.display_name || e.hostname || e.id, e.plan && _jsxs("span", { className: "text-muted-foreground", children: [" \u00B7 ", e.plan] })] }, e.id))) })] })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Checkbox, { id: "seed", checked: seedSampleData, onCheckedChange: (c) => setSeedSampleData(c === true) }), _jsx(Label, { htmlFor: "seed", className: "text-sm font-normal cursor-pointer", children: "Include sample data" })] })] })), installResult && (_jsx("div", { className: `rounded-md border p-3 text-sm ${installResult.ok ? 'border-green-500/30 bg-green-500/5 text-green-700' : 'border-destructive/30 bg-destructive/5 text-destructive'}`, children: installResult.message })), _jsxs(DialogFooter, { children: [_jsx(Button, { variant: "outline", onClick: () => setInstallOpen(false), children: "Close" }), !envsError && (_jsx(Button, { onClick: doInstall, disabled: !selectedEnv || installing || installResult?.ok === true, children: installing ? 'Installing…' : 'Install' }))] })] }) })] }));
203
214
  }
@@ -7,3 +7,4 @@ export { useRecentItems, type RecentItem } from './useRecentItems';
7
7
  export { useRecordApprovals, type ApprovalProcessLite, type ApprovalRequestLite } from './useRecordApprovals';
8
8
  export { useResponsiveSidebar } from './useResponsiveSidebar';
9
9
  export { useTrackRouteAsRecent, type UseTrackRouteAsRecentOptions } from './useTrackRouteAsRecent';
10
+ export { useChatConversation, type HydratedUIMessage, type UseChatConversationOptions, type UseChatConversationReturn, } from './useChatConversation';
@@ -7,3 +7,4 @@ export { useRecentItems } from './useRecentItems';
7
7
  export { useRecordApprovals } from './useRecordApprovals';
8
8
  export { useResponsiveSidebar } from './useResponsiveSidebar';
9
9
  export { useTrackRouteAsRecent } from './useTrackRouteAsRecent';
10
+ export { useChatConversation, } from './useChatConversation';
@@ -0,0 +1,31 @@
1
+ /** Minimal UIMessage shape compatible with `@ai-sdk/react`'s `useChat`. */
2
+ export interface HydratedUIMessage {
3
+ id: string;
4
+ role: 'user' | 'assistant' | 'system';
5
+ parts: Array<{
6
+ type: 'text';
7
+ text: string;
8
+ }>;
9
+ }
10
+ export interface UseChatConversationOptions {
11
+ /** Authenticated user id; hook is inert until this is defined. */
12
+ userId: string | undefined;
13
+ /**
14
+ * Optional scope (e.g. agent name) for keying separate conversations under
15
+ * the same user.
16
+ */
17
+ scope?: string;
18
+ /**
19
+ * Base URL of the AI service (no trailing slash). Hook calls
20
+ * `${apiBase}/conversations[/...]`. Required.
21
+ */
22
+ apiBase: string;
23
+ }
24
+ export interface UseChatConversationReturn {
25
+ conversationId: string | undefined;
26
+ initialMessages: HydratedUIMessage[];
27
+ isLoading: boolean;
28
+ /** Delete the current conversation + start a fresh one. */
29
+ reset: () => Promise<void>;
30
+ }
31
+ export declare function useChatConversation(options: UseChatConversationOptions): UseChatConversationReturn;
@@ -0,0 +1,188 @@
1
+ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
+ /**
3
+ * Server-backed AI chat conversation lifecycle.
4
+ *
5
+ * Binds a chat UI to an `ai_conversations` row owned by the signed-in user.
6
+ * On mount it tries the cached id (per user, optionally per scope); falls
7
+ * back to creating a fresh conversation when the cached one is gone
8
+ * (404/403).
9
+ */
10
+ import { useCallback, useEffect, useRef, useState } from 'react';
11
+ const CACHE_PREFIX = 'objectstack:ai-chat-conversation-id';
12
+ function cacheKey(userId, scope) {
13
+ return scope ? `${CACHE_PREFIX}:${userId}:${scope}` : `${CACHE_PREFIX}:${userId}`;
14
+ }
15
+ function readCache(key) {
16
+ try {
17
+ return localStorage.getItem(key) ?? undefined;
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ }
23
+ function writeCache(key, value) {
24
+ try {
25
+ if (value)
26
+ localStorage.setItem(key, value);
27
+ else
28
+ localStorage.removeItem(key);
29
+ }
30
+ catch {
31
+ /* ignore — private mode, quota, etc. */
32
+ }
33
+ }
34
+ function contentToText(content) {
35
+ if (typeof content === 'string')
36
+ return content;
37
+ if (Array.isArray(content)) {
38
+ return content
39
+ .map((part) => {
40
+ if (typeof part === 'string')
41
+ return part;
42
+ if (part &&
43
+ typeof part === 'object' &&
44
+ 'text' in part &&
45
+ typeof part.text === 'string') {
46
+ return part.text;
47
+ }
48
+ return '';
49
+ })
50
+ .join('');
51
+ }
52
+ return '';
53
+ }
54
+ function toUIMessages(rows) {
55
+ if (!rows)
56
+ return [];
57
+ const out = [];
58
+ rows.forEach((row, idx) => {
59
+ const role = row.role;
60
+ if (role !== 'user' && role !== 'assistant' && role !== 'system')
61
+ return;
62
+ const text = contentToText(row.content);
63
+ if (!text)
64
+ return;
65
+ out.push({
66
+ id: row.id ?? `msg-${idx}`,
67
+ role,
68
+ parts: [{ type: 'text', text }],
69
+ });
70
+ });
71
+ return out;
72
+ }
73
+ async function fetchConversation(apiBase, id) {
74
+ const res = await fetch(`${apiBase}/conversations/${encodeURIComponent(id)}`, {
75
+ credentials: 'include',
76
+ });
77
+ if (res.status === 404 || res.status === 403)
78
+ return null;
79
+ if (!res.ok)
80
+ throw new Error(`GET conversation failed: ${res.status}`);
81
+ return (await res.json());
82
+ }
83
+ async function createConversation(apiBase) {
84
+ const res = await fetch(`${apiBase}/conversations`, {
85
+ method: 'POST',
86
+ credentials: 'include',
87
+ headers: { 'Content-Type': 'application/json' },
88
+ body: '{}',
89
+ });
90
+ if (!res.ok)
91
+ throw new Error(`POST conversation failed: ${res.status}`);
92
+ return (await res.json());
93
+ }
94
+ async function deleteConversation(apiBase, id) {
95
+ await fetch(`${apiBase}/conversations/${encodeURIComponent(id)}`, {
96
+ method: 'DELETE',
97
+ credentials: 'include',
98
+ }).catch(() => {
99
+ /* best-effort */
100
+ });
101
+ }
102
+ export function useChatConversation(options) {
103
+ const { userId, scope, apiBase } = options;
104
+ const [conversationId, setConversationId] = useState(undefined);
105
+ const [initialMessages, setInitialMessages] = useState([]);
106
+ const [isLoading, setIsLoading] = useState(Boolean(userId));
107
+ const mountedRef = useRef(true);
108
+ useEffect(() => {
109
+ mountedRef.current = true;
110
+ return () => {
111
+ mountedRef.current = false;
112
+ };
113
+ }, []);
114
+ useEffect(() => {
115
+ if (!userId) {
116
+ setConversationId(undefined);
117
+ setInitialMessages([]);
118
+ setIsLoading(false);
119
+ return;
120
+ }
121
+ let cancelled = false;
122
+ const key = cacheKey(userId, scope);
123
+ setIsLoading(true);
124
+ (async () => {
125
+ try {
126
+ const cached = readCache(key);
127
+ if (cached) {
128
+ const existing = await fetchConversation(apiBase, cached);
129
+ if (cancelled)
130
+ return;
131
+ if (existing) {
132
+ setConversationId(existing.id);
133
+ setInitialMessages(toUIMessages(existing.messages));
134
+ return;
135
+ }
136
+ writeCache(key, undefined);
137
+ }
138
+ const fresh = await createConversation(apiBase);
139
+ if (cancelled)
140
+ return;
141
+ writeCache(key, fresh.id);
142
+ setConversationId(fresh.id);
143
+ setInitialMessages(toUIMessages(fresh.messages));
144
+ }
145
+ catch {
146
+ if (!cancelled) {
147
+ setConversationId(undefined);
148
+ setInitialMessages([]);
149
+ }
150
+ }
151
+ finally {
152
+ if (!cancelled)
153
+ setIsLoading(false);
154
+ }
155
+ })();
156
+ return () => {
157
+ cancelled = true;
158
+ };
159
+ }, [userId, scope, apiBase]);
160
+ const reset = useCallback(async () => {
161
+ if (!userId)
162
+ return;
163
+ const key = cacheKey(userId, scope);
164
+ setIsLoading(true);
165
+ try {
166
+ if (conversationId)
167
+ await deleteConversation(apiBase, conversationId);
168
+ writeCache(key, undefined);
169
+ const fresh = await createConversation(apiBase);
170
+ writeCache(key, fresh.id);
171
+ if (!mountedRef.current)
172
+ return;
173
+ setConversationId(fresh.id);
174
+ setInitialMessages([]);
175
+ }
176
+ catch {
177
+ if (mountedRef.current) {
178
+ setConversationId(undefined);
179
+ setInitialMessages([]);
180
+ }
181
+ }
182
+ finally {
183
+ if (mountedRef.current)
184
+ setIsLoading(false);
185
+ }
186
+ }, [conversationId, userId, scope, apiBase]);
187
+ return { conversationId, initialMessages, isLoading, reset };
188
+ }
@@ -14,6 +14,12 @@ export interface ConsoleFloatingChatbotProps {
14
14
  defaultAgent?: string;
15
15
  /** Whether the floating panel should open immediately on mount. */
16
16
  defaultOpen?: boolean;
17
+ /**
18
+ * Authenticated user id. When provided, the chat hydrates from (and writes
19
+ * to) a server-backed `ai_conversations` row keyed by `userId` + agent.
20
+ * Inert until defined — the floating panel still works in local-only mode.
21
+ */
22
+ userId?: string;
17
23
  }
18
- export default function ConsoleFloatingChatbot({ appLabel, objects, apiBase: apiBaseProp, defaultAgent: defaultAgentProp, defaultOpen, }: ConsoleFloatingChatbotProps): import("react/jsx-runtime").JSX.Element;
24
+ export default function ConsoleFloatingChatbot({ appLabel, objects, apiBase: apiBaseProp, defaultAgent: defaultAgentProp, defaultOpen, userId, }: ConsoleFloatingChatbotProps): import("react/jsx-runtime").JSX.Element;
19
25
  export {};
@@ -14,6 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
14
14
  import React from 'react';
15
15
  import { FloatingChatbot, useObjectChat, useAgents, useHitlInChat, } from '@object-ui/plugin-chatbot';
16
16
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@object-ui/components';
17
+ import { useChatConversation } from '../hooks';
17
18
  const DEFAULT_AI_PATH = '/api/v1/ai';
18
19
  function resolveApiBase(explicit) {
19
20
  if (explicit)
@@ -25,7 +26,7 @@ function resolveApiBase(explicit) {
25
26
  const serverUrl = env.VITE_SERVER_URL ?? '';
26
27
  return `${serverUrl.replace(/\/$/, '')}${DEFAULT_AI_PATH}`;
27
28
  }
28
- function ChatbotInner({ appLabel, objects, agents, agentsLoading, agentsError, activeAgent, onAgentChange, chatApi, apiBase, defaultOpen = false, }) {
29
+ function ChatbotInner({ appLabel, objects, agents, agentsLoading, agentsError, activeAgent, onAgentChange, chatApi, apiBase, defaultOpen = false, conversationId, }) {
29
30
  const objectNames = objects.map((o) => o.label || o.name).join(', ');
30
31
  const activeAgentLabel = React.useMemo(() => {
31
32
  const found = agents.find((a) => a.name === activeAgent);
@@ -36,7 +37,7 @@ function ChatbotInner({ appLabel, objects, agents, agentsLoading, agentsError, a
36
37
  : `Hello! I'm your **${appLabel}** assistant. ${agentsError ? '(Backend unreachable — running in offline demo mode.)' : ''}`;
37
38
  const { messages, isLoading, error, sendMessage, stop, reload, clear, } = useObjectChat({
38
39
  api: chatApi,
39
- conversationId: activeAgent ? `${appLabel}:${activeAgent}` : undefined,
40
+ conversationId,
40
41
  body: {
41
42
  context: {
42
43
  activeApp: appLabel,
@@ -85,7 +86,7 @@ function ChatbotInner({ appLabel, objects, agents, agentsLoading, agentsError, a
85
86
  ? 'Loading agents...'
86
87
  : 'Ask anything...', onSendMessage: (content) => sendMessage(content), onClear: clear, onStop: isLoading ? stop : undefined, onReload: reload, isLoading: isLoading, error: error, enableMarkdown: true, onToolApprove: hitl.decide, toolDecisions: hitl.decisions, toolApproveLabel: "Approve & run", toolDenyLabel: "Reject", toolDenyReason: "Operator rejected from chat" }));
87
88
  }
88
- export default function ConsoleFloatingChatbot({ appLabel, objects, apiBase: apiBaseProp, defaultAgent: defaultAgentProp, defaultOpen = false, }) {
89
+ export default function ConsoleFloatingChatbot({ appLabel, objects, apiBase: apiBaseProp, defaultAgent: defaultAgentProp, defaultOpen = false, userId, }) {
89
90
  const apiBase = React.useMemo(() => resolveApiBase(apiBaseProp), [apiBaseProp]);
90
91
  const env = import.meta.env ?? {};
91
92
  const envDefaultAgent = env.VITE_AI_DEFAULT_AGENT;
@@ -101,8 +102,16 @@ export default function ConsoleFloatingChatbot({ appLabel, objects, apiBase: api
101
102
  const chatApi = activeAgent
102
103
  ? `${apiBase}/agents/${encodeURIComponent(activeAgent)}/chat`
103
104
  : undefined;
104
- // `key` forces a clean remount whenever the active agent (and therefore the
105
- // chat API URL) changes required because `useObjectChat` locks its mode
106
- // (api vs local) on first render.
107
- return (_jsx(ChatbotInner, { appLabel: appLabel, objects: objects, agents: agents, agentsLoading: agentsLoading, agentsError: agentsError, activeAgent: activeAgent, onAgentChange: setActiveAgent, chatApi: chatApi, apiBase: apiBase, defaultOpen: defaultOpen }, chatApi ?? 'local'));
105
+ // Server-backed conversation. Scoped by agent so each agent gets its own
106
+ // persistent history. Hook is inert until `userId` is provided; without it
107
+ // the FAB continues to work in local-only mode (no persistence).
108
+ const { conversationId } = useChatConversation({
109
+ userId,
110
+ scope: activeAgent,
111
+ apiBase,
112
+ });
113
+ // `key` forces a clean remount whenever the chat endpoint OR the resolved
114
+ // conversation id changes — required because `useObjectChat` locks its mode
115
+ // (api vs local) and its `conversationId` on first render.
116
+ return (_jsx(ChatbotInner, { appLabel: appLabel, objects: objects, agents: agents, agentsLoading: agentsLoading, agentsError: agentsError, activeAgent: activeAgent, onAgentChange: setActiveAgent, chatApi: chatApi, apiBase: apiBase, defaultOpen: defaultOpen, conversationId: conversationId }, `${chatApi ?? 'local'}:${conversationId ?? 'pending'}`));
108
117
  }
@@ -16,7 +16,12 @@ interface ConsoleLayoutProps {
16
16
  onAppChange: (name: string) => void;
17
17
  objects: any[];
18
18
  connectionState?: ConnectionState;
19
+ /**
20
+ * Signed-in user id. Forwarded to the floating chatbot so it can hydrate
21
+ * server-backed conversation history. Omit for unauthenticated/local-only.
22
+ */
23
+ userId?: string;
19
24
  }
20
25
  /** Floating chatbot wired with useObjectChat for demo auto-response */
21
- export declare function ConsoleLayout({ children, activeAppName, activeApp, onAppChange, objects, connectionState }: ConsoleLayoutProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare function ConsoleLayout({ children, activeAppName, activeApp, onAppChange, objects, connectionState, userId, }: ConsoleLayoutProps): import("react/jsx-runtime").JSX.Element;
22
27
  export {};
@@ -28,7 +28,7 @@ function ConsoleLayoutInner({ children }) {
28
28
  }
29
29
  /** Floating chatbot wired with useObjectChat for demo auto-response */
30
30
  // (moved to ./ConsoleFloatingChatbot.tsx for code-splitting)
31
- export function ConsoleLayout({ children, activeAppName, activeApp, onAppChange, objects, connectionState }) {
31
+ export function ConsoleLayout({ children, activeAppName, activeApp, onAppChange, objects, connectionState, userId, }) {
32
32
  const appLabel = resolveI18nLabel(activeApp?.label) || activeAppName;
33
33
  const { isAiEnabled } = useDiscovery();
34
34
  // Trust an explicit `VITE_AI_BASE_URL` opt-in even when discovery reports
@@ -51,5 +51,5 @@ export function ConsoleLayout({ children, activeAppName, activeApp, onAppChange,
51
51
  ? `${resolveI18nLabel(activeApp.label)} — ObjectStack Console`
52
52
  : undefined,
53
53
  }
54
- : undefined, children: [_jsx(ConsoleLayoutInner, { children: children }), showChatbot && (_jsx(ConsoleChatbotFab, { appLabel: appLabel, objects: objects }))] }) }));
54
+ : undefined, children: [_jsx(ConsoleLayoutInner, { children: children }), showChatbot && (_jsx(ConsoleChatbotFab, { appLabel: appLabel, objects: objects, userId: userId }))] }) }));
55
55
  }
@@ -386,7 +386,8 @@ export function RecordDetailView({ dataSource, objects, onEdit, objectNameOverri
386
386
  const shouldRefresh = action.refreshAfter !== false;
387
387
  if (shouldRefresh)
388
388
  setActionRefreshKey(k => k + 1);
389
- return { success: true, data: json?.data, reload: shouldRefresh };
389
+ const result = json?.data;
390
+ return { success: true, data: result, reload: shouldRefresh };
390
391
  }
391
392
  catch (error) {
392
393
  return { success: false, error: error.message };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/app-shell",
3
- "version": "6.0.1",
3
+ "version": "6.0.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
@@ -28,35 +28,35 @@
28
28
  "@sentry/react": "^8.55.2",
29
29
  "lucide-react": "^1.16.0",
30
30
  "sonner": "^2.0.7",
31
- "@object-ui/auth": "6.0.1",
32
- "@object-ui/collaboration": "6.0.1",
33
- "@object-ui/components": "6.0.1",
34
- "@object-ui/core": "6.0.1",
35
- "@object-ui/data-objectstack": "6.0.1",
36
- "@object-ui/fields": "6.0.1",
37
- "@object-ui/i18n": "6.0.1",
38
- "@object-ui/layout": "6.0.1",
39
- "@object-ui/permissions": "6.0.1",
40
- "@object-ui/providers": "6.0.1",
41
- "@object-ui/react": "6.0.1",
42
- "@object-ui/types": "6.0.1"
31
+ "@object-ui/auth": "6.0.2",
32
+ "@object-ui/collaboration": "6.0.2",
33
+ "@object-ui/components": "6.0.2",
34
+ "@object-ui/core": "6.0.2",
35
+ "@object-ui/data-objectstack": "6.0.2",
36
+ "@object-ui/fields": "6.0.2",
37
+ "@object-ui/i18n": "6.0.2",
38
+ "@object-ui/layout": "6.0.2",
39
+ "@object-ui/permissions": "6.0.2",
40
+ "@object-ui/providers": "6.0.2",
41
+ "@object-ui/react": "6.0.2",
42
+ "@object-ui/types": "6.0.2"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "react": "^18.0.0 || ^19.0.0",
46
46
  "react-dom": "^18.0.0 || ^19.0.0",
47
47
  "react-router-dom": "^6.0.0 || ^7.0.0",
48
- "@object-ui/plugin-calendar": "^6.0.1",
49
- "@object-ui/plugin-charts": "^6.0.1",
50
- "@object-ui/plugin-chatbot": "^6.0.1",
51
- "@object-ui/plugin-dashboard": "^6.0.1",
52
- "@object-ui/plugin-designer": "^6.0.1",
53
- "@object-ui/plugin-detail": "^6.0.1",
54
- "@object-ui/plugin-form": "^6.0.1",
55
- "@object-ui/plugin-grid": "^6.0.1",
56
- "@object-ui/plugin-kanban": "^6.0.1",
57
- "@object-ui/plugin-list": "^6.0.1",
58
- "@object-ui/plugin-report": "^6.0.1",
59
- "@object-ui/plugin-view": "^6.0.1"
48
+ "@object-ui/plugin-calendar": "^6.0.2",
49
+ "@object-ui/plugin-charts": "^6.0.2",
50
+ "@object-ui/plugin-chatbot": "^6.0.2",
51
+ "@object-ui/plugin-dashboard": "^6.0.2",
52
+ "@object-ui/plugin-designer": "^6.0.2",
53
+ "@object-ui/plugin-detail": "^6.0.2",
54
+ "@object-ui/plugin-form": "^6.0.2",
55
+ "@object-ui/plugin-grid": "^6.0.2",
56
+ "@object-ui/plugin-kanban": "^6.0.2",
57
+ "@object-ui/plugin-list": "^6.0.2",
58
+ "@object-ui/plugin-report": "^6.0.2",
59
+ "@object-ui/plugin-view": "^6.0.2"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/node": "^25.9.0",