@object-ui/app-shell 5.3.2 → 5.4.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 CHANGED
@@ -1,5 +1,23 @@
1
1
  # @object-ui/app-shell — Changelog
2
2
 
3
+ ## 5.4.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [3a8c754]
8
+ - @object-ui/types@5.4.0
9
+ - @object-ui/auth@5.4.0
10
+ - @object-ui/collaboration@5.4.0
11
+ - @object-ui/components@5.4.0
12
+ - @object-ui/core@5.4.0
13
+ - @object-ui/data-objectstack@5.4.0
14
+ - @object-ui/fields@5.4.0
15
+ - @object-ui/layout@5.4.0
16
+ - @object-ui/permissions@5.4.0
17
+ - @object-ui/providers@5.4.0
18
+ - @object-ui/react@5.4.0
19
+ - @object-ui/i18n@5.4.0
20
+
3
21
  ## 5.3.2
4
22
 
5
23
  ### Patch Changes
@@ -61,17 +61,6 @@ function isNamedItem(item) {
61
61
  'name' in item &&
62
62
  typeof item.name === 'string');
63
63
  }
64
- /**
65
- * Merge `view` metadata (the @objectstack/spec View container, keyed by
66
- * target object name) into object definitions so that `objectDef.listViews`
67
- * is populated for the renderer (`@object-ui/plugin-view`) which expects it.
68
- *
69
- * Each View has shape `{ list?, form?, listViews?, formViews? }`. We collapse:
70
- * - `view.list` → `listViews[view.list.name || 'default']`
71
- * - `view.listViews` → spread into `listViews`
72
- *
73
- * Existing `obj.listViews` / `obj.list_views` win to preserve overrides.
74
- */
75
64
  function mergeViewsIntoObjects(objects, views) {
76
65
  if (!objects.length || !views.length)
77
66
  return objects;
@@ -80,14 +69,27 @@ function mergeViewsIntoObjects(objects, views) {
80
69
  const objName = view?.name || view?.list?.data?.object || view?.form?.data?.object;
81
70
  if (!objName)
82
71
  continue;
83
- const bucket = (byObject[objName] || (byObject[objName] = {}));
72
+ const bucket = (byObject[objName] || (byObject[objName] = { listViews: {}, formViews: {} }));
84
73
  if (view.list) {
85
- const k = view.list.name || 'default';
86
- bucket[k] = view.list;
74
+ // Preserve the primary list view as `obj.list` per @objectstack/spec
75
+ // ViewSchema. Also mirror it into `listViews` under its name so legacy
76
+ // consumers (that only iterate `listViews`) still see it. Consumers
77
+ // honoring `obj.list` (e.g. ObjectView) should dedup by id.
78
+ bucket.primary = view.list;
79
+ const k = view.list.name || 'list';
80
+ bucket.listViews[k] = view.list;
81
+ }
82
+ if (view.form) {
83
+ bucket.form = view.form;
87
84
  }
88
85
  if (view.listViews && typeof view.listViews === 'object') {
89
86
  for (const [k, v] of Object.entries(view.listViews)) {
90
- bucket[k] = v;
87
+ bucket.listViews[k] = v;
88
+ }
89
+ }
90
+ if (view.formViews && typeof view.formViews === 'object') {
91
+ for (const [k, v] of Object.entries(view.formViews)) {
92
+ bucket.formViews[k] = v;
91
93
  }
92
94
  }
93
95
  }
@@ -95,8 +97,22 @@ function mergeViewsIntoObjects(objects, views) {
95
97
  const extra = byObject[obj.name];
96
98
  if (!extra)
97
99
  return obj;
98
- const existing = obj.listViews || obj.list_views || {};
99
- return { ...obj, listViews: { ...extra, ...existing } };
100
+ const existingListViews = obj.listViews || obj.list_views || {};
101
+ const existingFormViews = obj.formViews || obj.form_views || {};
102
+ const merged = {
103
+ ...obj,
104
+ listViews: { ...extra.listViews, ...existingListViews },
105
+ };
106
+ if (Object.keys(extra.formViews).length || Object.keys(existingFormViews).length) {
107
+ merged.formViews = { ...extra.formViews, ...existingFormViews };
108
+ }
109
+ if (extra.primary && !obj.list) {
110
+ merged.list = extra.primary;
111
+ }
112
+ if (extra.form && !obj.form) {
113
+ merged.form = extra.form;
114
+ }
115
+ return merged;
100
116
  });
101
117
  }
102
118
  function emptyEntry() {
@@ -461,6 +461,13 @@ export function ObjectView({ dataSource, objects, onEdit, externalRefreshKey })
461
461
  }
462
462
  const definedViews = (objectDef.listViews || objectDef.list_views || {});
463
463
  const ids = Object.keys(definedViews);
464
+ // Include the primary view id so overrides apply to it too.
465
+ const primary = objectDef.list;
466
+ if (primary && typeof primary === 'object') {
467
+ const primaryId = primary.name || 'list';
468
+ if (!ids.includes(primaryId))
469
+ ids.unshift(primaryId);
470
+ }
464
471
  if (ids.length === 0) {
465
472
  setViewOverrides({});
466
473
  return;
@@ -505,7 +512,7 @@ export function ObjectView({ dataSource, objects, onEdit, externalRefreshKey })
505
512
  setViewOverrides(map);
506
513
  });
507
514
  return () => { cancelled = true; };
508
- }, [dataSource, objectName, objectDef.listViews, objectDef.list_views, refreshKey]);
515
+ }, [dataSource, objectName, objectDef.listViews, objectDef.list_views, objectDef.list, refreshKey]);
509
516
  // Resolve Views from objectDef.listViews (camelCase per @objectstack/spec)
510
517
  const views = useMemo(() => {
511
518
  // Default column resolution priority:
@@ -551,6 +558,29 @@ export function ObjectView({ dataSource, objects, onEdit, externalRefreshKey })
551
558
  type: (override?.type) || value.type || 'grid',
552
559
  };
553
560
  });
561
+ // Honor `objectDef.list` (the primary list view, per @objectstack/spec
562
+ // ViewSchema). MetadataProvider mirrors it into `listViews` so it's
563
+ // already in `viewList` above; promote it to the front and mark it as
564
+ // the default so `defaultViewId` picks it over secondary listViews.
565
+ const primary = objectDef.list;
566
+ if (primary && typeof primary === 'object') {
567
+ const primaryId = primary.name || 'list';
568
+ const idx = viewList.findIndex(v => v.id === primaryId);
569
+ if (idx >= 0) {
570
+ const [entry] = viewList.splice(idx, 1);
571
+ viewList.unshift({ ...entry, isDefault: true });
572
+ }
573
+ else {
574
+ const override = viewOverrides[primaryId];
575
+ viewList.unshift({
576
+ id: primaryId,
577
+ ...primary,
578
+ ...(override || {}),
579
+ type: (override?.type) || primary.type || 'grid',
580
+ isDefault: true,
581
+ });
582
+ }
583
+ }
554
584
  if (viewList.length === 0) {
555
585
  viewList.push({
556
586
  id: 'all',
@@ -566,7 +596,10 @@ export function ObjectView({ dataSource, objects, onEdit, externalRefreshKey })
566
596
  const id = sv.id || sv._id;
567
597
  if (!id)
568
598
  continue;
569
- const normalized = {
599
+ // Drop undefined fields so a partial overlay (e.g. baseline row
600
+ // with no user customization) does not stomp `isDefault`/`columns`
601
+ // populated from the metadata view it shadows.
602
+ const rawNormalized = {
570
603
  label: sv.label || sv.name || id,
571
604
  type: sv.type || 'grid',
572
605
  columns: sv.columns,
@@ -582,6 +615,11 @@ export function ObjectView({ dataSource, objects, onEdit, externalRefreshKey })
582
615
  ...sv,
583
616
  id,
584
617
  };
618
+ const normalized = {};
619
+ for (const [k, v] of Object.entries(rawNormalized)) {
620
+ if (v !== undefined)
621
+ normalized[k] = v;
622
+ }
585
623
  if (metaIds.has(id)) {
586
624
  const idx = viewList.findIndex(v => v.id === id);
587
625
  viewList[idx] = { ...viewList[idx], ...normalized };
@@ -627,17 +665,24 @@ export function ObjectView({ dataSource, objects, onEdit, externalRefreshKey })
627
665
  return 1;
628
666
  const aSaved = savedViews.find((sv) => (sv.id || sv._id) === a.id);
629
667
  const bSaved = savedViews.find((sv) => (sv.id || sv._id) === b.id);
630
- if (!aSaved && !bSaved)
631
- return (indexOf.get(a.id) ?? 0) - (indexOf.get(b.id) ?? 0);
632
- if (!aSaved)
668
+ const aHasOrder = aSaved && typeof aSaved.sortOrder === 'number';
669
+ const bHasOrder = bSaved && typeof bSaved.sortOrder === 'number';
670
+ // Only an explicit user `sortOrder` should reorder views away from
671
+ // the metadata-declared sequence. A bare overlay row (no sortOrder)
672
+ // must not demote a metadata view: that would break primary-view
673
+ // promotion (which relies on declared order) for objects whose
674
+ // overlay seeded a baseline `sys_view` row without sort info.
675
+ if (aHasOrder && bHasOrder) {
676
+ if (aSaved.sortOrder !== bSaved.sortOrder) {
677
+ return aSaved.sortOrder - bSaved.sortOrder;
678
+ }
679
+ return (aSaved.created_at || '').localeCompare(bSaved.created_at || '');
680
+ }
681
+ if (aHasOrder)
633
682
  return -1;
634
- if (!bSaved)
683
+ if (bHasOrder)
635
684
  return 1;
636
- const ao = typeof aSaved.sortOrder === 'number' ? aSaved.sortOrder : Number.MAX_SAFE_INTEGER;
637
- const bo = typeof bSaved.sortOrder === 'number' ? bSaved.sortOrder : Number.MAX_SAFE_INTEGER;
638
- if (ao !== bo)
639
- return ao - bo;
640
- return (aSaved.created_at || '').localeCompare(bSaved.created_at || '');
685
+ return (indexOf.get(a.id) ?? 0) - (indexOf.get(b.id) ?? 0);
641
686
  });
642
687
  return viewList;
643
688
  }, [objectDef, savedViews, viewOverrides, t]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/app-shell",
3
- "version": "5.3.2",
3
+ "version": "5.4.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
@@ -28,35 +28,35 @@
28
28
  "@sentry/react": "^8.55.2",
29
29
  "lucide-react": "^1.16.0",
30
30
  "sonner": "^2.0.7",
31
- "@object-ui/auth": "5.3.2",
32
- "@object-ui/collaboration": "5.3.2",
33
- "@object-ui/components": "5.3.2",
34
- "@object-ui/core": "5.3.2",
35
- "@object-ui/data-objectstack": "5.3.2",
36
- "@object-ui/fields": "5.3.2",
37
- "@object-ui/i18n": "5.3.2",
38
- "@object-ui/layout": "5.3.2",
39
- "@object-ui/permissions": "5.3.2",
40
- "@object-ui/providers": "5.3.2",
41
- "@object-ui/react": "5.3.2",
42
- "@object-ui/types": "5.3.2"
31
+ "@object-ui/auth": "5.4.0",
32
+ "@object-ui/collaboration": "5.4.0",
33
+ "@object-ui/components": "5.4.0",
34
+ "@object-ui/core": "5.4.0",
35
+ "@object-ui/data-objectstack": "5.4.0",
36
+ "@object-ui/fields": "5.4.0",
37
+ "@object-ui/i18n": "5.4.0",
38
+ "@object-ui/layout": "5.4.0",
39
+ "@object-ui/permissions": "5.4.0",
40
+ "@object-ui/providers": "5.4.0",
41
+ "@object-ui/react": "5.4.0",
42
+ "@object-ui/types": "5.4.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "react": "^18.0.0 || ^19.0.0",
46
46
  "react-dom": "^18.0.0 || ^19.0.0",
47
47
  "react-router-dom": "^6.0.0 || ^7.0.0",
48
- "@object-ui/plugin-calendar": "^5.3.2",
49
- "@object-ui/plugin-charts": "^5.3.2",
50
- "@object-ui/plugin-chatbot": "^5.3.2",
51
- "@object-ui/plugin-dashboard": "^5.3.2",
52
- "@object-ui/plugin-designer": "^5.3.2",
53
- "@object-ui/plugin-detail": "^5.3.2",
54
- "@object-ui/plugin-form": "^5.3.2",
55
- "@object-ui/plugin-grid": "^5.3.2",
56
- "@object-ui/plugin-kanban": "^5.3.2",
57
- "@object-ui/plugin-list": "^5.3.2",
58
- "@object-ui/plugin-report": "^5.3.2",
59
- "@object-ui/plugin-view": "^5.3.2"
48
+ "@object-ui/plugin-calendar": "^5.4.0",
49
+ "@object-ui/plugin-charts": "^5.4.0",
50
+ "@object-ui/plugin-chatbot": "^5.4.0",
51
+ "@object-ui/plugin-dashboard": "^5.4.0",
52
+ "@object-ui/plugin-designer": "^5.4.0",
53
+ "@object-ui/plugin-detail": "^5.4.0",
54
+ "@object-ui/plugin-form": "^5.4.0",
55
+ "@object-ui/plugin-grid": "^5.4.0",
56
+ "@object-ui/plugin-kanban": "^5.4.0",
57
+ "@object-ui/plugin-list": "^5.4.0",
58
+ "@object-ui/plugin-report": "^5.4.0",
59
+ "@object-ui/plugin-view": "^5.4.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/node": "^25.9.0",