refactor(pending): one pending-op-owned preview for chat, /pending and flow views (#1537)
* refactor(pending): one pending-op-owned preview for chat, /pending and flow views A staged pending_operation was rendered three separate ways: the /pending page's OperationPreview switch (8 specialized renderers keyed on operation_type), ApprovalCard's own PreviewBlock (near-duplicate renderers keyed on 4 hardcoded MCP tool names), and AgentChat's toolNameFor() hack that mapped stored operation_types onto 'gnubok_'-prefixed tool names on hydration. This is the weakest seam ahead of flow-run views (plan seam 8.3): every new operation type had to be taught to render in two places and silently degraded in the third. Now there is one owner: - components/pending-operations/OperationPreview.tsx: the /pending renderers moved verbatim, dispatched on operation_type, consumed by /pending, ApprovalCard and future flow-run views. - components/pending-operations/vocabulary.ts: operation labels, single-action warnings and the one canonical rejection-category list (ApprovalCard's copy was byte-identical and is deleted). - lib/pending-operations/tool-name.ts: the single translation point between bare operation_types and 'gnubok_' tool names, with tests. toolNameFor gotcha fixed on the way: ApprovalCard's old dispatch only recognized 4 tool names, so a hydrated card for any other operation type (attach_document_to_transaction, match_transaction_invoice, ...) silently fell back to a raw generic preview. Hydration now passes the stored operation_type straight through attachStagedOperations to the card, and live streamed cards derive it from the event's tool name, so every operation type keeps its specialized preview on resume. Per-surface chrome (list row on /pending vs inline chat card) is deliberately kept: only the preview + vocabulary were the duplicated seam. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * chore: drop a stray hunt_title copy rename that rode along 'Kvittojakten' -> 'Leta efter underlag' in messages/sv.json was uncommitted working-tree state from another session, swept into the extraction commit by git add breadth. It is a product-naming call with no en.json counterpart and does not belong in this refactor; preserved in this branch's first commit if it turns out to be wanted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(pending): carry params to chat previews; guard preview amounts CodeRabbit round on #1537, both real. (1) AttachDocumentPreview renders its DocumentViewButton from params.document_id, which neither chat path carried: the staged_operation stream event now includes the tool-use input (the same values the staging tool stored as pending_operations.params) and hydration selects the params column, so an attach-document card in chat shows its evidence button live and on resume. (2) InvoicePreview and CreateTransactionPreview cast amounts straight into formatCurrency; a payload without one rendered 'NaN kr'. They now share the same show-the-gap guard the legacy summary already had. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
Jakob Wennberg
parent
22eaab82e5
commit
ce6efdb3dc
@@ -1,5 +1,5 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import { attachStagedOperations, toolNameFor } from '../AgentChat'
|
||||
import { attachStagedOperations } from '../AgentChat'
|
||||
import type { StoredStagedOperation } from '@/types'
|
||||
|
||||
/**
|
||||
@@ -43,31 +43,35 @@ describe('attachStagedOperations', () => {
|
||||
expect(assistant!.staged![0]).toMatchObject({
|
||||
operation_id: 'op-1',
|
||||
risk_level: 'low',
|
||||
tool_name: 'gnubok_categorize_transaction',
|
||||
operation_type: 'categorize_transaction',
|
||||
message: 'Kontering: Circle K, 689 kr',
|
||||
})
|
||||
})
|
||||
|
||||
it('maps the stored operation_type onto the tool name the preview dispatches on', () => {
|
||||
// pending_operations stores the bare action name; the live card carries the
|
||||
// MCP tool name, and ApprovalCard's PreviewBlock keys off that. Getting this
|
||||
// wrong is invisible in a mock but drops every hydrated card to the flat
|
||||
// generic preview instead of the journal-line one. These four are the
|
||||
// operation types that have a specialized renderer.
|
||||
it('passes the stored operation_type through untranslated, for EVERY type', () => {
|
||||
// The predecessor mapped operation_type onto an MCP tool name here and
|
||||
// ApprovalCard's old preview dispatch only recognized 4 of them, so a
|
||||
// hydrated attach_document_to_transaction card (among others) silently
|
||||
// fell back to the raw generic preview. The stored bare name now drives
|
||||
// the dispatch directly; nothing may rewrite it on the way.
|
||||
for (const t of [
|
||||
'categorize_transaction',
|
||||
'create_invoice',
|
||||
'create_voucher',
|
||||
'correct_entry',
|
||||
'attach_document_to_transaction',
|
||||
'match_transaction_invoice',
|
||||
'create_customer',
|
||||
'create_transaction',
|
||||
]) {
|
||||
expect(toolNameFor(t)).toBe(`gnubok_${t}`)
|
||||
const out = attachStagedOperations(
|
||||
[{ role: 'assistant', text: 'svar' }],
|
||||
[op({ operation_type: t })],
|
||||
)
|
||||
expect(out[0]!.staged![0]!.operation_type).toBe(t)
|
||||
}
|
||||
})
|
||||
|
||||
it('leaves an already-prefixed operation type alone', () => {
|
||||
expect(toolNameFor('gnubok_create_voucher')).toBe('gnubok_create_voucher')
|
||||
})
|
||||
|
||||
it('keeps risk level for medium and high, and floors anything unknown to low', () => {
|
||||
const messages = [{ role: 'assistant' as const, text: 'svar' }]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user