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..ac4d3ce 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";
@@ -65,12 +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);
- // Evidence panel: show TracePanel when item has generic agentic data.
- const showEvidence = !!item && hasEvidenceData(item);
+ // 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 {
@@ -132,8 +136,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("shows evidence empty state when item is null in single-turn mode", () => {
render(
,
);
- // Search tab should be visible (RAG compat surface)
- const searchBtns = screen.getAllByRole("button", { name: /Search/i });
- expect(searchBtns.length).toBeGreaterThan(0);
+ // Evidence panel should render with its empty state
+ expect(screen.getByText(/Evidence & Review/i)).toBeInTheDocument();
+ expect(
+ 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 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", () => {