From 9888e055ff6fa3c2ccc1ef64758299fb3f025ca0 Mon Sep 17 00:00:00 2001 From: "Vineyard, Andrew" Date: Fri, 13 Mar 2026 16:44:28 -0500 Subject: [PATCH 1/2] fix(application): improve evidence review behavior MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - render expected tools without tool calls - restore null selection behavior in references pane - stop ignoring root public assets 🛠️ - Generated by Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- frontend/src/components/app/TracePanel.tsx | 341 +++++++++++------- .../app/pages/ReferencesSection.tsx | 17 +- .../app/pages/ReferencesSection.test.tsx | 23 +- 3 files changed, 232 insertions(+), 149 deletions(-) diff --git a/frontend/src/components/app/TracePanel.tsx b/frontend/src/components/app/TracePanel.tsx index ae2235c..763b6f1 100644 --- a/frontend/src/components/app/TracePanel.tsx +++ b/frontend/src/components/app/TracePanel.tsx @@ -7,6 +7,7 @@ import type { PluginPayload, Reference, ToolCallRecord, + ToolExpectation, } from "../../models/groundTruth"; import { getItemReferences, hasEvidenceData } from "../../models/groundTruth"; import { cn } from "../../models/utils"; @@ -274,6 +275,73 @@ function FeedbackViewer({ data }: ViewerProps) { ); } +const EXPECTED_TOOL_GROUPS = [ + { key: "required", label: "Required" }, + { key: "optional", label: "Optional" }, + { key: "notNeeded", label: "Not Needed" }, +] as const; + +function formatToolExpectationArguments( + argumentsValue: ToolExpectation["arguments"], +): string | null { + if (argumentsValue == null) return null; + if (typeof argumentsValue === "string") return argumentsValue; + return JSON.stringify(argumentsValue, null, 2); +} + +function ExpectedToolsSection({ + expectedTools, +}: { + expectedTools: ExpectedTools; +}) { + const groups = EXPECTED_TOOL_GROUPS.map(({ key, label }) => ({ + key, + label, + tools: expectedTools[key] ?? [], + })).filter((group) => group.tools.length > 0); + + if (!groups.length) return null; + + return ( +
+
+ Expected Tools +
+
+ {groups.map((group) => ( +
+
+ {group.label} +
+
+ {group.tools.map((tool) => { + const formattedArguments = formatToolExpectationArguments( + tool.arguments, + ); + return ( +
+
+ {tool.name} +
+ {formattedArguments && ( +
+												{formattedArguments}
+											
+ )} +
+ ); + })} +
+
+ ))} +
+
+ ); +} + function PluginPayloadViewer({ data }: ViewerProps) { const { slot, payload } = data as PluginRenderData; const hasData = Object.keys(payload.data ?? {}).length > 0; @@ -354,7 +422,7 @@ export default function TracePanel({ onUpdateReference, onRemoveReference, }: { - item: GroundTruthItem; + item?: GroundTruthItem | null; className?: string; onUpdateContextEntries?: (entries: ContextEntry[]) => void; onUpdateExpectedTools?: (tools: ExpectedTools) => void; @@ -364,27 +432,14 @@ export default function TracePanel({ onRemoveReference?: (refId: string) => void; }) { const [expanded, setExpanded] = useState(true); - - if (!hasEvidenceData(item)) { - return ( -
- No trace or evidence data available for this item. -
- ); - } - - const toolCalls = item.toolCalls ?? []; - const references = getItemReferences(item); - const contextEntries = item.contextEntries ?? []; - const metadata = item.metadata ?? {}; - const plugins = item.plugins ?? {}; - const feedback = item.feedback ?? []; - const tracePayload = item.tracePayload ?? {}; + const hasEvidence = !!item && hasEvidenceData(item); + const toolCalls = item?.toolCalls ?? []; + const references = item ? getItemReferences(item) : []; + const contextEntries = item?.contextEntries ?? []; + const metadata = item?.metadata ?? {}; + const plugins = item?.plugins ?? {}; + const feedback = item?.feedback ?? []; + const tracePayload = item?.tracePayload ?? {}; const sentiment = deriveSentiment(feedback); const hasMoreDetails = contextEntries.length > 0 || @@ -426,136 +481,152 @@ export default function TracePanel({ {expanded && (
-
- -
- - - - {toolCalls.length > 0 && ( + {hasEvidence && item ? ( <> -
- Tool Calls ({toolCalls.length}) +
+
- {toolCalls.map((tc, i) => ( - { - if (!onUpdateExpectedTools) return; - onUpdateExpectedTools( - (next as ToolCallRenderData).expectedTools ?? { - required: [], - }, - ); - }} - /> - ))} - - )} - {hasMoreDetails && ( - -
- {(contextEntries.length > 0 || onUpdateContextEntries) && ( - - - onUpdateContextEntries?.(next as ContextEntry[]) - } - /> - - )} + + + {item.expectedTools && ( + + )} - {Object.keys(metadata).length > 0 && ( - + {toolCalls.length > 0 && ( + <> +
+ Tool Calls ({toolCalls.length}) +
+ {toolCalls.map((tc, i) => ( { + if (!onUpdateExpectedTools) return; + onUpdateExpectedTools( + (next as ToolCallRenderData).expectedTools ?? { + required: [], + }, + ); }} - mode="viewer" /> -
- )} + ))} + + )} - {Object.entries(plugins).length > 0 && ( - -
- {Object.entries(plugins).map(([slot, payload]) => ( + {hasMoreDetails && ( + +
+ {(contextEntries.length > 0 || onUpdateContextEntries) && ( + + + onUpdateContextEntries?.(next as ContextEntry[]) + } + /> + + )} + + {Object.keys(metadata).length > 0 && ( + - ))} -
-
- )} - - {Object.keys(tracePayload).length > 0 && ( - - - - )} -
-
+ + )} + + {Object.entries(plugins).length > 0 && ( + +
+ {Object.entries(plugins).map(([slot, payload]) => ( + + ))} +
+
+ )} + + {Object.keys(tracePayload).length > 0 && ( + + + + )} +
+
+ )} + + ) : ( +
+ No trace or evidence data available yet. +
)}
)} diff --git a/frontend/src/components/app/pages/ReferencesSection.tsx b/frontend/src/components/app/pages/ReferencesSection.tsx index 31eaf4f..f7c11f4 100644 --- a/frontend/src/components/app/pages/ReferencesSection.tsx +++ b/frontend/src/components/app/pages/ReferencesSection.tsx @@ -4,9 +4,9 @@ * Phase 4 redesign: this component is now a generic right-pane host rather * than a purely retrieval-specific panel. It renders: * - * 1. Evidence & Trace panel (TracePanel) — always shown when the current item - * has generic agentic data (toolCalls, traceIds, metadata, feedback, - * expectedTools). This is the primary Phase 4 evidence surface. + * 1. Evidence & Trace panel (TracePanel) — shown whenever the current item + * slot contains a real selected item. This keeps the newer agentic review + * surface as the default experience once selection/loading is resolved. * * 2. RAG compatibility panel (ReferencesTabs) — shown as an opt-in section * when the item has references OR when in single-turn mode. This surface @@ -23,7 +23,6 @@ import type { GroundTruthItem, Reference, } from "../../../models/groundTruth"; -import { hasEvidenceData } from "../../../models/groundTruth"; import { cn, urlToTitle } from "../../../models/utils"; import ReferencesTabs from "../../app/ReferencesPanel/ReferencesTabs"; import TracePanel from "../../app/TracePanel"; @@ -69,8 +68,10 @@ export default function ReferencesSection({ // Multi-turn items manage references per-turn via the conversation editor. const showRagCompat = !isMultiTurn; - // Evidence panel: show TracePanel when item has generic agentic data. - const showEvidence = !!item && hasEvidenceData(item); + // Render the evidence surface only for real selected items. + // `null` represents no current selection / loading; `undefined` still means + // the caller omitted the item prop entirely for backward compatibility. + const showEvidence = item !== undefined && item !== null; async function runSearch() { try { @@ -132,8 +133,8 @@ export default function ReferencesSection({ : "rounded-2xl border bg-white shadow-sm h-[calc(100vh-5.5rem)]", )} > - {/* Evidence & Trace panel (generic agentic data) */} - {showEvidence && item && ( + {/* Evidence & Trace panel (default agentic surface) */} + {showEvidence && (
{ expect(screen.getByText(/Evidence & Review/i)).toBeInTheDocument(); }); - it("shows RAG compat panel when in single-turn mode", () => { + it("does not show the evidence pane when item is null in single-turn mode", () => { render( , ); + expect(screen.queryByText(/Evidence & Review/i)).not.toBeInTheDocument(); + expect( + screen.queryByText(/No trace or evidence data available yet/i), + ).not.toBeInTheDocument(); // Search tab should be visible (RAG compat surface) const searchBtns = screen.getAllByRole("button", { name: /Search/i }); expect(searchBtns.length).toBeGreaterThan(0); }); - it("shows empty state when multi-turn mode and no evidence or references", () => { + it("shows the trace empty state when multi-turn mode has no evidence yet", () => { const item = makeItem(); // no toolCalls, no traceIds, etc. render(); - // No references, no evidence data → empty state + expect(screen.getByText(/Evidence & Review/i)).toBeInTheDocument(); expect( - screen.getByText(/No evidence or references available/i), + screen.getByText(/No trace or evidence data available yet/i), ).toBeInTheDocument(); }); - it("shows TracePanel header when item has expectedTools", () => { + it("shows expected-tool details when item has expectedTools without toolCalls", () => { const item = makeItem({ expectedTools: { - required: [{ name: "search" }], + required: [{ name: "search", arguments: { query: "refund policy" } }], + optional: [{ name: "fetch" }], }, toolCalls: [], }); render(); expect(screen.getByText(/Evidence & Review/i)).toBeInTheDocument(); + expect(screen.getByText(/Expected Tools/i)).toBeInTheDocument(); + expect(screen.getByText(/^Required$/i)).toBeInTheDocument(); + expect(screen.getByText(/^Optional$/i)).toBeInTheDocument(); + expect(screen.getByText(/^search$/i)).toBeInTheDocument(); + expect(screen.getByText(/refund policy/i)).toBeInTheDocument(); + expect(screen.getByText(/^fetch$/i)).toBeInTheDocument(); }); it("shows generic evidence for context-only items", () => { From 5a6de28e3c10f19dc513e96b25a3db4c588dd55c Mon Sep 17 00:00:00 2001 From: "Vineyard, Andrew" Date: Mon, 16 Mar 2026 13:04:23 -0500 Subject: [PATCH 2/2] fix(frontend): show evidence pane as default in null-selection state When no item is selected, the right pane now renders the TracePanel empty state instead of the legacy Search/Selected RAG tabs. The RAG compat surface still appears for single-turn items that are actually selected, and the legacy prop-omitted path remains unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../components/app/pages/ReferencesSection.tsx | 17 ++++++++++------- .../app/pages/ReferencesSection.test.tsx | 15 ++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/app/pages/ReferencesSection.tsx b/frontend/src/components/app/pages/ReferencesSection.tsx index f7c11f4..ac4d3ce 100644 --- a/frontend/src/components/app/pages/ReferencesSection.tsx +++ b/frontend/src/components/app/pages/ReferencesSection.tsx @@ -64,14 +64,17 @@ export default function ReferencesSection({ const [searchSelected, setSearchSelected] = useState>(new Set()); const searchInputRef = useRef(null); - // RAG compat surface: only show ReferencesTabs in single-turn mode. - // Multi-turn items manage references per-turn via the conversation editor. - const showRagCompat = !isMultiTurn; + // RAG compat surface: show ReferencesTabs when the item prop was omitted + // entirely (legacy RAG-only callers) OR when a real single-turn item is + // selected. Hide it when the selection is explicitly null (no item chosen) + // or the item is multi-turn (references are managed per-turn). + const showRagCompat = item === undefined || (!!item && !isMultiTurn); - // Render the evidence surface only for real selected items. - // `null` represents no current selection / loading; `undefined` still means - // the caller omitted the item prop entirely for backward compatibility. - const showEvidence = item !== undefined && item !== null; + // The evidence surface is the default right-pane experience whenever the + // caller passes the item prop (even as null for no-selection). It renders + // the full trace/tool-call view for items with data and an empty-state + // placeholder otherwise. + const showEvidence = item !== undefined; async function runSearch() { try { diff --git a/frontend/tests/unit/components/app/pages/ReferencesSection.test.tsx b/frontend/tests/unit/components/app/pages/ReferencesSection.test.tsx index abab72d..aa011c4 100644 --- a/frontend/tests/unit/components/app/pages/ReferencesSection.test.tsx +++ b/frontend/tests/unit/components/app/pages/ReferencesSection.test.tsx @@ -113,17 +113,18 @@ describe("ReferencesSection – generic right pane (Phase 4)", () => { expect(screen.getByText(/Evidence & Review/i)).toBeInTheDocument(); }); - it("does not show the evidence pane when item is null in single-turn mode", () => { + it("shows evidence empty state when item is null in single-turn mode", () => { render( , ); - expect(screen.queryByText(/Evidence & Review/i)).not.toBeInTheDocument(); + // Evidence panel should render with its empty state + expect(screen.getByText(/Evidence & Review/i)).toBeInTheDocument(); expect( - screen.queryByText(/No trace or evidence data available yet/i), - ).not.toBeInTheDocument(); - // Search tab should be visible (RAG compat surface) - const searchBtns = screen.getAllByRole("button", { name: /Search/i }); - expect(searchBtns.length).toBeGreaterThan(0); + screen.getByText(/No trace or evidence data available yet/i), + ).toBeInTheDocument(); + // Search tabs should NOT show when selection is explicitly null + const searchBtns = screen.queryAllByRole("button", { name: /^Search$/i }); + expect(searchBtns).toHaveLength(0); }); it("shows the trace empty state when multi-turn mode has no evidence yet", () => {