@object-ui/app-shell 4.4.0 → 4.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # @object-ui/app-shell — Changelog
2
2
 
3
+ ## 4.5.0
4
+
5
+ ### Patch Changes
6
+
7
+ - d714e85: Lookup display-name resolution now falls back through a Salesforce-style chain
8
+ when an `$expand`'d reference object lacks a top-level `name`/`label`/
9
+ `display_name`/`title` field:
10
+ 1. Standard display fields (existing behaviour)
11
+ 2. `salutation first_name last_name` composite — handles person records that
12
+ only carry first/last name parts
13
+ 3. `email` — last-resort identifier, beats the opaque id
14
+
15
+ Applies to `LookupCellRenderer`, `PageHeader.subtitle` interpolation,
16
+ `DetailView` page-mode `titleFormat`, and the shared `formatRecordTitle`
17
+ utility. Concretely: a Contact reference with `first_name: Bob`, `last_name:
18
+ Lin` and no `name` field now renders as `Bob Lin` everywhere — instead of
19
+ the email or [object Object] fallback.
20
+
21
+ - Updated dependencies [ab5e281]
22
+ - Updated dependencies [d714e85]
23
+ - Updated dependencies [6b6afd1]
24
+ - Updated dependencies [22fa558]
25
+ - Updated dependencies [aa7855f]
26
+ - Updated dependencies [170d89f]
27
+ - @object-ui/types@4.5.0
28
+ - @object-ui/fields@4.5.0
29
+ - @object-ui/layout@4.5.0
30
+ - @object-ui/components@4.5.0
31
+ - @object-ui/i18n@4.5.0
32
+ - @object-ui/auth@4.5.0
33
+ - @object-ui/collaboration@4.5.0
34
+ - @object-ui/core@4.5.0
35
+ - @object-ui/data-objectstack@4.5.0
36
+ - @object-ui/permissions@4.5.0
37
+ - @object-ui/react@4.5.0
38
+
3
39
  ## 4.4.0
4
40
 
5
41
  ### Patch Changes
@@ -69,14 +69,24 @@ export function formatRecordTitle(titleFormat, record) {
69
69
  break;
70
70
  value = value[p];
71
71
  }
72
- // Auto-extract display name from expanded reference objects.
72
+ // Auto-extract display name from expanded reference objects, with a
73
+ // Salesforce-style fallback chain.
73
74
  if (value && typeof value === 'object') {
74
- value =
75
- value.name ??
76
- value.label ??
77
- value.display_name ??
78
- value.title ??
79
- null;
75
+ const o = value;
76
+ let display = o.name ?? o.full_name ?? o.display_name ?? o.label ?? o.title ?? o.subject ?? null;
77
+ if (display == null || (typeof display === 'string' && !display.trim())) {
78
+ const composite = [o.salutation, o.first_name, o.last_name]
79
+ .filter((p) => typeof p === 'string' && p.trim())
80
+ .map((p) => p.trim())
81
+ .join(' ');
82
+ if (composite)
83
+ display = composite;
84
+ else if (typeof o.email === 'string' && o.email.trim())
85
+ display = o.email.trim();
86
+ else
87
+ display = null;
88
+ }
89
+ value = display;
80
90
  }
81
91
  if (value === null || value === undefined || value === '') {
82
92
  return EMPTY_TOKEN;
@@ -358,18 +358,84 @@ export function RecordDetailView({ dataSource, objects, onEdit }) {
358
358
  }
359
359
  let cancelled = false;
360
360
  setHistoryLoading(true);
361
+ // Pull `old_value` and `new_value` so we can render a per-field diff
362
+ // ("Industry: finance → healthcare") instead of just the action verb.
363
+ // The backend already authorises sys_audit_log row access; column-level
364
+ // redaction would need to happen server-side, not by omitting columns here.
361
365
  dataSource
362
366
  .find('sys_audit_log', {
363
367
  $filter: { record_id: pureRecordId, object_name: objectDef.name },
364
368
  $orderby: { created_at: 'desc' },
365
369
  $top: 50,
366
- $select: ['id', 'created_at', 'action', 'user_id'],
370
+ $select: ['id', 'created_at', 'action', 'user_id', 'old_value', 'new_value'],
367
371
  })
368
- .then((res) => {
372
+ .then(async (res) => {
369
373
  if (cancelled)
370
374
  return;
371
375
  const items = Array.isArray(res) ? res : res?.data || [];
372
- setHistoryEntries(items);
376
+ // 1) Resolve actor display names + avatars in a single batched call
377
+ // so the timeline never falls back to a raw UUID.
378
+ const userIds = Array.from(new Set(items
379
+ .map((it) => it?.user_id)
380
+ .filter((v) => typeof v === 'string' && v.length > 0)));
381
+ let userMap = new Map();
382
+ if (userIds.length > 0) {
383
+ try {
384
+ const usersRes = await dataSource.find('sys_user', {
385
+ $filter: { id: { $in: userIds } },
386
+ $top: userIds.length,
387
+ $select: ['id', 'name', 'email', 'image'],
388
+ });
389
+ const users = Array.isArray(usersRes) ? usersRes : usersRes?.data || [];
390
+ userMap = new Map(users.map((u) => [u.id, { name: u.name || u.email || null, image: u.image || null }]));
391
+ }
392
+ catch (err) {
393
+ console.warn('[RecordDetailView] Failed to resolve audit user names:', err);
394
+ }
395
+ }
396
+ // 2) Build a label map for the current object so diff lines show
397
+ // "Industry: …" rather than the snake_case "industry: …".
398
+ const fieldLabels = {};
399
+ for (const [name, def] of Object.entries(objectDef.fields || {})) {
400
+ if (def?.label)
401
+ fieldLabels[name] = def.label;
402
+ }
403
+ const parseJson = (v) => {
404
+ if (!v)
405
+ return null;
406
+ if (typeof v === 'object')
407
+ return v;
408
+ if (typeof v === 'string') {
409
+ try {
410
+ return JSON.parse(v);
411
+ }
412
+ catch {
413
+ return null;
414
+ }
415
+ }
416
+ return null;
417
+ };
418
+ const enriched = items.map((it) => {
419
+ const u = it?.user_id ? userMap.get(it.user_id) : undefined;
420
+ const oldObj = parseJson(it?.old_value) || {};
421
+ const newObj = parseJson(it?.new_value) || {};
422
+ const fields = new Set([...Object.keys(oldObj), ...Object.keys(newObj)]);
423
+ const changes = Array.from(fields)
424
+ .filter((f) => JSON.stringify(oldObj[f]) !== JSON.stringify(newObj[f]))
425
+ .map((f) => ({
426
+ field: f,
427
+ label: fieldLabels[f] || f,
428
+ from: oldObj[f],
429
+ to: newObj[f],
430
+ }));
431
+ return {
432
+ ...it,
433
+ user_name: u?.name ?? null,
434
+ user_avatar: u?.image ?? null,
435
+ changes: changes.length > 0 ? changes : undefined,
436
+ };
437
+ });
438
+ setHistoryEntries(enriched);
373
439
  })
374
440
  .catch((err) => {
375
441
  if (cancelled)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/app-shell",
3
- "version": "4.4.0",
3
+ "version": "4.5.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Minimal application shell for ObjectUI - framework-agnostic rendering engine",
@@ -27,34 +27,34 @@
27
27
  "dependencies": {
28
28
  "lucide-react": "^1.16.0",
29
29
  "sonner": "^2.0.7",
30
- "@object-ui/auth": "4.4.0",
31
- "@object-ui/collaboration": "4.4.0",
32
- "@object-ui/components": "4.4.0",
33
- "@object-ui/core": "4.4.0",
34
- "@object-ui/data-objectstack": "4.4.0",
35
- "@object-ui/fields": "4.4.0",
36
- "@object-ui/i18n": "4.4.0",
37
- "@object-ui/layout": "4.4.0",
38
- "@object-ui/permissions": "4.4.0",
39
- "@object-ui/react": "4.4.0",
40
- "@object-ui/types": "4.4.0"
30
+ "@object-ui/auth": "4.5.0",
31
+ "@object-ui/collaboration": "4.5.0",
32
+ "@object-ui/components": "4.5.0",
33
+ "@object-ui/core": "4.5.0",
34
+ "@object-ui/data-objectstack": "4.5.0",
35
+ "@object-ui/fields": "4.5.0",
36
+ "@object-ui/i18n": "4.5.0",
37
+ "@object-ui/layout": "4.5.0",
38
+ "@object-ui/permissions": "4.5.0",
39
+ "@object-ui/react": "4.5.0",
40
+ "@object-ui/types": "4.5.0"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "react": "^18.0.0 || ^19.0.0",
44
44
  "react-dom": "^18.0.0 || ^19.0.0",
45
45
  "react-router-dom": "^6.0.0 || ^7.0.0",
46
- "@object-ui/plugin-calendar": "^4.4.0",
47
- "@object-ui/plugin-charts": "^4.4.0",
48
- "@object-ui/plugin-chatbot": "^4.4.0",
49
- "@object-ui/plugin-dashboard": "^4.4.0",
50
- "@object-ui/plugin-designer": "^4.4.0",
51
- "@object-ui/plugin-detail": "^4.4.0",
52
- "@object-ui/plugin-form": "^4.4.0",
53
- "@object-ui/plugin-grid": "^4.4.0",
54
- "@object-ui/plugin-kanban": "^4.4.0",
55
- "@object-ui/plugin-list": "^4.4.0",
56
- "@object-ui/plugin-report": "^4.4.0",
57
- "@object-ui/plugin-view": "^4.4.0"
46
+ "@object-ui/plugin-calendar": "^4.5.0",
47
+ "@object-ui/plugin-charts": "^4.5.0",
48
+ "@object-ui/plugin-chatbot": "^4.5.0",
49
+ "@object-ui/plugin-dashboard": "^4.5.0",
50
+ "@object-ui/plugin-designer": "^4.5.0",
51
+ "@object-ui/plugin-detail": "^4.5.0",
52
+ "@object-ui/plugin-form": "^4.5.0",
53
+ "@object-ui/plugin-grid": "^4.5.0",
54
+ "@object-ui/plugin-kanban": "^4.5.0",
55
+ "@object-ui/plugin-list": "^4.5.0",
56
+ "@object-ui/plugin-report": "^4.5.0",
57
+ "@object-ui/plugin-view": "^4.5.0"
58
58
  },
59
59
  "devDependencies": {
60
60
  "@types/node": "^25.9.0",