@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.
Files changed (141) hide show
  1. package/CHANGELOG.md +560 -0
  2. package/dist/console/AppContent.js +23 -17
  3. package/dist/console/ConsoleShell.d.ts +16 -0
  4. package/dist/console/ConsoleShell.js +43 -2
  5. package/dist/console/ai/AiChatPage.js +47 -16
  6. package/dist/console/ai/LiveCanvas.d.ts +8 -2
  7. package/dist/console/ai/LiveCanvas.js +6 -4
  8. package/dist/console/home/HomeLayout.js +5 -7
  9. package/dist/console/home/HomePage.js +1 -9
  10. package/dist/console/organizations/CreateWorkspaceDialog.js +15 -1
  11. package/dist/console/organizations/OrganizationsPage.js +22 -3
  12. package/dist/console/organizations/provisionEnvironment.d.ts +53 -0
  13. package/dist/console/organizations/provisionEnvironment.js +64 -0
  14. package/dist/environment/EnvironmentEntitlementDialog.d.ts +34 -0
  15. package/dist/environment/EnvironmentEntitlementDialog.js +37 -0
  16. package/dist/environment/EnvironmentListToolbar.d.ts +33 -0
  17. package/dist/environment/EnvironmentListToolbar.js +59 -0
  18. package/dist/environment/entitlements.d.ts +90 -0
  19. package/dist/environment/entitlements.js +91 -0
  20. package/dist/environment/useEnvironmentEntitlements.d.ts +32 -0
  21. package/dist/environment/useEnvironmentEntitlements.js +108 -0
  22. package/dist/hooks/useActionModal.js +15 -1
  23. package/dist/hooks/useAiSurface.d.ts +59 -0
  24. package/dist/hooks/useAiSurface.js +78 -0
  25. package/dist/hooks/useChatConversation.d.ts +30 -0
  26. package/dist/hooks/useChatConversation.js +63 -0
  27. package/dist/hooks/useConsoleActionRuntime.d.ts +3 -0
  28. package/dist/hooks/useConsoleActionRuntime.js +42 -10
  29. package/dist/index.d.ts +5 -2
  30. package/dist/index.js +10 -2
  31. package/dist/layout/AppHeader.js +28 -4
  32. package/dist/layout/ConsoleFloatingChatbot.d.ts +6 -4
  33. package/dist/layout/ConsoleFloatingChatbot.js +41 -10
  34. package/dist/layout/ConsoleLayout.js +5 -6
  35. package/dist/layout/ContextSelectors.js +59 -35
  36. package/dist/layout/agentPicker.d.ts +56 -0
  37. package/dist/layout/agentPicker.js +40 -0
  38. package/dist/preview/CommitTimeline.d.ts +15 -0
  39. package/dist/preview/CommitTimeline.js +82 -0
  40. package/dist/preview/DraftPreviewBar.js +20 -7
  41. package/dist/preview/UnpublishedAppBar.js +11 -7
  42. package/dist/preview/commitHistory.d.ts +28 -0
  43. package/dist/preview/commitHistory.js +48 -0
  44. package/dist/providers/ExpressionProvider.js +9 -3
  45. package/dist/providers/MetadataProvider.js +9 -0
  46. package/dist/utils/index.d.ts +2 -2
  47. package/dist/utils/index.js +1 -1
  48. package/dist/utils/recordFormNavigation.d.ts +60 -0
  49. package/dist/utils/recordFormNavigation.js +35 -0
  50. package/dist/utils/resolvePageVarTokens.d.ts +31 -0
  51. package/dist/utils/resolvePageVarTokens.js +72 -0
  52. package/dist/views/CreateViewDialog.js +14 -1
  53. package/dist/views/FlowRunner.d.ts +2 -30
  54. package/dist/views/FlowRunner.js +18 -50
  55. package/dist/views/ObjectView.js +26 -12
  56. package/dist/views/ScreenView.d.ts +70 -0
  57. package/dist/views/ScreenView.js +73 -0
  58. package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
  59. package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
  60. package/dist/views/metadata-admin/DirectoryPage.js +2 -14
  61. package/dist/views/metadata-admin/JsonSourceEditor.d.ts +3 -1
  62. package/dist/views/metadata-admin/JsonSourceEditor.js +21 -3
  63. package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
  64. package/dist/views/metadata-admin/PackagesPage.js +58 -5
  65. package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
  66. package/dist/views/metadata-admin/ResourceEditPage.js +83 -24
  67. package/dist/views/metadata-admin/ResourceListPage.js +28 -19
  68. package/dist/views/metadata-admin/StudioHomePage.js +6 -14
  69. package/dist/views/metadata-admin/anchors.js +20 -2
  70. package/dist/views/metadata-admin/createBody.d.ts +26 -0
  71. package/dist/views/metadata-admin/createBody.js +30 -0
  72. package/dist/views/metadata-admin/i18n.js +108 -2
  73. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +10 -2
  74. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +136 -8
  75. package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +99 -4
  76. package/dist/views/metadata-admin/inspectors/FlowExprIssue.d.ts +21 -0
  77. package/dist/views/metadata-admin/inspectors/FlowExprIssue.js +13 -0
  78. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +20 -2
  79. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +71 -28
  80. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +4 -1
  81. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +24 -9
  82. package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +81 -4
  83. package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +4 -1
  84. package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +8 -3
  85. package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +5 -4
  86. package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +47 -12
  87. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +1 -1
  88. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +60 -2
  89. package/dist/views/metadata-admin/inspectors/VariableTextInput.d.ts +47 -0
  90. package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
  91. package/dist/views/metadata-admin/inspectors/_shared.d.ts +5 -1
  92. package/dist/views/metadata-admin/inspectors/_shared.js +2 -2
  93. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
  94. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +102 -0
  95. package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
  96. package/dist/views/metadata-admin/inspectors/flow-node-config.js +67 -11
  97. package/dist/views/metadata-admin/inspectors/flow-ref-check.d.ts +39 -0
  98. package/dist/views/metadata-admin/inspectors/flow-ref-check.js +114 -0
  99. package/dist/views/metadata-admin/inspectors/flow-scope.d.ts +109 -0
  100. package/dist/views/metadata-admin/inspectors/flow-scope.js +199 -0
  101. package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +14 -3
  102. package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
  103. package/dist/views/metadata-admin/inspectors/useFlowScope.d.ts +23 -0
  104. package/dist/views/metadata-admin/inspectors/useFlowScope.js +45 -0
  105. package/dist/views/metadata-admin/issuePath.d.ts +22 -0
  106. package/dist/views/metadata-admin/issuePath.js +65 -0
  107. package/dist/views/metadata-admin/package-scope.d.ts +41 -0
  108. package/dist/views/metadata-admin/package-scope.js +59 -0
  109. package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
  110. package/dist/views/metadata-admin/previews/DatasetPreview.js +21 -5
  111. package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +26 -1
  112. package/dist/views/metadata-admin/previews/FlowCanvas.js +143 -16
  113. package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
  114. package/dist/views/metadata-admin/previews/FlowPreview.js +47 -7
  115. package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +37 -3
  116. package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
  117. package/dist/views/metadata-admin/previews/PagePreview.js +112 -3
  118. package/dist/views/metadata-admin/previews/ProblemsPanel.d.ts +18 -0
  119. package/dist/views/metadata-admin/previews/ProblemsPanel.js +27 -0
  120. package/dist/views/metadata-admin/previews/ReportPreview.d.ts +9 -8
  121. package/dist/views/metadata-admin/previews/ReportPreview.js +33 -16
  122. package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
  123. package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
  124. package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +14 -0
  125. package/dist/views/metadata-admin/previews/flow-canvas-layout.js +37 -0
  126. package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +17 -1
  127. package/dist/views/metadata-admin/previews/flow-canvas-parts.js +23 -6
  128. package/dist/views/metadata-admin/previews/flow-expr-problems.d.ts +19 -0
  129. package/dist/views/metadata-admin/previews/flow-expr-problems.js +97 -0
  130. package/dist/views/metadata-admin/previews/flow-problems.d.ts +84 -0
  131. package/dist/views/metadata-admin/previews/flow-problems.js +209 -0
  132. package/dist/views/metadata-admin/previews/object-fields-io.d.ts +21 -0
  133. package/dist/views/metadata-admin/previews/object-fields-io.js +37 -2
  134. package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
  135. package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
  136. package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +20 -0
  137. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +7 -0
  138. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +76 -2
  139. package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +32 -3
  140. package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +119 -9
  141. 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
- React.useEffect(() => {
84
- let cancelled = false;
85
- (async () => {
86
- try {
87
- const res = await fetch(endpoint, {
88
- credentials: 'include',
89
- headers: { Accept: 'application/json' },
90
- });
91
- if (!res.ok)
92
- return;
93
- const json = await res.json();
94
- if (cancelled)
95
- return;
96
- const rows = extractRows(json);
97
- const opts = [];
98
- const seen = new Set();
99
- for (const row of rows) {
100
- if (!rowPasses(row, filters))
101
- continue;
102
- const value = getByPath(row, valueKey);
103
- if (value == null || typeof value !== 'string' || seen.has(value))
104
- continue;
105
- seen.add(value);
106
- const labelRaw = getByPath(row, labelKey);
107
- opts.push({ value, label: typeof labelRaw === 'string' && labelRaw ? labelRaw : value });
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
- catch {
113
- /* offline / unauthorized render with no options */
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
- return () => { cancelled = true; };
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
- return options;
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, children: [_jsx(SelectTrigger, { "aria-label": label, 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))) })] }));
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
- 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": "draft-preview-bar", children: [_jsx(Eye, { className: "h-4 w-4 shrink-0" }), _jsx("p", { className: "min-w-0 flex-1 truncate", children: t('preview.draftBar.message', {
82
- defaultValue: 'Draft preview you are seeing unpublished changes. Nothing here is live until you publish.',
83
- }) }), _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
84
- ? t('preview.draftBar.publishing', { defaultValue: 'Publishing…' })
85
- : 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' })] })] }));
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
- defaultValue: 'Unpublished app — fully functional, but only builders can see it. Publish to make it visible to your users.',
76
- }) }), _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
77
- ? t('preview.unpublishedBar.publishing', { defaultValue: 'Publishing…' })
78
- : t('preview.unpublishedBar.publish', { defaultValue: 'Publish' })] })] }));
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
- const context = { user, app, data, features };
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
- const scope = useMemo(() => ({ user, app, data, features }), [user, app, data, features]);
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
- return { ...fallback, evaluator: new ExpressionEvaluator(fallback) };
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]);
@@ -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';
@@ -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';