@object-ui/app-shell 11.3.0 → 11.5.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 (90) hide show
  1. package/CHANGELOG.md +700 -0
  2. package/README.md +32 -0
  3. package/dist/chrome/KeyboardShortcutsDialog.js +2 -1
  4. package/dist/console/AppContent.js +145 -26
  5. package/dist/console/ConsoleShell.js +25 -3
  6. package/dist/console/home/CloudOnboardingNext.d.ts +9 -0
  7. package/dist/console/home/CloudOnboardingNext.js +14 -4
  8. package/dist/console/home/HomePage.js +34 -7
  9. package/dist/console/organizations/CreateWorkspaceDialog.js +33 -3
  10. package/dist/console/organizations/OrganizationsPage.js +16 -7
  11. package/dist/context/CommandPaletteProvider.js +2 -1
  12. package/dist/hooks/useConsoleActionRuntime.js +32 -3
  13. package/dist/hooks/useObjectActions.js +16 -4
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.js +3 -0
  16. package/dist/layout/AppHeader.js +13 -5
  17. package/dist/layout/AppSidebar.js +10 -4
  18. package/dist/observability/sentry.d.ts +5 -0
  19. package/dist/observability/sentry.js +6 -1
  20. package/dist/preview/DraftChangesPanel.d.ts +31 -1
  21. package/dist/preview/DraftChangesPanel.js +146 -18
  22. package/dist/urlParams.d.ts +68 -0
  23. package/dist/urlParams.js +76 -0
  24. package/dist/utils/appRoute.d.ts +15 -0
  25. package/dist/utils/appRoute.js +22 -0
  26. package/dist/utils/deriveRelatedLists.d.ts +20 -5
  27. package/dist/utils/deriveRelatedLists.js +31 -13
  28. package/dist/utils/index.d.ts +1 -1
  29. package/dist/utils/index.js +1 -1
  30. package/dist/utils/pageTabsUrlSync.d.ts +32 -0
  31. package/dist/utils/pageTabsUrlSync.js +43 -0
  32. package/dist/utils/recordFormNavigation.d.ts +40 -0
  33. package/dist/utils/recordFormNavigation.js +30 -0
  34. package/dist/utils/resolveViewId.d.ts +23 -0
  35. package/dist/utils/resolveViewId.js +37 -0
  36. package/dist/utils/warnSuppressedListNav.d.ts +10 -0
  37. package/dist/utils/warnSuppressedListNav.js +40 -0
  38. package/dist/views/InterfaceListPage.d.ts +1 -0
  39. package/dist/views/InterfaceListPage.js +7 -5
  40. package/dist/views/ObjectDataPage.d.ts +29 -0
  41. package/dist/views/ObjectDataPage.js +227 -0
  42. package/dist/views/ObjectView.js +65 -13
  43. package/dist/views/RecordDetailView.js +191 -123
  44. package/dist/views/RecordFormPage.js +7 -1
  45. package/dist/views/RelatedRecordActionsBridge.d.ts +33 -0
  46. package/dist/views/RelatedRecordActionsBridge.js +147 -0
  47. package/dist/views/metadata-admin/PackagesPage.js +18 -7
  48. package/dist/views/metadata-admin/PermissionMatrixEditor.d.ts +18 -1
  49. package/dist/views/metadata-admin/PermissionMatrixEditor.js +73 -14
  50. package/dist/views/metadata-admin/ResourceEditPage.js +39 -0
  51. package/dist/views/metadata-admin/i18n.d.ts +12 -21
  52. package/dist/views/metadata-admin/i18n.js +557 -6
  53. package/dist/views/metadata-admin/inspectors/AppNavInspector.d.ts +11 -4
  54. package/dist/views/metadata-admin/inspectors/AppNavInspector.js +141 -7
  55. package/dist/views/metadata-admin/inspectors/FlowReferenceField.d.ts +14 -0
  56. package/dist/views/metadata-admin/inspectors/FlowReferenceField.js +76 -5
  57. package/dist/views/metadata-admin/inspectors/ObjectFieldInspector.js +60 -30
  58. package/dist/views/metadata-admin/inspectors/flow-node-config.d.ts +8 -1
  59. package/dist/views/metadata-admin/inspectors/flow-node-config.js +3 -2
  60. package/dist/views/metadata-admin/inspectors/nav-target.d.ts +52 -0
  61. package/dist/views/metadata-admin/inspectors/nav-target.js +149 -0
  62. package/dist/views/metadata-admin/nav-selection.d.ts +20 -0
  63. package/dist/views/metadata-admin/nav-selection.js +81 -0
  64. package/dist/views/metadata-admin/permission-slice.d.ts +66 -0
  65. package/dist/views/metadata-admin/permission-slice.js +70 -0
  66. package/dist/views/metadata-admin/previews/AppNavCanvas.js +19 -7
  67. package/dist/views/metadata-admin/previews/AppPreview.js +4 -2
  68. package/dist/views/metadata-admin/previews/FlowRunsPanel.d.ts +16 -7
  69. package/dist/views/metadata-admin/previews/FlowRunsPanel.js +18 -2
  70. package/dist/views/studio-design/BuilderLanding.d.ts +15 -0
  71. package/dist/views/studio-design/BuilderLanding.js +126 -0
  72. package/dist/views/studio-design/ObjectFormDesigner.d.ts +33 -0
  73. package/dist/views/studio-design/ObjectFormDesigner.js +231 -0
  74. package/dist/views/studio-design/ObjectSettingsPanel.d.ts +30 -0
  75. package/dist/views/studio-design/ObjectSettingsPanel.js +46 -0
  76. package/dist/views/studio-design/ObjectValidationsPanel.d.ts +30 -0
  77. package/dist/views/studio-design/ObjectValidationsPanel.js +80 -0
  78. package/dist/views/studio-design/PackageIdInput.d.ts +31 -0
  79. package/dist/views/studio-design/PackageIdInput.js +40 -0
  80. package/dist/views/studio-design/StudioDesignSurface.d.ts +13 -0
  81. package/dist/views/studio-design/StudioDesignSurface.js +962 -145
  82. package/dist/views/studio-design/metadataError.d.ts +23 -0
  83. package/dist/views/studio-design/metadataError.js +44 -0
  84. package/dist/views/studio-design/packageSurfaces.d.ts +49 -0
  85. package/dist/views/studio-design/packageSurfaces.js +34 -0
  86. package/dist/views/studio-design/packages-io.d.ts +38 -0
  87. package/dist/views/studio-design/packages-io.js +73 -0
  88. package/dist/views/studio-design/skeletons.d.ts +16 -0
  89. package/dist/views/studio-design/skeletons.js +51 -0
  90. package/package.json +42 -39
@@ -15,7 +15,8 @@ import { parseUserFilterParams, applyUserFilterParams } from './userFilterUrlSta
15
15
  const ObjectChart = lazy(() => import('@object-ui/plugin-charts').then((m) => ({ default: m.ObjectChart })));
16
16
  const ImportWizard = lazy(() => import('@object-ui/plugin-grid').then((m) => ({ default: m.ImportWizard })));
17
17
  import { ListView } from '@object-ui/plugin-list';
18
- import { ObjectView as PluginObjectView, ViewTabBar, ManageViewsDialog } from '@object-ui/plugin-view';
18
+ import { ObjectView as PluginObjectView, ViewTabBar, ManageViewsDialog, deriveRecordSurface, overlayWidthFor } from '@object-ui/plugin-view';
19
+ import { RECORD_DRAWER_PARAM } from '../urlParams';
19
20
  // Plugin registration is handled by the host app (e.g. apps/console/src/main.tsx
20
21
  // uses ComponentRegistry.registerLazy so heavy plugins stay code-split).
21
22
  // Do NOT add eager `import '@object-ui/plugin-*'` side-effect imports here.
@@ -34,6 +35,8 @@ import { ManagedByBadge } from '../components/ManagedByBadge';
34
35
  import { RecordDetailView } from './RecordDetailView';
35
36
  import { resolveCrudAffordances } from '../utils/crudAffordances';
36
37
  import { resolveManagedByEmptyState } from '../utils/managedByEmptyState';
38
+ import { resolveViewId } from '../utils/resolveViewId';
39
+ import { warnSuppressedListNav } from '../utils/warnSuppressedListNav';
37
40
  import { useObjectActions } from '../hooks/useObjectActions';
38
41
  import { useObjectTranslation, useObjectLabel } from '@object-ui/i18n';
39
42
  import { usePermissions } from '@object-ui/permissions';
@@ -190,8 +193,9 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
190
193
  'updated_by', 'updatedBy', '_version', '_rev',
191
194
  ]);
192
195
  let defaultColumns = [];
193
- if (Array.isArray(objectDef?.compactLayout) && objectDef.compactLayout.length > 0) {
194
- defaultColumns = objectDef.compactLayout.filter((n) => objectDef.fields?.[n]);
196
+ const curated = objectDef?.highlightFields;
197
+ if (Array.isArray(curated) && curated.length > 0) {
198
+ defaultColumns = curated.filter((n) => objectDef.fields?.[n]);
195
199
  }
196
200
  else if (objectDef?.fields) {
197
201
  defaultColumns = Object.entries(objectDef.fields)
@@ -430,7 +434,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
430
434
  // Resolve Views from objectDef.listViews (camelCase per @objectstack/spec)
431
435
  const views = useMemo(() => {
432
436
  // Default column resolution priority:
433
- // 1. `compactLayout` (curated primary business fields).
437
+ // 1. The `highlightFields` semantic role (ADR-0085).
434
438
  // 2. Business fields only — exclude system-managed identifiers/audit
435
439
  // columns (id, created_at, updated_at, …) and fields explicitly
436
440
  // marked hidden/readonly on the schema. First 5 kept for compactness.
@@ -440,8 +444,9 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
440
444
  'updated_by', 'updatedBy', '_version', '_rev',
441
445
  ]);
442
446
  const resolveDefaultColumns = () => {
443
- if (Array.isArray(objectDef.compactLayout) && objectDef.compactLayout.length > 0) {
444
- return objectDef.compactLayout.filter((n) => objectDef.fields?.[n]);
447
+ const curated = objectDef.highlightFields;
448
+ if (Array.isArray(curated) && curated.length > 0) {
449
+ return curated.filter((n) => objectDef.fields?.[n]);
445
450
  }
446
451
  if (objectDef.fields) {
447
452
  return Object.entries(objectDef.fields)
@@ -606,7 +611,20 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
606
611
  const def = views.find((v) => v.isDefault);
607
612
  return def?.id;
608
613
  }, [views]);
609
- const activeViewId = viewId || searchParams.get('view') || defaultViewId || views[0]?.id;
614
+ // Canonical view ids are fully qualified (`<object>.<viewKind>`, see
615
+ // MetadataProvider), but nav items emit `viewName` verbatim — usually
616
+ // the short form — and legacy embedded listViews carry bare keys (incl.
617
+ // the `'all'` fallback). Resolve the URL-requested name in both
618
+ // directions, and never swallow a miss silently (#2217).
619
+ const requestedViewId = viewId || searchParams.get('view') || undefined;
620
+ const resolvedViewId = useMemo(() => resolveViewId(requestedViewId, views.map((v) => v.id), objectDef.name), [requestedViewId, views, objectDef.name]);
621
+ useEffect(() => {
622
+ if (requestedViewId && !resolvedViewId) {
623
+ console.warn(`[ObjectView] Requested view "${requestedViewId}" not found on object "${objectDef.name}"; ` +
624
+ `falling back to the default view. Known views: ${views.map((v) => v.id).join(', ')}`);
625
+ }
626
+ }, [requestedViewId, resolvedViewId, objectDef.name, views]);
627
+ const activeViewId = resolvedViewId || defaultViewId || views[0]?.id;
610
628
  const baseView = views.find((v) => v.id === activeViewId) || views[0];
611
629
  const activeView = viewDraft && viewDraft.id === baseView?.id
612
630
  ? { ...baseView, ...viewDraft }
@@ -874,9 +892,27 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
874
892
  // RecordDetailView owns its own route — only same-page click navigation
875
893
  // is drawer-by-default. Per-view config can still override (e.g. a heavy
876
894
  // detail object can set `navigation.mode = 'page'`).
877
- const detailNavigation = useMemo(() => activeView?.navigation ??
878
- objectDef.navigation ?? { mode: 'drawer', width: 'min(92vw, 1280px)' }, [activeView?.navigation, objectDef.navigation]);
879
- const drawerRecordId = searchParams.get('recordId');
895
+ const detailNavigation = useMemo(() => {
896
+ const authored = activeView?.navigation ?? objectDef.navigation;
897
+ if (authored) {
898
+ // Authored config wins. For an overlay, resolve the `size`
899
+ // bucket (or 'auto') to a viewport-clamped width when no explicit
900
+ // width was given — #2578: a pixel width can't be authored blind.
901
+ if (authored.mode === 'page')
902
+ return authored;
903
+ return {
904
+ ...authored,
905
+ width: authored.width ?? overlayWidthFor(authored.size, objectDef),
906
+ };
907
+ }
908
+ // #2578: derive surface + width from FIELD COUNT. Field-heavy → full
909
+ // page (cramped in a drawer); light → a drawer sized to the content
910
+ // and clamped to the viewport. Mobile always pages (in deriveRecordSurface).
911
+ return deriveRecordSurface(objectDef) === 'page'
912
+ ? { mode: 'page' }
913
+ : { mode: 'drawer', width: overlayWidthFor('auto', objectDef) };
914
+ }, [activeView?.navigation, objectDef]);
915
+ const drawerRecordId = searchParams.get(RECORD_DRAWER_PARAM);
880
916
  /**
881
917
  * URL-derived equality filters in the form `?filter[<field>]=<value>`.
882
918
  * Used by related-list "View All" buttons to scope the destination list
@@ -945,7 +981,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
945
981
  const handleDrawerClose = () => {
946
982
  navOverlay.close();
947
983
  const newParams = new URLSearchParams(searchParams);
948
- newParams.delete('recordId');
984
+ newParams.delete(RECORD_DRAWER_PARAM);
949
985
  setSearchParams(newParams);
950
986
  };
951
987
  /**
@@ -969,7 +1005,7 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
969
1005
  const id = (record?.id ?? record?._id);
970
1006
  if (id != null) {
971
1007
  const next = new URLSearchParams(searchParams);
972
- next.set('recordId', String(id));
1008
+ next.set(RECORD_DRAWER_PARAM, String(id));
973
1009
  setSearchParams(next);
974
1010
  return;
975
1011
  }
@@ -1066,6 +1102,11 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
1066
1102
  className: 'h-[400px] w-full',
1067
1103
  } }) }, key));
1068
1104
  }
1105
+ // ADR-0053 wrong-context authoring: userFilters/quickFilters on an
1106
+ // object list view are suppressed below — say so instead of letting
1107
+ // the author stare at a toolbar with nothing where their filter
1108
+ // controls should be (#2219).
1109
+ warnSuppressedListNav(objectDef.name, viewDef.id || viewDef.name || '', viewDef, listSchema);
1069
1110
  const fullSchema = {
1070
1111
  ...listSchema,
1071
1112
  // Propagate appearance/view-config properties for live preview
@@ -1369,11 +1410,22 @@ function ObjectViewInner({ dataSource, objects, onEdit, externalRefreshKey }) {
1369
1410
  // Import buttons stay visible without pushing the page
1370
1411
  // title off-screen.
1371
1412
  mobileMaxVisible: 0,
1372
- } })))] }) }) }), affordances.create && can(objectDef.name, 'create') && (_jsx("button", { type: "button", onClick: actions.create, className: "sm:hidden fixed right-4 bottom-36 z-40 h-12 w-12 rounded-full bg-primary text-primary-foreground shadow-lg active:scale-95 transition-transform inline-flex items-center justify-center", "aria-label": t('console.objectView.new'), "data-testid": "mobile-fab-create", children: _jsx(Plus, { className: "h-5 w-5" }) })), showImport && (_jsx(Suspense, { fallback: null, children: _jsx(ImportWizard, { open: showImport, onOpenChange: setShowImport, objectName: objectDef.name, objectLabel: objectLabel(objectDef), fields: Object.entries(objectDef.fields || {}).map(([name, def]) => ({
1413
+ } })))] }) }) }), affordances.create && can(objectDef.name, 'create') && (_jsx("button", { type: "button", onClick: actions.create, className: "sm:hidden fixed right-4 bottom-36 z-40 h-12 w-12 rounded-full bg-primary text-primary-foreground shadow-lg active:scale-95 transition-transform inline-flex items-center justify-center", "aria-label": t('console.objectView.new'), "data-testid": "mobile-fab-create", children: _jsx(Plus, { className: "h-5 w-5" }) })), showImport && (_jsx(Suspense, { fallback: null, children: _jsx(ImportWizard, { open: showImport, onOpenChange: setShowImport, objectName: objectDef.name, objectLabel: objectLabel(objectDef), fields: Object.entries(objectDef.fields || {})
1414
+ // Only writable fields are importable targets. Computed
1415
+ // types (formula/summary/autonumber) and fields flagged
1416
+ // readonly / write:false are server-rejected, so we omit
1417
+ // them from the mapping step rather than let a user map to
1418
+ // a column the import will silently drop.
1419
+ .filter(([, def]) => !['formula', 'summary', 'autonumber'].includes(def?.type) &&
1420
+ !def?.readonly &&
1421
+ def?.permissions?.write !== false)
1422
+ .map(([name, def]) => ({
1373
1423
  name,
1374
1424
  label: def?.label || name,
1375
1425
  type: def?.type || 'text',
1376
1426
  required: !!def?.required,
1427
+ // Enum options seed the downloadable template's example row.
1428
+ ...(def?.options ? { options: def.options } : {}),
1377
1429
  })), dataSource: dataSource, onComplete: (result) => {
1378
1430
  setRefreshKey(k => k + 1);
1379
1431
  const ok = result.importedRows;