@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
|
@@ -48,6 +48,8 @@ import { getMetadataDefaultInspector } from './default-inspector-registry';
|
|
|
48
48
|
import { detectLocale, t, tFormat, translateValidationMessage } from './i18n';
|
|
49
49
|
import { JsonSourceEditor } from './JsonSourceEditor';
|
|
50
50
|
import { validateMetadataDraft, hasClientValidator } from './clientValidation';
|
|
51
|
+
import { describeIssuePath } from './issuePath';
|
|
52
|
+
import { buildCreateModeBody } from './createBody';
|
|
51
53
|
// react-resizable-panels' `direction` prop type does not always narrow
|
|
52
54
|
// cleanly in our TS config; cast at the boundary (precedent:
|
|
53
55
|
// packages/components/src/custom/navigation-overlay.tsx).
|
|
@@ -59,7 +61,7 @@ const PanelGroup = ResizablePanelGroup;
|
|
|
59
61
|
* editable via the no-selection default inspector. Other types keep
|
|
60
62
|
* the conventional "name it first, design after save" create flow.
|
|
61
63
|
*/
|
|
62
|
-
const CREATE_MODE_CANVAS_TYPES = new Set(['object']);
|
|
64
|
+
const CREATE_MODE_CANVAS_TYPES = new Set(['object', 'report', 'dataset']);
|
|
63
65
|
/**
|
|
64
66
|
* Top-level metadata keys that a type's canvas PreviewComponent owns and
|
|
65
67
|
* edits visually (e.g. the object designer owns `fields` + `fieldGroups`).
|
|
@@ -310,6 +312,25 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
310
312
|
// Issues to DISPLAY (banner + inline). Suppressed on a pristine create form
|
|
311
313
|
// so a blank new item doesn't open covered in required-field errors.
|
|
312
314
|
const displayIssues = React.useMemo(() => (createMode && !createDirty ? [] : issues), [createMode, createDirty, issues]);
|
|
315
|
+
// Server-computed diagnostics handed to a canvas Preview (e.g. the flow
|
|
316
|
+
// Problems panel + on-canvas badges). Errors prefer the live client-side Zod
|
|
317
|
+
// issues when a client validator exists (so they track every keystroke);
|
|
318
|
+
// warnings stay server-sourced. Mirrors the read-only banner's source
|
|
319
|
+
// selection, flattened to a path-keyed, severity-tagged list.
|
|
320
|
+
const previewDiagnostics = React.useMemo(() => {
|
|
321
|
+
const diag = layered?._diagnostics;
|
|
322
|
+
const errs = hasClientValidator(type)
|
|
323
|
+
? displayIssues.map((i) => ({ path: i.path, message: translateValidationMessage(i.message, locale) }))
|
|
324
|
+
: (diag?.errors ?? []).map((i) => ({ path: i.path, message: translateValidationMessage(i.message, locale) }));
|
|
325
|
+
const warns = (diag?.warnings ?? []).map((i) => ({
|
|
326
|
+
path: i.path,
|
|
327
|
+
message: translateValidationMessage(i.message, locale),
|
|
328
|
+
}));
|
|
329
|
+
return [
|
|
330
|
+
...errs.map((e) => ({ path: e.path || undefined, message: e.message, severity: 'error' })),
|
|
331
|
+
...warns.map((w) => ({ path: w.path || undefined, message: w.message, severity: 'warning' })),
|
|
332
|
+
];
|
|
333
|
+
}, [layered, displayIssues, type, locale]);
|
|
313
334
|
// Per-item draft pending publish (mode=draft saves land here).
|
|
314
335
|
// When non-null, the editor is "viewing the draft" and we surface
|
|
315
336
|
// Publish / Discard-draft actions.
|
|
@@ -749,7 +770,10 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
749
770
|
const PC = getMetadataPreview(type);
|
|
750
771
|
if (!PC)
|
|
751
772
|
return;
|
|
752
|
-
|
|
773
|
+
// See `isArtifactItem` below — a `sys_metadata`-tagged code layer is a
|
|
774
|
+
// published org object, NOT a packaged artifact, so it stays editable.
|
|
775
|
+
const isArtifact = layered?.code != null
|
|
776
|
+
&& layered.code?._packageId !== 'sys_metadata';
|
|
753
777
|
const cw = isArtifact
|
|
754
778
|
? !!entry?.allowOrgOverride
|
|
755
779
|
: !!(entry?.allowOrgOverride || entry?.allowRuntimeCreate);
|
|
@@ -815,22 +839,29 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
815
839
|
const key = path.split('.')[0];
|
|
816
840
|
if (!key)
|
|
817
841
|
return path;
|
|
818
|
-
|
|
819
|
-
const
|
|
820
|
-
|
|
821
|
-
const
|
|
822
|
-
for (const
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
842
|
+
// Resolve the human label for the HEAD segment from the form/schema.
|
|
843
|
+
const headLabel = (() => {
|
|
844
|
+
const formForLabels = (createMode && config.createSchema ? undefined : entry?.form);
|
|
845
|
+
const sections = Array.isArray(formForLabels?.sections) ? formForLabels.sections : [];
|
|
846
|
+
for (const section of sections) {
|
|
847
|
+
const fields = Array.isArray(section?.fields) ? section.fields : [];
|
|
848
|
+
for (const field of fields) {
|
|
849
|
+
if (typeof field === 'string') {
|
|
850
|
+
if (field === key)
|
|
851
|
+
return field;
|
|
852
|
+
}
|
|
853
|
+
else if (field?.field === key) {
|
|
854
|
+
return String(field.label ?? key);
|
|
855
|
+
}
|
|
829
856
|
}
|
|
830
857
|
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
858
|
+
const props = (schema?.properties ?? {});
|
|
859
|
+
return String(props[key]?.title ?? key);
|
|
860
|
+
})();
|
|
861
|
+
// For a NESTED path (e.g. `widgets.2.layout`) append a readable trail naming
|
|
862
|
+
// the offending element + sub-field, so a terse "Widgets: Invalid input"
|
|
863
|
+
// becomes "Widgets → priority_split → layout".
|
|
864
|
+
return describeIssuePath(headLabel, path, draft);
|
|
834
865
|
}
|
|
835
866
|
async function doSave(force) {
|
|
836
867
|
setSaving(true);
|
|
@@ -842,10 +873,14 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
842
873
|
// or `{ list: { data: { object } } }` for view) is present so
|
|
843
874
|
// the saved body satisfies its JSONSchema. User-supplied values
|
|
844
875
|
// always win over the defaults.
|
|
876
|
+
// Prefer the server's authoritative create seed (from /meta/types — the
|
|
877
|
+
// single source of truth in @objectstack/spec) over the locally hardcoded
|
|
878
|
+
// createDefaults, so the create shape can't drift from the spec's required
|
|
879
|
+
// fields (the dashboard-`layout` / action-`body` 422 family). `createSeed`
|
|
880
|
+
// is a runtime field absent from the bundled GetMetaTypes type, hence the cast.
|
|
881
|
+
const specCreateSeed = entry?.createSeed;
|
|
845
882
|
let builtBody = createMode
|
|
846
|
-
? (config
|
|
847
|
-
? config.createBuildBody(draft)
|
|
848
|
-
: { ...(config.createDefaults ?? {}), ...draft })
|
|
883
|
+
? buildCreateModeBody(config, draft, specCreateSeed)
|
|
849
884
|
// Edit mode: serialise the editor draft back to the wire shape
|
|
850
885
|
// (inverse of `toDraft` — e.g. `view` folds the `{ list | form }`
|
|
851
886
|
// family key back into the ViewItem `config` wrapper).
|
|
@@ -929,7 +964,15 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
929
964
|
if (!createMode && !stayInEditing)
|
|
930
965
|
setEditing(false);
|
|
931
966
|
if (createMode) {
|
|
932
|
-
|
|
967
|
+
// Preserve the active query string (notably `?package=…`) so the
|
|
968
|
+
// post-create navigation lands on the item in the SAME package the
|
|
969
|
+
// author was working in. Without this the param is dropped and the
|
|
970
|
+
// editor falls back to the user's default package, where the freshly
|
|
971
|
+
// saved draft doesn't exist — so it reloads a blank form.
|
|
972
|
+
const qs = searchParams.toString();
|
|
973
|
+
navigate(`../${encodeURIComponent(savedName)}${qs ? `?${qs}` : ''}`, {
|
|
974
|
+
relative: 'path',
|
|
975
|
+
});
|
|
933
976
|
}
|
|
934
977
|
}
|
|
935
978
|
catch (err) {
|
|
@@ -939,6 +982,14 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
939
982
|
setDestructiveIssues(Array.isArray(i) ? i : []);
|
|
940
983
|
setPendingItem(draft);
|
|
941
984
|
}
|
|
985
|
+
// ADR-0070 D1/D3 — the kernel rejects authoring into a read-only
|
|
986
|
+
// code/installed package (`writable_package_required`, also HTTP 422,
|
|
987
|
+
// so this MUST precede the generic invalid_metadata branch below).
|
|
988
|
+
// Surface an actionable message guiding the author to pick or create a
|
|
989
|
+
// writable base rather than mangling it into phantom field issues.
|
|
990
|
+
else if (err?.code === 'writable_package_required') {
|
|
991
|
+
setError(t('engine.package.writableRequired', locale));
|
|
992
|
+
}
|
|
942
993
|
// Map schema validation → inline field errors.
|
|
943
994
|
else if (err?.status === 422 || err?.code === 'invalid_metadata' || err?.code === 'invalid_payload') {
|
|
944
995
|
const i = err?.body?.issues ?? [];
|
|
@@ -1101,7 +1152,15 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
1101
1152
|
// - artifact-backed items (layered.code != null) need allowOrgOverride
|
|
1102
1153
|
// - DB-only items (no artifact) need allowOrgOverride OR allowRuntimeCreate
|
|
1103
1154
|
// - createMode is always writable (the server will gate on intent)
|
|
1104
|
-
|
|
1155
|
+
// A non-null `code` layer alone is NOT proof of a code (artifact) package:
|
|
1156
|
+
// a published org object also surfaces its active version in `code`, but
|
|
1157
|
+
// tagged with the `sys_metadata` provenance sentinel. Mirror the server's
|
|
1158
|
+
// `isArtifactBacked` (which excludes `_packageId === 'sys_metadata'`) so an
|
|
1159
|
+
// org-authored object stays editable after publish instead of being mis-read
|
|
1160
|
+
// as a read-only packaged item.
|
|
1161
|
+
const isArtifactItem = !createMode
|
|
1162
|
+
&& layered?.code != null
|
|
1163
|
+
&& layered.code?._packageId !== 'sys_metadata';
|
|
1105
1164
|
// ADR-0010 — server-computed lock flags. undefined means "no opinion"
|
|
1106
1165
|
// (older server / non-lockable item) → preserve legacy behaviour.
|
|
1107
1166
|
const lockEditable = layered?.editable !== false;
|
|
@@ -1320,7 +1379,7 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
1320
1379
|
? 'flex h-full min-h-0 flex-col'
|
|
1321
1380
|
: 'p-6 space-y-6 max-w-7xl', children: [(error || readOnly || hasDraft || isLocked) && (_jsxs("div", { className: PreviewComponent
|
|
1322
1381
|
? 'px-6 pt-4 space-y-3'
|
|
1323
|
-
: 'space-y-3', children: [error && (_jsx("div", { className: "text-sm text-destructive border border-destructive/30 rounded p-3 bg-destructive/5", children: error })), isLocked && (_jsxs("div", { className: "text-xs text-amber-900 border border-amber-300/70 bg-amber-50/70 rounded-md px-3 py-2.5 dark:text-amber-200 dark:border-amber-700/40 dark:bg-amber-950/20 flex items-start gap-2.5", children: [_jsx(Lock, { className: "h-3.5 w-3.5 mt-0.5 shrink-0 opacity-80" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "font-medium", children: [layered?.lock === 'full' && t('engine.edit.lockFull', locale), layered?.lock === 'no-overlay' && t('engine.edit.lockNoOverlay', locale), layered?.lock === 'no-delete' && t('engine.edit.lockNoDelete', locale)] }), lockReason && _jsx("div", { className: "mt-0.5 opacity-90", children: lockReason }), layered?.lockDocsUrl && (_jsxs("a", { href: layered.lockDocsUrl, target: "_blank", rel: "noopener noreferrer", className: "mt-1 inline-flex items-center gap-1 text-amber-800 underline hover:text-amber-900 dark:text-amber-200 dark:hover:text-amber-100", children: [locale === 'zh-CN' ? '查看文档' : 'View docs', " \u2192"] })), layered?.packageId && (_jsx("div", { className: "mt-0.5 text-amber-700 dark:text-amber-300/80", children: _jsxs("code", { className: "font-mono", children: [layered.packageId, layered.packageVersion ? `@${layered.packageVersion}` : ''] }) }))] }), showArtifactLockedBanner && (_jsx(Button, { size: "sm", variant: "outline", className: "shrink-0 h-7 bg-background/60", onClick: () => navigate(`../new`, { relative: 'path' }), children: t('engine.list.create', locale) }))] })), hasDraft && !createMode && (_jsxs("div", { className: "text-xs text-emerald-900 border border-emerald-300 bg-emerald-50 rounded p-3 dark:text-emerald-200 dark:border-emerald-700/50 dark:bg-emerald-950/30 flex items-center gap-3", children: [_jsx(Send, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "flex-1", children: t('engine.edit.draftPending', locale) }), canWrite && (_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: doDiscardDraft, disabled: saving || publishing, className: "h-7", children: t('engine.edit.discardDraft', locale) }), _jsx(Button, { size: "sm", onClick: doPublish, disabled: saving || publishing || isDirty, className: "h-7 bg-emerald-600 hover:bg-emerald-700 text-emerald-50", children: publishing ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (t('engine.edit.publish', locale)) })] }))] })), readOnly && !isLocked && (_jsxs("div", { className: "text-xs text-amber-800 border border-amber-300/70 bg-amber-50/70 rounded-md px-3 py-2.5 dark:text-amber-200 dark:border-amber-700/40 dark:bg-amber-950/20 flex items-start gap-3", children: [_jsx("div", { className: "flex-1", children: showArtifactLockedBanner ? (
|
|
1382
|
+
: 'space-y-3', children: [error && (_jsx("div", { className: "text-sm text-destructive border border-destructive/30 rounded p-3 bg-destructive/5", children: error })), isLocked && (_jsxs("div", { className: "text-xs text-amber-900 border border-amber-300/70 bg-amber-50/70 rounded-md px-3 py-2.5 dark:text-amber-200 dark:border-amber-700/40 dark:bg-amber-950/20 flex items-start gap-2.5", children: [_jsx(Lock, { className: "h-3.5 w-3.5 mt-0.5 shrink-0 opacity-80" }), _jsxs("div", { className: "flex-1 min-w-0", children: [_jsxs("div", { className: "font-medium", children: [layered?.lock === 'full' && t('engine.edit.lockFull', locale), layered?.lock === 'no-overlay' && t('engine.edit.lockNoOverlay', locale), layered?.lock === 'no-delete' && t('engine.edit.lockNoDelete', locale)] }), lockReason && _jsx("div", { className: "mt-0.5 opacity-90", children: lockReason }), layered?.lockDocsUrl && (_jsxs("a", { href: layered.lockDocsUrl, target: "_blank", rel: "noopener noreferrer", className: "mt-1 inline-flex items-center gap-1 text-amber-800 underline hover:text-amber-900 dark:text-amber-200 dark:hover:text-amber-100", children: [locale === 'zh-CN' ? '查看文档' : 'View docs', " \u2192"] })), layered?.packageId && (_jsx("div", { className: "mt-0.5 text-amber-700 dark:text-amber-300/80", children: _jsxs("code", { className: "font-mono", children: [layered.packageId, layered.packageVersion ? `@${layered.packageVersion}` : ''] }) }))] }), showArtifactLockedBanner && (_jsx(Button, { size: "sm", variant: "outline", className: "shrink-0 h-7 bg-background/60", onClick: () => navigate(`../new`, { relative: 'path' }), children: t('engine.list.create', locale) }))] })), hasDraft && !createMode && (_jsxs("div", { className: "text-xs text-emerald-900 border border-emerald-300 bg-emerald-50 rounded p-3 dark:text-emerald-200 dark:border-emerald-700/50 dark:bg-emerald-950/30 flex items-center gap-3", children: [_jsx(Send, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "flex-1", children: t('engine.edit.draftPending', locale) }), canWrite && (_jsxs(_Fragment, { children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: doDiscardDraft, disabled: saving || publishing, className: "h-7", children: t('engine.edit.discardDraft', locale) }), _jsx(Button, { size: "sm", onClick: doPublish, disabled: saving || publishing || isDirty, className: "h-7 bg-emerald-600 hover:bg-emerald-700 text-emerald-50", children: publishing ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (t('engine.edit.publish', locale)) })] }))] })), readOnly && !isLocked && (_jsxs("div", { "data-testid": "readonly-banner", className: "text-xs text-amber-800 border border-amber-300/70 bg-amber-50/70 rounded-md px-3 py-2.5 dark:text-amber-200 dark:border-amber-700/40 dark:bg-amber-950/20 flex items-start gap-3", children: [_jsx("div", { className: "flex-1", children: showArtifactLockedBanner ? (
|
|
1324
1383
|
/* Type allows runtime-create but THIS item ships from
|
|
1325
1384
|
a code package. Tell the user clearly and provide
|
|
1326
1385
|
a CTA to author their own. */
|
|
@@ -1404,7 +1463,7 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
1404
1463
|
const titleKey = kind === 'error'
|
|
1405
1464
|
? 'engine.edit.diagnostics.title'
|
|
1406
1465
|
: 'engine.edit.diagnostics.warnTitle';
|
|
1407
|
-
return (_jsxs("div", { className: `flex items-start gap-2 text-xs border rounded p-2.5 ${cls}`, children: [_jsx(AlertTriangle, { className: "h-4 w-4 mt-0.5 shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-medium", children: tFormat(titleKey, locale, { count: items.length }) }), _jsxs("ul", { className: "mt-1 space-y-0.5 font-mono text-[11px]", children: [head.map((e, i) => (_jsxs("li", { className: "truncate", children: [_jsx("span", { className: "opacity-70", children: e.path ? labelForIssuePath(e.path) : '(root)' }), ": ", e.message] }, i))), rest > 0 && (_jsx("li", { className: "opacity-70", children: tFormat('engine.edit.diagnostics.more', locale, { count: rest }) }))] })] })] }, kind));
|
|
1466
|
+
return (_jsxs("div", { "data-testid": kind === 'error' ? 'metadata-validation-banner' : undefined, className: `flex items-start gap-2 text-xs border rounded p-2.5 ${cls}`, children: [_jsx(AlertTriangle, { className: "h-4 w-4 mt-0.5 shrink-0" }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-medium", children: tFormat(titleKey, locale, { count: items.length }) }), _jsxs("ul", { className: "mt-1 space-y-0.5 font-mono text-[11px]", children: [head.map((e, i) => (_jsxs("li", { className: "truncate", children: [_jsx("span", { className: "opacity-70", children: e.path ? labelForIssuePath(e.path) : '(root)' }), ": ", e.message] }, i))), rest > 0 && (_jsx("li", { className: "opacity-70", children: tFormat('engine.edit.diagnostics.more', locale, { count: rest }) }))] })] })] }, kind));
|
|
1408
1467
|
};
|
|
1409
1468
|
return (_jsxs("div", { className: "space-y-2", children: [hasErrs && renderBlock('error', errs), hasWarns && renderBlock('warning', warns)] }));
|
|
1410
1469
|
})(), PreviewComponent ? (_jsx("div", { className: isFullscreen
|
|
@@ -1421,7 +1480,7 @@ function MetadataResourceEditPageImpl({ type, name, createMode, embedded, }) {
|
|
|
1421
1480
|
? t('engine.edit.exitFullscreen', locale)
|
|
1422
1481
|
: t('engine.edit.fullscreen', locale), children: isFullscreen ? (_jsx(Minimize2, { className: "h-3.5 w-3.5" })) : (_jsx(Maximize2, { className: "h-3.5 w-3.5" })) })] })] }), _jsx("div", { className: "flex-1 min-h-0 overflow-auto p-4 bg-[radial-gradient(circle_at_1px_1px,theme(colors.border)_1px,transparent_0)] [background-size:16px_16px] bg-muted/30", children: _jsx(PreviewComponent, { type: type, name: name, draft: draft, baseline: !createMode
|
|
1423
1482
|
? (layered?.effective ?? undefined)
|
|
1424
|
-
: undefined, editing: editing && !previewOnly, selection: previewOnly ? null : selection, onSelectionChange: setSelection, locale: locale, onPatch: (patch) => handleDraftChange((d) => ({ ...d, ...patch })) }) })] }) }), _jsx(ResizableHandle, { withHandle: true, className: inspectorCollapsed
|
|
1483
|
+
: undefined, editing: editing && !previewOnly, selection: previewOnly ? null : selection, onSelectionChange: setSelection, locale: locale, diagnostics: previewDiagnostics, onPatch: (patch) => handleDraftChange((d) => ({ ...d, ...patch })) }) })] }) }), _jsx(ResizableHandle, { withHandle: true, className: inspectorCollapsed
|
|
1425
1484
|
? 'hidden'
|
|
1426
1485
|
: 'w-1.5 bg-border/40 hover:bg-primary/40 active:bg-primary/60 transition-colors' }), _jsx(ResizablePanel, { panelRef: inspectorPanelRef, defaultSize: lastInspectorSizeRef.current, minSize: 22, collapsible: true, collapsedSize: 0, onResize: (size) => {
|
|
1427
1486
|
const pct = size.asPercentage;
|
|
@@ -21,9 +21,11 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '
|
|
|
21
21
|
import { Empty, EmptyTitle, EmptyDescription } from '@object-ui/components';
|
|
22
22
|
import { PageShell } from './PageShell';
|
|
23
23
|
import { MetadataTypeActions } from './MetadataTypeActions';
|
|
24
|
+
import { CreatePackageDialog } from './PackagesPage';
|
|
24
25
|
import { useMetadataClient, useMetadataTypes, matchesQuery, } from './useMetadata';
|
|
25
26
|
import { getMetadataResource, resolveResourceConfig, } from './registry';
|
|
26
27
|
import { t, tFormat, translateMetadataType, detectLocale } from './i18n';
|
|
28
|
+
import { buildPackageScopeOptions, LOCAL_PACKAGE_ID, isLocalScope } from './package-scope';
|
|
27
29
|
/**
|
|
28
30
|
* Derive provenance from item._packageId. The `loadMetaFromDb` path
|
|
29
31
|
* tags objects with the synthetic packageId 'sys_metadata' (see
|
|
@@ -82,20 +84,7 @@ function DefaultMetadataList({ type, appName }) {
|
|
|
82
84
|
const list = await client.list('package');
|
|
83
85
|
if (cancelled)
|
|
84
86
|
return;
|
|
85
|
-
|
|
86
|
-
const rows = (list ?? [])
|
|
87
|
-
.map((raw) => {
|
|
88
|
-
const item = raw && typeof raw === 'object' && 'item' in raw ? raw.item : raw;
|
|
89
|
-
const m = (item?.manifest ?? item ?? {});
|
|
90
|
-
return {
|
|
91
|
-
id: m.id,
|
|
92
|
-
scope: m.scope,
|
|
93
|
-
name: m.name || m.id,
|
|
94
|
-
};
|
|
95
|
-
})
|
|
96
|
-
.filter((p) => p.id && !SYSTEM_SCOPES.has(p.scope));
|
|
97
|
-
rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
98
|
-
setProjectPackages(rows.map((p) => ({ id: p.id, name: p.name })));
|
|
87
|
+
setProjectPackages(buildPackageScopeOptions(list));
|
|
99
88
|
}
|
|
100
89
|
catch {
|
|
101
90
|
if (!cancelled)
|
|
@@ -196,6 +185,22 @@ function DefaultMetadataList({ type, appName }) {
|
|
|
196
185
|
const pkgSuffix = activePackage
|
|
197
186
|
? `?package=${encodeURIComponent(activePackage)}`
|
|
198
187
|
: '';
|
|
188
|
+
// ADR-0070 D3 — never start a create that would orphan the item. When a real
|
|
189
|
+
// writable base exists, create into it (defaulting away from the Local/null
|
|
190
|
+
// scope); when none exists yet, prompt to create a base first.
|
|
191
|
+
const [showCreateBase, setShowCreateBase] = React.useState(false);
|
|
192
|
+
const handleCreate = React.useCallback(() => {
|
|
193
|
+
const realBases = (projectPackages ?? []).filter((p) => !isLocalScope(p.id));
|
|
194
|
+
if (projectPackages !== null && realBases.length === 0) {
|
|
195
|
+
setShowCreateBase(true);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (realBases.length > 0 && (!activePackage || isLocalScope(activePackage))) {
|
|
199
|
+
navigate(`./new?package=${encodeURIComponent(realBases[0].id)}`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
navigate(`./new${pkgSuffix}`);
|
|
203
|
+
}, [projectPackages, activePackage, pkgSuffix, navigate]);
|
|
199
204
|
React.useEffect(() => {
|
|
200
205
|
let cancelled = false;
|
|
201
206
|
setLoading(true);
|
|
@@ -249,8 +254,12 @@ function DefaultMetadataList({ type, appName }) {
|
|
|
249
254
|
if (!activePackage)
|
|
250
255
|
return false;
|
|
251
256
|
const pkg = row.item?._packageId;
|
|
252
|
-
const
|
|
253
|
-
|
|
257
|
+
const isLocal = !pkg || pkg === LOCAL_PACKAGE_ID;
|
|
258
|
+
// Local/Custom scope surfaces this environment's runtime-authored items
|
|
259
|
+
// (untagged / `sys_metadata` provenance); a code package shows its own.
|
|
260
|
+
if (activePackage === LOCAL_PACKAGE_ID)
|
|
261
|
+
return isLocal;
|
|
262
|
+
return !isLocal && pkg === activePackage;
|
|
254
263
|
}), [items, activePackage, config]);
|
|
255
264
|
// User-driven filters (search query + source provenance) on top of scope.
|
|
256
265
|
const filtered = scopedItems.filter((row) => {
|
|
@@ -303,14 +312,14 @@ function DefaultMetadataList({ type, appName }) {
|
|
|
303
312
|
},
|
|
304
313
|
]
|
|
305
314
|
: []),
|
|
306
|
-
], actions: _jsxs(_Fragment, { children: [_jsx(MetadataTypeActions, { entry: entry, location: "list_toolbar", onAfter: () => setRefreshKey((k) => k + 1) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setRefreshKey((k) => k + 1), title: t('engine.list.refresh', locale), children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), (entry?.allowOrgOverride || entry?.allowRuntimeCreate) && (_jsxs(Button, { size: "sm", variant: config.createFields ? 'default' : 'outline', onClick:
|
|
315
|
+
], actions: _jsxs(_Fragment, { children: [_jsx(MetadataTypeActions, { entry: entry, location: "list_toolbar", onAfter: () => setRefreshKey((k) => k + 1) }), _jsx(Button, { variant: "ghost", size: "sm", onClick: () => setRefreshKey((k) => k + 1), title: t('engine.list.refresh', locale), children: _jsx(RefreshCw, { className: "h-4 w-4" }) }), (entry?.allowOrgOverride || entry?.allowRuntimeCreate) && (_jsxs(Button, { size: "sm", variant: config.createFields ? 'default' : 'outline', onClick: handleCreate, title: config.createFields
|
|
307
316
|
? tFormat('engine.list.createHint', locale, { type: typeLabel })
|
|
308
|
-
: undefined, children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), t('engine.list.create', locale)] }))] }), children: _jsxs("div", { className: "p-6 space-y-4", children: [_jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsxs("div", { className: "relative flex-1 min-w-[200px] max-w-md", children: [_jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" }), _jsx(Input, { className: "pl-8", placeholder: t('engine.list.search', locale), value: query, onChange: (e) => setQuery(e.target.value) })] }), _jsxs(Select, { value: sourceFilter, onValueChange: setSourceFilter, children: [_jsx(SelectTrigger, { className: "w-[180px]", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsxs(SelectItem, { value: "all", children: [t('engine.list.allSources', locale), " (", sourceCounts.all, ")"] }), _jsxs(SelectItem, { value: "artifact", children: [t('engine.list.source.artifact', locale), " (", sourceCounts.artifact, ")"] }), _jsxs(SelectItem, { value: "runtime", children: [t('engine.list.source.runtime', locale), " (", sourceCounts.runtime, ")"] })] })] })] }), (loading || projectPackages === null) && (_jsxs("div", { className: "text-sm text-muted-foreground", children: [t('engine.edit.loading', locale), " ", type, "\u2026"] })), error && (_jsx("div", { className: "text-sm text-destructive border border-destructive/30 rounded p-3 bg-destructive/5", children: error })), !loading && !error && projectPackages !== null && projectPackages.length === 0 && (_jsxs(Empty, { children: [_jsx(EmptyTitle, { children: "No project packages installed" }), _jsx(EmptyDescription, { children: "Studio only shows metadata that belongs to a project software package. Install or create a project package to manage its metadata here." })] })), !loading && !error && projectPackages !== null && projectPackages.length > 0 && filtered.length === 0 && (_jsxs(Empty, { children: [_jsx(EmptyTitle, { children: scopedItems.length === 0
|
|
317
|
+
: undefined, children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), t('engine.list.create', locale)] }))] }), children: _jsxs("div", { className: "p-6 space-y-4", children: [_jsx(CreatePackageDialog, { open: showCreateBase, onOpenChange: setShowCreateBase, onCreated: (id) => navigate(`./new?package=${encodeURIComponent(id)}`) }), _jsxs("div", { className: "flex items-center gap-3 flex-wrap", children: [_jsxs("div", { className: "relative flex-1 min-w-[200px] max-w-md", children: [_jsx(Search, { className: "absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground" }), _jsx(Input, { className: "pl-8", placeholder: t('engine.list.search', locale), value: query, onChange: (e) => setQuery(e.target.value) })] }), _jsxs(Select, { value: sourceFilter, onValueChange: setSourceFilter, children: [_jsx(SelectTrigger, { className: "w-[180px]", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsxs(SelectItem, { value: "all", children: [t('engine.list.allSources', locale), " (", sourceCounts.all, ")"] }), _jsxs(SelectItem, { value: "artifact", children: [t('engine.list.source.artifact', locale), " (", sourceCounts.artifact, ")"] }), _jsxs(SelectItem, { value: "runtime", children: [t('engine.list.source.runtime', locale), " (", sourceCounts.runtime, ")"] })] })] })] }), (loading || projectPackages === null) && (_jsxs("div", { className: "text-sm text-muted-foreground", children: [t('engine.edit.loading', locale), " ", type, "\u2026"] })), error && (_jsx("div", { className: "text-sm text-destructive border border-destructive/30 rounded p-3 bg-destructive/5", children: error })), !loading && !error && projectPackages !== null && projectPackages.length === 0 && (_jsxs(Empty, { children: [_jsx(EmptyTitle, { children: "No project packages installed" }), _jsx(EmptyDescription, { children: "Studio only shows metadata that belongs to a project software package. Install or create a project package to manage its metadata here." })] })), !loading && !error && projectPackages !== null && projectPackages.length > 0 && filtered.length === 0 && (_jsxs(Empty, { children: [_jsx(EmptyTitle, { children: scopedItems.length === 0
|
|
309
318
|
? tFormat('engine.list.emptyType', locale, { type: typeLabel })
|
|
310
319
|
: tFormat('engine.list.emptyQuery', locale, { query }) }), _jsx(EmptyDescription, { children: config.emptyStateHint ??
|
|
311
320
|
(entry?.allowOrgOverride || entry?.allowRuntimeCreate
|
|
312
321
|
? tFormat('engine.list.createHint', locale, { type: typeLabel })
|
|
313
|
-
: t('engine.list.readOnlyHint', locale)) }), scopedItems.length === 0 && (entry?.allowOrgOverride || entry?.allowRuntimeCreate) && (_jsx("div", { className: "mt-4", children: _jsxs(Button, { onClick:
|
|
322
|
+
: t('engine.list.readOnlyHint', locale)) }), scopedItems.length === 0 && (entry?.allowOrgOverride || entry?.allowRuntimeCreate) && (_jsx("div", { className: "mt-4", children: _jsxs(Button, { onClick: handleCreate, children: [_jsx(Plus, { className: "h-4 w-4 mr-1" }), t('engine.list.create', locale)] }) }))] })), !loading && filtered.length > 0 && (_jsx("div", { className: "border rounded-lg overflow-hidden", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { className: "bg-muted/40 text-xs uppercase tracking-wider text-muted-foreground", children: _jsxs("tr", { children: [columns.map((c) => (_jsx("th", { className: "px-3 py-2 text-left font-medium", style: c.width ? { width: c.width } : undefined, children: localizeColumnLabel(c) }, c.key))), _jsx("th", { className: "px-3 py-2 text-right font-medium w-[80px]", children: t('engine.list.col.source', locale) })] }) }), _jsx("tbody", { className: "divide-y", children: filtered.map((row, i) => {
|
|
314
323
|
const pk = config.primaryKey ?? 'name';
|
|
315
324
|
const name = String(row.item[pk] ?? `(unnamed-${i})`);
|
|
316
325
|
// ADR-0048 — link to this row's OWNING package so the editor
|
|
@@ -28,6 +28,7 @@ import { useRecentItems } from '../../context/RecentItemsProvider';
|
|
|
28
28
|
import { useMetadataClient, useMetadataTypes, useGlobalDiagnostics, } from './useMetadata';
|
|
29
29
|
import { MetadataQuickFind } from './QuickFind';
|
|
30
30
|
import { translateMetadataType, translateMetadataDomain, t, tFormat, detectLocale, } from './i18n';
|
|
31
|
+
import { buildPackageScopeOptions, LOCAL_PACKAGE_ID } from './package-scope';
|
|
31
32
|
const HIDDEN_TYPES = new Set(['field', 'package']);
|
|
32
33
|
const DOMAIN_ICONS = {
|
|
33
34
|
data: Database,
|
|
@@ -104,20 +105,7 @@ export function StudioHomePage() {
|
|
|
104
105
|
const list = await client.list('package');
|
|
105
106
|
if (cancelled)
|
|
106
107
|
return;
|
|
107
|
-
|
|
108
|
-
const rows = (list ?? [])
|
|
109
|
-
.map((raw) => {
|
|
110
|
-
const item = raw && typeof raw === 'object' && 'item' in raw ? raw.item : raw;
|
|
111
|
-
const m = (item?.manifest ?? item ?? {});
|
|
112
|
-
return {
|
|
113
|
-
id: m.id,
|
|
114
|
-
scope: m.scope,
|
|
115
|
-
name: m.name || m.id,
|
|
116
|
-
};
|
|
117
|
-
})
|
|
118
|
-
.filter((p) => p.id && !SYSTEM_SCOPES.has(p.scope));
|
|
119
|
-
rows.sort((a, b) => a.name.localeCompare(b.name));
|
|
120
|
-
setProjectPackages(rows.map((p) => ({ id: p.id, name: p.name })));
|
|
108
|
+
setProjectPackages(buildPackageScopeOptions(list));
|
|
121
109
|
}
|
|
122
110
|
catch {
|
|
123
111
|
if (!cancelled)
|
|
@@ -155,6 +143,10 @@ export function StudioHomePage() {
|
|
|
155
143
|
return false;
|
|
156
144
|
if (!activePackage)
|
|
157
145
|
return false;
|
|
146
|
+
// Local/Custom scope: show every runtime-creatable type so the user can
|
|
147
|
+
// start authoring any kind of metadata here, even with zero items yet.
|
|
148
|
+
if (activePackage === LOCAL_PACKAGE_ID)
|
|
149
|
+
return e.allowOrgOverride || e.allowRuntimeCreate;
|
|
158
150
|
return (packagesByType[e.type] ?? []).includes(activePackage);
|
|
159
151
|
}), [activePackage, entries, packagesByType]);
|
|
160
152
|
const writable = React.useMemo(() => visible.filter((e) => e.allowOrgOverride || e.allowRuntimeCreate), [visible]);
|
|
@@ -352,6 +352,15 @@ export function registerBuiltinAnchors() {
|
|
|
352
352
|
createDerive: [
|
|
353
353
|
{ from: 'label', to: 'name', transform: 'slugify', untilUserEdits: true },
|
|
354
354
|
],
|
|
355
|
+
// A new action defaults to `type: 'script'` (ActionType.default), which the
|
|
356
|
+
// spec requires to carry an executable `body` or `target` — otherwise the
|
|
357
|
+
// draft fails validation on save (422) and AppPlugin registers no engine
|
|
358
|
+
// handler (the #2169 "Mark Done" runtime miss). Seed a no-op L2 body so
|
|
359
|
+
// "New action -> name -> Save" round-trips; the author edits the source after.
|
|
360
|
+
createDefaults: {
|
|
361
|
+
type: 'script',
|
|
362
|
+
body: { language: 'js', source: 'return { success: true };' },
|
|
363
|
+
},
|
|
355
364
|
});
|
|
356
365
|
// dashboard / report — surface ones bound to a specific object.
|
|
357
366
|
// Many will not have an explicit anchor; those simply don't appear.
|
|
@@ -377,11 +386,20 @@ export function registerBuiltinAnchors() {
|
|
|
377
386
|
groupLabel: 'Reports',
|
|
378
387
|
order: 81,
|
|
379
388
|
}],
|
|
380
|
-
|
|
389
|
+
// ADR-0021 single-form: a report is dataset-bound — `objectName` and the
|
|
390
|
+
// legacy `columns` array were removed from ReportSchema, so seeding them
|
|
391
|
+
// here produced a stub that failed server validation ("a report needs
|
|
392
|
+
// `dataset` + `values`"). Report-create now lights up the canvas +
|
|
393
|
+
// ReportDefaultInspector (see CREATE_MODE_CANVAS_TYPES), where the author
|
|
394
|
+
// picks the dataset/measures/dimensions directly; we just seed a sensible
|
|
395
|
+
// starting type.
|
|
396
|
+
createFields: ['label', 'name', 'description'],
|
|
381
397
|
createDerive: [
|
|
382
398
|
{ from: 'label', to: 'name', transform: 'slugify', untilUserEdits: true },
|
|
383
399
|
],
|
|
384
|
-
|
|
400
|
+
// Seed `drilldown:true` so the inspector toggle reflects the schema default
|
|
401
|
+
// (otherwise it shows OFF on a fresh report while the spec default is true).
|
|
402
|
+
createDefaults: { type: 'summary', drilldown: true },
|
|
385
403
|
});
|
|
386
404
|
// Cosmetic defaults for the `object` type list page — gives Object the
|
|
387
405
|
// same look as every other metadata type while still surfacing the
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
import type { MetadataResourceConfig } from './registry';
|
|
9
|
+
/**
|
|
10
|
+
* Build the create-mode save body for a new metadata item.
|
|
11
|
+
*
|
|
12
|
+
* Prefers the server's authoritative **create seed** (delivered per type on the
|
|
13
|
+
* `/meta/types` registry entry — the single source of truth in
|
|
14
|
+
* `@objectstack/spec`) over the locally hardcoded `createDefaults`. This is the
|
|
15
|
+
* drift-stop for the recurring "the designer emits a minimal shape the spec
|
|
16
|
+
* rejects, so create→save 422s" family (dashboard `layout`, action `body`):
|
|
17
|
+
* the structural defaults now come from the same place the spec validates
|
|
18
|
+
* against, so they cannot diverge. Falls back to `createDefaults` when the
|
|
19
|
+
* server provides no seed (older server, or canvas-create types whose shape is
|
|
20
|
+
* built interactively).
|
|
21
|
+
*
|
|
22
|
+
* User-supplied draft values always win over the seed's placeholders.
|
|
23
|
+
* `createBuildBody` (dynamic identity, e.g. a view's qualified name) still takes
|
|
24
|
+
* precedence — it incorporates user input the static seed cannot.
|
|
25
|
+
*/
|
|
26
|
+
export declare function buildCreateModeBody(config: Pick<MetadataResourceConfig, 'createBuildBody' | 'createDefaults'>, draft: Record<string, unknown>, specCreateSeed: Record<string, unknown> | undefined): Record<string, unknown>;
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
* Build the create-mode save body for a new metadata item.
|
|
10
|
+
*
|
|
11
|
+
* Prefers the server's authoritative **create seed** (delivered per type on the
|
|
12
|
+
* `/meta/types` registry entry — the single source of truth in
|
|
13
|
+
* `@objectstack/spec`) over the locally hardcoded `createDefaults`. This is the
|
|
14
|
+
* drift-stop for the recurring "the designer emits a minimal shape the spec
|
|
15
|
+
* rejects, so create→save 422s" family (dashboard `layout`, action `body`):
|
|
16
|
+
* the structural defaults now come from the same place the spec validates
|
|
17
|
+
* against, so they cannot diverge. Falls back to `createDefaults` when the
|
|
18
|
+
* server provides no seed (older server, or canvas-create types whose shape is
|
|
19
|
+
* built interactively).
|
|
20
|
+
*
|
|
21
|
+
* User-supplied draft values always win over the seed's placeholders.
|
|
22
|
+
* `createBuildBody` (dynamic identity, e.g. a view's qualified name) still takes
|
|
23
|
+
* precedence — it incorporates user input the static seed cannot.
|
|
24
|
+
*/
|
|
25
|
+
export function buildCreateModeBody(config, draft, specCreateSeed) {
|
|
26
|
+
if (config.createBuildBody) {
|
|
27
|
+
return config.createBuildBody(draft);
|
|
28
|
+
}
|
|
29
|
+
return { ...(specCreateSeed ?? config.createDefaults ?? {}), ...draft };
|
|
30
|
+
}
|