@object-ui/app-shell 7.2.0 → 7.3.0
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 +41 -0
- package/dist/components/ManagedByBadge.js +1 -1
- package/dist/console/ai/AiChatPage.js +29 -6
- package/dist/console/ai/BuildDebugDrawer.d.ts +20 -0
- package/dist/console/ai/BuildDebugDrawer.js +75 -0
- package/dist/console/ai/buildDebugApi.d.ts +94 -0
- package/dist/console/ai/buildDebugApi.js +16 -0
- package/dist/console/organizations/OrganizationsPage.js +13 -4
- package/dist/console/organizations/manage/OrganizationLayout.js +1 -1
- package/dist/layout/AppHeader.js +4 -3
- package/dist/layout/ConsoleFloatingChatbot.js +6 -2
- package/dist/layout/ContextSelectors.js +0 -19
- package/dist/layout/WorkspaceSwitcher.d.ts +14 -0
- package/dist/layout/WorkspaceSwitcher.js +76 -0
- package/dist/utils/managedByEmptyState.d.ts +1 -1
- package/dist/utils/managedByEmptyState.js +20 -2
- package/dist/views/ObjectView.js +1 -1
- package/dist/views/metadata-admin/ResourceListPage.js +9 -11
- package/dist/views/metadata-admin/StudioHomePage.js +1 -5
- package/dist/views/metadata-admin/i18n.js +0 -2
- package/dist/views/metadata-admin/package-scope.d.ts +9 -34
- package/dist/views/metadata-admin/package-scope.js +11 -41
- package/package.json +38 -38
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# @object-ui/app-shell — Changelog
|
|
2
2
|
|
|
3
|
+
## 7.3.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 17ae00c: feat(studio): remove the "Local / Custom" stopgap scope from the package selector (ADR-0070 D5)
|
|
8
|
+
|
|
9
|
+
The package-scope selector no longer offers a synthetic "Local / Custom (this
|
|
10
|
+
env)" entry (the `package_id = null` / `sys_metadata` orphan bucket from
|
|
11
|
+
objectui#1946). That was a deliberate stopgap; ADR-0070 makes every
|
|
12
|
+
runtime-authored item live in a writable **base**, the kernel rejects orphan
|
|
13
|
+
creates (`writable_package_required`), and legacy orphans are adopted into a
|
|
14
|
+
base via "Adopt loose items". With no authoring path producing orphans, the
|
|
15
|
+
bucket has no reason to exist.
|
|
16
|
+
|
|
17
|
+
- `buildPackageScopeOptions` now returns only writable bases (drops the appended
|
|
18
|
+
sentinel); `isLocalScope` / `LOCAL_PACKAGE_ID` / `writableBaseOptions` and the
|
|
19
|
+
inline `LOCAL_SCOPE_ID` in `ContextSelectors` are removed.
|
|
20
|
+
- The create-flow and list/home scope filters simplify accordingly (a real base
|
|
21
|
+
is always the active scope; never the null/local sentinel).
|
|
22
|
+
- Read-side `sys_metadata` provenance handling (classifying a row as
|
|
23
|
+
runtime-authored, artifact detection in the editor) is unchanged — the kernel
|
|
24
|
+
still keeps `null` as a legacy read tag.
|
|
25
|
+
|
|
26
|
+
Closes the D5 tail of #2278 (the migration tooling it depended on already
|
|
27
|
+
shipped).
|
|
28
|
+
|
|
29
|
+
- Updated dependencies [788dbf9]
|
|
30
|
+
- @object-ui/fields@7.3.0
|
|
31
|
+
- @object-ui/types@7.3.0
|
|
32
|
+
- @object-ui/core@7.3.0
|
|
33
|
+
- @object-ui/i18n@7.3.0
|
|
34
|
+
- @object-ui/react@7.3.0
|
|
35
|
+
- @object-ui/components@7.3.0
|
|
36
|
+
- @object-ui/layout@7.3.0
|
|
37
|
+
- @object-ui/data-objectstack@7.3.0
|
|
38
|
+
- @object-ui/auth@7.3.0
|
|
39
|
+
- @object-ui/permissions@7.3.0
|
|
40
|
+
- @object-ui/plugin-editor@7.3.0
|
|
41
|
+
- @object-ui/collaboration@7.3.0
|
|
42
|
+
- @object-ui/providers@7.3.0
|
|
43
|
+
|
|
3
44
|
## 7.2.0
|
|
4
45
|
|
|
5
46
|
### Minor Changes
|
|
@@ -27,7 +27,7 @@ const VARIANTS = {
|
|
|
27
27
|
icon: ShieldAlert,
|
|
28
28
|
short: 'Identity',
|
|
29
29
|
title: 'Managed by the identity provider',
|
|
30
|
-
body: (display) => `This object's schema is owned by ${display}. Direct edits bypass password hashing, session validation, two-factor checks, and audit hooks.
|
|
30
|
+
body: (display) => `This object's schema is owned by ${display}. Direct edits bypass password hashing, session validation, two-factor checks, and audit hooks. Manage these records through your authentication provider's sign-in, invitation, and security flows instead.`,
|
|
31
31
|
tone: 'border-amber-300/60 bg-amber-50 text-amber-900 hover:bg-amber-100 dark:border-amber-500/40 dark:bg-amber-950/40 dark:text-amber-100',
|
|
32
32
|
},
|
|
33
33
|
};
|
|
@@ -18,8 +18,8 @@ import { useAuth } from '@object-ui/auth';
|
|
|
18
18
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
19
19
|
import { toast } from 'sonner';
|
|
20
20
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Button, ShareDialog, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, Empty, EmptyTitle, EmptyDescription, cn, } from '@object-ui/components';
|
|
21
|
-
import { PanelLeft, PanelLeftClose, PanelLeftOpen, Share2 } from 'lucide-react';
|
|
22
|
-
import { ChatbotEnhanced, useAgents, useObjectChat, useHitlInChat, resolveDefaultAgentName, PLATFORM_DEFAULT_AGENT, agentRouteName, resolveAgentParam, isBuiltinAgentName, isBuildAgent, isAskAgent, publishHealthFromResponse, detectDraftResult, detectProposedPlan, buildProgressFromDraftReview, } from '@object-ui/plugin-chatbot';
|
|
21
|
+
import { Bug, PanelLeft, PanelLeftClose, PanelLeftOpen, Share2 } from 'lucide-react';
|
|
22
|
+
import { ChatbotEnhanced, useAgents, useObjectChat, useAiModels, useHitlInChat, resolveDefaultAgentName, PLATFORM_DEFAULT_AGENT, agentRouteName, resolveAgentParam, isBuiltinAgentName, isBuildAgent, isAskAgent, publishHealthFromResponse, detectDraftResult, detectProposedPlan, buildProgressFromDraftReview, } from '@object-ui/plugin-chatbot';
|
|
23
23
|
import { AppHeader } from '../../layout/AppHeader';
|
|
24
24
|
import { fetchPendingDraftCount } from '../../preview/draftStatus';
|
|
25
25
|
import { emitMetadataRefresh } from '../../assistant/assistantBus';
|
|
@@ -30,6 +30,7 @@ import { fetchConversation, sanitizeChatMessagesForCache, useChatConversation, w
|
|
|
30
30
|
import { useReconcileOnError } from '../../hooks/useReconcileOnError';
|
|
31
31
|
import { ConversationsSidebar } from './ConversationsSidebar';
|
|
32
32
|
import { LiveCanvas } from './LiveCanvas';
|
|
33
|
+
import { BuildDebugDrawer } from './BuildDebugDrawer';
|
|
33
34
|
const DEFAULT_AI_PATH = '/api/v1/ai';
|
|
34
35
|
function partString(part, key) {
|
|
35
36
|
const value = part[key];
|
|
@@ -363,6 +364,11 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
363
364
|
// existing conversation, and the flag is stripped once the fresh id is
|
|
364
365
|
// mirrored into the URL.
|
|
365
366
|
const forceNewConversation = searchParams.get('new') !== null;
|
|
367
|
+
// ADR-0070 "Edit with AI": the package the user opened to edit (from the app
|
|
368
|
+
// list's per-app action). Forwarded to the build agent as `context.packageId`
|
|
369
|
+
// so its metadata reads scope to that app and edits bind to it from the first
|
|
370
|
+
// message (the agent seeds it as the conversation's active package).
|
|
371
|
+
const editPackageId = searchParams.get('package')?.trim() || undefined;
|
|
366
372
|
const navigate = useNavigate();
|
|
367
373
|
const { setContext } = useNavigationContext();
|
|
368
374
|
useEffect(() => {
|
|
@@ -491,6 +497,7 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
491
497
|
const [refreshKey, setRefreshKey] = useState(0);
|
|
492
498
|
const [titleHints, setTitleHints] = useState({});
|
|
493
499
|
const [shareOpen, setShareOpen] = useState(false);
|
|
500
|
+
const [debugOpen, setDebugOpen] = useState(false);
|
|
494
501
|
const [mobileChatsOpen, setMobileChatsOpen] = useState(false);
|
|
495
502
|
const { collapsed: chatsCollapsed, toggle: toggleChatsCollapsed, handleCanvasOpenChange, } = useCollapsibleChatsList();
|
|
496
503
|
// Keyboard shortcuts (ChatGPT/Claude parity): ⌘⇧O new chat, ⌘⇧S toggle list.
|
|
@@ -609,7 +616,7 @@ export function AiChatPage({ apiBase: apiBaseProp, defaultAgent: defaultAgentPro
|
|
|
609
616
|
? t('console.ai.showChats', { defaultValue: 'Show chats' })
|
|
610
617
|
: t('console.ai.hideChats', { defaultValue: 'Hide chats' }), title: chatsCollapsed
|
|
611
618
|
? t('console.ai.showChats', { defaultValue: 'Show chats' })
|
|
612
|
-
: t('console.ai.hideChats', { defaultValue: 'Hide chats' }), "data-testid": "ai-chat-collapse-sidebar-trigger", "aria-pressed": chatsCollapsed, children: chatsCollapsed ? _jsx(PanelLeftOpen, { className: "h-4 w-4" }) : _jsx(PanelLeftClose, { className: "h-4 w-4" }) })] })), _jsx("div", { className: "min-w-0 flex-1", children: _jsx(AppHeader, { variant: "home" }) })] }), noAgents ? (_jsx(AiUnavailable, { hasError: Boolean(agentsError), onRetry: refetchAgents, onHome: () => navigate('/home'), t: t })) : (_jsxs(_Fragment, { children: [_jsx(Sheet, { open: mobileChatsOpen, onOpenChange: setMobileChatsOpen, children: _jsxs(SheetContent, { side: "left", className: "w-[320px] p-0 sm:max-w-[360px]", "data-testid": "ai-chat-mobile-sidebar", children: [_jsxs(SheetHeader, { className: "sr-only", children: [_jsx(SheetTitle, { children: t('console.ai.chats') }), _jsx(SheetDescription, { children: t('console.ai.chatsDescription') })] }), _jsx(ConversationsSidebar, { userId: userId, apiBase: apiBase, activeAgent: activeAgent, refreshKey: refreshKey, titleHints: titleHints, className: "h-full border-r-0", onNavigate: () => setMobileChatsOpen(false) })] }) }), conversationId && (_jsx(ShareDialog, { open: shareOpen, onOpenChange: setShareOpen, objectName: "ai_conversations", recordId: conversationId, recordLabel: "this conversation", apiBase: restApiBase, publicBaseUrl: publicShareBase })), _jsxs("div", { className: "flex min-h-0 flex-1 w-full bg-muted/20", children: [!chatsCollapsed && (_jsx(ConversationsSidebar, { userId: userId, apiBase: apiBase, activeAgent: activeAgent, refreshKey: refreshKey, titleHints: titleHints, className: "hidden w-72 shrink-0 border-r md:flex" })), _jsx("main", { className: "flex min-w-0 flex-1 flex-col", children: _jsx(ChatPane, { agents: agents, agentsLoading: agentsLoading, agentsError: agentsError, activeAgent: activeAgent, chatApi: chatApi, apiBase: apiBase, conversationId: conversationId, initialMessages: initialMessages, onSent: handleSent, onShare: () => setShareOpen(true), onCanvasOpenChange: handleCanvasOpenChange }, `${chatApi ?? 'local'}:${conversationId ?? 'pending'}`) })] })] }))] }));
|
|
619
|
+
: t('console.ai.hideChats', { defaultValue: 'Hide chats' }), "data-testid": "ai-chat-collapse-sidebar-trigger", "aria-pressed": chatsCollapsed, children: chatsCollapsed ? _jsx(PanelLeftOpen, { className: "h-4 w-4" }) : _jsx(PanelLeftClose, { className: "h-4 w-4" }) })] })), _jsx("div", { className: "min-w-0 flex-1", children: _jsx(AppHeader, { variant: "home" }) })] }), noAgents ? (_jsx(AiUnavailable, { hasError: Boolean(agentsError), onRetry: refetchAgents, onHome: () => navigate('/home'), t: t })) : (_jsxs(_Fragment, { children: [_jsx(Sheet, { open: mobileChatsOpen, onOpenChange: setMobileChatsOpen, children: _jsxs(SheetContent, { side: "left", className: "w-[320px] p-0 sm:max-w-[360px]", "data-testid": "ai-chat-mobile-sidebar", children: [_jsxs(SheetHeader, { className: "sr-only", children: [_jsx(SheetTitle, { children: t('console.ai.chats') }), _jsx(SheetDescription, { children: t('console.ai.chatsDescription') })] }), _jsx(ConversationsSidebar, { userId: userId, apiBase: apiBase, activeAgent: activeAgent, refreshKey: refreshKey, titleHints: titleHints, className: "h-full border-r-0", onNavigate: () => setMobileChatsOpen(false) })] }) }), conversationId && (_jsx(ShareDialog, { open: shareOpen, onOpenChange: setShareOpen, objectName: "ai_conversations", recordId: conversationId, recordLabel: "this conversation", apiBase: restApiBase, publicBaseUrl: publicShareBase })), conversationId && isBuildAgent(activeAgent) && (_jsx(BuildDebugDrawer, { apiBase: apiBase, conversationId: conversationId, open: debugOpen, onOpenChange: setDebugOpen })), _jsxs("div", { className: "flex min-h-0 flex-1 w-full bg-muted/20", children: [!chatsCollapsed && (_jsx(ConversationsSidebar, { userId: userId, apiBase: apiBase, activeAgent: activeAgent, refreshKey: refreshKey, titleHints: titleHints, className: "hidden w-72 shrink-0 border-r md:flex" })), _jsx("main", { className: "flex min-w-0 flex-1 flex-col", children: _jsx(ChatPane, { agents: agents, agentsLoading: agentsLoading, agentsError: agentsError, activeAgent: activeAgent, chatApi: chatApi, apiBase: apiBase, conversationId: conversationId, editPackageId: editPackageId, initialMessages: initialMessages, onSent: handleSent, onShare: () => setShareOpen(true), onDebug: () => setDebugOpen(true), showDebug: isBuildAgent(activeAgent), onCanvasOpenChange: handleCanvasOpenChange }, `${chatApi ?? 'local'}:${conversationId ?? 'pending'}`) })] })] }))] }));
|
|
613
620
|
}
|
|
614
621
|
/**
|
|
615
622
|
* Graceful state for `/ai` when the agent catalog resolved empty — shown
|
|
@@ -627,7 +634,7 @@ function AiUnavailable({ hasError, onRetry, onHome, t, }) {
|
|
|
627
634
|
defaultValue: "This deployment doesn't have an AI assistant enabled. Everything else works as usual.",
|
|
628
635
|
}) }), _jsxs("div", { className: "mt-6 flex flex-col items-center gap-3 sm:flex-row", children: [hasError && (_jsx(Button, { variant: "outline", onClick: onRetry, "data-testid": "ai-unavailable-retry", children: t('console.ai.unavailableRetry', { defaultValue: 'Try again' }) })), _jsx(Button, { onClick: onHome, "data-testid": "ai-unavailable-home", children: t('console.ai.unavailableHome', { defaultValue: 'Back to home' }) })] })] }) }));
|
|
629
636
|
}
|
|
630
|
-
function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, apiBase, conversationId, initialMessages, onSent, onShare, onCanvasOpenChange, }) {
|
|
637
|
+
function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, apiBase, conversationId, editPackageId, initialMessages, onSent, onShare, onDebug, showDebug, onCanvasOpenChange, }) {
|
|
631
638
|
const { t } = useObjectTranslation();
|
|
632
639
|
const navigate = useNavigate();
|
|
633
640
|
// The agent dropdown is a LAUNCHER now (not an in-surface mode toggle): it
|
|
@@ -692,9 +699,18 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
|
|
|
692
699
|
// ADR-0013 D2: reconcile a stream-transport failure instead of blindly
|
|
693
700
|
// retrying. Shared across chat surfaces — see useReconcileOnError.
|
|
694
701
|
const { errorSuppressed, handleChatError, setMessagesRef, resetSuppression } = useReconcileOnError({ chatApi, conversationId });
|
|
702
|
+
// ADR-0028: plan-filtered selectable AI model on the full-page Build/Ask
|
|
703
|
+
// surface. The footer <select> in ChatbotEnhanced renders only for 2+ models,
|
|
704
|
+
// so free / single-model envs see nothing. Mirrors ConsoleFloatingChatbot;
|
|
705
|
+
// the chosen model rides each request via useObjectChat's `model` below.
|
|
706
|
+
const { models: aiModels, defaultModelId } = useAiModels({ apiBase });
|
|
707
|
+
const [selectedModelId, setSelectedModelId] = useState(undefined);
|
|
708
|
+
const effectiveModelId = selectedModelId ?? defaultModelId;
|
|
695
709
|
const { messages, isLoading, error, sendMessage, stop, reload, clear, setMessages, } = useObjectChat({
|
|
696
710
|
api: chatApi,
|
|
697
711
|
conversationId,
|
|
712
|
+
// ADR-0028: the user's picked model (or the env default) rides each request.
|
|
713
|
+
model: effectiveModelId,
|
|
698
714
|
onError: handleChatError,
|
|
699
715
|
body: {
|
|
700
716
|
context: {
|
|
@@ -703,6 +719,9 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
|
|
|
703
719
|
// Tell the agent the environment's publish posture so its narration
|
|
704
720
|
// matches reality (an auto-published build is live, not "to publish").
|
|
705
721
|
autoPublishAiBuilds: getRuntimeConfig().features.autoPublishAiBuilds,
|
|
722
|
+
// ADR-0070 "Edit with AI": scope the build agent to the app the user
|
|
723
|
+
// opened to edit. Cloud seeds it as the conversation's active package.
|
|
724
|
+
...(editPackageId ? { packageId: editPackageId } : {}),
|
|
706
725
|
},
|
|
707
726
|
},
|
|
708
727
|
initialMessages: hydrated,
|
|
@@ -741,7 +760,7 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
|
|
|
741
760
|
sendMessage(content, files);
|
|
742
761
|
onSent(content);
|
|
743
762
|
}, [sendMessage, onSent]);
|
|
744
|
-
const headerSlot = (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b border-border/50 px-4 pb-2 pt-3 sm:px-6", children: [_jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: showAgentLauncher ? (_jsxs(Select, { value: activeAgent, onValueChange: (name) => navigate(`/ai/${agentRouteName(name)}`), disabled: agentsLoading, children: [_jsx(SelectTrigger, { className: "h-7 w-auto min-w-0 border-0 bg-transparent px-1.5 text-xs shadow-none hover:bg-accent focus:ring-0 focus:ring-offset-0 focus-visible:ring-1 focus-visible:ring-border/80 focus-visible:ring-offset-0 sm:min-w-[160px]", "data-testid": "ai-chat-agent-picker", "aria-label": t('console.ai.switchAssistant', { defaultValue: 'Switch assistant' }), children: _jsx(SelectValue, { placeholder: t('console.ai.chooseAgent', { defaultValue: 'Choose assistant…' }) }) }), _jsx(SelectContent, { align: "start", children: agents.map((agent) => (_jsxs(SelectItem, { value: agent.name, className: "text-xs", children: [_jsx("span", { className: "font-medium", children: localizeAgentLabel(t, agent.name, agent.label) }), agent.description ? (_jsx("span", { className: "block text-muted-foreground text-[10px] truncate max-w-[260px]", children: agent.description })) : null] }, agent.name))) })] })) : (_jsx("span", { className: "truncate text-xs font-medium text-foreground/85", children: activeAgentLabel })) }),
|
|
763
|
+
const headerSlot = (_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b border-border/50 px-4 pb-2 pt-3 sm:px-6", children: [_jsx("div", { className: "flex min-w-0 flex-1 items-center gap-2", children: showAgentLauncher ? (_jsxs(Select, { value: activeAgent, onValueChange: (name) => navigate(`/ai/${agentRouteName(name)}`), disabled: agentsLoading, children: [_jsx(SelectTrigger, { className: "h-7 w-auto min-w-0 border-0 bg-transparent px-1.5 text-xs shadow-none hover:bg-accent focus:ring-0 focus:ring-offset-0 focus-visible:ring-1 focus-visible:ring-border/80 focus-visible:ring-offset-0 sm:min-w-[160px]", "data-testid": "ai-chat-agent-picker", "aria-label": t('console.ai.switchAssistant', { defaultValue: 'Switch assistant' }), children: _jsx(SelectValue, { placeholder: t('console.ai.chooseAgent', { defaultValue: 'Choose assistant…' }) }) }), _jsx(SelectContent, { align: "start", children: agents.map((agent) => (_jsxs(SelectItem, { value: agent.name, className: "text-xs", children: [_jsx("span", { className: "font-medium", children: localizeAgentLabel(t, agent.name, agent.label) }), agent.description ? (_jsx("span", { className: "block text-muted-foreground text-[10px] truncate max-w-[260px]", children: agent.description })) : null] }, agent.name))) })] })) : (_jsx("span", { className: "truncate text-xs font-medium text-foreground/85", children: activeAgentLabel })) }), _jsxs("div", { className: "flex shrink-0 items-center gap-1", children: [showDebug && onDebug ? (_jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7 text-muted-foreground hover:text-foreground", onClick: onDebug, disabled: !conversationId, "aria-label": "Build Doctor", "data-testid": "ai-chat-debug-button", title: conversationId ? 'Build Doctor — what actually landed?' : 'Send a message first', children: _jsx(Bug, { className: "h-3.5 w-3.5" }) })) : null, _jsx(Button, { variant: "ghost", size: "icon", className: "h-7 w-7 text-muted-foreground hover:text-foreground", onClick: onShare, disabled: !conversationId, "aria-label": t('console.ai.share'), "data-testid": "ai-chat-share-button", title: conversationId ? t('console.ai.shareTitle') : t('console.ai.shareDisabledTitle'), children: _jsx(Share2, { className: "h-3.5 w-3.5" }) })] }), agentsError ? (_jsx("span", { className: "basis-full text-[10px] text-amber-700 dark:text-amber-400", title: agentsError.message, children: t('console.ai.offlineDemoMode') })) : null] }));
|
|
745
764
|
return (_jsxs("div", { ref: split.containerRef, className: "relative flex min-h-0 flex-1 px-0", children: [_jsx("div", { "data-chat-column": true, className: canvasApp
|
|
746
765
|
? 'flex min-h-0 shrink-0 justify-center'
|
|
747
766
|
: 'flex min-h-0 flex-1 justify-center', style: canvasApp ? { width: split.width } : undefined, children: _jsx(ChatbotEnhanced, { className: "min-h-0 flex-1 bg-background md:max-w-5xl", onUpgrade: () => window.open(cloudPricingDeepLink(), '_blank', 'noopener,noreferrer'), surface: "plain", maxHeight: "100%", headerSlot: headerSlot, messages: messages, placeholder: activeAgent
|
|
@@ -775,7 +794,11 @@ function ChatPane({ agents, agentsLoading, agentsError, activeAgent, chatApi, ap
|
|
|
775
794
|
stopResponse: t('console.ai.stopResponse'),
|
|
776
795
|
trace: t('console.ai.trace'),
|
|
777
796
|
viewTrace: t('console.ai.viewTrace'),
|
|
778
|
-
},
|
|
797
|
+
},
|
|
798
|
+
// ADR-0028: selectable AI model — ChatbotEnhanced renders the footer
|
|
799
|
+
// <select> only when 2+ models are offered (free / single-model envs
|
|
800
|
+
// see none). The picked model flows to useObjectChat above.
|
|
801
|
+
models: aiModels, selectedModelId: effectiveModelId, onModelChange: setSelectedModelId, suggestions: suggestions, onSendMessage: handleSend, onClear: clear, hideClearBar: true, onStop: isLoading ? stop : undefined, onReload: reload, isLoading: isLoading, error: errorSuppressed ? undefined : error, enableMarkdown: true, onToolApprove: hitl.decide, toolDecisions: hitl.decisions, toolApproveLabel: "Approve & run", toolDenyLabel: "Reject", toolDenyReason: "Operator rejected from chat",
|
|
779
802
|
// Build-tree "Open app": jump straight into the app the agent just built.
|
|
780
803
|
onOpenBuiltApp: (appName, appSegment) => navigate(`/apps/${encodeURIComponent(appSegment ?? appName)}`), openBuiltAppLabel: t('console.ai.openBuiltApp', { defaultValue: 'Open app' }),
|
|
781
804
|
// Live lifecycle truth for draft cards: the server's pending count per
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BuildDebugDrawer — self-serve "what actually landed?" panel for a build
|
|
3
|
+
* conversation. Opens a right-side sheet, calls the admin build-debug endpoint
|
|
4
|
+
* (see buildDebugApi.ts), and renders the reconciliation: agent-CLAIMED vs LIVE
|
|
5
|
+
* `sys_metadata`. The headline is the verdict + the two failure modes the chat
|
|
6
|
+
* can't show — PROPOSED-BUT-ORPHANED (a confirm card no turn applied) and
|
|
7
|
+
* CLAIMED-BUT-MISSING (said applied, isn't live). Read-only; no DB credentials.
|
|
8
|
+
*
|
|
9
|
+
* Distinct from `useReconcileOnError` (ADR-0013 D2 stream-failure recovery) —
|
|
10
|
+
* this reconciles the BUILD against live metadata, not a transport drop.
|
|
11
|
+
*/
|
|
12
|
+
import React from 'react';
|
|
13
|
+
interface BuildDebugDrawerProps {
|
|
14
|
+
apiBase: string;
|
|
15
|
+
conversationId?: string;
|
|
16
|
+
open: boolean;
|
|
17
|
+
onOpenChange: (open: boolean) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function BuildDebugDrawer({ apiBase, conversationId, open, onOpenChange }: BuildDebugDrawerProps): React.JSX.Element;
|
|
20
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
|
|
3
|
+
/**
|
|
4
|
+
* BuildDebugDrawer — self-serve "what actually landed?" panel for a build
|
|
5
|
+
* conversation. Opens a right-side sheet, calls the admin build-debug endpoint
|
|
6
|
+
* (see buildDebugApi.ts), and renders the reconciliation: agent-CLAIMED vs LIVE
|
|
7
|
+
* `sys_metadata`. The headline is the verdict + the two failure modes the chat
|
|
8
|
+
* can't show — PROPOSED-BUT-ORPHANED (a confirm card no turn applied) and
|
|
9
|
+
* CLAIMED-BUT-MISSING (said applied, isn't live). Read-only; no DB credentials.
|
|
10
|
+
*
|
|
11
|
+
* Distinct from `useReconcileOnError` (ADR-0013 D2 stream-failure recovery) —
|
|
12
|
+
* this reconciles the BUILD against live metadata, not a transport drop.
|
|
13
|
+
*/
|
|
14
|
+
import { useEffect, useState } from 'react';
|
|
15
|
+
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription, } from '@object-ui/components';
|
|
16
|
+
import { Bug, CheckCircle2, AlertTriangle, XCircle, Loader2, CircleSlash } from 'lucide-react';
|
|
17
|
+
import { fetchBuildDebug } from './buildDebugApi';
|
|
18
|
+
export function BuildDebugDrawer({ apiBase, conversationId, open, onOpenChange }) {
|
|
19
|
+
const [report, setReport] = useState(null);
|
|
20
|
+
const [loading, setLoading] = useState(false);
|
|
21
|
+
const [error, setError] = useState(null);
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!open || !conversationId)
|
|
24
|
+
return;
|
|
25
|
+
let cancelled = false;
|
|
26
|
+
setLoading(true);
|
|
27
|
+
setError(null);
|
|
28
|
+
setReport(null);
|
|
29
|
+
fetchBuildDebug(apiBase, conversationId)
|
|
30
|
+
.then((r) => {
|
|
31
|
+
if (cancelled)
|
|
32
|
+
return;
|
|
33
|
+
if (!r)
|
|
34
|
+
setError('Not available — the conversation was not found or you are not authorized.');
|
|
35
|
+
else
|
|
36
|
+
setReport(r);
|
|
37
|
+
})
|
|
38
|
+
.catch((e) => {
|
|
39
|
+
if (!cancelled)
|
|
40
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
41
|
+
})
|
|
42
|
+
.finally(() => {
|
|
43
|
+
if (!cancelled)
|
|
44
|
+
setLoading(false);
|
|
45
|
+
});
|
|
46
|
+
return () => {
|
|
47
|
+
cancelled = true;
|
|
48
|
+
};
|
|
49
|
+
}, [open, conversationId, apiBase]);
|
|
50
|
+
const rec = report?.reconciliation;
|
|
51
|
+
const problems = rec ? rec.orphaned.length + rec.missing.length + rec.errors.length : 0;
|
|
52
|
+
return (_jsx(Sheet, { open: open, onOpenChange: onOpenChange, children: _jsxs(SheetContent, { side: "right", className: "w-full overflow-y-auto sm:max-w-xl", children: [_jsxs(SheetHeader, { children: [_jsxs(SheetTitle, { className: "flex items-center gap-2", children: [_jsx(Bug, { className: "h-4 w-4" }), " Build Doctor"] }), _jsx(SheetDescription, { children: "What the agent claimed vs what is actually live. Read-only diagnostic." })] }), _jsxs("div", { className: "mt-4 space-y-4 text-sm", children: [loading && (_jsxs("div", { className: "flex items-center gap-2 text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), " Reconciling\u2026"] })), error && !loading && (_jsx("div", { className: "rounded-md border border-destructive/30 bg-destructive/10 p-3 text-destructive", children: error })), report && !loading && (_jsxs(_Fragment, { children: [_jsxs("div", { className: "rounded-md border bg-muted/30 p-3 text-xs text-muted-foreground", children: [_jsx("div", { className: "font-medium text-foreground", children: report.title ?? '(untitled)' }), _jsxs("div", { className: "mt-1", children: [report.summary.userTurns, " turn(s) \u00B7 ", report.summary.messages, " msgs \u00B7", ' ', report.summary.totalTokens.toLocaleString(), " tok \u00B7", ' ', (report.summary.llmMs / 1000).toFixed(1), "s LLM", report.summary.models.length ? ` · ${report.summary.models.join(', ')}` : ''] })] }), rec && (_jsxs("div", { className: rec.ok
|
|
53
|
+
? 'flex items-center gap-2 rounded-md border border-emerald-500/30 bg-emerald-500/10 p-3 text-emerald-700 dark:text-emerald-400'
|
|
54
|
+
: 'flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/10 p-3 text-destructive', children: [rec.ok ? _jsx(CheckCircle2, { className: "h-4 w-4" }) : _jsx(AlertTriangle, { className: "h-4 w-4" }), _jsx("span", { className: "font-medium", children: rec.ok
|
|
55
|
+
? `All ${rec.liveCount} attempted change(s) are live — nothing evaporated.`
|
|
56
|
+
: `${problems} discrepancy(ies) — what the chat said doesn't match what's live.` })] })), rec && rec.orphaned.length > 0 && (_jsx(FindingSection, { icon: _jsx(CircleSlash, { className: "h-4 w-4 text-destructive" }), title: "Proposed but never applied", hint: "A confirm card the agent proposed but no later turn applied \u2014 the change silently evaporated.", findings: rec.orphaned, tone: "destructive" })), rec && rec.missing.length > 0 && (_jsx(FindingSection, { icon: _jsx(AlertTriangle, { className: "h-4 w-4 text-amber-600" }), title: "Claimed but missing", hint: "A tool result said it was applied, but the artifact isn't live in sys_metadata.", findings: rec.missing, tone: "amber" })), rec && rec.errors.length > 0 && (_jsx(FindingSection, { icon: _jsx(XCircle, { className: "h-4 w-4 text-destructive" }), title: "Tool errors", hint: "Tool calls that returned an error during the build.", findings: rec.errors, tone: "destructive" })), report.verify && (_jsxs("div", { className: "rounded-md border p-3 text-xs", children: [_jsx("div", { className: "font-medium text-foreground", children: "Build check (verify_build)" }), _jsxs("div", { className: "mt-1 text-muted-foreground", children: ["Your app:", ' ', report.verify.userIssues.length === 0 ? (_jsx("span", { className: "text-emerald-600 dark:text-emerald-400", children: "0 issues" })) : (_jsxs("span", { className: "text-destructive", children: [report.verify.userIssues.length, " issue(s)"] })), report.verify.platformNoise > 0
|
|
57
|
+
? ` · ${report.verify.platformNoise} platform sys_* finding(s) hidden`
|
|
58
|
+
: ''] }), report.verify.userIssues.map((is, i) => (_jsxs("div", { className: "mt-1 text-destructive", children: ["[", is.severity, "] ", is.code, " ", is.artifact ? `${is.artifact.type}:${is.artifact.name}` : ''] }, i)))] })), report.pendingActions.length > 0 && (_jsxs("div", { className: "rounded-md border p-3 text-xs", children: [_jsx("div", { className: "font-medium text-foreground", children: "Pending actions" }), report.pendingActions.map((p, i) => (_jsxs("div", { className: "mt-1 text-muted-foreground", children: [p.tool ?? '?', " \u00B7 ", p.object ?? '-', " \u00B7 ", _jsx("span", { className: "font-mono", children: p.status ?? '-' })] }, i)))] })), _jsxs("details", { className: "rounded-md border p-3 text-xs", children: [_jsxs("summary", { className: "cursor-pointer font-medium text-foreground", children: ["Timeline (", report.timeline.length, ")"] }), _jsx("div", { className: "mt-2 space-y-1 font-mono text-[11px] leading-relaxed", children: report.timeline.map((e, i) => (_jsx(TimelineRow, { entry: e }, i))) })] })] }))] })] }) }));
|
|
59
|
+
}
|
|
60
|
+
function FindingSection({ icon, title, hint, findings, tone, }) {
|
|
61
|
+
const border = tone === 'destructive' ? 'border-destructive/30' : 'border-amber-500/30';
|
|
62
|
+
return (_jsxs("div", { className: `rounded-md border ${border} p-3`, children: [_jsxs("div", { className: "flex items-center gap-2 font-medium text-foreground", children: [icon, " ", title, " (", findings.length, ")"] }), _jsx("div", { className: "mt-1 text-xs text-muted-foreground", children: hint }), _jsx("div", { className: "mt-2 space-y-1", children: findings.map((f, i) => (_jsxs("div", { className: "font-mono text-xs", children: [f.t ? `${f.t} · ` : '', f.tool, " \u2192 ", f.artifact.type, ":", f.artifact.name, _jsxs("span", { className: "text-muted-foreground", children: [" (", f.status, ")"] })] }, i))) })] }));
|
|
63
|
+
}
|
|
64
|
+
function TimelineRow({ entry }) {
|
|
65
|
+
if (entry.kind === 'user') {
|
|
66
|
+
return (_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: entry.t }), " \uD83D\uDC64 ", entry.text] }));
|
|
67
|
+
}
|
|
68
|
+
if (entry.kind === 'assistant-text') {
|
|
69
|
+
return (_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: entry.t }), " \uD83E\uDD16 ", entry.text] }));
|
|
70
|
+
}
|
|
71
|
+
if (entry.kind === 'assistant-calls') {
|
|
72
|
+
return (_jsxs("div", { children: [_jsx("span", { className: "text-muted-foreground", children: entry.t }), " \uD83E\uDD16 \u2192", ' ', entry.calls.map((c) => c.name).join(', ')] }));
|
|
73
|
+
}
|
|
74
|
+
return (_jsxs("div", { className: entry.isError ? 'text-destructive' : '', children: [_jsx("span", { className: "text-muted-foreground", children: entry.t }), " \u21B3 ", entry.name, entry.status ? ` (${entry.status})` : ''] }));
|
|
75
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Client for the admin build-debug endpoint
|
|
3
|
+
* (`GET /api/v1/ai/conversations/:id/debug`, service-ai).
|
|
4
|
+
*
|
|
5
|
+
* The chat renders the build agent's self-report, never what actually landed.
|
|
6
|
+
* This fetches the server-side reconciliation — agent-CLAIMED vs LIVE
|
|
7
|
+
* `sys_metadata` — so a signed-in admin can diagnose a build that "went wrong"
|
|
8
|
+
* from the browser, with no DB credentials. Mirrors `fetchConversation`'s
|
|
9
|
+
* same-origin cookie auth.
|
|
10
|
+
*/
|
|
11
|
+
export interface ArtifactRef {
|
|
12
|
+
type: string;
|
|
13
|
+
name: string;
|
|
14
|
+
}
|
|
15
|
+
export interface MutationFinding {
|
|
16
|
+
t?: string;
|
|
17
|
+
tool: string;
|
|
18
|
+
status: string;
|
|
19
|
+
artifact: ArtifactRef;
|
|
20
|
+
}
|
|
21
|
+
export interface VerifyIssue {
|
|
22
|
+
severity: string;
|
|
23
|
+
code: string;
|
|
24
|
+
artifact?: ArtifactRef;
|
|
25
|
+
}
|
|
26
|
+
export interface VerifySummary {
|
|
27
|
+
status: string;
|
|
28
|
+
errors: number;
|
|
29
|
+
warnings: number;
|
|
30
|
+
userIssues: VerifyIssue[];
|
|
31
|
+
platformNoise: number;
|
|
32
|
+
}
|
|
33
|
+
export type TimelineEntry = {
|
|
34
|
+
t: string;
|
|
35
|
+
kind: 'user';
|
|
36
|
+
text: string;
|
|
37
|
+
} | {
|
|
38
|
+
t: string;
|
|
39
|
+
kind: 'assistant-text';
|
|
40
|
+
text: string;
|
|
41
|
+
model?: string;
|
|
42
|
+
tokens?: number;
|
|
43
|
+
ms?: number;
|
|
44
|
+
} | {
|
|
45
|
+
t: string;
|
|
46
|
+
kind: 'assistant-calls';
|
|
47
|
+
calls: Array<{
|
|
48
|
+
name: string;
|
|
49
|
+
args: unknown;
|
|
50
|
+
}>;
|
|
51
|
+
model?: string;
|
|
52
|
+
tokens?: number;
|
|
53
|
+
ms?: number;
|
|
54
|
+
} | {
|
|
55
|
+
t: string;
|
|
56
|
+
kind: 'tool-result';
|
|
57
|
+
name: string;
|
|
58
|
+
isError: boolean;
|
|
59
|
+
status?: string;
|
|
60
|
+
preview: string;
|
|
61
|
+
};
|
|
62
|
+
export interface BuildDebugReport {
|
|
63
|
+
conversationId: string;
|
|
64
|
+
title: string | null;
|
|
65
|
+
summary: {
|
|
66
|
+
models: string[];
|
|
67
|
+
userTurns: number;
|
|
68
|
+
messages: number;
|
|
69
|
+
totalTokens: number;
|
|
70
|
+
llmMs: number;
|
|
71
|
+
};
|
|
72
|
+
reconciliation: {
|
|
73
|
+
orphaned: MutationFinding[];
|
|
74
|
+
missing: MutationFinding[];
|
|
75
|
+
errors: MutationFinding[];
|
|
76
|
+
liveCount: number;
|
|
77
|
+
ok: boolean;
|
|
78
|
+
};
|
|
79
|
+
verify: VerifySummary | null;
|
|
80
|
+
timeline: TimelineEntry[];
|
|
81
|
+
pendingActions: Array<{
|
|
82
|
+
tool: string | null;
|
|
83
|
+
object: string | null;
|
|
84
|
+
status: string | null;
|
|
85
|
+
error: string | null;
|
|
86
|
+
createdAt: string | null;
|
|
87
|
+
}>;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Fetch the reconciliation report for a build conversation. Returns null on
|
|
91
|
+
* 403/404 (not authorized / unknown conversation) so the caller can show an
|
|
92
|
+
* empty state instead of throwing.
|
|
93
|
+
*/
|
|
94
|
+
export declare function fetchBuildDebug(apiBase: string, conversationId: string): Promise<BuildDebugReport | null>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
+
/**
|
|
3
|
+
* Fetch the reconciliation report for a build conversation. Returns null on
|
|
4
|
+
* 403/404 (not authorized / unknown conversation) so the caller can show an
|
|
5
|
+
* empty state instead of throwing.
|
|
6
|
+
*/
|
|
7
|
+
export async function fetchBuildDebug(apiBase, conversationId) {
|
|
8
|
+
const res = await fetch(`${apiBase}/conversations/${encodeURIComponent(conversationId)}/debug`, {
|
|
9
|
+
credentials: 'include',
|
|
10
|
+
});
|
|
11
|
+
if (res.status === 404 || res.status === 403 || res.status === 401)
|
|
12
|
+
return null;
|
|
13
|
+
if (!res.ok)
|
|
14
|
+
throw new Error(`GET build debug failed: ${res.status}`);
|
|
15
|
+
return (await res.json());
|
|
16
|
+
}
|
|
@@ -99,12 +99,21 @@ export function OrganizationsPage() {
|
|
|
99
99
|
return;
|
|
100
100
|
if (isOrganizationsLoading)
|
|
101
101
|
return;
|
|
102
|
-
if (
|
|
103
|
-
return; // came to
|
|
102
|
+
if (wantsCreate)
|
|
103
|
+
return; // came to create — don't bounce
|
|
104
104
|
if (orgList.length !== 1)
|
|
105
105
|
return;
|
|
106
106
|
autoSelectedRef.current = true;
|
|
107
|
-
|
|
107
|
+
// Single-org users have no real choice to make. In manage mode (`?manage=1`,
|
|
108
|
+
// used by the Cloud app "Members" nav and the avatar "My Organizations"
|
|
109
|
+
// entry), skip the pointless one-item picker and deep-link straight to that
|
|
110
|
+
// org's member management; otherwise switch into the org and land on home.
|
|
111
|
+
if (manageMode) {
|
|
112
|
+
navigate(`/organizations/${orgList[0].slug}/members`, { replace: true });
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
void handleSelect(orgList[0]);
|
|
116
|
+
}
|
|
108
117
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
109
118
|
}, [isOrganizationsLoading, orgList.length, manageMode, wantsCreate]);
|
|
110
119
|
// Open the create dialog when arriving via the header "Create workspace"
|
|
@@ -119,7 +128,7 @@ export function OrganizationsPage() {
|
|
|
119
128
|
// Show a spinner while we're either still loading, or about to auto-redirect
|
|
120
129
|
// because there's only one org. This prevents the picker from briefly
|
|
121
130
|
// flashing on screen for single-org users.
|
|
122
|
-
const willAutoSelect = !
|
|
131
|
+
const willAutoSelect = !wantsCreate && !isOrganizationsLoading && orgList.length === 1;
|
|
123
132
|
if (isOrganizationsLoading || willAutoSelect) {
|
|
124
133
|
return (_jsx("div", { className: "flex flex-1 items-center justify-center py-20", children: _jsx(Loader2, { className: "h-5 w-5 animate-spin text-muted-foreground" }) }));
|
|
125
134
|
}
|
|
@@ -45,7 +45,7 @@ export function OrganizationLayout() {
|
|
|
45
45
|
{ to: 'invitations', label: t('organization.tabs.invitations', { defaultValue: 'Invitations' }) },
|
|
46
46
|
{ to: 'settings', label: t('organization.tabs.settings', { defaultValue: 'Settings' }) },
|
|
47
47
|
];
|
|
48
|
-
return (_jsxs("div", { className: "flex min-h-svh w-full flex-col bg-background", "data-testid": "organization-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: "orgs" }) }), _jsx("div", { className: "border-b", children: _jsxs("div", { className: "mx-auto w-full max-w-5xl px-4 sm:px-6 pt-6", children: [_jsxs("button", { onClick: () => navigate('/organizations'), className: "mb-3 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-3.5 w-3.5" }), t('organization.backToList', { defaultValue: 'Back to organizations' })] }), _jsx("div", { className: "flex items-center justify-between gap-4", children: _jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "truncate text-2xl font-bold tracking-tight", children: org.name }), _jsx("p", { className: "text-xs text-muted-foreground", children: org.slug })] }) }), _jsx("nav", { className: "mt-4 flex gap-1 -mb-px", children: tabs.map((tab) => (_jsx(NavLink, { to: tab.to, end: true, className: ({ isActive }) => `border-b-2 px-3 py-2 text-sm font-medium transition-colors ${isActive
|
|
48
|
+
return (_jsxs("div", { className: "flex min-h-svh w-full flex-col bg-background", "data-testid": "organization-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: "orgs" }) }), _jsx("div", { className: "border-b", children: _jsxs("div", { className: "mx-auto w-full max-w-5xl px-4 sm:px-6 pt-6", children: [(organizations ?? []).length > 1 && (_jsxs("button", { onClick: () => navigate('/organizations'), className: "mb-3 inline-flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground", children: [_jsx(ArrowLeft, { className: "h-3.5 w-3.5" }), t('organization.backToList', { defaultValue: 'Back to organizations' })] })), _jsx("div", { className: "flex items-center justify-between gap-4", children: _jsxs("div", { className: "min-w-0", children: [_jsx("h1", { className: "truncate text-2xl font-bold tracking-tight", children: org.name }), _jsx("p", { className: "text-xs text-muted-foreground", children: org.slug })] }) }), _jsx("nav", { className: "mt-4 flex gap-1 -mb-px", children: tabs.map((tab) => (_jsx(NavLink, { to: tab.to, end: true, className: ({ isActive }) => `border-b-2 px-3 py-2 text-sm font-medium transition-colors ${isActive
|
|
49
49
|
? 'border-primary text-foreground'
|
|
50
50
|
: 'border-transparent text-muted-foreground hover:text-foreground'}`, children: tab.label }, tab.to))) })] }) }), _jsx("main", { className: "flex-1 min-w-0 overflow-auto", children: _jsx("div", { className: "mx-auto w-full max-w-5xl px-4 sm:px-6 py-6", children: _jsx(Outlet, { context: { org } }) }) })] }));
|
|
51
51
|
}
|
package/dist/layout/AppHeader.js
CHANGED
|
@@ -20,11 +20,12 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
20
20
|
*/
|
|
21
21
|
import { useLocation, useParams, Link, useNavigate } from 'react-router-dom';
|
|
22
22
|
import { Button, DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuGroup, Avatar, AvatarImage, AvatarFallback, cn, } from '@object-ui/components';
|
|
23
|
-
import { Search, HelpCircle, ChevronDown, Check, Lock, LogOut,
|
|
23
|
+
import { Search, HelpCircle, ChevronDown, Check, Lock, LogOut, Plus, Layers, Bot, User, BookOpen, ExternalLink, Keyboard, } from 'lucide-react';
|
|
24
24
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
25
25
|
import { useOffline } from '@object-ui/react';
|
|
26
26
|
import { PresenceAvatars, useTenantPresence } from '@object-ui/collaboration';
|
|
27
27
|
import { ModeToggle } from './ModeToggle';
|
|
28
|
+
import { WorkspaceSwitcher } from './WorkspaceSwitcher';
|
|
28
29
|
import { LocaleSwitcher } from './LocaleSwitcher';
|
|
29
30
|
import { ConnectionStatus } from './ConnectionStatus';
|
|
30
31
|
import { InboxPopover } from './InboxPopover';
|
|
@@ -558,7 +559,7 @@ export function AppHeader({ variant, appName, objects, connectionState, presence
|
|
|
558
559
|
}
|
|
559
560
|
}
|
|
560
561
|
const lastSegmentLabel = extraSegments[extraSegments.length - 1]?.label || appName || '';
|
|
561
|
-
return (_jsxs("div", { className: "flex items-center justify-between w-full h-full", children: [_jsxs("div", { className: "flex items-center min-w-0 flex-1", children: [_jsx(Link, { to: "/home", className: cn("flex items-center justify-center h-7 w-7 shrink-0 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", isApp && "hidden sm:flex"), title: getProductName(), children: _jsx(Layers, { className: "h-4 w-4" }) }), resolvedVariant === 'home' && (_jsx("span", { className: "hidden sm:inline ml-2 text-sm font-semibold tracking-tight", children: getProductName() })), resolvedVariant === 'orgs' && (_jsxs(_Fragment, { children: [_jsx(PathSep, {}), _jsx("span", { className: "text-sm font-medium text-foreground/80 px-1.5", children: t('organizations.title', { defaultValue: 'Organizations' }) })] })), isApp && (_jsxs(_Fragment, { children: [_jsx(LocalizedSidebarTrigger, { className: "lg:hidden shrink-0 ml-1", "aria-label": t('common.toggleSidebar', { defaultValue: 'Toggle sidebar' }) }), activeAppName && onAppChange ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "hidden sm:flex items-center", children: _jsx(PathSep, {}) }), _jsx("div", { className: "hidden sm:flex items-center", children: _jsx(AppSwitcher, { activeAppName: activeAppName, onAppChange: onAppChange }) })] })) : appName ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "hidden sm:flex items-center", children: _jsx(PathSep, {}) }), _jsx("span", { className: "hidden sm:inline text-sm font-medium text-foreground/80 px-1.5", children: appName })] })) : null, extraSegments.map((seg, i) => {
|
|
562
|
+
return (_jsxs("div", { className: "flex items-center justify-between w-full h-full", children: [_jsxs("div", { className: "flex items-center min-w-0 flex-1", children: [_jsx(Link, { to: "/home", className: cn("flex items-center justify-center h-7 w-7 shrink-0 rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors", isApp && "hidden sm:flex"), title: getProductName(), children: _jsx(Layers, { className: "h-4 w-4" }) }), resolvedVariant === 'home' && (_jsx("span", { className: "hidden sm:inline ml-2 text-sm font-semibold tracking-tight", children: getProductName() })), _jsx(WorkspaceSwitcher, {}), resolvedVariant === 'orgs' && (_jsxs(_Fragment, { children: [_jsx(PathSep, {}), _jsx("span", { className: "text-sm font-medium text-foreground/80 px-1.5", children: t('organizations.title', { defaultValue: 'Organizations' }) })] })), isApp && (_jsxs(_Fragment, { children: [_jsx(LocalizedSidebarTrigger, { className: "lg:hidden shrink-0 ml-1", "aria-label": t('common.toggleSidebar', { defaultValue: 'Toggle sidebar' }) }), activeAppName && onAppChange ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "hidden sm:flex items-center", children: _jsx(PathSep, {}) }), _jsx("div", { className: "hidden sm:flex items-center", children: _jsx(AppSwitcher, { activeAppName: activeAppName, onAppChange: onAppChange }) })] })) : appName ? (_jsxs(_Fragment, { children: [_jsx("span", { className: "hidden sm:flex items-center", children: _jsx(PathSep, {}) }), _jsx("span", { className: "hidden sm:inline text-sm font-medium text-foreground/80 px-1.5", children: appName })] })) : null, extraSegments.map((seg, i) => {
|
|
562
563
|
const isLast = i === extraSegments.length - 1;
|
|
563
564
|
return (_jsxs("span", { className: "hidden sm:flex items-center min-w-0", children: [_jsx(PathSep, {}), seg.siblings && seg.siblings.length > 1 ? (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { className: `flex items-center gap-1 rounded-md px-1.5 py-1 text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring hover:bg-accent hover:text-foreground ${!isLast ? 'text-foreground/60' : 'text-foreground/80'}`, children: [seg.label, _jsx(ChevronDown, { className: "h-3.5 w-3.5 text-muted-foreground" })] }), _jsxs(DropdownMenuContent, { align: "start", sideOffset: 8, className: "w-56 max-h-72 overflow-y-auto", children: [_jsx(DropdownMenuLabel, { className: "text-xs text-muted-foreground font-normal", children: "Switch Object" }), _jsx(DropdownMenuSeparator, {}), seg.siblings.map((sibling) => (_jsx(DropdownMenuItem, { asChild: true, children: _jsx(Link, { to: sibling.href, className: "w-full", children: sibling.label }) }, sibling.href)))] })] })) : seg.href ? (_jsx(Link, { to: seg.href, className: `rounded-md px-1.5 py-1 text-sm font-medium transition-colors hover:bg-accent hover:text-foreground truncate max-w-[160px] ${isLast ? 'text-foreground/80' : 'text-foreground/60'}`, children: seg.label })) : (_jsx("span", { className: `px-1.5 py-1 text-sm font-medium truncate max-w-[160px] ${isLast ? 'text-foreground/80' : 'text-foreground/60'}`, children: seg.label }))] }, i));
|
|
564
565
|
}), mobileSwitcher && mobileSwitcher.views.length > 0 ? (mobileSwitcher.views.length > 1 ? (_jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsxs("button", { type: "button", className: "sm:hidden flex items-center gap-0.5 min-w-0 ml-1 rounded-md px-1.5 py-1 text-sm font-medium hover:bg-accent active:bg-accent/80 transition-colors", "aria-label": t('topbar.switchView', { defaultValue: 'Switch view' }), children: [_jsx("span", { className: "truncate max-w-[180px]", children: mobileSwitcher.triggerLabel ??
|
|
@@ -572,7 +573,7 @@ export function AppHeader({ variant, appName, objects, connectionState, presence
|
|
|
572
573
|
}) })] })) : (_jsx("span", { className: "text-sm font-medium sm:hidden truncate min-w-0 ml-1", children: mobileSwitcher.triggerLabel ?? mobileSwitcher.views[0].label }))) : (_jsx("span", { className: "text-sm font-medium sm:hidden truncate min-w-0 ml-1", children: lastSegmentLabel }))] }))] }), _jsxs("div", { className: "flex items-center gap-0.5 sm:gap-1 shrink-0 [&>*+*[data-topbar-group]]:ml-1 [&>[data-topbar-group]+[data-topbar-group]]:border-l [&>[data-topbar-group]+[data-topbar-group]]:border-border/60 [&>[data-topbar-group]+[data-topbar-group]]:pl-1 sm:[&>[data-topbar-group]+[data-topbar-group]]:pl-2 sm:[&>[data-topbar-group]+[data-topbar-group]]:ml-2", children: [!isOnline && (_jsxs("div", { className: "flex items-center gap-1 px-2 py-1 rounded-full bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-200 text-xs font-medium", children: [_jsx("span", { className: "h-2 w-2 rounded-full bg-yellow-500 animate-pulse" }), t('topbar.offline', { defaultValue: 'Offline' })] })), isApp && connectionState && _jsx(ConnectionStatus, { state: connectionState }), isApp && activeUsers.length > 0 && (_jsx("div", { className: "hidden md:flex items-center shrink-0", title: t('topbar.usersOnline', { defaultValue: 'Users currently online' }), children: _jsx(PresenceAvatars, { users: activeUsers, size: "sm", maxVisible: 3, showStatus: true }) })), _jsxs("div", { "data-topbar-group": true, className: "flex items-center gap-0.5 sm:gap-1 shrink-0", children: [_jsxs("button", { type: "button", "data-testid": "action:command-palette:open", "aria-label": t('console.search', { defaultValue: 'Search…' }), "aria-keyshortcuts": "Meta+K Control+K", onClick: openCommandPalette, className: "hidden lg:flex relative items-center gap-2 w-48 xl:w-64 h-8 px-3 text-sm rounded-md border bg-muted/50 text-muted-foreground hover:bg-muted transition-colors", children: [_jsx(Search, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "flex-1 text-left text-xs", children: t('console.search', { defaultValue: 'Search…' }) }), _jsxs("kbd", { className: "pointer-events-none inline-flex h-5 items-center gap-0.5 rounded border bg-background px-1.5 text-[10px] font-medium text-muted-foreground", children: [_jsx("span", { className: "text-xs", children: "\u2318" }), "K"] })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "lg:hidden h-8 w-8 shrink-0", "data-testid": "action:command-palette:open-mobile", onClick: openCommandPalette, "aria-label": t('console.search', { defaultValue: 'Search…' }), children: _jsx(Search, { className: "h-4 w-4" }) })] }), _jsxs("div", { "data-topbar-group": true, className: "flex items-center gap-0.5 shrink-0", children: [_jsx(InboxPopover, { notifications: notifications, unreadCount: unreadCount, pendingApprovalsCount: pendingApprovalsCount, activities: activeActivities, onMarkAllRead: markAllRead, onMarkRead: markNotificationRead }), aiEnabled && (_jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 shrink-0", asChild: true, "aria-label": t('topbar.aiAssistant', { defaultValue: 'AI Assistant' }), children: _jsx(Link, { to: "/ai", children: _jsx(Bot, { className: "h-4 w-4" }) }) })), _jsxs(DropdownMenu, { onOpenChange: (open) => { if (open)
|
|
573
574
|
void loadHelpDocs(); }, children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 hidden md:flex shrink-0", "aria-label": t('sidebar.helpTooltip', { defaultValue: 'Help & Documentation' }), children: _jsx(HelpCircle, { className: "h-4 w-4" }) }) }), _jsxs(DropdownMenuContent, { align: "end", className: "min-w-56 rounded-lg", sideOffset: 4, children: [currentAppDocs.length > 0 && currentAppPackageId ? (_jsxs(DropdownMenuItem, { className: "cursor-pointer", onClick: () => navigate(currentAppDocs.length === 1
|
|
574
575
|
? `/apps/${currentAppPackageId}/docs/${currentAppDocs[0].name}`
|
|
575
|
-
: `/apps/${currentAppPackageId}/docs`), children: [_jsx(BookOpen, { className: "mr-2 h-4 w-4" }), t('help.appDocs', { defaultValue: "This app's docs" })] })) : null, _jsxs(DropdownMenuItem, { className: "cursor-pointer", onClick: () => navigate('/docs'), children: [_jsx(Layers, { className: "mr-2 h-4 w-4" }), t('help.allDocs', { defaultValue: 'All documentation' })] }), isApp ? (_jsxs(DropdownMenuItem, { className: "cursor-pointer", "data-testid": "action:keyboard-shortcuts:open", onClick: openShortcuts, children: [_jsx(Keyboard, { className: "mr-2 h-4 w-4" }), t('help.keyboardShortcuts', { defaultValue: 'Keyboard shortcuts' })] })) : null, _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { asChild: true, className: "cursor-pointer", children: _jsxs("a", { href: "https://docs.objectstack.ai", target: "_blank", rel: "noopener noreferrer", children: [_jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), t('help.onlineDocs', { defaultValue: 'Online documentation' })] }) })] })] })] }), _jsxs("div", { "data-topbar-group": true, className: "flex items-center gap-0.5 shrink-0", children: [" ", _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 shrink-0 rounded-full", children: _jsxs(Avatar, { className: "h-7 w-7 rounded-full", children: [_jsx(AvatarImage, { src: user?.image, alt: user?.name ?? 'User' }), _jsx(AvatarFallback, { className: "rounded-full bg-primary text-primary-foreground text-xs", children: getUserInitials(user) })] }) }) }), _jsxs(DropdownMenuContent, { align: "end", className: "min-w-64 rounded-lg", sideOffset: 4, children: [_jsx(DropdownMenuLabel, { className: "p-0 font-normal", children: _jsxs("div", { className: "flex items-center gap-2 px-2 py-2", children: [_jsxs(Avatar, { className: "h-8 w-8 rounded-lg", children: [_jsx(AvatarImage, { src: user?.image, alt: user?.name ?? 'User' }), _jsx(AvatarFallback, { className: "rounded-lg bg-primary text-primary-foreground", children: getUserInitials(user) })] }), _jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [_jsx("span", { className: "truncate font-semibold", children: user?.name ?? 'User' }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: user?.email ?? '' })] })] }) }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuGroup, { children: [_jsxs(DropdownMenuItem, { onClick: () => navigate('/apps/account/component/account/profile_card'), className: "cursor-pointer", children: [_jsx(User, { className: "mr-2 h-4 w-4" }), t('user.profile', { defaultValue: 'Profile' })] }), hasOrgSection &&
|
|
576
|
+
: `/apps/${currentAppPackageId}/docs`), children: [_jsx(BookOpen, { className: "mr-2 h-4 w-4" }), t('help.appDocs', { defaultValue: "This app's docs" })] })) : null, _jsxs(DropdownMenuItem, { className: "cursor-pointer", onClick: () => navigate('/docs'), children: [_jsx(Layers, { className: "mr-2 h-4 w-4" }), t('help.allDocs', { defaultValue: 'All documentation' })] }), isApp ? (_jsxs(DropdownMenuItem, { className: "cursor-pointer", "data-testid": "action:keyboard-shortcuts:open", onClick: openShortcuts, children: [_jsx(Keyboard, { className: "mr-2 h-4 w-4" }), t('help.keyboardShortcuts', { defaultValue: 'Keyboard shortcuts' })] })) : null, _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { asChild: true, className: "cursor-pointer", children: _jsxs("a", { href: "https://docs.objectstack.ai", target: "_blank", rel: "noopener noreferrer", children: [_jsx(ExternalLink, { className: "mr-2 h-4 w-4" }), t('help.onlineDocs', { defaultValue: 'Online documentation' })] }) })] })] })] }), _jsxs("div", { "data-topbar-group": true, className: "flex items-center gap-0.5 shrink-0", children: [" ", _jsxs(DropdownMenu, { children: [_jsx(DropdownMenuTrigger, { asChild: true, children: _jsx(Button, { variant: "ghost", size: "icon", className: "h-8 w-8 shrink-0 rounded-full", children: _jsxs(Avatar, { className: "h-7 w-7 rounded-full", children: [_jsx(AvatarImage, { src: user?.image, alt: user?.name ?? 'User' }), _jsx(AvatarFallback, { className: "rounded-full bg-primary text-primary-foreground text-xs", children: getUserInitials(user) })] }) }) }), _jsxs(DropdownMenuContent, { align: "end", className: "min-w-64 rounded-lg", sideOffset: 4, children: [_jsx(DropdownMenuLabel, { className: "p-0 font-normal", children: _jsxs("div", { className: "flex items-center gap-2 px-2 py-2", children: [_jsxs(Avatar, { className: "h-8 w-8 rounded-lg", children: [_jsx(AvatarImage, { src: user?.image, alt: user?.name ?? 'User' }), _jsx(AvatarFallback, { className: "rounded-lg bg-primary text-primary-foreground", children: getUserInitials(user) })] }), _jsxs("div", { className: "grid flex-1 text-left text-sm leading-tight", children: [_jsx("span", { className: "truncate font-semibold", children: user?.name ?? 'User' }), _jsx("span", { className: "truncate text-xs text-muted-foreground", children: user?.email ?? '' })] })] }) }), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuGroup, { children: [_jsxs(DropdownMenuItem, { onClick: () => navigate('/apps/account/component/account/profile_card'), className: "cursor-pointer", children: [_jsx(User, { className: "mr-2 h-4 w-4" }), t('user.profile', { defaultValue: 'Profile' })] }), hasOrgSection && !multiOrgDisabled && (_jsxs(DropdownMenuItem, { onClick: () => navigate('/organizations?create=1'), className: "cursor-pointer", "data-testid": "header-create-workspace", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), t('organizations.create', { defaultValue: 'Create workspace' })] })), (metadataApps || [])
|
|
576
577
|
.filter((a) => a.active !== false && a.hidden === true && a.name !== 'account')
|
|
577
578
|
.map((app) => {
|
|
578
579
|
const AppIcon = getIcon(app.icon);
|
|
@@ -104,8 +104,12 @@ function buildChatLocale(language, appLabel, agentName, fallbackAgentLabel, obje
|
|
|
104
104
|
planApprove: '开始搭建',
|
|
105
105
|
planAdjust: '调整方案',
|
|
106
106
|
planBuilt: '已搭建',
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
// These messages the button SENDS must match the cloud confirm gate's
|
|
108
|
+
// APPROVAL_RE (service-ai-studio confirm-gate.ts) or the agent re-proposes
|
|
109
|
+
// and "开始搭建" looks inert — the gate anchors Chinese approval on 确认 /
|
|
110
|
+
// 直接搭建, so a bare "…搭建吧" does NOT match. Keep these 确认-anchored.
|
|
111
|
+
planApproveMessage: '确认,开始搭建。',
|
|
112
|
+
planApproveDefaultsMessage: '确认搭建,未决问题按你的合理假设和默认处理。',
|
|
109
113
|
planAnswer: (question, option) => `关于「${question}」,我选择「${option}」。`,
|
|
110
114
|
publishOk: '已发布,对象已生效。',
|
|
111
115
|
publishFailed: '发布失败',
|
|
@@ -19,16 +19,6 @@ import { useLocation, useNavigate } from 'react-router-dom';
|
|
|
19
19
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@object-ui/components';
|
|
20
20
|
import { getIcon } from '../utils/getIcon';
|
|
21
21
|
import { resolveI18nLabel } from '../utils';
|
|
22
|
-
// Local/Custom scope sentinel — kept inline (not imported from metadata-admin)
|
|
23
|
-
// so this layout module never forms an import cycle with the metadata-admin
|
|
24
|
-
// views. Mirrors `LOCAL_PACKAGE_ID` in views/metadata-admin/package-scope.ts.
|
|
25
|
-
const LOCAL_SCOPE_ID = 'sys_metadata';
|
|
26
|
-
function localScopeLabel() {
|
|
27
|
-
const lang = (typeof document !== 'undefined' && document.documentElement?.lang) ||
|
|
28
|
-
(typeof navigator !== 'undefined' && navigator.language) ||
|
|
29
|
-
'';
|
|
30
|
-
return /^zh/i.test(lang) ? '本地 / 自定义(本环境)' : 'Local / Custom (this env)';
|
|
31
|
-
}
|
|
32
22
|
const ALL_SENTINEL = '__all__';
|
|
33
23
|
/** Read a (possibly dotted) property path off a row, e.g. `manifest.id`. */
|
|
34
24
|
function getByPath(row, key) {
|
|
@@ -117,15 +107,6 @@ function useSelectorOptions(def) {
|
|
|
117
107
|
opts.push({ value, label: typeof labelRaw === 'string' && labelRaw ? labelRaw : value });
|
|
118
108
|
}
|
|
119
109
|
opts.sort((a, b) => a.label.localeCompare(b.label));
|
|
120
|
-
// The package-scope selector gets a stable "Local / Custom (this env)"
|
|
121
|
-
// entry for this environment's runtime, DB-authored metadata — it is
|
|
122
|
-
// never a real package row (`package_id = null` / `sys_metadata`
|
|
123
|
-
// provenance) yet must always be selectable so org-authored items are
|
|
124
|
-
// discoverable and editable. The metadata list/get API already treats
|
|
125
|
-
// `?package=sys_metadata` as exactly this local scope.
|
|
126
|
-
if (/package/i.test(endpoint) && !opts.some((o) => o.value === LOCAL_SCOPE_ID)) {
|
|
127
|
-
opts.push({ value: LOCAL_SCOPE_ID, label: localScopeLabel() });
|
|
128
|
-
}
|
|
129
110
|
setOptions(opts);
|
|
130
111
|
}
|
|
131
112
|
catch {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WorkspaceSwitcher
|
|
3
|
+
*
|
|
4
|
+
* Header-left organization (workspace) switcher — the standard place users
|
|
5
|
+
* expect "which org am I in / switch org" to live (Linear/Vercel/GitHub style).
|
|
6
|
+
*
|
|
7
|
+
* - Single-org users (the vast majority): just the org name, NO dropdown. There
|
|
8
|
+
* is nothing to switch to, so a one-item menu would be pure friction.
|
|
9
|
+
* - Multi-org users: the active org name + a dropdown to switch orgs inline
|
|
10
|
+
* (full-page reload so the active-org context refreshes app-wide, mirroring
|
|
11
|
+
* OrganizationsPage), plus shortcuts to manage members / create a workspace.
|
|
12
|
+
* - No org context at all: renders nothing.
|
|
13
|
+
*/
|
|
14
|
+
export declare function WorkspaceSwitcher(): import("react").JSX.Element | null;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* WorkspaceSwitcher
|
|
4
|
+
*
|
|
5
|
+
* Header-left organization (workspace) switcher — the standard place users
|
|
6
|
+
* expect "which org am I in / switch org" to live (Linear/Vercel/GitHub style).
|
|
7
|
+
*
|
|
8
|
+
* - Single-org users (the vast majority): just the org name, NO dropdown. There
|
|
9
|
+
* is nothing to switch to, so a one-item menu would be pure friction.
|
|
10
|
+
* - Multi-org users: the active org name + a dropdown to switch orgs inline
|
|
11
|
+
* (full-page reload so the active-org context refreshes app-wide, mirroring
|
|
12
|
+
* OrganizationsPage), plus shortcuts to manage members / create a workspace.
|
|
13
|
+
* - No org context at all: renders nothing.
|
|
14
|
+
*/
|
|
15
|
+
import { useEffect, useState } from 'react';
|
|
16
|
+
import { useNavigate } from 'react-router-dom';
|
|
17
|
+
import { useAuth } from '@object-ui/auth';
|
|
18
|
+
import { useObjectTranslation } from '@object-ui/i18n';
|
|
19
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, } from '@object-ui/components';
|
|
20
|
+
import { ChevronsUpDown, Check, Plus, Users } from 'lucide-react';
|
|
21
|
+
import { resolveHomeUrl } from '../console/organizations/resolveHomeUrl';
|
|
22
|
+
function getOrgInitials(name) {
|
|
23
|
+
return name
|
|
24
|
+
.split(/[\s_-]+/)
|
|
25
|
+
.map((w) => w[0])
|
|
26
|
+
.join('')
|
|
27
|
+
.toUpperCase()
|
|
28
|
+
.slice(0, 2);
|
|
29
|
+
}
|
|
30
|
+
function OrgBadge({ name }) {
|
|
31
|
+
return (_jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded bg-muted text-[10px] font-semibold text-muted-foreground", children: getOrgInitials(name) }));
|
|
32
|
+
}
|
|
33
|
+
export function WorkspaceSwitcher() {
|
|
34
|
+
const { t } = useObjectTranslation();
|
|
35
|
+
const navigate = useNavigate();
|
|
36
|
+
const { organizations, activeOrganization, switchOrganization, getAuthConfig } = useAuth();
|
|
37
|
+
const [multiOrgDisabled, setMultiOrgDisabled] = useState(false);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
let cancelled = false;
|
|
40
|
+
getAuthConfig?.()
|
|
41
|
+
.then((cfg) => {
|
|
42
|
+
if (!cancelled)
|
|
43
|
+
setMultiOrgDisabled(cfg?.features?.multiOrgEnabled === false);
|
|
44
|
+
})
|
|
45
|
+
.catch(() => {
|
|
46
|
+
/* leave default — create entry stays available */
|
|
47
|
+
});
|
|
48
|
+
return () => {
|
|
49
|
+
cancelled = true;
|
|
50
|
+
};
|
|
51
|
+
}, [getAuthConfig]);
|
|
52
|
+
const orgList = organizations ?? [];
|
|
53
|
+
const current = activeOrganization ?? orgList[0] ?? null;
|
|
54
|
+
// No organization context (e.g. a brand-new user before provisioning) — show
|
|
55
|
+
// nothing rather than an empty switcher.
|
|
56
|
+
if (!current)
|
|
57
|
+
return null;
|
|
58
|
+
// Single-org: static label, no dropdown.
|
|
59
|
+
if (orgList.length <= 1) {
|
|
60
|
+
return (_jsxs("span", { className: "ml-2 hidden max-w-[12rem] items-center gap-1.5 sm:inline-flex", "data-testid": "workspace-name", children: [_jsx(OrgBadge, { name: current.name }), _jsx("span", { className: "truncate text-sm font-medium text-foreground/80", children: current.name })] }));
|
|
61
|
+
}
|
|
62
|
+
const handleSwitch = async (org) => {
|
|
63
|
+
if (org.id === current.id)
|
|
64
|
+
return;
|
|
65
|
+
try {
|
|
66
|
+
await switchOrganization(org.id);
|
|
67
|
+
// switchOrganization only updates state; reload to home so the new active
|
|
68
|
+
// org propagates to every data scope app-wide (same as OrganizationsPage).
|
|
69
|
+
window.location.href = resolveHomeUrl();
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
console.error('[WorkspaceSwitcher] switch failed', err);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { className: "ml-2 inline-flex max-w-[14rem] items-center gap-1.5 rounded-md px-1.5 py-1 text-sm font-medium text-foreground/80 transition-colors hover:bg-accent hover:text-foreground", "data-testid": "workspace-switcher", children: [_jsx(OrgBadge, { name: current.name }), _jsx("span", { className: "hidden truncate sm:inline", children: current.name }), _jsx(ChevronsUpDown, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" })] }), _jsxs(DropdownMenuContent, { align: "start", className: "w-60", children: [_jsx(DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: t('organization.switcher.label', { defaultValue: 'Switch organization' }) }), orgList.map((org) => (_jsxs(DropdownMenuItem, { onClick: () => handleSwitch(org), className: "cursor-pointer gap-2", children: [_jsx(OrgBadge, { name: org.name }), _jsx("span", { className: "flex-1 truncate", children: org.name }), org.id === current.id && _jsx(Check, { className: "h-4 w-4 shrink-0" })] }, org.id))), _jsx(DropdownMenuSeparator, {}), _jsxs(DropdownMenuItem, { onClick: () => navigate(`/organizations/${current.slug}/members`), className: "cursor-pointer gap-2", "data-testid": "workspace-manage-members", children: [_jsx(Users, { className: "h-4 w-4" }), t('organization.switcher.manageMembers', { defaultValue: 'Manage members' })] }), !multiOrgDisabled && (_jsxs(DropdownMenuItem, { onClick: () => navigate('/organizations?create=1'), className: "cursor-pointer gap-2", "data-testid": "workspace-create", children: [_jsx(Plus, { className: "h-4 w-4" }), t('organizations.create', { defaultValue: 'Create workspace' })] }))] })] }));
|
|
76
|
+
}
|
|
@@ -32,5 +32,5 @@ export interface ManagedByEmptyState {
|
|
|
32
32
|
* `defaultValue` used as the English fallback when a locale lacks the key).
|
|
33
33
|
*/
|
|
34
34
|
type TranslateFn = (key: string, options?: Record<string, unknown>) => string;
|
|
35
|
-
export declare function resolveManagedByEmptyState(managedBy: string | undefined | null, t: TranslateFn): ManagedByEmptyState | undefined;
|
|
35
|
+
export declare function resolveManagedByEmptyState(managedBy: string | undefined | null, t: TranslateFn, objectName?: string | null): ManagedByEmptyState | undefined;
|
|
36
36
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export function resolveManagedByEmptyState(managedBy, t) {
|
|
1
|
+
export function resolveManagedByEmptyState(managedBy, t, objectName) {
|
|
2
2
|
switch (managedBy) {
|
|
3
3
|
case 'system':
|
|
4
4
|
return {
|
|
@@ -17,11 +17,29 @@ export function resolveManagedByEmptyState(managedBy, t) {
|
|
|
17
17
|
}),
|
|
18
18
|
};
|
|
19
19
|
case 'better-auth':
|
|
20
|
+
// `sys_user` is the one identity table with a concrete onboarding
|
|
21
|
+
// answer, so give it actionable guidance: teammates arrive via an
|
|
22
|
+
// org-level invite + SSO just-in-time provisioning (ADR-0024 D9), and
|
|
23
|
+
// app end-users self-register. We deliberately do NOT name the env-level
|
|
24
|
+
// "Invite User" action — it is multi-org-gated and hidden in single-org —
|
|
25
|
+
// nor a "Reset Password" toolbar action, which does not exist (cloud#580).
|
|
26
|
+
// Every other identity table (sessions, accounts, tokens, jwks,
|
|
27
|
+
// verifications, …) is written purely by auth flows, so keep the generic
|
|
28
|
+
// copy — naming an invite/signup CTA on a token list would be wrong.
|
|
29
|
+
if (objectName === 'sys_user') {
|
|
30
|
+
return {
|
|
31
|
+
icon: 'ShieldAlert',
|
|
32
|
+
title: t('list.managedBy.betterAuthUser.title', { defaultValue: 'No users yet' }),
|
|
33
|
+
message: t('list.managedBy.betterAuthUser.message', {
|
|
34
|
+
defaultValue: 'User accounts are provisioned by the authentication provider, not created here. Invite teammates to your organization and they appear automatically on first sign-in (SSO just-in-time provisioning). App end-users arrive when they sign up through your app.',
|
|
35
|
+
}),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
20
38
|
return {
|
|
21
39
|
icon: 'ShieldAlert',
|
|
22
40
|
title: t('list.managedBy.betterAuth.title', { defaultValue: 'No identity records' }),
|
|
23
41
|
message: t('list.managedBy.betterAuth.message', {
|
|
24
|
-
defaultValue: '
|
|
42
|
+
defaultValue: 'These records are created by the authentication provider — through sign-in, provisioning, and security flows — not added by hand here.',
|
|
25
43
|
}),
|
|
26
44
|
};
|
|
27
45
|
default:
|
package/dist/views/ObjectView.js
CHANGED
|
@@ -1189,7 +1189,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
|
|
|
1189
1189
|
virtualScroll: viewDef.virtualScroll ?? listSchema.virtualScroll,
|
|
1190
1190
|
emptyState: viewEmptyState(objectDef.name, viewDef.name || viewDef.id || '', viewDef.emptyState
|
|
1191
1191
|
?? listSchema.emptyState
|
|
1192
|
-
?? resolveManagedByEmptyState(objectDef?.managedBy, t)),
|
|
1192
|
+
?? resolveManagedByEmptyState(objectDef?.managedBy, t, objectDef.name)),
|
|
1193
1193
|
aria: viewDef.aria ?? listSchema.aria,
|
|
1194
1194
|
// Propagate filter/sort as default filters/sort for data flow
|
|
1195
1195
|
...((() => {
|
|
@@ -25,7 +25,7 @@ import { CreatePackageDialog } from './PackagesPage';
|
|
|
25
25
|
import { useMetadataClient, useMetadataTypes, matchesQuery, } from './useMetadata';
|
|
26
26
|
import { getMetadataResource, resolveResourceConfig, } from './registry';
|
|
27
27
|
import { t, tFormat, translateMetadataType, detectLocale } from './i18n';
|
|
28
|
-
import { buildPackageScopeOptions
|
|
28
|
+
import { buildPackageScopeOptions } from './package-scope';
|
|
29
29
|
/**
|
|
30
30
|
* Derive provenance from item._packageId. The `loadMetaFromDb` path
|
|
31
31
|
* tags objects with the synthetic packageId 'sys_metadata' (see
|
|
@@ -190,13 +190,13 @@ function DefaultMetadataList({ type, appName }) {
|
|
|
190
190
|
// scope); when none exists yet, prompt to create a base first.
|
|
191
191
|
const [showCreateBase, setShowCreateBase] = React.useState(false);
|
|
192
192
|
const handleCreate = React.useCallback(() => {
|
|
193
|
-
const
|
|
194
|
-
if (projectPackages !== null &&
|
|
193
|
+
const bases = projectPackages ?? [];
|
|
194
|
+
if (projectPackages !== null && bases.length === 0) {
|
|
195
195
|
setShowCreateBase(true);
|
|
196
196
|
return;
|
|
197
197
|
}
|
|
198
|
-
if (
|
|
199
|
-
navigate(`./new?package=${encodeURIComponent(
|
|
198
|
+
if (bases.length > 0 && !activePackage) {
|
|
199
|
+
navigate(`./new?package=${encodeURIComponent(bases[0].id)}`);
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
202
|
navigate(`./new${pkgSuffix}`);
|
|
@@ -254,12 +254,10 @@ function DefaultMetadataList({ type, appName }) {
|
|
|
254
254
|
if (!activePackage)
|
|
255
255
|
return false;
|
|
256
256
|
const pkg = row.item?._packageId;
|
|
257
|
-
|
|
258
|
-
//
|
|
259
|
-
// (
|
|
260
|
-
|
|
261
|
-
return isLocal;
|
|
262
|
-
return !isLocal && pkg === activePackage;
|
|
257
|
+
// Only rows tagged with the active writable base match. Untagged /
|
|
258
|
+
// `sys_metadata`-provenance legacy rows have no scope of their own
|
|
259
|
+
// (ADR-0070 D5 — the package-less "Local / Custom" scope is removed).
|
|
260
|
+
return pkg === activePackage;
|
|
263
261
|
}), [items, activePackage, config]);
|
|
264
262
|
// User-driven filters (search query + source provenance) on top of scope.
|
|
265
263
|
const filtered = scopedItems.filter((row) => {
|
|
@@ -28,7 +28,7 @@ import { useRecentItems } from '../../context/RecentItemsProvider';
|
|
|
28
28
|
import { useMetadataClient, useMetadataTypes, useGlobalDiagnostics, } from './useMetadata';
|
|
29
29
|
import { MetadataQuickFind } from './QuickFind';
|
|
30
30
|
import { translateMetadataType, translateMetadataDomain, t, tFormat, detectLocale, } from './i18n';
|
|
31
|
-
import { buildPackageScopeOptions
|
|
31
|
+
import { buildPackageScopeOptions } from './package-scope';
|
|
32
32
|
const HIDDEN_TYPES = new Set(['field', 'package']);
|
|
33
33
|
const DOMAIN_ICONS = {
|
|
34
34
|
data: Database,
|
|
@@ -143,10 +143,6 @@ export function StudioHomePage() {
|
|
|
143
143
|
return false;
|
|
144
144
|
if (!activePackage)
|
|
145
145
|
return false;
|
|
146
|
-
// Local/Custom scope: show every runtime-creatable type so the user can
|
|
147
|
-
// start authoring any kind of metadata here, even with zero items yet.
|
|
148
|
-
if (activePackage === LOCAL_PACKAGE_ID)
|
|
149
|
-
return e.allowOrgOverride || e.allowRuntimeCreate;
|
|
150
146
|
return (packagesByType[e.type] ?? []).includes(activePackage);
|
|
151
147
|
}), [activePackage, entries, packagesByType]);
|
|
152
148
|
const writable = React.useMemo(() => visible.filter((e) => e.allowOrgOverride || e.allowRuntimeCreate), [visible]);
|
|
@@ -181,7 +181,6 @@ const ENGINE_STRINGS_EN = {
|
|
|
181
181
|
'engine.list.warnCount': '{count} warning(s):',
|
|
182
182
|
'engine.list.allSources': 'All sources',
|
|
183
183
|
'engine.list.allPackages': 'All packages',
|
|
184
|
-
'engine.package.local': 'Local / Custom (this env)',
|
|
185
184
|
'engine.package.writableRequired': 'Pick or create a writable base (package) first — this item cannot be authored into a read-only code package.',
|
|
186
185
|
'engine.list.packageFilter': 'Package',
|
|
187
186
|
'engine.list.source.artifact': 'Artifact',
|
|
@@ -878,7 +877,6 @@ const ENGINE_STRINGS_ZH = {
|
|
|
878
877
|
'engine.list.warnCount': '{count} 个警告:',
|
|
879
878
|
'engine.list.allSources': '全部来源',
|
|
880
879
|
'engine.list.allPackages': '全部软件包',
|
|
881
|
-
'engine.package.local': '本地 / 自定义(本环境)',
|
|
882
880
|
'engine.list.packageFilter': '软件包',
|
|
883
881
|
'engine.list.source.artifact': '代码包',
|
|
884
882
|
'engine.list.source.runtime': '运行时',
|
|
@@ -1,41 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* server's runtime-only provenance, see framework #2252).
|
|
2
|
+
* Build the Studio package-scope options from the raw `package` metadata list:
|
|
3
|
+
* the **writable bases** (project-scoped, DB-backed packages) only — the sole
|
|
4
|
+
* valid authoring destinations (ADR-0070 D2). Code/installed (system|cloud)
|
|
5
|
+
* packages are filtered out.
|
|
7
6
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* selector
|
|
12
|
-
*
|
|
13
|
-
* un-navigable (the route redirected to "new"). Surfacing the local scope as a
|
|
14
|
-
* first-class, always-present selector entry makes it discoverable and editable.
|
|
15
|
-
*/
|
|
16
|
-
export declare const LOCAL_PACKAGE_ID = "sys_metadata";
|
|
17
|
-
/**
|
|
18
|
-
* Build the Studio package-scope options from the raw `package` metadata list.
|
|
19
|
-
* Filters out system/cloud-scoped packages and appends a stable
|
|
20
|
-
* "Local / Custom (this environment)" scope so runtime metadata authored here
|
|
21
|
-
* is always selectable/visible — even when zero items exist yet.
|
|
7
|
+
* There is no package-less "Local / Custom" scope: every runtime-authored item
|
|
8
|
+
* lives in a writable base (ADR-0070 D1/D5 — the kernel rejects orphan creates
|
|
9
|
+
* with `writable_package_required`, and legacy orphans are adopted into a base),
|
|
10
|
+
* so the selector never offers an orphan bucket. The kernel keeps `null` /
|
|
11
|
+
* `sys_metadata` provenance only as a read-side rehydration tag for legacy rows.
|
|
22
12
|
*/
|
|
23
13
|
export declare function buildPackageScopeOptions(rawList: unknown[] | null | undefined): {
|
|
24
14
|
id: string;
|
|
25
15
|
name: string;
|
|
26
16
|
}[];
|
|
27
|
-
/**
|
|
28
|
-
* True for the runtime/null "Local / Custom" sentinel scope. Per ADR-0070 D5
|
|
29
|
-
* this is a *migration* surface (move loose items into a base), never a valid
|
|
30
|
-
* create destination — callers gate "create" on a real writable base.
|
|
31
|
-
*/
|
|
32
|
-
export declare function isLocalScope(id: string | null | undefined): boolean;
|
|
33
|
-
/**
|
|
34
|
-
* The writable bases (project-scoped DB packages) from the raw package list —
|
|
35
|
-
* the only valid authoring destinations (ADR-0070 D2). Excludes code/installed
|
|
36
|
-
* (system|cloud) packages AND the Local sentinel.
|
|
37
|
-
*/
|
|
38
|
-
export declare function writableBaseOptions(rawList: unknown[] | null | undefined): {
|
|
39
|
-
id: string;
|
|
40
|
-
name: string;
|
|
41
|
-
}[];
|
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
|
|
2
|
-
import { detectLocale, t } from './i18n';
|
|
3
|
-
/**
|
|
4
|
-
* Sentinel "package" id for this environment's runtime, DB-authored metadata —
|
|
5
|
-
* items with no code-package binding (`package_id IS NULL`). The metadata
|
|
6
|
-
* list/get API treats `?package=sys_metadata` as exactly that local scope on
|
|
7
|
-
* READ, and a WRITE under it persists `package_id = null` (matching the
|
|
8
|
-
* server's runtime-only provenance, see framework #2252).
|
|
9
|
-
*
|
|
10
|
-
* Why this exists: a self-hosted, metadata-customizable environment is
|
|
11
|
-
* single-tenant — there is no "org" dimension here; the real axis is
|
|
12
|
-
* code-package vs. runtime (DB-authored). Before this scope, the package
|
|
13
|
-
* selector only listed code packages, so metadata authored at runtime
|
|
14
|
-
* (`package_id = null`) was filtered out of every code-package view and became
|
|
15
|
-
* un-navigable (the route redirected to "new"). Surfacing the local scope as a
|
|
16
|
-
* first-class, always-present selector entry makes it discoverable and editable.
|
|
17
|
-
*/
|
|
18
|
-
export const LOCAL_PACKAGE_ID = 'sys_metadata';
|
|
19
2
|
const SYSTEM_SCOPES = new Set(['system', 'cloud']);
|
|
20
3
|
/**
|
|
21
|
-
* Build the Studio package-scope options from the raw `package` metadata list
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
4
|
+
* Build the Studio package-scope options from the raw `package` metadata list:
|
|
5
|
+
* the **writable bases** (project-scoped, DB-backed packages) only — the sole
|
|
6
|
+
* valid authoring destinations (ADR-0070 D2). Code/installed (system|cloud)
|
|
7
|
+
* packages are filtered out.
|
|
8
|
+
*
|
|
9
|
+
* There is no package-less "Local / Custom" scope: every runtime-authored item
|
|
10
|
+
* lives in a writable base (ADR-0070 D1/D5 — the kernel rejects orphan creates
|
|
11
|
+
* with `writable_package_required`, and legacy orphans are adopted into a base),
|
|
12
|
+
* so the selector never offers an orphan bucket. The kernel keeps `null` /
|
|
13
|
+
* `sys_metadata` provenance only as a read-side rehydration tag for legacy rows.
|
|
25
14
|
*/
|
|
26
15
|
export function buildPackageScopeOptions(rawList) {
|
|
27
16
|
const rows = (rawList ?? [])
|
|
@@ -36,24 +25,5 @@ export function buildPackageScopeOptions(rawList) {
|
|
|
36
25
|
})
|
|
37
26
|
.filter((p) => p.id && !SYSTEM_SCOPES.has(p.scope));
|
|
38
27
|
rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
39
|
-
|
|
40
|
-
// Append (never default) so the existing first-code-package default is
|
|
41
|
-
// preserved; the user opts into the local scope explicitly.
|
|
42
|
-
return [...opts, { id: LOCAL_PACKAGE_ID, name: t('engine.package.local', detectLocale()) }];
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* True for the runtime/null "Local / Custom" sentinel scope. Per ADR-0070 D5
|
|
46
|
-
* this is a *migration* surface (move loose items into a base), never a valid
|
|
47
|
-
* create destination — callers gate "create" on a real writable base.
|
|
48
|
-
*/
|
|
49
|
-
export function isLocalScope(id) {
|
|
50
|
-
return !id || id === LOCAL_PACKAGE_ID;
|
|
51
|
-
}
|
|
52
|
-
/**
|
|
53
|
-
* The writable bases (project-scoped DB packages) from the raw package list —
|
|
54
|
-
* the only valid authoring destinations (ADR-0070 D2). Excludes code/installed
|
|
55
|
-
* (system|cloud) packages AND the Local sentinel.
|
|
56
|
-
*/
|
|
57
|
-
export function writableBaseOptions(rawList) {
|
|
58
|
-
return buildPackageScopeOptions(rawList).filter((o) => o.id !== LOCAL_PACKAGE_ID);
|
|
28
|
+
return rows.map((p) => ({ id: p.id, name: p.name }));
|
|
59
29
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/app-shell",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
|
|
@@ -33,36 +33,36 @@
|
|
|
33
33
|
"qrcode": "^1.5.4",
|
|
34
34
|
"sonner": "^2.0.7",
|
|
35
35
|
"zod": "^4.4.3",
|
|
36
|
-
"@object-ui/auth": "7.
|
|
37
|
-
"@object-ui/collaboration": "7.
|
|
38
|
-
"@object-ui/components": "7.
|
|
39
|
-
"@object-ui/core": "7.
|
|
40
|
-
"@object-ui/
|
|
41
|
-
"@object-ui/
|
|
42
|
-
"@object-ui/i18n": "7.
|
|
43
|
-
"@object-ui/layout": "7.
|
|
44
|
-
"@object-ui/permissions": "7.
|
|
45
|
-
"@object-ui/plugin-editor": "7.
|
|
46
|
-
"@object-ui/providers": "7.
|
|
47
|
-
"@object-ui/react": "7.
|
|
48
|
-
"@object-ui/types": "7.
|
|
36
|
+
"@object-ui/auth": "7.3.0",
|
|
37
|
+
"@object-ui/collaboration": "7.3.0",
|
|
38
|
+
"@object-ui/components": "7.3.0",
|
|
39
|
+
"@object-ui/core": "7.3.0",
|
|
40
|
+
"@object-ui/fields": "7.3.0",
|
|
41
|
+
"@object-ui/data-objectstack": "7.3.0",
|
|
42
|
+
"@object-ui/i18n": "7.3.0",
|
|
43
|
+
"@object-ui/layout": "7.3.0",
|
|
44
|
+
"@object-ui/permissions": "7.3.0",
|
|
45
|
+
"@object-ui/plugin-editor": "7.3.0",
|
|
46
|
+
"@object-ui/providers": "7.3.0",
|
|
47
|
+
"@object-ui/react": "7.3.0",
|
|
48
|
+
"@object-ui/types": "7.3.0"
|
|
49
49
|
},
|
|
50
50
|
"peerDependencies": {
|
|
51
51
|
"react": "^18.0.0 || ^19.0.0",
|
|
52
52
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
53
53
|
"react-router-dom": "^6.0.0 || ^7.0.0",
|
|
54
|
-
"@object-ui/plugin-calendar": "^7.
|
|
55
|
-
"@object-ui/plugin-
|
|
56
|
-
"@object-ui/plugin-
|
|
57
|
-
"@object-ui/plugin-dashboard": "^7.
|
|
58
|
-
"@object-ui/plugin-designer": "^7.
|
|
59
|
-
"@object-ui/plugin-detail": "^7.
|
|
60
|
-
"@object-ui/plugin-form": "^7.
|
|
61
|
-
"@object-ui/plugin-
|
|
62
|
-
"@object-ui/plugin-
|
|
63
|
-
"@object-ui/plugin-list": "^7.
|
|
64
|
-
"@object-ui/plugin-report": "^7.
|
|
65
|
-
"@object-ui/plugin-view": "^7.
|
|
54
|
+
"@object-ui/plugin-calendar": "^7.3.0",
|
|
55
|
+
"@object-ui/plugin-chatbot": "^7.3.0",
|
|
56
|
+
"@object-ui/plugin-charts": "^7.3.0",
|
|
57
|
+
"@object-ui/plugin-dashboard": "^7.3.0",
|
|
58
|
+
"@object-ui/plugin-designer": "^7.3.0",
|
|
59
|
+
"@object-ui/plugin-detail": "^7.3.0",
|
|
60
|
+
"@object-ui/plugin-form": "^7.3.0",
|
|
61
|
+
"@object-ui/plugin-kanban": "^7.3.0",
|
|
62
|
+
"@object-ui/plugin-grid": "^7.3.0",
|
|
63
|
+
"@object-ui/plugin-list": "^7.3.0",
|
|
64
|
+
"@object-ui/plugin-report": "^7.3.0",
|
|
65
|
+
"@object-ui/plugin-view": "^7.3.0"
|
|
66
66
|
},
|
|
67
67
|
"devDependencies": {
|
|
68
68
|
"@types/node": "^26.0.0",
|
|
@@ -75,18 +75,18 @@
|
|
|
75
75
|
"sonner": "^2.0.7",
|
|
76
76
|
"typescript": "^6.0.3",
|
|
77
77
|
"vite": "^8.0.16",
|
|
78
|
-
"@object-ui/plugin-calendar": "7.
|
|
79
|
-
"@object-ui/plugin-charts": "7.
|
|
80
|
-
"@object-ui/plugin-chatbot": "7.
|
|
81
|
-
"@object-ui/plugin-dashboard": "7.
|
|
82
|
-
"@object-ui/plugin-designer": "7.
|
|
83
|
-
"@object-ui/plugin-detail": "7.
|
|
84
|
-
"@object-ui/plugin-form": "7.
|
|
85
|
-
"@object-ui/plugin-grid": "7.
|
|
86
|
-
"@object-ui/plugin-kanban": "7.
|
|
87
|
-
"@object-ui/plugin-list": "7.
|
|
88
|
-
"@object-ui/plugin-report": "7.
|
|
89
|
-
"@object-ui/plugin-view": "7.
|
|
78
|
+
"@object-ui/plugin-calendar": "7.3.0",
|
|
79
|
+
"@object-ui/plugin-charts": "7.3.0",
|
|
80
|
+
"@object-ui/plugin-chatbot": "7.3.0",
|
|
81
|
+
"@object-ui/plugin-dashboard": "7.3.0",
|
|
82
|
+
"@object-ui/plugin-designer": "7.3.0",
|
|
83
|
+
"@object-ui/plugin-detail": "7.3.0",
|
|
84
|
+
"@object-ui/plugin-form": "7.3.0",
|
|
85
|
+
"@object-ui/plugin-grid": "7.3.0",
|
|
86
|
+
"@object-ui/plugin-kanban": "7.3.0",
|
|
87
|
+
"@object-ui/plugin-list": "7.3.0",
|
|
88
|
+
"@object-ui/plugin-report": "7.3.0",
|
|
89
|
+
"@object-ui/plugin-view": "7.3.0"
|
|
90
90
|
},
|
|
91
91
|
"keywords": [
|
|
92
92
|
"objectui",
|