diff --git a/.changeset/approvals-reassign-and-system-actor-display.md b/.changeset/approvals-reassign-and-system-actor-display.md new file mode 100644 index 0000000000..3ba1e22f97 --- /dev/null +++ b/.changeset/approvals-reassign-and-system-actor-display.md @@ -0,0 +1,21 @@ +--- +"@object-ui/console": patch +"@object-ui/app-shell": patch +"@object-ui/i18n": patch +--- + +fix(console,app-shell): readable reassign hand-off + "System" label for svc:* audit actors — objectstack#4365 / objectstack#4366 + +- **Approvals inbox** (`ApprovalsInboxPage`): a reassign timeline entry now + renders "from A to B" from the structured + `reassign_from`/`reassign_to` fields (and their server-resolved + `*_name` companions) that objectstack#4365 added to + `sys_approval_action`, instead of relying on the old default comment that + baked two raw user ids into user-facing text. Legacy rows without the + structured fields keep the comment fallback. New i18n key + `approvalsInbox.reassignFromTo` across all ten locales. +- **Record history** (`RecordDetailView`): an audit row attributed to a + service principal (`svc:*` on the `actor` column — e.g. a + `runAs:'system'` flow's `svc:flow:` label from objectstack#4366) now + renders the localized "System" label instead of the raw principal string; + the raw value stays on the entry for tooling. diff --git a/apps/console/src/pages/system/ApprovalsInboxPage.tsx b/apps/console/src/pages/system/ApprovalsInboxPage.tsx index 01dc8bf1f0..697de71063 100644 --- a/apps/console/src/pages/system/ApprovalsInboxPage.tsx +++ b/apps/console/src/pages/system/ApprovalsInboxPage.tsx @@ -1904,6 +1904,25 @@ export function ApprovalsInboxPage() { {formatRelative(a.created_at)} + {/* Structured reassign hand-off (framework#4365): render + "from A to B" from the resolved parties. Legacy rows + predate the fields and only carry the old default + comment — those keep the comment fallback below. */} + {(() => { + const ax = a as typeof a & { + reassign_from?: string; reassign_to?: string; + reassign_from_name?: string; reassign_to_name?: string; + }; + if (a.action !== 'reassign' || (!ax.reassign_from && !ax.reassign_to)) return null; + return ( +
+ {tr('reassignFromTo', 'from {{from}} to {{to}}', { + from: ax.reassign_from_name ?? formatIdentity(ax.reassign_from), + to: ax.reassign_to_name ?? formatIdentity(ax.reassign_to), + })} +
+ ); + })()} {a.comment && (
"{a.comment}"
)} diff --git a/packages/app-shell/src/views/RecordDetailView.tsx b/packages/app-shell/src/views/RecordDetailView.tsx index 37d0b49d50..8ce300734c 100644 --- a/packages/app-shell/src/views/RecordDetailView.tsx +++ b/packages/app-shell/src/views/RecordDetailView.tsx @@ -1225,12 +1225,18 @@ export function RecordDetailView({ dataSource, objects, onEdit, objectNameOverri const enriched = items.map((it, idx) => { const u = it?.user_id ? userMap.get(it.user_id) : undefined; // Attribution fallback chain: resolved user name → service/automation - // principal from the `actor` column (e.g. "svc:scheduler") → null + // principal from the `actor` column (e.g. "svc:flow:") → null // (the timeline then shows its localized unknown-user text). - const actorLabel = + // A `svc:*` principal is a machine, not a person — render the + // localized "System" label rather than the raw principal string + // (framework#4366); the raw value stays available on the entry. + const rawActor = typeof it?.actor === 'string' && it.actor.trim() && it.actor !== it?.user_id ? it.actor.trim() : null; + const actorLabel = rawActor?.startsWith('svc:') + ? t('detail.systemActor', { defaultValue: 'System' }) + : rawActor; const changes = rawChangesPerItem[idx].map((c) => ({ field: c.field, label: fieldLabels[c.field] || c.field, diff --git a/packages/i18n/src/locales/ar.ts b/packages/i18n/src/locales/ar.ts index 6f9e7cfddd..3deace13ef 100644 --- a/packages/i18n/src/locales/ar.ts +++ b/packages/i18n/src/locales/ar.ts @@ -2757,6 +2757,7 @@ const ar = { slaRemaining: 'SLA المتبقي {{dur}}', slaOverdue: 'SLA متأخر {{dur}}', actReassign: 'إعادة إسناد', + reassignFromTo: 'من {{from}} إلى {{to}}', actRemind: 'تذكير', actRequestInfo: 'طلب معلومات', actComment: 'تعليق', diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts index 0ded657a49..842f396998 100644 --- a/packages/i18n/src/locales/de.ts +++ b/packages/i18n/src/locales/de.ts @@ -2757,6 +2757,7 @@ const de = { slaRemaining: 'SLA noch {{dur}}', slaOverdue: 'SLA überfällig {{dur}}', actReassign: 'Übergeben', + reassignFromTo: 'von {{from}} an {{to}}', actRemind: 'Erinnert', actRequestInfo: 'Rückfrage gestellt', actComment: 'Kommentiert', diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts index e3126b99c8..9b978c3d0c 100644 --- a/packages/i18n/src/locales/en.ts +++ b/packages/i18n/src/locales/en.ts @@ -2861,6 +2861,7 @@ const en = { slaRemaining: 'SLA {{dur}} left', slaOverdue: 'SLA overdue {{dur}}', actReassign: 'Reassigned', + reassignFromTo: 'from {{from}} to {{to}}', actRemind: 'Reminder sent', actRequestInfo: 'Requested more info', actComment: 'Commented', diff --git a/packages/i18n/src/locales/es.ts b/packages/i18n/src/locales/es.ts index a108cad4d0..575be8a03f 100644 --- a/packages/i18n/src/locales/es.ts +++ b/packages/i18n/src/locales/es.ts @@ -2757,6 +2757,7 @@ const es = { slaRemaining: 'SLA: quedan {{dur}}', slaOverdue: 'SLA vencido {{dur}}', actReassign: 'Reasignada', + reassignFromTo: 'de {{from}} a {{to}}', actRemind: 'Recordatorio', actRequestInfo: 'Pidió más información', actComment: 'Comentó', diff --git a/packages/i18n/src/locales/fr.ts b/packages/i18n/src/locales/fr.ts index 8450348fc0..dded42060d 100644 --- a/packages/i18n/src/locales/fr.ts +++ b/packages/i18n/src/locales/fr.ts @@ -2757,6 +2757,7 @@ const fr = { slaRemaining: 'SLA : {{dur}} restants', slaOverdue: 'SLA dépassé de {{dur}}', actReassign: 'Réattribuée', + reassignFromTo: 'de {{from}} à {{to}}', actRemind: 'Relance', actRequestInfo: 'Infos demandées', actComment: 'Commentaire', diff --git a/packages/i18n/src/locales/ja.ts b/packages/i18n/src/locales/ja.ts index ddae4e46e8..ba09c3ce7e 100644 --- a/packages/i18n/src/locales/ja.ts +++ b/packages/i18n/src/locales/ja.ts @@ -2757,6 +2757,7 @@ const ja = { slaRemaining: 'SLA 残り {{dur}}', slaOverdue: 'SLA 超過 {{dur}}', actReassign: '引き継ぎ', + reassignFromTo: '{{from}} から {{to}} へ', actRemind: 'リマインド', actRequestInfo: '追加情報を要求', actComment: 'コメント', diff --git a/packages/i18n/src/locales/ko.ts b/packages/i18n/src/locales/ko.ts index ee3f7c4bac..4ab017215a 100644 --- a/packages/i18n/src/locales/ko.ts +++ b/packages/i18n/src/locales/ko.ts @@ -2757,6 +2757,7 @@ const ko = { slaRemaining: 'SLA {{dur}} 남음', slaOverdue: 'SLA {{dur}} 초과', actReassign: '재할당', + reassignFromTo: '{{from}} 님에서 {{to}} 님으로', actRemind: '독촉', actRequestInfo: '보완 요청', actComment: '댓글', diff --git a/packages/i18n/src/locales/pt.ts b/packages/i18n/src/locales/pt.ts index caebbbd623..acfe61b904 100644 --- a/packages/i18n/src/locales/pt.ts +++ b/packages/i18n/src/locales/pt.ts @@ -2757,6 +2757,7 @@ const pt = { slaRemaining: 'SLA: faltam {{dur}}', slaOverdue: 'SLA vencido há {{dur}}', actReassign: 'Reatribuída', + reassignFromTo: 'de {{from}} para {{to}}', actRemind: 'Lembrete', actRequestInfo: 'Pediu mais informações', actComment: 'Comentou', diff --git a/packages/i18n/src/locales/ru.ts b/packages/i18n/src/locales/ru.ts index d39370bb8a..8371224fb5 100644 --- a/packages/i18n/src/locales/ru.ts +++ b/packages/i18n/src/locales/ru.ts @@ -2757,6 +2757,7 @@ const ru = { slaRemaining: 'SLA: осталось {{dur}}', slaOverdue: 'SLA просрочен на {{dur}}', actReassign: 'Передано', + reassignFromTo: 'от {{from}} к {{to}}', actRemind: 'Напоминание', actRequestInfo: 'Запрошены данные', actComment: 'Комментарий', diff --git a/packages/i18n/src/locales/zh.ts b/packages/i18n/src/locales/zh.ts index 45df89a752..4c6642c6a5 100644 --- a/packages/i18n/src/locales/zh.ts +++ b/packages/i18n/src/locales/zh.ts @@ -2845,6 +2845,7 @@ const zh = { slaRemaining: 'SLA 剩余 {{dur}}', slaOverdue: 'SLA 已超期 {{dur}}', actReassign: '转签', + reassignFromTo: '从 {{from}} 转给 {{to}}', actRemind: '催办', actRequestInfo: '要求补充材料', actComment: '评论',