@object-ui/app-shell 7.1.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 (95) hide show
  1. package/CHANGELOG.md +279 -0
  2. package/dist/console/AppContent.js +9 -15
  3. package/dist/console/ConsoleShell.d.ts +16 -0
  4. package/dist/console/ConsoleShell.js +43 -2
  5. package/dist/console/ai/AiChatPage.js +36 -9
  6. package/dist/console/home/HomeLayout.js +5 -7
  7. package/dist/console/home/HomePage.js +1 -9
  8. package/dist/console/organizations/CreateWorkspaceDialog.js +15 -1
  9. package/dist/console/organizations/OrganizationsPage.js +22 -3
  10. package/dist/console/organizations/provisionEnvironment.d.ts +53 -0
  11. package/dist/console/organizations/provisionEnvironment.js +64 -0
  12. package/dist/environment/EnvironmentEntitlementDialog.d.ts +34 -0
  13. package/dist/environment/EnvironmentEntitlementDialog.js +37 -0
  14. package/dist/environment/EnvironmentListToolbar.d.ts +33 -0
  15. package/dist/environment/EnvironmentListToolbar.js +59 -0
  16. package/dist/environment/entitlements.d.ts +90 -0
  17. package/dist/environment/entitlements.js +91 -0
  18. package/dist/environment/useEnvironmentEntitlements.d.ts +32 -0
  19. package/dist/environment/useEnvironmentEntitlements.js +108 -0
  20. package/dist/hooks/useActionModal.js +15 -1
  21. package/dist/hooks/useAiSurface.d.ts +59 -0
  22. package/dist/hooks/useAiSurface.js +78 -0
  23. package/dist/hooks/useConsoleActionRuntime.d.ts +3 -0
  24. package/dist/hooks/useConsoleActionRuntime.js +36 -8
  25. package/dist/index.d.ts +3 -1
  26. package/dist/index.js +5 -1
  27. package/dist/layout/AppHeader.js +28 -4
  28. package/dist/layout/ConsoleFloatingChatbot.js +16 -2
  29. package/dist/layout/ConsoleLayout.js +5 -6
  30. package/dist/preview/DraftPreviewBar.js +20 -7
  31. package/dist/providers/ExpressionProvider.js +9 -3
  32. package/dist/utils/index.d.ts +2 -2
  33. package/dist/utils/index.js +1 -1
  34. package/dist/utils/recordFormNavigation.d.ts +60 -0
  35. package/dist/utils/recordFormNavigation.js +35 -0
  36. package/dist/utils/resolvePageVarTokens.d.ts +31 -0
  37. package/dist/utils/resolvePageVarTokens.js +72 -0
  38. package/dist/views/CreateViewDialog.js +14 -1
  39. package/dist/views/ObjectView.js +26 -12
  40. package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
  41. package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
  42. package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
  43. package/dist/views/metadata-admin/PackagesPage.js +49 -4
  44. package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
  45. package/dist/views/metadata-admin/ResourceEditPage.js +36 -4
  46. package/dist/views/metadata-admin/ResourceListPage.js +21 -4
  47. package/dist/views/metadata-admin/createBody.d.ts +26 -0
  48. package/dist/views/metadata-admin/createBody.js +30 -0
  49. package/dist/views/metadata-admin/i18n.js +20 -0
  50. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +8 -0
  51. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +17 -3
  52. package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +16 -2
  53. package/dist/views/metadata-admin/inspectors/FlowExprIssue.d.ts +21 -0
  54. package/dist/views/metadata-admin/inspectors/FlowExprIssue.js +13 -0
  55. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +20 -2
  56. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +71 -28
  57. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +4 -1
  58. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +24 -9
  59. package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +15 -3
  60. package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +4 -1
  61. package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +8 -3
  62. package/dist/views/metadata-admin/inspectors/VariableTextInput.d.ts +47 -0
  63. package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
  64. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +6 -1
  65. package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
  66. package/dist/views/metadata-admin/inspectors/flow-node-config.js +21 -10
  67. package/dist/views/metadata-admin/inspectors/flow-ref-check.d.ts +39 -0
  68. package/dist/views/metadata-admin/inspectors/flow-ref-check.js +114 -0
  69. package/dist/views/metadata-admin/inspectors/flow-scope.d.ts +109 -0
  70. package/dist/views/metadata-admin/inspectors/flow-scope.js +199 -0
  71. package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +14 -3
  72. package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
  73. package/dist/views/metadata-admin/inspectors/useFlowScope.d.ts +23 -0
  74. package/dist/views/metadata-admin/inspectors/useFlowScope.js +45 -0
  75. package/dist/views/metadata-admin/package-scope.d.ts +15 -0
  76. package/dist/views/metadata-admin/package-scope.js +16 -0
  77. package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
  78. package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +22 -3
  79. package/dist/views/metadata-admin/previews/FlowCanvas.js +45 -6
  80. package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
  81. package/dist/views/metadata-admin/previews/FlowPreview.js +42 -30
  82. package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
  83. package/dist/views/metadata-admin/previews/ProblemsPanel.d.ts +18 -0
  84. package/dist/views/metadata-admin/previews/ProblemsPanel.js +27 -0
  85. package/dist/views/metadata-admin/previews/ReportPreview.d.ts +9 -8
  86. package/dist/views/metadata-admin/previews/ReportPreview.js +33 -16
  87. package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +9 -1
  88. package/dist/views/metadata-admin/previews/flow-canvas-parts.js +5 -3
  89. package/dist/views/metadata-admin/previews/flow-expr-problems.d.ts +19 -0
  90. package/dist/views/metadata-admin/previews/flow-expr-problems.js +97 -0
  91. package/dist/views/metadata-admin/previews/flow-problems.d.ts +84 -0
  92. package/dist/views/metadata-admin/previews/flow-problems.js +209 -0
  93. package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +9 -0
  94. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +4 -2
  95. package/package.json +38 -38
@@ -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
  }
@@ -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
  }
@@ -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';
@@ -55,6 +55,66 @@ export declare function resolveRecordFormTarget(opts: {
55
55
  _id?: string | number;
56
56
  } | null | undefined;
57
57
  }): RecordFormTarget;
58
+ /**
59
+ * Subset of the default form-view shape consumed by
60
+ * {@link resolveFormViewLayout}. This is the flattened `config` body of the
61
+ * object's default `viewKind: 'form'` ViewItem (ADR-0017), merged onto the
62
+ * object by `MetadataProvider` as `objectDef.form` (and mirrored under
63
+ * `objectDef.formViews.default` for the legacy aggregated-container shape).
64
+ */
65
+ export interface FormViewDefinition {
66
+ /**
67
+ * Layout family declared by the form view (`simple` | `tabbed` | `wizard`
68
+ * | `split`). Only `tabbed` changes the *modal's* internal layout; the
69
+ * others still render their curated sections, just stacked.
70
+ */
71
+ type?: string;
72
+ /** Curated field sections — the selection, order, and grouping to render. */
73
+ sections?: any[];
74
+ /** Inline child collections (master-detail). */
75
+ subforms?: any[];
76
+ }
77
+ /**
78
+ * Object metadata subset carrying the default form view, as merged onto the
79
+ * runtime objects list (`useMetadata().objects`).
80
+ */
81
+ export interface ObjectDefinitionForFormView {
82
+ form?: FormViewDefinition | null;
83
+ formViews?: {
84
+ default?: FormViewDefinition | null;
85
+ } | null;
86
+ }
87
+ /**
88
+ * Layout props derived from an object's default form view, ready to spread
89
+ * into a `<ModalForm>` schema.
90
+ */
91
+ export interface FormViewModalLayout {
92
+ /** Curated sections to render (omitted when the form view declares none). */
93
+ sections?: any[];
94
+ /** `'tabbed'` when the form view is tabbed; omitted otherwise (stacked). */
95
+ contentLayout?: 'tabbed';
96
+ /** Inline child collections for a master-detail modal. */
97
+ subforms?: any[];
98
+ }
99
+ /**
100
+ * Resolve a `<ModalForm>`'s layout props from an object's DEFAULT FORM VIEW
101
+ * (curated sections + field selection/order, plus master-detail subforms).
102
+ *
103
+ * The create / edit record modal otherwise falls back to the raw object
104
+ * schema — rendering every field in schema order and ignoring the curated
105
+ * form view entirely. This resolver lets the New/Edit modal honor the same
106
+ * view-driven layout the full-screen record page (`RecordFormPage`) does.
107
+ *
108
+ * Resolution mirrors `RecordFormPage`: prefer `objectDef.form` (the default
109
+ * ViewItem) and fall back to `objectDef.formViews.default` (legacy container).
110
+ *
111
+ * Returns an EMPTY object when the object has no form view, or a form view
112
+ * that declares no sections — the caller then keeps its existing behavior
113
+ * (a flat field list, i.e. the raw object schema). Empty `sections` /
114
+ * `subforms` arrays are treated as absent so an empty curation never blanks
115
+ * out the form.
116
+ */
117
+ export declare function resolveFormViewLayout(objectDef: ObjectDefinitionForFormView | null | undefined): FormViewModalLayout;
58
118
  /**
59
119
  * Action descriptor accepted by the navigate-create / navigate-edit
60
120
  * handlers. Loose-typed because the same shape is constructed dynamically
@@ -41,6 +41,41 @@ export function resolveRecordFormTarget(opts) {
41
41
  }
42
42
  return { kind: 'page', url: `${baseUrl}/${objectDef.name}/new` };
43
43
  }
44
+ /**
45
+ * Resolve a `<ModalForm>`'s layout props from an object's DEFAULT FORM VIEW
46
+ * (curated sections + field selection/order, plus master-detail subforms).
47
+ *
48
+ * The create / edit record modal otherwise falls back to the raw object
49
+ * schema — rendering every field in schema order and ignoring the curated
50
+ * form view entirely. This resolver lets the New/Edit modal honor the same
51
+ * view-driven layout the full-screen record page (`RecordFormPage`) does.
52
+ *
53
+ * Resolution mirrors `RecordFormPage`: prefer `objectDef.form` (the default
54
+ * ViewItem) and fall back to `objectDef.formViews.default` (legacy container).
55
+ *
56
+ * Returns an EMPTY object when the object has no form view, or a form view
57
+ * that declares no sections — the caller then keeps its existing behavior
58
+ * (a flat field list, i.e. the raw object schema). Empty `sections` /
59
+ * `subforms` arrays are treated as absent so an empty curation never blanks
60
+ * out the form.
61
+ */
62
+ export function resolveFormViewLayout(objectDef) {
63
+ const formView = objectDef?.form ?? objectDef?.formViews?.default;
64
+ if (!formView)
65
+ return {};
66
+ const layout = {};
67
+ if (Array.isArray(formView.sections) && formView.sections.length > 0) {
68
+ layout.sections = formView.sections;
69
+ // Only 'tabbed' maps to a modal content layout; 'wizard'/'split' have no
70
+ // modal equivalent and degrade to a stacked section list.
71
+ if (formView.type === 'tabbed')
72
+ layout.contentLayout = 'tabbed';
73
+ }
74
+ if (Array.isArray(formView.subforms) && formView.subforms.length > 0) {
75
+ layout.subforms = formView.subforms;
76
+ }
77
+ return layout;
78
+ }
44
79
  /**
45
80
  * Resolve the URL for a `navigate_create` action.
46
81
  *
@@ -0,0 +1,31 @@
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
+ * resolvePageVarTokens — resolve `{{page.<path>}}` tokens against a page-variable
9
+ * snapshot. The data-entry bridge for SDUI forms: an interactive input
10
+ * (`element:text_input`, `element:record_picker`) writes a page variable; a
11
+ * submit action references it in its params/body as `{{page.<var>}}`; this
12
+ * resolves those tokens against the live snapshot — published into the action
13
+ * context by `PageVariableActionBridge` — just before the request body is built.
14
+ *
15
+ * - A WHOLE-VALUE token (`"{{page.amount}}"`) is replaced by the variable's RAW
16
+ * value, preserving its type — a number stays a number, an object stays an
17
+ * object — so numeric/boolean/array form fields submit with the right JSON
18
+ * type rather than being stringified.
19
+ * - An EMBEDDED token (`"/orgs/{{page.slug}}/setup"`) is string-interpolated.
20
+ * - Resolution walks nested objects/arrays. A whole-value miss resolves to ''
21
+ * (kept as a present-but-empty field); an embedded miss drops to ''.
22
+ *
23
+ * Distinct from the single-brace `{field}` row-record interpolation used in API
24
+ * target URLs (different brace count, different source) so the two never collide.
25
+ */
26
+ /**
27
+ * Deep-resolve every `{{page.<var>}}` token in `value` against `pageVariables`.
28
+ * Returns `value` unchanged when there is no snapshot. Non-string leaves pass
29
+ * through untouched; the input is never mutated (objects/arrays are copied).
30
+ */
31
+ export declare function resolvePageVarTokens<T>(value: T, pageVariables?: Record<string, any> | null): T;
@@ -0,0 +1,72 @@
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
+ * resolvePageVarTokens — resolve `{{page.<path>}}` tokens against a page-variable
9
+ * snapshot. The data-entry bridge for SDUI forms: an interactive input
10
+ * (`element:text_input`, `element:record_picker`) writes a page variable; a
11
+ * submit action references it in its params/body as `{{page.<var>}}`; this
12
+ * resolves those tokens against the live snapshot — published into the action
13
+ * context by `PageVariableActionBridge` — just before the request body is built.
14
+ *
15
+ * - A WHOLE-VALUE token (`"{{page.amount}}"`) is replaced by the variable's RAW
16
+ * value, preserving its type — a number stays a number, an object stays an
17
+ * object — so numeric/boolean/array form fields submit with the right JSON
18
+ * type rather than being stringified.
19
+ * - An EMBEDDED token (`"/orgs/{{page.slug}}/setup"`) is string-interpolated.
20
+ * - Resolution walks nested objects/arrays. A whole-value miss resolves to ''
21
+ * (kept as a present-but-empty field); an embedded miss drops to ''.
22
+ *
23
+ * Distinct from the single-brace `{field}` row-record interpolation used in API
24
+ * target URLs (different brace count, different source) so the two never collide.
25
+ */
26
+ const WHOLE_RE = /^\s*\{\{\s*page\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\}\}\s*$/;
27
+ function lookup(path, vars) {
28
+ let node = vars;
29
+ for (const seg of path.split('.')) {
30
+ if (node == null)
31
+ return undefined;
32
+ node = node[seg];
33
+ }
34
+ return node;
35
+ }
36
+ function resolveString(str, vars) {
37
+ if (!str.includes('{{'))
38
+ return str;
39
+ const whole = str.match(WHOLE_RE);
40
+ if (whole) {
41
+ const v = lookup(whole[1], vars);
42
+ return v === undefined ? '' : v;
43
+ }
44
+ // Fresh global regex per call — avoids shared `lastIndex` state.
45
+ return str.replace(/\{\{\s*page\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)\s*\}\}/g, (_m, path) => {
46
+ const v = lookup(path, vars);
47
+ return v == null ? '' : String(v);
48
+ });
49
+ }
50
+ function walk(value, vars) {
51
+ if (typeof value === 'string')
52
+ return resolveString(value, vars);
53
+ if (Array.isArray(value))
54
+ return value.map((v) => walk(v, vars));
55
+ if (value && typeof value === 'object') {
56
+ const out = {};
57
+ for (const [k, v] of Object.entries(value))
58
+ out[k] = walk(v, vars);
59
+ return out;
60
+ }
61
+ return value;
62
+ }
63
+ /**
64
+ * Deep-resolve every `{{page.<var>}}` token in `value` against `pageVariables`.
65
+ * Returns `value` unchanged when there is no snapshot. Non-string leaves pass
66
+ * through untouched; the input is never mutated (objects/arrays are copied).
67
+ */
68
+ export function resolvePageVarTokens(value, pageVariables) {
69
+ if (!pageVariables)
70
+ return value;
71
+ return walk(value, pageVariables);
72
+ }
@@ -18,7 +18,7 @@ import { useEffect, useMemo, useState } from 'react';
18
18
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, Input, Button, cn, } from '@object-ui/components';
19
19
  import { useObjectTranslation } from '@object-ui/i18n';
20
20
  import { deriveFieldOptions, isImageLikeField, isGeoLikeField, pickPreferredField, KANBAN_GROUP_PREFERRED, PRIMARY_DATE_PREFERRED, END_DATE_PREFERRED, } from '@object-ui/plugin-view';
21
- import { LayoutGrid, KanbanSquare, Calendar as CalendarIcon, Image as ImageIcon, GanttChartSquare, Clock, Map as MapIcon, BarChart3, AlertCircle, } from 'lucide-react';
21
+ import { LayoutGrid, KanbanSquare, Calendar as CalendarIcon, Image as ImageIcon, GanttChartSquare, Clock, Map as MapIcon, BarChart3, ListTree, AlertCircle, } from 'lucide-react';
22
22
  function buildViewTypeMeta(t) {
23
23
  return [
24
24
  { type: 'grid', icon: LayoutGrid, label: t('console.objectView.viewTypeGrid'), description: t('console.objectView.viewTypeGridDesc') },
@@ -29,6 +29,7 @@ function buildViewTypeMeta(t) {
29
29
  { type: 'gantt', icon: GanttChartSquare, label: t('console.objectView.viewTypeGantt'), description: t('console.objectView.viewTypeGanttDesc') },
30
30
  { type: 'map', icon: MapIcon, label: t('console.objectView.viewTypeMap'), description: t('console.objectView.viewTypeMapDesc') },
31
31
  { type: 'chart', icon: BarChart3, label: t('console.objectView.viewTypeChart'), description: t('console.objectView.viewTypeChartDesc') },
32
+ { type: 'tree', icon: ListTree, label: t('console.objectView.viewTypeTree'), description: t('console.objectView.viewTypeTreeDesc') },
32
33
  ];
33
34
  }
34
35
  /** Suggest a non-colliding default name like "Grid 1", "Grid 2", … */
@@ -154,6 +155,18 @@ const REQUIRED_FIELDS_BY_TYPE = {
154
155
  filter: (f) => f.type === 'number',
155
156
  },
156
157
  ],
158
+ tree: [
159
+ {
160
+ key: 'parentField',
161
+ i18nKey: 'console.objectView.parentField',
162
+ helpI18nKey: 'console.objectView.parentFieldHelp',
163
+ // Self-referencing pointer: a `tree` field, or a lookup/master_detail
164
+ // back to the same object. `rawType` carries the unnormalized field type.
165
+ filter: (f) => f.rawType === 'tree' ||
166
+ f.rawType === 'lookup' ||
167
+ f.rawType === 'master_detail',
168
+ },
169
+ ],
157
170
  // grid has no strictly required fields at create time
158
171
  };
159
172
  export function CreateViewDialog({ open, onOpenChange, onCreate, existingLabels, availableTypes, objectDef, }) {
@@ -42,6 +42,8 @@ import { useRealtimeSubscription, useConflictResolution } from '@object-ui/colla
42
42
  import { ActionProvider, useNavigationOverlay, SchemaRenderer } from '@object-ui/react';
43
43
  import { toast } from 'sonner';
44
44
  import { useConsoleActionRuntime } from '../hooks/useConsoleActionRuntime';
45
+ import { useEnvironmentEntitlements } from '../environment/useEnvironmentEntitlements';
46
+ import { EnvironmentListToolbar } from '../environment/EnvironmentListToolbar';
45
47
  /** Map view types to Lucide icons (Airtable-style) */
46
48
  const VIEW_TYPE_ICONS = {
47
49
  grid: TableIcon,
@@ -272,6 +274,27 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
272
274
  onRefresh: () => setRefreshKey((k) => k + 1),
273
275
  });
274
276
  const { confirmHandler, toastHandler } = actionRuntime;
277
+ // Environment list (sys_environment) is entitlement- + state-aware: born
278
+ // with one production env per org, its single `create_environment` action
279
+ // means different things — "Set up your production environment", "Add
280
+ // development environment", or an upgrade prompt — depending on org state.
281
+ // Resolved org-side here; the hook is a no-op for every other object.
282
+ const isEnvironmentList = objectDef.name === 'sys_environment';
283
+ const environmentEntitlements = useEnvironmentEntitlements({
284
+ enabled: isEnvironmentList,
285
+ dataSource,
286
+ authFetch: actionRuntime.authFetch,
287
+ apiBase: import.meta.env?.VITE_SERVER_URL || '',
288
+ refreshKey,
289
+ });
290
+ // Localized `list_toolbar` actions, shared by the generic action bar and the
291
+ // environment-aware toolbar (the action:bar renderer filters by location).
292
+ const localizedToolbarActions = useMemo(() => (objectDef.actions || []).map((a) => ({
293
+ ...a,
294
+ label: actionLabel(objectDef.name, a.name, a.label || a.name),
295
+ ...(a.confirmText !== undefined && { confirmText: actionConfirm(objectDef.name, a.name, a.confirmText) }),
296
+ ...(a.successMessage !== undefined && { successMessage: actionSuccess(objectDef.name, a.name, a.successMessage) }),
297
+ })), [objectDef, actionLabel, actionConfirm, actionSuccess]);
275
298
  // Resolve which generic CRUD affordances belong in the toolbar for
276
299
  // this object's lifecycle bucket (`managedBy`). config tables show
277
300
  // New/Edit/Delete but no CSV Import; system / append-only / better-auth
@@ -1333,19 +1356,10 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
1333
1356
  ? t('common.removeFromFavorites', { defaultValue: 'Remove from favorites' })
1334
1357
  : t('common.addToFavorites', { defaultValue: 'Add to favorites' }), "data-testid": `object-favorite-btn-${objectName}`, children: isFavorite(`object:${objectName}`)
1335
1358
  ? _jsx(Star, { className: "h-4 w-4 fill-amber-400 text-amber-400" })
1336
- : _jsx(StarOff, { className: "h-4 w-4" }) })), affordances.create && can(objectDef.name, 'create') && (_jsxs(Button, { size: "sm", onClick: actions.create, className: "shadow-none gap-1.5 sm:gap-2 h-8 sm:h-9", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: t('console.objectView.new') })] })), affordances.import && can(objectDef.name, 'create') && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setShowImport(true), className: "hidden sm:inline-flex shadow-none gap-1.5 sm:gap-2 h-8 sm:h-9", title: t('console.objectView.importTitle'), "data-testid": "object-view-import-button", children: [_jsx(Upload, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: t('console.objectView.import') })] })), objectDef.actions?.some((a) => a.locations?.includes('list_toolbar')) && (_jsx(SchemaRenderer, { schema: {
1359
+ : _jsx(StarOff, { className: "h-4 w-4" }) })), affordances.create && can(objectDef.name, 'create') && (_jsxs(Button, { size: "sm", onClick: actions.create, className: "shadow-none gap-1.5 sm:gap-2 h-8 sm:h-9", children: [_jsx(Plus, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: t('console.objectView.new') })] })), affordances.import && can(objectDef.name, 'create') && (_jsxs(Button, { size: "sm", variant: "outline", onClick: () => setShowImport(true), className: "hidden sm:inline-flex shadow-none gap-1.5 sm:gap-2 h-8 sm:h-9", title: t('console.objectView.importTitle'), "data-testid": "object-view-import-button", children: [_jsx(Upload, { className: "h-4 w-4" }), _jsx("span", { className: "hidden sm:inline", children: t('console.objectView.import') })] })), objectDef.actions?.some((a) => a.locations?.includes('list_toolbar')) && (isEnvironmentList ? (_jsx(EnvironmentListToolbar, { actions: localizedToolbarActions, entitlements: environmentEntitlements, onUpgrade: actionRuntime.openEntitlementDialog })) : (_jsx(SchemaRenderer, { schema: {
1337
1360
  type: 'action:bar',
1338
1361
  location: 'list_toolbar',
1339
- actions: (objectDef.actions || []).map((a) => ({
1340
- ...a,
1341
- label: actionLabel(objectDef.name, a.name, a.label || a.name),
1342
- ...(a.confirmText !== undefined && {
1343
- confirmText: actionConfirm(objectDef.name, a.name, a.confirmText),
1344
- }),
1345
- ...(a.successMessage !== undefined && {
1346
- successMessage: actionSuccess(objectDef.name, a.name, a.successMessage),
1347
- }),
1348
- })),
1362
+ actions: localizedToolbarActions,
1349
1363
  size: 'sm',
1350
1364
  variant: 'outline',
1351
1365
  // On mobile, collapse all schema-driven toolbar actions
@@ -1353,7 +1367,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
1353
1367
  // Import buttons stay visible without pushing the page
1354
1368
  // title off-screen.
1355
1369
  mobileMaxVisible: 0,
1356
- } }))] }) }) }), affordances.create && can(objectDef.name, 'create') && (_jsx("button", { type: "button", onClick: actions.create, className: "sm:hidden fixed right-4 bottom-36 z-40 h-12 w-12 rounded-full bg-primary text-primary-foreground shadow-lg active:scale-95 transition-transform inline-flex items-center justify-center", "aria-label": t('console.objectView.new'), "data-testid": "mobile-fab-create", children: _jsx(Plus, { className: "h-5 w-5" }) })), showImport && (_jsx(Suspense, { fallback: null, children: _jsx(ImportWizard, { open: showImport, onOpenChange: setShowImport, objectName: objectDef.name, objectLabel: objectLabel(objectDef), fields: Object.entries(objectDef.fields || {}).map(([name, def]) => ({
1370
+ } })))] }) }) }), affordances.create && can(objectDef.name, 'create') && (_jsx("button", { type: "button", onClick: actions.create, className: "sm:hidden fixed right-4 bottom-36 z-40 h-12 w-12 rounded-full bg-primary text-primary-foreground shadow-lg active:scale-95 transition-transform inline-flex items-center justify-center", "aria-label": t('console.objectView.new'), "data-testid": "mobile-fab-create", children: _jsx(Plus, { className: "h-5 w-5" }) })), showImport && (_jsx(Suspense, { fallback: null, children: _jsx(ImportWizard, { open: showImport, onOpenChange: setShowImport, objectName: objectDef.name, objectLabel: objectLabel(objectDef), fields: Object.entries(objectDef.fields || {}).map(([name, def]) => ({
1357
1371
  name,
1358
1372
  label: def?.label || name,
1359
1373
  type: def?.type || 'text',
@@ -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
+ * AssignedUsersSection — "Manage Assignments" for a permission set.
9
+ *
10
+ * The admin's mental model is "who holds this role / AI seat" — so this is a
11
+ * people-first list (name + email + remove), not a raw junction table. It reads
12
+ * `sys_user_permission_set` for the set, resolves each `user_id` to a real
13
+ * person, and uses the reusable `RecordPickerDialog` to assign more. Server-side
14
+ * rules on the junction insert (e.g. the AI-seat cap) are caught and shown as a
15
+ * friendly, localized inline message — not a raw developer error.
16
+ *
17
+ * Permission-set-agnostic: every role gets the same UI, and the AI seat
18
+ * (`ai_seat`) is just one of them. The generic add-by-picker engine (spec
19
+ * RecordRelatedListProps.add) powers the capability; this is the polished
20
+ * surface for the high-value case.
21
+ */
22
+ import * as React from 'react';
23
+ export interface AssignedUsersSectionProps {
24
+ /** The permission set's machine name (e.g. `ai_seat`, `admin_full_access`). */
25
+ permissionSetName: string;
26
+ }
27
+ export declare function AssignedUsersSection({ permissionSetName }: AssignedUsersSectionProps): React.JSX.Element;
28
+ export default AssignedUsersSection;
@@ -0,0 +1,151 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } 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
+ * AssignedUsersSection — "Manage Assignments" for a permission set.
10
+ *
11
+ * The admin's mental model is "who holds this role / AI seat" — so this is a
12
+ * people-first list (name + email + remove), not a raw junction table. It reads
13
+ * `sys_user_permission_set` for the set, resolves each `user_id` to a real
14
+ * person, and uses the reusable `RecordPickerDialog` to assign more. Server-side
15
+ * rules on the junction insert (e.g. the AI-seat cap) are caught and shown as a
16
+ * friendly, localized inline message — not a raw developer error.
17
+ *
18
+ * Permission-set-agnostic: every role gets the same UI, and the AI seat
19
+ * (`ai_seat`) is just one of them. The generic add-by-picker engine (spec
20
+ * RecordRelatedListProps.add) powers the capability; this is the polished
21
+ * surface for the high-value case.
22
+ */
23
+ import * as React from 'react';
24
+ import { Button } from '@object-ui/components';
25
+ import { RecordPickerDialog } from '@object-ui/fields';
26
+ import { useAdapter } from '@object-ui/react';
27
+ import { Plus, X, Users, Loader2, AlertCircle } from 'lucide-react';
28
+ import { detectLocale } from './i18n';
29
+ /** Minimal locale-aware copy (zh vs everything-else) — keeps the surface in the user's language. */
30
+ function useCopy() {
31
+ const zh = React.useMemo(() => detectLocale().toLowerCase().startsWith('zh'), []);
32
+ return React.useMemo(() => zh
33
+ ? {
34
+ title: '已分配用户',
35
+ add: '添加用户',
36
+ remove: '移除',
37
+ empty: '还没有分配任何用户。点击「添加用户」来分配。',
38
+ loading: '加载中…',
39
+ pickTitle: '选择要分配的用户',
40
+ seatFull: (n) => 'AI 席位已用完(' + n + '/' + n + ')。请先移除一个用户,或在许可证中提升席位上限,再分配新用户。',
41
+ addFailed: '分配失败,请重试。',
42
+ countOf: (n) => n + ' 人',
43
+ }
44
+ : {
45
+ title: 'Assigned Users',
46
+ add: 'Add user',
47
+ remove: 'Remove',
48
+ empty: 'No users assigned yet. Click "Add user" to assign.',
49
+ loading: 'Loading…',
50
+ pickTitle: 'Select users to assign',
51
+ seatFull: (n) => 'All ' + n + ' AI seat(s) are in use. Remove a user or raise the license cap before assigning another.',
52
+ addFailed: 'Failed to assign. Please try again.',
53
+ countOf: (n) => String(n),
54
+ }, [zh]);
55
+ }
56
+ const asArray = (res) => Array.isArray(res) ? res : res?.records ?? res?.items ?? res?.data ?? [];
57
+ const personLabel = (u) => u?.full_name || u?.name || u?.display_name || u?.email || String(u?.id ?? '');
58
+ export function AssignedUsersSection({ permissionSetName }) {
59
+ const adapter = useAdapter();
60
+ const c = useCopy();
61
+ const [setId, setSetId] = React.useState(null);
62
+ const [rows, setRows] = React.useState([]);
63
+ const [loading, setLoading] = React.useState(true);
64
+ const [pickerOpen, setPickerOpen] = React.useState(false);
65
+ const [busy, setBusy] = React.useState(false);
66
+ const [error, setError] = React.useState(null);
67
+ const load = React.useCallback(async () => {
68
+ setLoading(true);
69
+ try {
70
+ const sets = asArray(await adapter.find('sys_permission_set', { $filter: { name: permissionSetName }, limit: 1 }));
71
+ const id = sets[0]?.id ? String(sets[0].id) : null;
72
+ setSetId(id);
73
+ if (!id) {
74
+ setRows([]);
75
+ return;
76
+ }
77
+ const grants = asArray(await adapter.find('sys_user_permission_set', { $filter: { permission_set_id: id }, $top: 500 }));
78
+ const userIds = [...new Set(grants.map((g) => g.user_id).filter(Boolean).map(String))];
79
+ const users = userIds.length
80
+ ? asArray(await adapter.find('sys_user', { $filter: { id: { $in: userIds } }, $top: 500 }))
81
+ : [];
82
+ const byId = new Map(users.map((u) => [String(u.id), u]));
83
+ setRows(grants
84
+ .filter((g) => g.user_id)
85
+ .map((g) => {
86
+ const u = byId.get(String(g.user_id));
87
+ return {
88
+ grantId: String(g.id),
89
+ userId: String(g.user_id),
90
+ name: u ? personLabel(u) : String(g.user_id),
91
+ email: u?.email ?? '',
92
+ };
93
+ }));
94
+ }
95
+ catch {
96
+ setRows([]);
97
+ }
98
+ finally {
99
+ setLoading(false);
100
+ }
101
+ }, [adapter, permissionSetName]);
102
+ React.useEffect(() => {
103
+ void load();
104
+ }, [load]);
105
+ const assignedIds = React.useMemo(() => new Set(rows.map((r) => r.userId)), [rows]);
106
+ const addUsers = React.useCallback(async (records) => {
107
+ if (!setId)
108
+ return;
109
+ setBusy(true);
110
+ setError(null);
111
+ try {
112
+ for (const u of records || []) {
113
+ const uid = u?.id != null ? String(u.id) : null;
114
+ if (!uid || assignedIds.has(uid))
115
+ continue;
116
+ await adapter.create('sys_user_permission_set', { permission_set_id: setId, user_id: uid });
117
+ }
118
+ await load();
119
+ }
120
+ catch (err) {
121
+ const raw = String(err?.body?.error ?? err?.error ?? err?.message ?? '');
122
+ const capMatch = raw.match(/(\d+)\s*of\s*(\d+)\s*seat/i);
123
+ if (/cap reached|seat cap|ai[-_ ]?seat/i.test(raw)) {
124
+ setError(c.seatFull(capMatch ? Number(capMatch[2]) : rows.length));
125
+ }
126
+ else {
127
+ const cleaned = raw.replace(/^\s*\[[^\]]*\]\s*/, '').trim();
128
+ setError(cleaned || c.addFailed);
129
+ }
130
+ }
131
+ finally {
132
+ setBusy(false);
133
+ setPickerOpen(false);
134
+ }
135
+ }, [adapter, setId, assignedIds, load, rows.length, c]);
136
+ const removeUser = React.useCallback(async (grantId) => {
137
+ setError(null);
138
+ try {
139
+ await adapter.delete('sys_user_permission_set', grantId);
140
+ await load();
141
+ }
142
+ catch {
143
+ /* keep the row; a failed delete is non-destructive */
144
+ }
145
+ }, [adapter, load]);
146
+ return (_jsxs("div", { className: "px-4 py-4", children: [_jsxs("div", { className: "flex items-center justify-between mb-3", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm font-medium", children: [_jsx(Users, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { children: c.title }), !loading && (_jsx("span", { className: "text-xs text-muted-foreground font-normal", children: c.countOf(rows.length) }))] }), _jsxs(Button, { variant: "outline", size: "sm", disabled: busy || !setId, onClick: () => {
147
+ setError(null);
148
+ setPickerOpen(true);
149
+ }, className: "gap-1 h-8 text-xs", children: [busy ? _jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }) : _jsx(Plus, { className: "h-3.5 w-3.5" }), c.add] })] }), error && (_jsxs("div", { className: "mb-3 flex items-start gap-2 rounded-md border border-amber-300/60 bg-amber-50 dark:bg-amber-950/30 px-3 py-2 text-xs text-amber-800 dark:text-amber-200", role: "alert", children: [_jsx(AlertCircle, { className: "h-3.5 w-3.5 mt-0.5 shrink-0" }), _jsx("span", { children: error })] })), loading ? (_jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground py-3", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }), c.loading] })) : rows.length === 0 ? (_jsx("div", { className: "text-xs text-muted-foreground italic py-3", children: c.empty })) : (_jsx("ul", { className: "divide-y rounded-md border", children: rows.map((r) => (_jsxs("li", { className: "flex items-center gap-3 px-3 py-2", children: [_jsx("div", { className: "flex h-7 w-7 items-center justify-center rounded-full bg-primary/10 text-primary text-xs font-medium shrink-0", children: (r.name || '?').slice(0, 1).toUpperCase() }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-sm truncate", children: r.name }), r.email && r.email !== r.name && (_jsx("div", { className: "text-xs text-muted-foreground truncate", children: r.email }))] }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => void removeUser(r.grantId), "aria-label": c.remove, title: c.remove, className: "h-7 w-7 p-0 text-muted-foreground hover:text-destructive shrink-0", children: _jsx(X, { className: "h-4 w-4" }) })] }, r.grantId))) })), setId && (_jsx(RecordPickerDialog, { open: pickerOpen, onOpenChange: (o) => setPickerOpen(o), multiple: true, dataSource: adapter, objectName: "sys_user", title: c.pickTitle, onSelect: () => { }, onSelectRecords: (records) => void addUsers(records) }))] }));
150
+ }
151
+ export default AssignedUsersSection;
@@ -14,5 +14,10 @@
14
14
  * (see framework `http-dispatcher.handlePackages`).
15
15
  */
16
16
  import * as React from 'react';
17
+ export declare function CreatePackageDialog({ open, onOpenChange, onCreated, }: {
18
+ open: boolean;
19
+ onOpenChange: (v: boolean) => void;
20
+ onCreated: (id: string) => void;
21
+ }): React.JSX.Element;
17
22
  export declare function PackagesPage(): React.JSX.Element;
18
23
  export default PackagesPage;