@object-ui/app-shell 7.0.0 → 7.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. package/CHANGELOG.md +560 -0
  2. package/dist/console/AppContent.js +23 -17
  3. package/dist/console/ConsoleShell.d.ts +16 -0
  4. package/dist/console/ConsoleShell.js +43 -2
  5. package/dist/console/ai/AiChatPage.js +47 -16
  6. package/dist/console/ai/LiveCanvas.d.ts +8 -2
  7. package/dist/console/ai/LiveCanvas.js +6 -4
  8. package/dist/console/home/HomeLayout.js +5 -7
  9. package/dist/console/home/HomePage.js +1 -9
  10. package/dist/console/organizations/CreateWorkspaceDialog.js +15 -1
  11. package/dist/console/organizations/OrganizationsPage.js +22 -3
  12. package/dist/console/organizations/provisionEnvironment.d.ts +53 -0
  13. package/dist/console/organizations/provisionEnvironment.js +64 -0
  14. package/dist/environment/EnvironmentEntitlementDialog.d.ts +34 -0
  15. package/dist/environment/EnvironmentEntitlementDialog.js +37 -0
  16. package/dist/environment/EnvironmentListToolbar.d.ts +33 -0
  17. package/dist/environment/EnvironmentListToolbar.js +59 -0
  18. package/dist/environment/entitlements.d.ts +90 -0
  19. package/dist/environment/entitlements.js +91 -0
  20. package/dist/environment/useEnvironmentEntitlements.d.ts +32 -0
  21. package/dist/environment/useEnvironmentEntitlements.js +108 -0
  22. package/dist/hooks/useActionModal.js +15 -1
  23. package/dist/hooks/useAiSurface.d.ts +59 -0
  24. package/dist/hooks/useAiSurface.js +78 -0
  25. package/dist/hooks/useChatConversation.d.ts +30 -0
  26. package/dist/hooks/useChatConversation.js +63 -0
  27. package/dist/hooks/useConsoleActionRuntime.d.ts +3 -0
  28. package/dist/hooks/useConsoleActionRuntime.js +42 -10
  29. package/dist/index.d.ts +5 -2
  30. package/dist/index.js +10 -2
  31. package/dist/layout/AppHeader.js +28 -4
  32. package/dist/layout/ConsoleFloatingChatbot.d.ts +6 -4
  33. package/dist/layout/ConsoleFloatingChatbot.js +41 -10
  34. package/dist/layout/ConsoleLayout.js +5 -6
  35. package/dist/layout/ContextSelectors.js +59 -35
  36. package/dist/layout/agentPicker.d.ts +56 -0
  37. package/dist/layout/agentPicker.js +40 -0
  38. package/dist/preview/CommitTimeline.d.ts +15 -0
  39. package/dist/preview/CommitTimeline.js +82 -0
  40. package/dist/preview/DraftPreviewBar.js +20 -7
  41. package/dist/preview/UnpublishedAppBar.js +11 -7
  42. package/dist/preview/commitHistory.d.ts +28 -0
  43. package/dist/preview/commitHistory.js +48 -0
  44. package/dist/providers/ExpressionProvider.js +9 -3
  45. package/dist/providers/MetadataProvider.js +9 -0
  46. package/dist/utils/index.d.ts +2 -2
  47. package/dist/utils/index.js +1 -1
  48. package/dist/utils/recordFormNavigation.d.ts +60 -0
  49. package/dist/utils/recordFormNavigation.js +35 -0
  50. package/dist/utils/resolvePageVarTokens.d.ts +31 -0
  51. package/dist/utils/resolvePageVarTokens.js +72 -0
  52. package/dist/views/CreateViewDialog.js +14 -1
  53. package/dist/views/FlowRunner.d.ts +2 -30
  54. package/dist/views/FlowRunner.js +18 -50
  55. package/dist/views/ObjectView.js +26 -12
  56. package/dist/views/ScreenView.d.ts +70 -0
  57. package/dist/views/ScreenView.js +73 -0
  58. package/dist/views/metadata-admin/AssignedUsersSection.d.ts +28 -0
  59. package/dist/views/metadata-admin/AssignedUsersSection.js +151 -0
  60. package/dist/views/metadata-admin/DirectoryPage.js +2 -14
  61. package/dist/views/metadata-admin/JsonSourceEditor.d.ts +3 -1
  62. package/dist/views/metadata-admin/JsonSourceEditor.js +21 -3
  63. package/dist/views/metadata-admin/PackagesPage.d.ts +5 -0
  64. package/dist/views/metadata-admin/PackagesPage.js +58 -5
  65. package/dist/views/metadata-admin/PermissionMatrixEditor.js +2 -1
  66. package/dist/views/metadata-admin/ResourceEditPage.js +83 -24
  67. package/dist/views/metadata-admin/ResourceListPage.js +28 -19
  68. package/dist/views/metadata-admin/StudioHomePage.js +6 -14
  69. package/dist/views/metadata-admin/anchors.js +20 -2
  70. package/dist/views/metadata-admin/createBody.d.ts +26 -0
  71. package/dist/views/metadata-admin/createBody.js +30 -0
  72. package/dist/views/metadata-admin/i18n.js +108 -2
  73. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.d.ts +10 -2
  74. package/dist/views/metadata-admin/inspectors/DatasetDefaultInspector.js +136 -8
  75. package/dist/views/metadata-admin/inspectors/FlowEdgeInspector.js +99 -4
  76. package/dist/views/metadata-admin/inspectors/FlowExprIssue.d.ts +21 -0
  77. package/dist/views/metadata-admin/inspectors/FlowExprIssue.js +13 -0
  78. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.d.ts +20 -2
  79. package/dist/views/metadata-admin/inspectors/FlowKeyValueField.js +71 -28
  80. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.d.ts +4 -1
  81. package/dist/views/metadata-admin/inspectors/FlowNodeConfigField.js +24 -9
  82. package/dist/views/metadata-admin/inspectors/FlowNodeInspector.js +81 -4
  83. package/dist/views/metadata-admin/inspectors/FlowObjectListField.d.ts +4 -1
  84. package/dist/views/metadata-admin/inspectors/FlowObjectListField.js +8 -3
  85. package/dist/views/metadata-admin/inspectors/ObjectDefaultInspector.js +5 -4
  86. package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +47 -12
  87. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.d.ts +1 -1
  88. package/dist/views/metadata-admin/inspectors/ReportDefaultInspector.js +60 -2
  89. package/dist/views/metadata-admin/inspectors/VariableTextInput.d.ts +47 -0
  90. package/dist/views/metadata-admin/inspectors/VariableTextInput.js +95 -0
  91. package/dist/views/metadata-admin/inspectors/_shared.d.ts +5 -1
  92. package/dist/views/metadata-admin/inspectors/_shared.js +2 -2
  93. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.d.ts +24 -0
  94. package/dist/views/metadata-admin/inspectors/datasetFilterCondition.js +102 -0
  95. package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +16 -1
  96. package/dist/views/metadata-admin/inspectors/flow-node-config.js +67 -11
  97. package/dist/views/metadata-admin/inspectors/flow-ref-check.d.ts +39 -0
  98. package/dist/views/metadata-admin/inspectors/flow-ref-check.js +114 -0
  99. package/dist/views/metadata-admin/inspectors/flow-scope.d.ts +109 -0
  100. package/dist/views/metadata-admin/inspectors/flow-scope.js +199 -0
  101. package/dist/views/metadata-admin/inspectors/useDatasetFields.d.ts +14 -3
  102. package/dist/views/metadata-admin/inspectors/useDatasetFields.js +0 -0
  103. package/dist/views/metadata-admin/inspectors/useFlowScope.d.ts +23 -0
  104. package/dist/views/metadata-admin/inspectors/useFlowScope.js +45 -0
  105. package/dist/views/metadata-admin/issuePath.d.ts +22 -0
  106. package/dist/views/metadata-admin/issuePath.js +65 -0
  107. package/dist/views/metadata-admin/package-scope.d.ts +41 -0
  108. package/dist/views/metadata-admin/package-scope.js +59 -0
  109. package/dist/views/metadata-admin/preview-registry.d.ts +12 -0
  110. package/dist/views/metadata-admin/previews/DatasetPreview.js +21 -5
  111. package/dist/views/metadata-admin/previews/FlowCanvas.d.ts +26 -1
  112. package/dist/views/metadata-admin/previews/FlowCanvas.js +143 -16
  113. package/dist/views/metadata-admin/previews/FlowPreview.d.ts +1 -1
  114. package/dist/views/metadata-admin/previews/FlowPreview.js +47 -7
  115. package/dist/views/metadata-admin/previews/FlowSimulatorPanel.js +37 -3
  116. package/dist/views/metadata-admin/previews/ObjectFormCanvas.js +9 -4
  117. package/dist/views/metadata-admin/previews/PagePreview.js +112 -3
  118. package/dist/views/metadata-admin/previews/ProblemsPanel.d.ts +18 -0
  119. package/dist/views/metadata-admin/previews/ProblemsPanel.js +27 -0
  120. package/dist/views/metadata-admin/previews/ReportPreview.d.ts +9 -8
  121. package/dist/views/metadata-admin/previews/ReportPreview.js +33 -16
  122. package/dist/views/metadata-admin/previews/ScreenPreview.d.ts +38 -0
  123. package/dist/views/metadata-admin/previews/ScreenPreview.js +61 -0
  124. package/dist/views/metadata-admin/previews/flow-canvas-layout.d.ts +14 -0
  125. package/dist/views/metadata-admin/previews/flow-canvas-layout.js +37 -0
  126. package/dist/views/metadata-admin/previews/flow-canvas-parts.d.ts +17 -1
  127. package/dist/views/metadata-admin/previews/flow-canvas-parts.js +23 -6
  128. package/dist/views/metadata-admin/previews/flow-expr-problems.d.ts +19 -0
  129. package/dist/views/metadata-admin/previews/flow-expr-problems.js +97 -0
  130. package/dist/views/metadata-admin/previews/flow-problems.d.ts +84 -0
  131. package/dist/views/metadata-admin/previews/flow-problems.js +209 -0
  132. package/dist/views/metadata-admin/previews/object-fields-io.d.ts +21 -0
  133. package/dist/views/metadata-admin/previews/object-fields-io.js +37 -2
  134. package/dist/views/metadata-admin/previews/screen-spec.d.ts +43 -0
  135. package/dist/views/metadata-admin/previews/screen-spec.js +108 -0
  136. package/dist/views/metadata-admin/previews/simulator/flow-sim-types.d.ts +20 -0
  137. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.d.ts +7 -0
  138. package/dist/views/metadata-admin/previews/simulator/flow-sim-validate.js +76 -2
  139. package/dist/views/metadata-admin/previews/simulator/flow-simulator.d.ts +32 -3
  140. package/dist/views/metadata-admin/previews/simulator/flow-simulator.js +119 -9
  141. package/package.json +38 -38
@@ -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
- const isArtifact = layered?.code != null;
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
- const formForLabels = (createMode && config.createSchema ? undefined : entry?.form);
819
- const sections = Array.isArray(formForLabels?.sections) ? formForLabels.sections : [];
820
- for (const section of sections) {
821
- const fields = Array.isArray(section?.fields) ? section.fields : [];
822
- for (const field of fields) {
823
- if (typeof field === 'string') {
824
- if (field === key)
825
- return field;
826
- }
827
- else if (field?.field === key) {
828
- return String(field.label ?? key);
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
- const props = (schema?.properties ?? {});
833
- return String(props[key]?.title ?? key);
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.createBuildBody
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
- navigate(`../${encodeURIComponent(savedName)}`, { relative: 'path' });
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
- const isArtifactItem = !createMode && layered?.code != null;
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
- const SYSTEM_SCOPES = new Set(['system', 'cloud']);
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 effectivePkg = !pkg || pkg === 'sys_metadata' ? null : pkg;
253
- return effectivePkg === activePackage;
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: () => navigate(`./new${pkgSuffix}`), title: config.createFields
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: () => navigate(`./new${pkgSuffix}`), 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) => {
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
- const SYSTEM_SCOPES = new Set(['system', 'cloud']);
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
- createFields: ['label', 'name', 'objectName', 'description'],
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
- createDefaults: { columns: [] },
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
+ }