diff --git a/app/(dashboard)/bookkeeping/[id]/page.tsx b/app/(dashboard)/bookkeeping/[id]/page.tsx index 2effb33d..119cb918 100644 --- a/app/(dashboard)/bookkeeping/[id]/page.tsx +++ b/app/(dashboard)/bookkeeping/[id]/page.tsx @@ -8,7 +8,7 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' import { Button } from '@/components/ui/button' import { AccountNumber } from '@/components/ui/account-number' import { Textarea } from '@/components/ui/textarea' -import { Loader2, ArrowLeft, Paperclip, AlertTriangle, Lock, MessageSquare, Pencil, Check, X, Copy, ChevronDown, CalendarClock, FileText, Link2, RotateCcw, Scissors, PenLine } from 'lucide-react' +import { Loader2, ArrowLeft, Paperclip, AlertTriangle, Lock, MessageSquare, Pencil, Check, X, Copy, ChevronDown, CalendarClock, FileText, Link2, RotateCcw, Scissors, PenLine, Bot } from 'lucide-react' import { DropdownMenu, DropdownMenuTrigger, @@ -65,6 +65,34 @@ type RattelseLogRow = { created_at: string } +/** + * Human "who committed this" line from the committed_actor_* snapshot + * (WHO relayed the commit; commit_method records HOW). The claude.ai MCP + * connector mints a gnubok_sk_ API key, so MCP traffic arrives as + * actor_type='api_key' with the key name as the label. + */ +function committedByLabel( + actorType: string, + actorLabel: string | null, + t: (key: string, values?: Record) => string, +): string { + switch (actorType) { + case 'user': + return actorLabel || t('committed_by_user') + case 'api_key': + case 'mcp_oauth': + return actorLabel ? t('committed_by_ai', { label: actorLabel }) : t('committed_by_ai_plain') + case 'agent_chat': + return t('committed_by_agent_chat') + case 'cron': + return t('committed_by_cron') + case 'system': + return t('committed_by_system') + default: + return actorType + } +} + export default function JournalEntryDetailPage({ params }: { params: Promise<{ id: string }> }) { const { id } = use(params) const router = useRouter() @@ -580,6 +608,15 @@ export default function JournalEntryDetailPage({ params }: { params: Promise<{ i {formatDate(entry.committed_at)} )} + {entry.committed_at && entry.committed_actor_type && ( +
+ {t('field_committed_by')} + + {entry.committed_actor_type !== 'user' && } + {committedByLabel(entry.committed_actor_type, entry.committed_actor_label, t)} + +
+ )}
{t('field_type')} {sourceTypeLabels[entry.source_type] || entry.source_type} diff --git a/app/(dashboard)/pending/page.tsx b/app/(dashboard)/pending/page.tsx index d37a4a64..8364706e 100644 --- a/app/(dashboard)/pending/page.tsx +++ b/app/(dashboard)/pending/page.tsx @@ -887,6 +887,14 @@ export default function PendingOperationsPage() { detailOpId === op.id ? 'bg-secondary/25' : 'hover:bg-secondary/35', )} > + {/* Same actor mark as the pending rows, minus the curved + op-thread (it points at an action row history lacks). */} + + {isAgent ? : } + staged: StagedOperationResult } diff --git a/messages/en.json b/messages/en.json index 1dd398ca..56385eb2 100644 --- a/messages/en.json +++ b/messages/en.json @@ -4707,6 +4707,13 @@ "details_title": "Journal entry details", "field_date": "Date", "field_posted_at": "Posted", + "field_committed_by": "Posted by", + "committed_by_user": "User", + "committed_by_ai": "AI assistant · {label}", + "committed_by_ai_plain": "AI assistant", + "committed_by_agent_chat": "The AI assistant (chat)", + "committed_by_cron": "Automated job", + "committed_by_system": "System", "field_type": "Type", "field_source_voucher": "Original voucher", "field_note": "Note", diff --git a/messages/sv.json b/messages/sv.json index 3c397d5a..26b32c7e 100644 --- a/messages/sv.json +++ b/messages/sv.json @@ -4707,6 +4707,13 @@ "details_title": "Verifikationsdetaljer", "field_date": "Datum", "field_posted_at": "Bokförd", + "field_committed_by": "Bokförd av", + "committed_by_user": "Användare", + "committed_by_ai": "AI-assistent · {label}", + "committed_by_ai_plain": "AI-assistent", + "committed_by_agent_chat": "AI-assistenten (chatt)", + "committed_by_cron": "Automatiskt jobb", + "committed_by_system": "System", "field_type": "Typ", "field_source_voucher": "Ursprungligt verifikat", "field_note": "Anteckning", diff --git a/tests/helpers.ts b/tests/helpers.ts index 43af5163..4eda84a3 100644 --- a/tests/helpers.ts +++ b/tests/helpers.ts @@ -258,6 +258,8 @@ export function makeJournalEntry(overrides: Partial = {}): Journal attachment_urls: null, notes: null, commit_method: null, + committed_actor_type: null, + committed_actor_label: null, rubric_version: null, created_at: '2024-06-15T14:30:00Z', updated_at: '2024-06-15T14:30:00Z', diff --git a/types/index.ts b/types/index.ts index a392c87a..2dcff277 100644 --- a/types/index.ts +++ b/types/index.ts @@ -1789,6 +1789,13 @@ export interface JournalEntry { attachment_urls: string[] | null notes: string | null commit_method: string | null + // WHO relayed the commit; complements commit_method = HOW. Stamped at + // commit time since migration 20260619120000. actor_type is NULL or one of + // 'user' | 'api_key' | 'mcp_oauth' | 'cron' | 'system' | 'agent_chat' + // (the DB CHECK is the authority); actor_label is a credential snapshot + // (e.g. the API key name). + committed_actor_type: string | null + committed_actor_label: string | null rubric_version: string | null source_voucher_series: string | null source_voucher_number: number | null