@object-ui/app-shell 7.0.0 → 7.2.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 +560 -0
- package/dist/console/AppContent.js +23 -17
- package/dist/console/ConsoleShell.d.ts +16 -0
- package/dist/console/ConsoleShell.js +43 -2
- package/dist/console/ai/AiChatPage.js +47 -16
- package/dist/console/ai/LiveCanvas.d.ts +8 -2
- package/dist/console/ai/LiveCanvas.js +6 -4
- package/dist/console/home/HomeLayout.js +5 -7
- package/dist/console/home/HomePage.js +1 -9
- package/dist/console/organizations/CreateWorkspaceDialog.js +15 -1
- package/dist/console/organizations/OrganizationsPage.js +22 -3
- package/dist/console/organizations/provisionEnvironment.d.ts +53 -0
- package/dist/console/organizations/provisionEnvironment.js +64 -0
- package/dist/environment/EnvironmentEntitlementDialog.d.ts +34 -0
- package/dist/environment/EnvironmentEntitlementDialog.js +37 -0
- package/dist/environment/EnvironmentListToolbar.d.ts +33 -0
- package/dist/environment/EnvironmentListToolbar.js +59 -0
- package/dist/environment/entitlements.d.ts +90 -0
- package/dist/environment/entitlements.js +91 -0
- package/dist/environment/useEnvironmentEntitlements.d.ts +32 -0
- package/dist/environment/useEnvironmentEntitlements.js +108 -0
- package/dist/hooks/useActionModal.js +15 -1
- package/dist/hooks/useAiSurface.d.ts +59 -0
- package/dist/hooks/useAiSurface.js +78 -0
- package/dist/hooks/useChatConversation.d.ts +30 -0
- package/dist/hooks/useChatConversation.js +63 -0
- package/dist/hooks/useConsoleActionRuntime.d.ts +3 -0
- package/dist/hooks/useConsoleActionRuntime.js +42 -10
- package/dist/index.d.ts +5 -2
- package/dist/index.js +10 -2
- package/dist/layout/AppHeader.js +28 -4
- package/dist/layout/ConsoleFloatingChatbot.d.ts +6 -4
- package/dist/layout/ConsoleFloatingChatbot.js +41 -10
- package/dist/layout/ConsoleLayout.js +5 -6
- package/dist/layout/ContextSelectors.js +59 -35
- package/dist/layout/agentPicker.d.ts +56 -0
- package/dist/layout/agentPicker.js +40 -0
- package/dist/preview/CommitTimeline.d.ts +15 -0
- package/dist/preview/CommitTimeline.js +82 -0
- package/dist/preview/DraftPreviewBar.js +20 -7
- package/dist/preview/UnpublishedAppBar.js +11 -7
- package/dist/preview/commitHistory.d.ts +28 -0
- package/dist/preview/commitHistory.js +48 -0
- package/dist/providers/ExpressionProvider.js +9 -3
- package/dist/providers/MetadataProvider.js +9 -0
- package/dist/utils/index.d.ts +2 -2
- package/dist/utils/index.js +1 -1
- package/dist/utils/recordFormNavigation.d.ts +60 -0
- package/dist/utils/recordFormNavigation.js +35 -0
- package/dist/utils/resolvePageVarTokens.d.ts +31 -0
- package/dist/utils/resolvePageVarTokens.js +72 -0
- package/dist/views/CreateViewDialog.js +14 -1
- package/dist/views/FlowRunner.d.ts +2 -30
- package/dist/views/FlowRunner.js +18 -50
- package/dist/views/ObjectView.js +26 -12
- package/dist/views/ScreenView.d.ts +70 -0
- package/dist/views/ScreenView.js +73 -0
- package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
- package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
- package/dist/views/metadata-admin/DirectoryPage.js +2 -14
- package/dist/views/metadata-admin/JsonSourceEditor.d.ts +3 -1
- package/dist/views/metadata-admin/JsonSourceEditor.js +21 -3
- package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
- package/dist/views/metadata-admin/PackagesPage.js +58 -5
- package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
- package/dist/views/metadata-admin/ResourceEditPage.js +83 -24
- package/dist/views/metadata-admin/ResourceListPage.js +28 -19
- package/dist/views/metadata-admin/StudioHomePage.js +6 -14
- package/dist/views/metadata-admin/anchors.js +20 -2
- package/dist/views/metadata-admin/createBody.d.ts +26 -0
- package/dist/views/metadata-admin/createBody.js +30 -0
- package/dist/views/metadata-admin/i18n.js +108 -2
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +10 -2
- package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +136 -8
- package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +99 -4
- package/dist/views/metadata-admin/inspectors/FlowExprIssue.d.ts +21 -0
- package/dist/views/metadata-admin/inspectors/FlowExprIssue.js +13 -0
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +20 -2
- package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +71 -28
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +4 -1
- package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +24 -9
- package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +81 -4
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +4 -1
- package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +8 -3
- package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +5 -4
- package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +47 -12
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +1 -1
- package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +60 -2
- package/dist/views/metadata-admin/inspectors/VariableTextInput.d.ts +47 -0
- package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
- package/dist/views/metadata-admin/inspectors/_shared.d.ts +5 -1
- package/dist/views/metadata-admin/inspectors/_shared.js +2 -2
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
- package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +102 -0
- package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
- package/dist/views/metadata-admin/inspectors/flow-node-config.js +67 -11
- package/dist/views/metadata-admin/inspectors/flow-ref-check.d.ts +39 -0
- package/dist/views/metadata-admin/inspectors/flow-ref-check.js +114 -0
- package/dist/views/metadata-admin/inspectors/flow-scope.d.ts +109 -0
- package/dist/views/metadata-admin/inspectors/flow-scope.js +199 -0
- package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +14 -3
- package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
- package/dist/views/metadata-admin/inspectors/useFlowScope.d.ts +23 -0
- package/dist/views/metadata-admin/inspectors/useFlowScope.js +45 -0
- package/dist/views/metadata-admin/issuePath.d.ts +22 -0
- package/dist/views/metadata-admin/issuePath.js +65 -0
- package/dist/views/metadata-admin/package-scope.d.ts +41 -0
- package/dist/views/metadata-admin/package-scope.js +59 -0
- package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
- package/dist/views/metadata-admin/previews/DatasetPreview.js +21 -5
- package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +26 -1
- package/dist/views/metadata-admin/previews/FlowCanvas.js +143 -16
- package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
- package/dist/views/metadata-admin/previews/FlowPreview.js +47 -7
- package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +37 -3
- package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
- package/dist/views/metadata-admin/previews/PagePreview.js +112 -3
- package/dist/views/metadata-admin/previews/ProblemsPanel.d.ts +18 -0
- package/dist/views/metadata-admin/previews/ProblemsPanel.js +27 -0
- package/dist/views/metadata-admin/previews/ReportPreview.d.ts +9 -8
- package/dist/views/metadata-admin/previews/ReportPreview.js +33 -16
- package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
- package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +14 -0
- package/dist/views/metadata-admin/previews/flow-canvas-layout.js +37 -0
- package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +17 -1
- package/dist/views/metadata-admin/previews/flow-canvas-parts.js +23 -6
- package/dist/views/metadata-admin/previews/flow-expr-problems.d.ts +19 -0
- package/dist/views/metadata-admin/previews/flow-expr-problems.js +97 -0
- package/dist/views/metadata-admin/previews/flow-problems.d.ts +84 -0
- package/dist/views/metadata-admin/previews/flow-problems.js +209 -0
- package/dist/views/metadata-admin/previews/object-fields-io.d.ts +21 -0
- package/dist/views/metadata-admin/previews/object-fields-io.js +37 -2
- package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
- package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +20 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +7 -0
- package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +76 -2
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +32 -3
- package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +119 -9
- package/package.json +38 -38
|
@@ -19,6 +19,16 @@ 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
|
+
}
|
|
22
32
|
const ALL_SENTINEL = '__all__';
|
|
23
33
|
/** Read a (possibly dotted) property path off a row, e.g. `manifest.id`. */
|
|
24
34
|
function getByPath(row, key) {
|
|
@@ -80,43 +90,56 @@ function useSelectorOptions(def) {
|
|
|
80
90
|
const labelKey = def.optionsSource.labelKey || 'name';
|
|
81
91
|
const filters = def.optionsSource.filter;
|
|
82
92
|
const filterKey = JSON.stringify(filters ?? []);
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
opts.sort((a, b) => a.label.localeCompare(b.label));
|
|
110
|
-
setOptions(opts);
|
|
93
|
+
// Stable, re-runnable fetch. Without an explicit refetch the option list is
|
|
94
|
+
// read once on mount, so a package created elsewhere (PackagesPage) stays
|
|
95
|
+
// invisible in this dropdown until a full page reload. We refetch on
|
|
96
|
+
// dropdown-open and on a global `objectui:packages-changed` signal.
|
|
97
|
+
const load = React.useCallback(async () => {
|
|
98
|
+
try {
|
|
99
|
+
const res = await fetch(endpoint, {
|
|
100
|
+
credentials: 'include',
|
|
101
|
+
headers: { Accept: 'application/json' },
|
|
102
|
+
});
|
|
103
|
+
if (!res.ok)
|
|
104
|
+
return;
|
|
105
|
+
const json = await res.json();
|
|
106
|
+
const rows = extractRows(json);
|
|
107
|
+
const opts = [];
|
|
108
|
+
const seen = new Set();
|
|
109
|
+
for (const row of rows) {
|
|
110
|
+
if (!rowPasses(row, filters))
|
|
111
|
+
continue;
|
|
112
|
+
const value = getByPath(row, valueKey);
|
|
113
|
+
if (value == null || typeof value !== 'string' || seen.has(value))
|
|
114
|
+
continue;
|
|
115
|
+
seen.add(value);
|
|
116
|
+
const labelRaw = getByPath(row, labelKey);
|
|
117
|
+
opts.push({ value, label: typeof labelRaw === 'string' && labelRaw ? labelRaw : value });
|
|
111
118
|
}
|
|
112
|
-
|
|
113
|
-
|
|
119
|
+
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() });
|
|
114
128
|
}
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
setOptions(opts);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
/* offline / unauthorized — render with no options */
|
|
133
|
+
}
|
|
117
134
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
118
135
|
}, [endpoint, valueKey, labelKey, filterKey]);
|
|
119
|
-
|
|
136
|
+
React.useEffect(() => {
|
|
137
|
+
void load();
|
|
138
|
+
const onChanged = () => { void load(); };
|
|
139
|
+
window.addEventListener('objectui:packages-changed', onChanged);
|
|
140
|
+
return () => window.removeEventListener('objectui:packages-changed', onChanged);
|
|
141
|
+
}, [load]);
|
|
142
|
+
return { options, refetch: load };
|
|
120
143
|
}
|
|
121
144
|
/**
|
|
122
145
|
* Hook: resolves the active values for an app's context selectors and
|
|
@@ -189,7 +212,7 @@ export function useAppContextSelectors(appName, selectors, t) {
|
|
|
189
212
|
return { contextValues, element };
|
|
190
213
|
}
|
|
191
214
|
function SelectorControl({ def, value, onChange, t, }) {
|
|
192
|
-
const options = useSelectorOptions(def);
|
|
215
|
+
const { options, refetch } = useSelectorOptions(def);
|
|
193
216
|
const Icon = getIcon(def.icon);
|
|
194
217
|
const rawLabel = resolveI18nLabel(def.label, t) || def.id;
|
|
195
218
|
const label = rawLabel === 'Package'
|
|
@@ -214,5 +237,6 @@ function SelectorControl({ def, value, onChange, t, }) {
|
|
|
214
237
|
}
|
|
215
238
|
}, [hasConcrete, onChange, options]);
|
|
216
239
|
const current = hasConcrete ? value : '';
|
|
217
|
-
return (_jsxs(Select, { value: current, onValueChange: onChange,
|
|
240
|
+
return (_jsxs(Select, { value: current, onValueChange: onChange, onOpenChange: (open) => { if (open)
|
|
241
|
+
refetch(); }, children: [_jsx(SelectTrigger, { "aria-label": label, "data-testid": "package-switcher", className: "h-9 w-full gap-2 rounded-md border-sidebar-border/70 bg-sidebar/80 px-2 text-xs font-medium text-sidebar-foreground shadow-none transition-colors hover:bg-sidebar-accent focus:ring-1 focus:ring-sidebar-ring data-[state=open]:bg-sidebar-accent [&>svg]:h-3.5 [&>svg]:w-3.5 [&>svg]:shrink-0", children: _jsxs("div", { className: "flex min-w-0 flex-1 items-center gap-2 overflow-hidden", children: [_jsx("span", { className: "flex h-5 w-5 shrink-0 items-center justify-center rounded border border-sidebar-border/70 bg-sidebar-accent text-sidebar-foreground/70", children: _jsx(Icon, { className: "h-3 w-3" }) }), _jsx(SelectValue, { placeholder: placeholder, className: "truncate" })] }) }), _jsx(SelectContent, { className: "w-[var(--radix-select-trigger-width)]", children: options.map((opt) => (_jsx(SelectItem, { value: opt.value, children: opt.label }, opt.value))) })] }));
|
|
218
242
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentPicker
|
|
3
|
+
*
|
|
4
|
+
* Pure decision logic for the floating assistant's Build/Ask switcher. Kept in
|
|
5
|
+
* its own module (no React, no chat deps) so it can be unit-tested without
|
|
6
|
+
* dragging in the heavy chat component graph (FloatingChatbot → streamdown →
|
|
7
|
+
* shiki → @ai-sdk, ~20MB) that `ConsoleFloatingChatbot` pulls in.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { type AgentDescriptor } from '@object-ui/plugin-chatbot';
|
|
11
|
+
/** Minimal catalog shape the decision needs — just the agent name. */
|
|
12
|
+
type AgentLike = Pick<AgentDescriptor, 'name'>;
|
|
13
|
+
export interface AgentPickerDecisionInput {
|
|
14
|
+
/** Live agent catalog from `useAgents` (the single source of truth). */
|
|
15
|
+
agents: AgentLike[];
|
|
16
|
+
/**
|
|
17
|
+
* Explicit host override. When defined it wins outright — `true` forces the
|
|
18
|
+
* switcher on, `false` forces it off — regardless of catalog or env.
|
|
19
|
+
*/
|
|
20
|
+
showAgentPickerProp?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* `VITE_AI_SHOW_AGENT_PICKER === 'true'` — the power-user / admin global
|
|
23
|
+
* escape hatch that forces the switcher on without touching app metadata.
|
|
24
|
+
*/
|
|
25
|
+
envOptIn?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether AI Studio (authoring / "online development") is enabled for this
|
|
28
|
+
* deployment. When off, the build agent must not be reachable from the panel,
|
|
29
|
+
* so the auto-reveal is suppressed even if the catalog still serves `build`.
|
|
30
|
+
* Mirrors `ConsoleLayout`'s `aiStudioEnabled` gate. Defaults to true.
|
|
31
|
+
*/
|
|
32
|
+
aiStudioEnabled?: boolean;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* True when the live catalog exposes BOTH a data/query (`ask`) and an authoring
|
|
36
|
+
* (`build`) agent — alias-aware via {@link isAskAgent}/{@link isBuildAgent}, so
|
|
37
|
+
* a catalog still serving the legacy `data_chat`/`metadata_assistant` ids counts
|
|
38
|
+
* too. This is the "AI development is unlocked for this viewer" signal, the same
|
|
39
|
+
* `askAvailable && buildAvailable` notion HomePage uses to surface "Build with AI".
|
|
40
|
+
*/
|
|
41
|
+
export declare function isAiDevUnlocked(agents: AgentLike[]): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Decide whether the floating assistant should reveal its Build/Ask switcher.
|
|
44
|
+
*
|
|
45
|
+
* Restrained by design (the original "end users shouldn't have to choose" rule):
|
|
46
|
+
* a pure end-user surface bound to a single `ask` agent never sees it. Precedence:
|
|
47
|
+
* 1. `showAgentPickerProp` — explicit host override wins (`true`/`false`).
|
|
48
|
+
* 2. `envOptIn` — `VITE_AI_SHOW_AGENT_PICKER` forces it on globally.
|
|
49
|
+
* 3. Auto-reveal — AI development is unlocked ({@link isAiDevUnlocked}) AND
|
|
50
|
+
* authoring isn't deployment-disabled (`aiStudioEnabled`).
|
|
51
|
+
*
|
|
52
|
+
* Returns the *intent* only: the render site still requires more than one agent
|
|
53
|
+
* (`agents.length > 1`) to draw an actual choice.
|
|
54
|
+
*/
|
|
55
|
+
export declare function shouldShowAgentPicker({ agents, showAgentPickerProp, envOptIn, aiStudioEnabled, }: AgentPickerDecisionInput): boolean;
|
|
56
|
+
export {};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agentPicker
|
|
3
|
+
*
|
|
4
|
+
* Pure decision logic for the floating assistant's Build/Ask switcher. Kept in
|
|
5
|
+
* its own module (no React, no chat deps) so it can be unit-tested without
|
|
6
|
+
* dragging in the heavy chat component graph (FloatingChatbot → streamdown →
|
|
7
|
+
* shiki → @ai-sdk, ~20MB) that `ConsoleFloatingChatbot` pulls in.
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { isAskAgent, isBuildAgent } from '@object-ui/plugin-chatbot';
|
|
11
|
+
/**
|
|
12
|
+
* True when the live catalog exposes BOTH a data/query (`ask`) and an authoring
|
|
13
|
+
* (`build`) agent — alias-aware via {@link isAskAgent}/{@link isBuildAgent}, so
|
|
14
|
+
* a catalog still serving the legacy `data_chat`/`metadata_assistant` ids counts
|
|
15
|
+
* too. This is the "AI development is unlocked for this viewer" signal, the same
|
|
16
|
+
* `askAvailable && buildAvailable` notion HomePage uses to surface "Build with AI".
|
|
17
|
+
*/
|
|
18
|
+
export function isAiDevUnlocked(agents) {
|
|
19
|
+
return (agents.some((a) => isAskAgent(a.name)) && agents.some((a) => isBuildAgent(a.name)));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Decide whether the floating assistant should reveal its Build/Ask switcher.
|
|
23
|
+
*
|
|
24
|
+
* Restrained by design (the original "end users shouldn't have to choose" rule):
|
|
25
|
+
* a pure end-user surface bound to a single `ask` agent never sees it. Precedence:
|
|
26
|
+
* 1. `showAgentPickerProp` — explicit host override wins (`true`/`false`).
|
|
27
|
+
* 2. `envOptIn` — `VITE_AI_SHOW_AGENT_PICKER` forces it on globally.
|
|
28
|
+
* 3. Auto-reveal — AI development is unlocked ({@link isAiDevUnlocked}) AND
|
|
29
|
+
* authoring isn't deployment-disabled (`aiStudioEnabled`).
|
|
30
|
+
*
|
|
31
|
+
* Returns the *intent* only: the render site still requires more than one agent
|
|
32
|
+
* (`agents.length > 1`) to draw an actual choice.
|
|
33
|
+
*/
|
|
34
|
+
export function shouldShowAgentPicker({ agents, showAgentPickerProp, envOptIn = false, aiStudioEnabled = true, }) {
|
|
35
|
+
if (showAgentPickerProp !== undefined)
|
|
36
|
+
return showAgentPickerProp;
|
|
37
|
+
if (envOptIn)
|
|
38
|
+
return true;
|
|
39
|
+
return aiStudioEnabled && isAiDevUnlocked(agents);
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
export interface CommitTimelineProps {
|
|
9
|
+
open: boolean;
|
|
10
|
+
onOpenChange: (open: boolean) => void;
|
|
11
|
+
packageId: string;
|
|
12
|
+
/** Called after a successful revert so the host can refresh the app view. */
|
|
13
|
+
onReverted?: () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare function CommitTimeline({ open, onOpenChange, packageId, onReverted }: CommitTimelineProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ObjectUI
|
|
4
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* ADR-0067 — the package "Build history" timeline. Lists every commit an AI
|
|
11
|
+
* build (or edit) landed, newest-first, with "Revert" per apply commit: undo
|
|
12
|
+
* that change set (artifacts it created are soft-removed; ones it edited are
|
|
13
|
+
* restored) as a NEW append-only revert commit. This is the history-not-confirm
|
|
14
|
+
* surface — the user reviews and reverts instead of approving each publish.
|
|
15
|
+
*
|
|
16
|
+
* Sibling of DraftChangesPanel (which lists PENDING drafts before a publish);
|
|
17
|
+
* this lists what already LANDED, and can undo it.
|
|
18
|
+
*/
|
|
19
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
20
|
+
import { GitBranch, Undo2, Loader2 } from 'lucide-react';
|
|
21
|
+
import { toast } from 'sonner';
|
|
22
|
+
import { Badge, Button, Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle, } from '@object-ui/components';
|
|
23
|
+
import { useObjectTranslation } from '@object-ui/i18n';
|
|
24
|
+
import { fetchCommits, revertCommit } from './commitHistory';
|
|
25
|
+
function relativeTime(iso) {
|
|
26
|
+
if (!iso)
|
|
27
|
+
return '';
|
|
28
|
+
const then = Date.parse(iso);
|
|
29
|
+
if (Number.isNaN(then))
|
|
30
|
+
return '';
|
|
31
|
+
const secs = Math.max(0, Math.round((Date.now() - then) / 1000));
|
|
32
|
+
if (secs < 60)
|
|
33
|
+
return `${secs}s`;
|
|
34
|
+
const mins = Math.round(secs / 60);
|
|
35
|
+
if (mins < 60)
|
|
36
|
+
return `${mins}m`;
|
|
37
|
+
const hrs = Math.round(mins / 60);
|
|
38
|
+
if (hrs < 24)
|
|
39
|
+
return `${hrs}h`;
|
|
40
|
+
return `${Math.round(hrs / 24)}d`;
|
|
41
|
+
}
|
|
42
|
+
export function CommitTimeline({ open, onOpenChange, packageId, onReverted }) {
|
|
43
|
+
const { t } = useObjectTranslation();
|
|
44
|
+
const [commits, setCommits] = useState(null);
|
|
45
|
+
const [error, setError] = useState(null);
|
|
46
|
+
const [reverting, setReverting] = useState(null);
|
|
47
|
+
const load = useCallback(async () => {
|
|
48
|
+
setCommits(null);
|
|
49
|
+
setError(null);
|
|
50
|
+
try {
|
|
51
|
+
setCommits(await fetchCommits(packageId));
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
setError(e.message);
|
|
55
|
+
}
|
|
56
|
+
}, [packageId]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (open)
|
|
59
|
+
void load();
|
|
60
|
+
}, [open, load]);
|
|
61
|
+
const onRevert = async (commitId) => {
|
|
62
|
+
setReverting(commitId);
|
|
63
|
+
try {
|
|
64
|
+
await revertCommit(packageId, commitId);
|
|
65
|
+
toast.success(t('preview.history.reverted', { defaultValue: 'Reverted — the change has been undone.' }));
|
|
66
|
+
onReverted?.();
|
|
67
|
+
await load();
|
|
68
|
+
}
|
|
69
|
+
catch (e) {
|
|
70
|
+
toast.error(`${t('preview.history.revertFailed', { defaultValue: 'Revert failed' })}: ${e.message}`);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
setReverting(null);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
return (_jsx(Sheet, { open: open, onOpenChange: onOpenChange, children: _jsxs(SheetContent, { side: "right", className: "w-[420px] sm:max-w-[420px]", "data-testid": "commit-timeline-panel", children: [_jsxs(SheetHeader, { children: [_jsx(SheetTitle, { children: t('preview.history.title', { defaultValue: 'Build history' }) }), _jsx(SheetDescription, { children: t('preview.history.description', {
|
|
77
|
+
defaultValue: 'Every change to this app, newest first. Revert any step to undo it — no publish confirmation needed.',
|
|
78
|
+
}) })] }), _jsx("div", { className: "mt-4 flex flex-col gap-2 overflow-y-auto px-4 pb-6", children: error ? (_jsxs("p", { className: "text-sm text-destructive", children: [t('preview.history.loadFailed', { defaultValue: 'Could not load history:' }), " ", error] })) : commits === null ? (_jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin" }), t('preview.history.loading', { defaultValue: 'Loading history…' })] })) : commits.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: t('preview.history.empty', { defaultValue: 'No history yet for this app.' }) })) : (commits.map((c) => (_jsxs("div", { className: "flex items-start gap-2.5 rounded-md border px-2.5 py-2 text-sm", "data-testid": "commit-row", children: [_jsx(GitBranch, { className: `mt-0.5 h-4 w-4 shrink-0 ${c.operation === 'revert' ? 'text-muted-foreground' : 'text-primary'}` }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("p", { className: "truncate font-medium", children: c.message ??
|
|
79
|
+
(c.operation === 'revert'
|
|
80
|
+
? t('preview.history.revertLabel', { defaultValue: 'Reverted a change' })
|
|
81
|
+
: t('preview.history.applyLabel', { defaultValue: 'Build change' })) }), _jsxs("p", { className: "mt-0.5 flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground", children: [c.operation === 'revert' ? (_jsx(Badge, { variant: "outline", className: "border-muted-foreground/30", children: t('preview.history.revert', { defaultValue: 'revert' }) })) : null, _jsxs("span", { children: [c.itemCount, " ", t('preview.history.items', { defaultValue: 'item(s)' })] }), c.actor ? _jsxs("span", { children: ["\u00B7 ", c.actor] }) : null, c.createdAt ? _jsxs("span", { children: ["\u00B7 ", relativeTime(c.createdAt)] }) : null] })] }), c.operation === 'apply' ? (_jsx(Button, { size: "sm", variant: "ghost", className: "shrink-0", disabled: reverting !== null, onClick: () => onRevert(c.id), "data-testid": "commit-revert", children: reverting === c.id ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsxs(_Fragment, { children: [_jsx(Undo2, { className: "mr-1 h-3.5 w-3.5" }), t('preview.history.revertAction', { defaultValue: 'Revert' })] })) })) : null] }, c.id)))) })] }) }));
|
|
82
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* ObjectUI
|
|
4
4
|
* Copyright (c) 2024-present ObjectStack Inc.
|
|
@@ -16,7 +16,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
16
16
|
import { useEffect, useState } from 'react';
|
|
17
17
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
18
18
|
import { Eye, X, Rocket, GitCompareArrows } from 'lucide-react';
|
|
19
|
-
import { Button } from '@object-ui/components';
|
|
19
|
+
import { Button, cn } from '@object-ui/components';
|
|
20
20
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
21
21
|
import { usePreviewDrafts, markPreviewExit, PREVIEW_QUERY_FLAG } from './PreviewModeContext';
|
|
22
22
|
import { usePublishAllDrafts } from './usePublishAllDrafts';
|
|
@@ -78,9 +78,22 @@ export function DraftPreviewBar() {
|
|
|
78
78
|
}
|
|
79
79
|
catch { /* ignore */ } }, 300);
|
|
80
80
|
};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
// Under the auto-publish posture (and any time a draft preview is opened with
|
|
82
|
+
// nothing staged) there are zero pending drafts. Claiming "nothing is live
|
|
83
|
+
// until you publish" and offering a Publish button then is both false and a
|
|
84
|
+
// no-op, so the bar drops the publish affordance and softens to a neutral
|
|
85
|
+
// preview indicator. An UNKNOWN count (null — still loading or the fetch
|
|
86
|
+
// failed) keeps the publish path: we only relax when we KNOW the count is zero.
|
|
87
|
+
const noChanges = pendingCount === 0;
|
|
88
|
+
return (_jsxs("div", { className: cn('sticky top-0 z-40 flex items-center gap-3 border-b px-4 py-2 text-sm', noChanges
|
|
89
|
+
? 'border-slate-200 bg-slate-50 text-slate-700 dark:border-slate-700/60 dark:bg-slate-900/40 dark:text-slate-300'
|
|
90
|
+
: 'border-amber-300/70 bg-amber-50 text-amber-900 dark:border-amber-700/60 dark:bg-amber-950/40 dark:text-amber-200'), "data-testid": "draft-preview-bar", children: [_jsx(Eye, { className: "h-4 w-4 shrink-0" }), _jsx("p", { className: "min-w-0 flex-1 truncate", children: noChanges
|
|
91
|
+
? t('preview.draftBar.messageClean', {
|
|
92
|
+
defaultValue: 'Draft preview — no unpublished changes; everything here is already live.',
|
|
93
|
+
})
|
|
94
|
+
: t('preview.draftBar.message', {
|
|
95
|
+
defaultValue: 'Draft preview — you are seeing unpublished changes. Nothing here is live until you publish.',
|
|
96
|
+
}) }), !noChanges && (_jsxs(_Fragment, { children: [_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setChangesOpen(true), "data-testid": "draft-preview-changes", children: [_jsx(GitCompareArrows, { className: "mr-1 h-3.5 w-3.5" }), t('preview.draftBar.changes', { defaultValue: 'Changes' }), typeof pendingCount === 'number' ? ` (${pendingCount})` : ''] }), _jsxs(Button, { size: "sm", onClick: publish, disabled: publishing, "data-testid": "draft-preview-publish", children: [_jsx(Rocket, { className: "mr-1 h-3.5 w-3.5" }), publishing
|
|
97
|
+
? t('preview.draftBar.publishing', { defaultValue: 'Publishing…' })
|
|
98
|
+
: t('preview.draftBar.publish', { defaultValue: 'Publish' })] }), _jsx(DraftChangesPanel, { open: changesOpen, onOpenChange: setChangesOpen })] })), _jsxs(Button, { size: "sm", variant: "outline", onClick: exit, "data-testid": "draft-preview-exit", children: [_jsx(X, { className: "mr-1 h-3.5 w-3.5" }), t('preview.draftBar.exit', { defaultValue: 'Exit preview' })] })] }));
|
|
86
99
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* ObjectUI
|
|
4
4
|
* Copyright (c) 2024-present ObjectStack Inc.
|
|
@@ -20,12 +20,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
20
20
|
*/
|
|
21
21
|
import { useState } from 'react';
|
|
22
22
|
import { useLocation, useParams } from 'react-router-dom';
|
|
23
|
-
import { EyeOff, Rocket } from 'lucide-react';
|
|
23
|
+
import { EyeOff, History, Rocket } from 'lucide-react';
|
|
24
24
|
import { toast } from 'sonner';
|
|
25
25
|
import { Button } from '@object-ui/components';
|
|
26
26
|
import { useObjectTranslation } from '@object-ui/i18n';
|
|
27
27
|
import { useMetadata } from '../providers/MetadataProvider';
|
|
28
28
|
import { matchAppBySegment } from '../utils/appRoute';
|
|
29
|
+
import { CommitTimeline } from './CommitTimeline';
|
|
29
30
|
import { usePreviewDrafts } from './PreviewModeContext';
|
|
30
31
|
export function UnpublishedAppBar() {
|
|
31
32
|
const preview = usePreviewDrafts();
|
|
@@ -34,6 +35,7 @@ export function UnpublishedAppBar() {
|
|
|
34
35
|
const { apps, refresh } = useMetadata();
|
|
35
36
|
const { t } = useObjectTranslation();
|
|
36
37
|
const [publishing, setPublishing] = useState(false);
|
|
38
|
+
const [historyOpen, setHistoryOpen] = useState(false);
|
|
37
39
|
// The draft-preview watermark owns the preview tree; never stack both bars.
|
|
38
40
|
if (preview)
|
|
39
41
|
return null;
|
|
@@ -43,6 +45,8 @@ export function UnpublishedAppBar() {
|
|
|
43
45
|
const app = matchAppBySegment(apps ?? [], routeApp);
|
|
44
46
|
if (!app || app.hidden !== true)
|
|
45
47
|
return null;
|
|
48
|
+
// ADR-0067 — the package this app belongs to keys its commit timeline.
|
|
49
|
+
const packageId = app?.packageId ?? app?._packageId ?? null;
|
|
46
50
|
const publish = async () => {
|
|
47
51
|
setPublishing(true);
|
|
48
52
|
try {
|
|
@@ -71,9 +75,9 @@ export function UnpublishedAppBar() {
|
|
|
71
75
|
setPublishing(false);
|
|
72
76
|
}
|
|
73
77
|
};
|
|
74
|
-
return (_jsxs("div", { className: "sticky top-0 z-40 flex items-center gap-3 border-b border-amber-300/70 bg-amber-50 px-4 py-2 text-sm text-amber-900 dark:border-amber-700/60 dark:bg-amber-950/40 dark:text-amber-200", "data-testid": "unpublished-app-bar", children: [_jsx(EyeOff, { className: "h-4 w-4 shrink-0" }), _jsx("p", { className: "min-w-0 flex-1 truncate", children: t('preview.unpublishedBar.message', {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "sticky top-0 z-40 flex items-center gap-3 border-b border-amber-300/70 bg-amber-50 px-4 py-2 text-sm text-amber-900 dark:border-amber-700/60 dark:bg-amber-950/40 dark:text-amber-200", "data-testid": "unpublished-app-bar", children: [_jsx(EyeOff, { className: "h-4 w-4 shrink-0" }), _jsx("p", { className: "min-w-0 flex-1 truncate", children: t('preview.unpublishedBar.message', {
|
|
79
|
+
defaultValue: 'Unpublished app — fully functional, but only builders can see it. Publish to make it visible to your users.',
|
|
80
|
+
}) }), packageId ? (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setHistoryOpen(true), "data-testid": "unpublished-app-history", children: [_jsx(History, { className: "mr-1 h-3.5 w-3.5" }), t('preview.history.button', { defaultValue: 'History' })] })) : null, _jsxs(Button, { size: "sm", onClick: publish, disabled: publishing, "data-testid": "unpublished-app-publish", children: [_jsx(Rocket, { className: "mr-1 h-3.5 w-3.5" }), publishing
|
|
81
|
+
? t('preview.unpublishedBar.publishing', { defaultValue: 'Publishing…' })
|
|
82
|
+
: t('preview.unpublishedBar.publish', { defaultValue: 'Publish' })] })] }), packageId ? (_jsx(CommitTimeline, { open: historyOpen, onOpenChange: setHistoryOpen, packageId: packageId, onReverted: refresh })) : null] }));
|
|
79
83
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* ADR-0067 — package-scoped commit history reads/writes for the timeline.
|
|
10
|
+
*
|
|
11
|
+
* Each AI build (and Studio batch) lands as one revertible COMMIT on top of
|
|
12
|
+
* the metadata history. The history-not-confirm model: you don't approve each
|
|
13
|
+
* publish — you review this timeline and revert if a change was wrong. These
|
|
14
|
+
* helpers read the timeline (`GET /packages/:id/commits`) and revert one
|
|
15
|
+
* commit (`POST /packages/:id/commits/:cid/revert`). Cookie-authenticated like
|
|
16
|
+
* every console call; tolerant of the `{ data: ... }` / bare envelope shapes.
|
|
17
|
+
*/
|
|
18
|
+
export interface CommitEntry {
|
|
19
|
+
id: string;
|
|
20
|
+
operation: 'apply' | 'revert';
|
|
21
|
+
message?: string;
|
|
22
|
+
actor?: string;
|
|
23
|
+
aiModel?: string;
|
|
24
|
+
itemCount: number;
|
|
25
|
+
createdAt?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function fetchCommits(packageId: string): Promise<CommitEntry[]>;
|
|
28
|
+
export declare function revertCommit(packageId: string, commitId: string): Promise<void>;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
export async function fetchCommits(packageId) {
|
|
9
|
+
const res = await fetch(`/api/v1/packages/${encodeURIComponent(packageId)}/commits`, {
|
|
10
|
+
credentials: 'include',
|
|
11
|
+
headers: { Accept: 'application/json' },
|
|
12
|
+
cache: 'no-store',
|
|
13
|
+
});
|
|
14
|
+
if (!res.ok)
|
|
15
|
+
throw new Error(`commits HTTP ${res.status}`);
|
|
16
|
+
const data = (await res.json());
|
|
17
|
+
const list = Array.isArray(data)
|
|
18
|
+
? data
|
|
19
|
+
: data?.commits ??
|
|
20
|
+
data?.data?.commits ??
|
|
21
|
+
[];
|
|
22
|
+
return (Array.isArray(list) ? list : []).map((raw) => {
|
|
23
|
+
const c = raw;
|
|
24
|
+
return {
|
|
25
|
+
id: String(c.id),
|
|
26
|
+
operation: c.operation === 'revert' ? 'revert' : 'apply',
|
|
27
|
+
message: typeof c.message === 'string' ? c.message : undefined,
|
|
28
|
+
actor: typeof c.actor === 'string' ? c.actor : undefined,
|
|
29
|
+
aiModel: typeof c.aiModel === 'string' ? c.aiModel : undefined,
|
|
30
|
+
itemCount: typeof c.itemCount === 'number' ? c.itemCount : 0,
|
|
31
|
+
createdAt: typeof c.createdAt === 'string' ? c.createdAt : undefined,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
export async function revertCommit(packageId, commitId) {
|
|
36
|
+
const res = await fetch(`/api/v1/packages/${encodeURIComponent(packageId)}/commits/${encodeURIComponent(commitId)}/revert`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
credentials: 'include',
|
|
39
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
40
|
+
body: '{}',
|
|
41
|
+
});
|
|
42
|
+
const payload = (await res.json().catch(() => null));
|
|
43
|
+
const inner = payload?.data ?? payload ?? undefined;
|
|
44
|
+
if (!res.ok || inner?.success === false) {
|
|
45
|
+
const err = payload?.error;
|
|
46
|
+
throw new Error((typeof err === 'object' ? err?.message : err) ?? `HTTP ${res.status}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -20,14 +20,19 @@ import { PredicateScopeProvider } from '@object-ui/react';
|
|
|
20
20
|
const ExprCtx = createContext(null);
|
|
21
21
|
export function ExpressionProvider({ children, user = {}, app = {}, data = {}, features = {} }) {
|
|
22
22
|
const value = useMemo(() => {
|
|
23
|
-
|
|
23
|
+
// ADR-0068: expose the SAME user object under the canonical `current_user`
|
|
24
|
+
// plus the back-compat `user` alias and the server-RLS-parity `ctx.user`
|
|
25
|
+
// alias, so a predicate authored against any one form evaluates identically
|
|
26
|
+
// on client, server-formula, and server-RLS.
|
|
27
|
+
const context = { current_user: user, user, ctx: { user }, app, data, features };
|
|
24
28
|
const evaluator = new ExpressionEvaluator(context);
|
|
25
29
|
return { user, app, data, features, evaluator };
|
|
26
30
|
}, [user, app, data, features]);
|
|
27
31
|
// Also feed the predicate scope used by useCondition/useExpression in
|
|
28
32
|
// @object-ui/react so action visibility predicates (e.g. on toolbar
|
|
29
33
|
// buttons) can see deployment-level flags like features.multiOrgEnabled.
|
|
30
|
-
|
|
34
|
+
// Mirror the canonical `current_user`/`user`/`ctx.user` aliases here too.
|
|
35
|
+
const scope = useMemo(() => ({ current_user: user, user, ctx: { user }, app, data, features }), [user, app, data, features]);
|
|
31
36
|
return (_jsx(ExprCtx.Provider, { value: value, children: _jsx(PredicateScopeProvider, { scope: scope, children: children }) }));
|
|
32
37
|
}
|
|
33
38
|
/**
|
|
@@ -39,7 +44,8 @@ export function useExpressionContext() {
|
|
|
39
44
|
if (!ctx) {
|
|
40
45
|
// Return a safe default so components can be used outside the provider
|
|
41
46
|
const fallback = { user: {}, app: {}, data: {}, features: {} };
|
|
42
|
-
|
|
47
|
+
const evalContext = { current_user: {}, ctx: { user: {} }, ...fallback };
|
|
48
|
+
return { ...fallback, evaluator: new ExpressionEvaluator(evalContext) };
|
|
43
49
|
}
|
|
44
50
|
return ctx;
|
|
45
51
|
}
|
|
@@ -459,6 +459,15 @@ export function MetadataProvider({ children, adapter, ttlMs = DEFAULT_TTL_MS })
|
|
|
459
459
|
// NOT gated to preview-drafts mode: the live world every tree reads changed.
|
|
460
460
|
useEffect(() => {
|
|
461
461
|
return subscribeMetadataRefresh(() => {
|
|
462
|
+
// The adapter keeps its OWN object-schema cache (getObjectSchema) that the
|
|
463
|
+
// context refresh below does not touch. A publish/install just changed the
|
|
464
|
+
// live schema, so drop it too — otherwise create/edit forms (which read
|
|
465
|
+
// the adapter's getObjectSchema, NOT this context) keep showing the
|
|
466
|
+
// pre-publish field set until the adapter cache's 5-minute TTL lapses.
|
|
467
|
+
try {
|
|
468
|
+
adapterRef.current?.clearCache?.();
|
|
469
|
+
}
|
|
470
|
+
catch { /* adapter mid-swap */ }
|
|
462
471
|
void refresh();
|
|
463
472
|
});
|
|
464
473
|
}, [refresh]);
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility functions for ObjectStack Console
|
|
3
3
|
*/
|
|
4
|
-
export { resolveRecordFormTarget, } from './recordFormNavigation';
|
|
5
|
-
export type { ObjectDefinitionForNavigation, RecordFormTarget, } from './recordFormNavigation';
|
|
4
|
+
export { resolveRecordFormTarget, resolveFormViewLayout, } from './recordFormNavigation';
|
|
5
|
+
export type { ObjectDefinitionForNavigation, RecordFormTarget, ObjectDefinitionForFormView, FormViewDefinition, FormViewModalLayout, } from './recordFormNavigation';
|
|
6
6
|
export { deriveRelatedLists } from './deriveRelatedLists';
|
|
7
7
|
export type { DerivedRelatedList } from './deriveRelatedLists';
|
|
8
8
|
export { preferLocal } from './preferLocal';
|
package/dist/utils/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Utility functions for ObjectStack Console
|
|
3
3
|
*/
|
|
4
|
-
export { resolveRecordFormTarget, } from './recordFormNavigation';
|
|
4
|
+
export { resolveRecordFormTarget, resolveFormViewLayout, } from './recordFormNavigation';
|
|
5
5
|
export { deriveRelatedLists } from './deriveRelatedLists';
|
|
6
6
|
export { preferLocal } from './preferLocal';
|
|
7
7
|
export { appRouteSegment, matchAppBySegment } from './appRoute';
|