@object-ui/app-shell 4.7.0 → 5.0.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 +416 -0
- package/dist/console/ConsoleShell.js +2 -2
- package/dist/layout/AppHeader.js +14 -4
- package/dist/layout/AppSidebar.js +1 -0
- package/dist/layout/ConsoleLayout.js +12 -11
- package/dist/layout/MobileViewSwitcherContext.d.ts +79 -0
- package/dist/layout/MobileViewSwitcherContext.js +81 -0
- package/dist/layout/UnifiedSidebar.js +12 -43
- package/dist/utils/pageSchemaIntrospect.d.ts +20 -0
- package/dist/utils/pageSchemaIntrospect.js +51 -0
- package/dist/views/ObjectView.js +48 -28
- package/dist/views/RecordDetailView.js +197 -7
- package/package.json +24 -24
|
@@ -8,11 +8,11 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
8
8
|
*/
|
|
9
9
|
import { useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
10
10
|
import { useParams, useNavigate } from 'react-router-dom';
|
|
11
|
-
import { DetailView, RecordChatterPanel } from '@object-ui/plugin-detail';
|
|
11
|
+
import { DetailView, RecordChatterPanel, buildDefaultPageSchema } from '@object-ui/plugin-detail';
|
|
12
12
|
import { Empty, EmptyTitle, EmptyDescription } from '@object-ui/components';
|
|
13
13
|
import { PresenceAvatars } from '@object-ui/collaboration';
|
|
14
14
|
import { useAuth, createAuthenticatedFetch } from '@object-ui/auth';
|
|
15
|
-
import { ActionProvider, useObjectTranslation, useObjectLabel, usePageAssignment, RecordContextProvider, SchemaRenderer } from '@object-ui/react';
|
|
15
|
+
import { ActionProvider, useObjectTranslation, useObjectLabel, usePageAssignment, RecordContextProvider, SchemaRenderer, DiscussionContextProvider, HighlightFieldsProvider } from '@object-ui/react';
|
|
16
16
|
import { buildExpandFields } from '@object-ui/core';
|
|
17
17
|
import { toast } from 'sonner';
|
|
18
18
|
import { Database, Users } from 'lucide-react';
|
|
@@ -20,6 +20,7 @@ import { MetadataPanel, useMetadataInspector } from './MetadataInspector';
|
|
|
20
20
|
import { SkeletonDetail } from '../skeletons';
|
|
21
21
|
import { ManagedByBadge } from '../components/ManagedByBadge';
|
|
22
22
|
import { resolveCrudAffordances } from '../utils/crudAffordances';
|
|
23
|
+
import { hasExplicitDiscussion } from '../utils/pageSchemaIntrospect';
|
|
23
24
|
import { ActionConfirmDialog } from './ActionConfirmDialog';
|
|
24
25
|
import { ActionParamDialog } from './ActionParamDialog';
|
|
25
26
|
import { resolveActionParams } from '../utils/resolveActionParams';
|
|
@@ -79,11 +80,50 @@ export function RecordDetailView({ dataSource, objects, onEdit, objectNameOverri
|
|
|
79
80
|
// it via SchemaRenderer (which dispatches to the registered 'record'
|
|
80
81
|
// PageRenderer in @object-ui/components). Otherwise we fall through to
|
|
81
82
|
// the legacy auto-generated DetailView path below.
|
|
82
|
-
|
|
83
|
+
//
|
|
84
|
+
// Track 3 Phase G slice 6 — `renderViaSchema` is now default-on. The
|
|
85
|
+
// no-assignedPage branch synthesizes a canonical Page via
|
|
86
|
+
// `buildDefaultPageSchema(objectDef)` so the default detail page rides
|
|
87
|
+
// the same SchemaRenderer pipeline as custom pages. Kill-switches:
|
|
88
|
+
// 1) URL query param `?renderViaSchema=0` (per-request fallback to
|
|
89
|
+
// the legacy DetailView monolith — useful for debugging regressions)
|
|
90
|
+
// 2) `objectDef.detail?.renderViaSchema === false` (per-object opt-out)
|
|
91
|
+
const { page: assignedPage, slots: assignedSlots } = usePageAssignment(objectName);
|
|
92
|
+
const renderViaSchemaFlag = useMemo(() => {
|
|
93
|
+
if (typeof window !== 'undefined') {
|
|
94
|
+
try {
|
|
95
|
+
const qp = new URLSearchParams(window.location.search).get('renderViaSchema');
|
|
96
|
+
if (qp === '0' || qp === 'false')
|
|
97
|
+
return false;
|
|
98
|
+
if (qp === '1' || qp === 'true')
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
catch { }
|
|
102
|
+
}
|
|
103
|
+
if (objectDef?.detail?.renderViaSchema === false)
|
|
104
|
+
return false;
|
|
105
|
+
return true;
|
|
106
|
+
}, [objectDef]);
|
|
107
|
+
const synthesizedPage = useMemo(() => {
|
|
108
|
+
// Synthesizer drives two cases:
|
|
109
|
+
// 1) no assignedPage at all → pure default detail page
|
|
110
|
+
// 2) assignedSlots (slotted page) → synth with slot overrides
|
|
111
|
+
// In either case the page-record load effect below only needs
|
|
112
|
+
// "is there a page?"; the fully-detailed schema is rebuilt at
|
|
113
|
+
// render time once `detailSchema.sections` are known.
|
|
114
|
+
if (assignedPage)
|
|
115
|
+
return null;
|
|
116
|
+
if (!objectDef)
|
|
117
|
+
return null;
|
|
118
|
+
if (!renderViaSchemaFlag && !assignedSlots)
|
|
119
|
+
return null;
|
|
120
|
+
return buildDefaultPageSchema(objectDef, assignedSlots ? { slots: assignedSlots } : undefined);
|
|
121
|
+
}, [renderViaSchemaFlag, assignedPage, assignedSlots, objectDef]);
|
|
122
|
+
const effectivePage = assignedPage || synthesizedPage;
|
|
83
123
|
const [pageRecord, setPageRecord] = useState(null);
|
|
84
124
|
useEffect(() => {
|
|
85
125
|
let cancelled = false;
|
|
86
|
-
if (!
|
|
126
|
+
if (!effectivePage || !pureRecordId || !objectName || !dataSource?.findOne) {
|
|
87
127
|
setPageRecord(null);
|
|
88
128
|
return;
|
|
89
129
|
}
|
|
@@ -107,7 +147,7 @@ export function RecordDetailView({ dataSource, objects, onEdit, objectNameOverri
|
|
|
107
147
|
return () => {
|
|
108
148
|
cancelled = true;
|
|
109
149
|
};
|
|
110
|
-
}, [
|
|
150
|
+
}, [effectivePage, objectName, pureRecordId, dataSource, objectDef]);
|
|
111
151
|
// ─── Action Provider Handlers ───────────────────────────────────────
|
|
112
152
|
// Confirm dialog state (promise-based)
|
|
113
153
|
const [confirmState, setConfirmState] = useState({ open: false, message: '' });
|
|
@@ -1006,8 +1046,158 @@ export function RecordDetailView({ dataSource, objects, onEdit, objectNameOverri
|
|
|
1006
1046
|
if (!objectDef) {
|
|
1007
1047
|
return (_jsx("div", { className: "flex h-full items-center justify-center p-4", children: _jsxs(Empty, { children: [_jsx("div", { className: "mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-full bg-muted", children: _jsx(Database, { className: "h-6 w-6 text-muted-foreground" }) }), _jsx(EmptyTitle, { children: t('empty.objectNotFound') }), _jsx(EmptyDescription, { children: t('empty.objectNotFoundDescription', { name: objectName }) })] }) }));
|
|
1008
1048
|
}
|
|
1009
|
-
if (
|
|
1010
|
-
|
|
1049
|
+
if (effectivePage) {
|
|
1050
|
+
const disableDiscussion = effectivePage?.disableDiscussion === true;
|
|
1051
|
+
// When the page schema embeds an explicit `record:discussion` /
|
|
1052
|
+
// `record:chatter` slot, skip the bottom auto-append so the
|
|
1053
|
+
// author placement (or synth default) wins. The walker recurses
|
|
1054
|
+
// into `regions[]` so `buildDefaultPageSchema` output and
|
|
1055
|
+
// full-Lightning authored pages are both detected.
|
|
1056
|
+
const hasDiscussion = hasExplicitDiscussion(effectivePage);
|
|
1057
|
+
const showAutoDiscussion = !disableDiscussion && !hasDiscussion;
|
|
1058
|
+
// Slice 2 — when we're synthesizing (no author assignedPage), rebuild
|
|
1059
|
+
// the schema with the actual detailSchema.sections + highlight fields
|
|
1060
|
+
// so record:details renders the same field layout the legacy
|
|
1061
|
+
// DetailView would have produced.
|
|
1062
|
+
// Slice 4 — also forward header actions, related lists, activities,
|
|
1063
|
+
// and history so the synthesized page reaches parity with the
|
|
1064
|
+
// monolithic DetailView (tabs strip + record_header quick actions).
|
|
1065
|
+
// Business / custom actions authored on objectDef and routed to the
|
|
1066
|
+
// record_header location (e.g. Lead.convert, Contact.set_primary).
|
|
1067
|
+
const synthBusinessActions = (() => {
|
|
1068
|
+
const acts = detailSchema.actions;
|
|
1069
|
+
if (!Array.isArray(acts))
|
|
1070
|
+
return [];
|
|
1071
|
+
// detailSchema wraps actions in a `{type:'action:bar', actions:[]}`
|
|
1072
|
+
// shape; unwrap to the flat ActionDef[] the renderer expects.
|
|
1073
|
+
const bar = acts.find((a) => Array.isArray(a?.actions));
|
|
1074
|
+
const flat = bar?.actions ?? acts;
|
|
1075
|
+
return Array.isArray(flat) ? flat : [];
|
|
1076
|
+
})();
|
|
1077
|
+
// System actions (Edit / Share / Delete) — the legacy DetailView
|
|
1078
|
+
// monolith always synthesized these. The synth-path replacement
|
|
1079
|
+
// (Phase G slice 6) initially dropped them, leaving objects without
|
|
1080
|
+
// authored record_header actions with a bare header. Re-inject here
|
|
1081
|
+
// so every record page surfaces the basic affordances.
|
|
1082
|
+
const synthSystemActions = (() => {
|
|
1083
|
+
const affordances = resolveCrudAffordances(objectDef);
|
|
1084
|
+
const items = [];
|
|
1085
|
+
if (affordances.edit) {
|
|
1086
|
+
items.push({
|
|
1087
|
+
name: 'sys_edit',
|
|
1088
|
+
label: t('detail.edit', { defaultValue: 'Edit' }),
|
|
1089
|
+
type: 'script',
|
|
1090
|
+
locations: ['record_header'],
|
|
1091
|
+
variant: 'default',
|
|
1092
|
+
onClick: () => onEdit({ id: pureRecordId }),
|
|
1093
|
+
});
|
|
1094
|
+
}
|
|
1095
|
+
items.push({
|
|
1096
|
+
name: 'sys_share',
|
|
1097
|
+
label: t('detail.share', { defaultValue: 'Share' }),
|
|
1098
|
+
type: 'script',
|
|
1099
|
+
locations: ['record_header'],
|
|
1100
|
+
variant: 'outline',
|
|
1101
|
+
onClick: async () => {
|
|
1102
|
+
try {
|
|
1103
|
+
if (navigator.share) {
|
|
1104
|
+
await navigator.share({
|
|
1105
|
+
title: document.title,
|
|
1106
|
+
url: window.location.href,
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
else {
|
|
1110
|
+
await navigator.clipboard.writeText(window.location.href);
|
|
1111
|
+
toast.success(t('detail.linkCopied', { defaultValue: 'Link copied' }));
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
catch {
|
|
1115
|
+
// user dismissed share sheet — no-op
|
|
1116
|
+
}
|
|
1117
|
+
},
|
|
1118
|
+
});
|
|
1119
|
+
if (affordances.delete) {
|
|
1120
|
+
items.push({
|
|
1121
|
+
name: 'sys_delete',
|
|
1122
|
+
label: t('detail.delete', { defaultValue: 'Delete' }),
|
|
1123
|
+
type: 'script',
|
|
1124
|
+
locations: ['record_header'],
|
|
1125
|
+
variant: 'outline',
|
|
1126
|
+
onClick: async () => {
|
|
1127
|
+
const msg = t('detail.deleteConfirmation', {
|
|
1128
|
+
defaultValue: 'Are you sure you want to delete this record?',
|
|
1129
|
+
});
|
|
1130
|
+
if (!window.confirm(msg))
|
|
1131
|
+
return;
|
|
1132
|
+
try {
|
|
1133
|
+
await dataSource.delete(objectName, pureRecordId);
|
|
1134
|
+
toast.success(t('detail.deleted', { defaultValue: 'Record deleted' }));
|
|
1135
|
+
const baseAppUrl = appName ? `/apps/${appName}` : '';
|
|
1136
|
+
navigate(`${baseAppUrl}/${objectName}`, { replace: true });
|
|
1137
|
+
}
|
|
1138
|
+
catch (err) {
|
|
1139
|
+
toast.error(err?.message || 'Delete failed');
|
|
1140
|
+
}
|
|
1141
|
+
},
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
return items;
|
|
1145
|
+
})();
|
|
1146
|
+
// The synth path now hands ONLY business actions to the page schema.
|
|
1147
|
+
// System actions (Edit / Share / Delete) ride through
|
|
1148
|
+
// `RecordContext.headerSystemActions` instead, so they reach both
|
|
1149
|
+
// synth/slotted pages AND authored full-Lightning pages without
|
|
1150
|
+
// mutating the assignedPage tree. `PageHeaderRenderer` dedupes by
|
|
1151
|
+
// name so authored business actions still win on collision.
|
|
1152
|
+
const synthHeaderActions = synthBusinessActions.length > 0 ? synthBusinessActions : undefined;
|
|
1153
|
+
const synthRelated = Array.isArray(detailSchema.related)
|
|
1154
|
+
? detailSchema.related
|
|
1155
|
+
.filter((r) => r?.api && r?.referenceField)
|
|
1156
|
+
.map((r) => ({
|
|
1157
|
+
title: r.title,
|
|
1158
|
+
objectName: r.api,
|
|
1159
|
+
relationshipField: r.referenceField,
|
|
1160
|
+
...(Array.isArray(r.columns) ? { columns: r.columns } : {}),
|
|
1161
|
+
...(typeof r.pageSize === 'number' ? { limit: r.pageSize } : {}),
|
|
1162
|
+
...(r.icon ? { icon: r.icon } : {}),
|
|
1163
|
+
}))
|
|
1164
|
+
: undefined;
|
|
1165
|
+
const synthHistory = detailSchema.history
|
|
1166
|
+
? {
|
|
1167
|
+
entries: detailSchema.history.entries ?? [],
|
|
1168
|
+
loading: !!detailSchema.history.loading,
|
|
1169
|
+
emptyText: detailSchema.history.emptyText,
|
|
1170
|
+
}
|
|
1171
|
+
: undefined;
|
|
1172
|
+
const renderedPage = assignedPage
|
|
1173
|
+
? effectivePage
|
|
1174
|
+
: buildDefaultPageSchema(objectDef, {
|
|
1175
|
+
sections: detailSchema.sections,
|
|
1176
|
+
highlightFields: Array.isArray(detailSchema.highlightFields)
|
|
1177
|
+
? detailSchema.highlightFields
|
|
1178
|
+
.map((f) => (typeof f === 'string' ? f : f?.name))
|
|
1179
|
+
.filter((n) => !!n)
|
|
1180
|
+
: undefined,
|
|
1181
|
+
headerActions: synthHeaderActions,
|
|
1182
|
+
related: synthRelated,
|
|
1183
|
+
history: synthHistory,
|
|
1184
|
+
...(assignedSlots ? { slots: assignedSlots } : {}),
|
|
1185
|
+
});
|
|
1186
|
+
return (_jsxs("div", { className: "h-full bg-background overflow-hidden flex flex-col relative", children: [_jsxs("div", { className: "absolute top-2 sm:top-4 right-2 sm:right-4 z-50 flex items-center gap-2", children: [_jsx(ManagedByBadge, { managedBy: objectDef?.managedBy }), recordViewers.length > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", title: t('recordDetail.viewersTooltip'), children: [_jsx(Users, { className: "h-3.5 w-3.5 text-muted-foreground" }), _jsx(PresenceAvatars, { users: recordViewers, size: "sm", maxVisible: 4, showStatus: true })] }))] }), _jsx(RecordContextProvider, { objectName: objectName, recordId: pureRecordId, data: pageRecord, objectSchema: objectDef, dataSource: dataSource, embedded: embedded, headerSystemActions: synthSystemActions, children: _jsx(HighlightFieldsProvider, { children: _jsx(DiscussionContextProvider, { items: feedItems, onAddComment: handleAddComment, onAddReply: handleAddReply, onToggleReaction: handleToggleReaction, children: _jsx(ActionProvider, { context: { record: pageRecord || {}, objectName, user: currentUser }, onConfirm: confirmHandler, onToast: toastHandler, onNavigate: navigateHandler, onParamCollection: paramCollectionHandler, handlers: { api: apiHandler, flow: flowHandler, script: serverActionHandler, modal: serverActionHandler, approval: approvalHandler }, children: _jsxs("div", { className: "flex-1 overflow-hidden flex flex-row", children: [_jsxs("div", { className: "flex-1 overflow-auto p-3 sm:p-4 lg:p-6 scroll-pb-48", children: [_jsx(SchemaRenderer, { schema: renderedPage }), showAutoDiscussion && (_jsx("div", { className: "mt-6", children: _jsx(RecordChatterPanel, { config: {
|
|
1187
|
+
position: 'bottom',
|
|
1188
|
+
collapsible: false,
|
|
1189
|
+
feed: {
|
|
1190
|
+
enableReactions: true,
|
|
1191
|
+
enableThreading: true,
|
|
1192
|
+
showCommentInput: true,
|
|
1193
|
+
},
|
|
1194
|
+
}, items: feedItems, onAddComment: handleAddComment, onAddReply: handleAddReply, onToggleReaction: handleToggleReaction }) }))] }), _jsx(MetadataPanel, { open: showDebug, sections: [{ title: 'Page Schema', data: renderedPage }] })] }) }) }) }) }), _jsx(ActionConfirmDialog, { state: confirmState, onOpenChange: (open) => {
|
|
1195
|
+
if (!open)
|
|
1196
|
+
setConfirmState(s => ({ ...s, open: false }));
|
|
1197
|
+
} }), _jsx(ActionParamDialog, { state: paramState, onOpenChange: (open) => {
|
|
1198
|
+
if (!open)
|
|
1199
|
+
setParamState(s => ({ ...s, open: false }));
|
|
1200
|
+
} })] }));
|
|
1011
1201
|
}
|
|
1012
1202
|
return (_jsxs("div", { className: "h-full bg-background overflow-hidden flex flex-col relative", children: [_jsxs("div", { className: "absolute top-2 sm:top-4 right-2 sm:right-4 z-50 flex items-center gap-2", children: [_jsx(ManagedByBadge, { managedBy: objectDef?.managedBy }), recordViewers.length > 0 && (_jsxs("div", { className: "flex items-center gap-1.5", title: t('recordDetail.viewersTooltip'), children: [_jsx(Users, { className: "h-3.5 w-3.5 text-muted-foreground" }), _jsx(PresenceAvatars, { users: recordViewers, size: "sm", maxVisible: 4, showStatus: true })] }))] }), _jsxs("div", { className: "flex-1 overflow-hidden flex flex-row", children: [_jsx("div", { className: "flex-1 overflow-auto p-3 sm:p-4 lg:p-6 scroll-pb-48", children: _jsx(ActionProvider, { context: { record: {}, objectName, user: currentUser }, onConfirm: confirmHandler, onToast: toastHandler, onNavigate: navigateHandler, onParamCollection: paramCollectionHandler, handlers: { api: apiHandler, flow: flowHandler, script: serverActionHandler, modal: serverActionHandler, approval: approvalHandler }, children: _jsx(DetailView, { schema: detailSchema, dataSource: dataSource, objectLabel: objectLabel({ name: objectDef.name, label: objectDef.label }), onDataLoaded: (record) => {
|
|
1013
1203
|
if (!record || typeof record !== 'object')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/app-shell",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
|
|
@@ -27,34 +27,34 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"lucide-react": "^1.16.0",
|
|
29
29
|
"sonner": "^2.0.7",
|
|
30
|
-
"@object-ui/auth": "
|
|
31
|
-
"@object-ui/collaboration": "
|
|
32
|
-
"@object-ui/components": "
|
|
33
|
-
"@object-ui/core": "
|
|
34
|
-
"@object-ui/data-objectstack": "
|
|
35
|
-
"@object-ui/fields": "
|
|
36
|
-
"@object-ui/i18n": "
|
|
37
|
-
"@object-ui/layout": "
|
|
38
|
-
"@object-ui/permissions": "
|
|
39
|
-
"@object-ui/react": "
|
|
40
|
-
"@object-ui/types": "
|
|
30
|
+
"@object-ui/auth": "5.0.0",
|
|
31
|
+
"@object-ui/collaboration": "5.0.0",
|
|
32
|
+
"@object-ui/components": "5.0.0",
|
|
33
|
+
"@object-ui/core": "5.0.0",
|
|
34
|
+
"@object-ui/data-objectstack": "5.0.0",
|
|
35
|
+
"@object-ui/fields": "5.0.0",
|
|
36
|
+
"@object-ui/i18n": "5.0.0",
|
|
37
|
+
"@object-ui/layout": "5.0.0",
|
|
38
|
+
"@object-ui/permissions": "5.0.0",
|
|
39
|
+
"@object-ui/react": "5.0.0",
|
|
40
|
+
"@object-ui/types": "5.0.0"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"react": "^18.0.0 || ^19.0.0",
|
|
44
44
|
"react-dom": "^18.0.0 || ^19.0.0",
|
|
45
45
|
"react-router-dom": "^6.0.0 || ^7.0.0",
|
|
46
|
-
"@object-ui/plugin-calendar": "^
|
|
47
|
-
"@object-ui/plugin-charts": "^
|
|
48
|
-
"@object-ui/plugin-chatbot": "^
|
|
49
|
-
"@object-ui/plugin-dashboard": "^
|
|
50
|
-
"@object-ui/plugin-designer": "^
|
|
51
|
-
"@object-ui/plugin-detail": "^
|
|
52
|
-
"@object-ui/plugin-form": "^
|
|
53
|
-
"@object-ui/plugin-grid": "^
|
|
54
|
-
"@object-ui/plugin-kanban": "^
|
|
55
|
-
"@object-ui/plugin-list": "^
|
|
56
|
-
"@object-ui/plugin-report": "^
|
|
57
|
-
"@object-ui/plugin-view": "^
|
|
46
|
+
"@object-ui/plugin-calendar": "^5.0.0",
|
|
47
|
+
"@object-ui/plugin-charts": "^5.0.0",
|
|
48
|
+
"@object-ui/plugin-chatbot": "^5.0.0",
|
|
49
|
+
"@object-ui/plugin-dashboard": "^5.0.0",
|
|
50
|
+
"@object-ui/plugin-designer": "^5.0.0",
|
|
51
|
+
"@object-ui/plugin-detail": "^5.0.0",
|
|
52
|
+
"@object-ui/plugin-form": "^5.0.0",
|
|
53
|
+
"@object-ui/plugin-grid": "^5.0.0",
|
|
54
|
+
"@object-ui/plugin-kanban": "^5.0.0",
|
|
55
|
+
"@object-ui/plugin-list": "^5.0.0",
|
|
56
|
+
"@object-ui/plugin-report": "^5.0.0",
|
|
57
|
+
"@object-ui/plugin-view": "^5.0.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@types/node": "^25.9.0",
|