feat(runner): make evidence a devtools tab

This commit is contained in:
Dotta committed 2026-08-20 11:34:11 -05:00
1 parent 827ade0135
commit 641dbd406c
3 files changed
+21 -7

No files matched your search

@@ -238,6 +238,7 @@ test.describe("Capability issue thread", () => {
).toBeVisible();
await expect(panel.getByText("Agent tool — always")).toBeVisible();
await expect(panel.getByText("Agent tool — granted")).toBeVisible();
});
test("tool exposure groups are deduplicated, foldable, and open catalog details", async ({ page }) => {
@@ -716,6 +717,16 @@ test.describe("Capability clean-room chat", () => {
});
await openCleanRoom(page);
await page.getByTestId("evidence-toggle").click();
const panel = page.getByTestId("evidence-panel");
const tabs = panel.getByRole("tab");
await expect(tabs.first()).toHaveText(/Evidence/);
await expect(tabs.nth(1)).toHaveText(/Timeline/);
await expect(tabs.first()).toHaveAttribute("aria-selected", "true");
await expect(panel.locator("#evidence-turn-selector")).toBeVisible();
await panel.getByRole("tab", { name: /Timeline/ }).click();
await expect(panel.locator("#evidence-turn-selector")).toHaveCount(0);
await panel.getByRole("tab", { name: /Evidence/ }).click();
await expect(panel.locator("#evidence-turn-selector")).toBeVisible();
await page.getByRole("tab", { name: /Documents/ }).click();
await expect(page.getByRole("heading", { name: "Runner plan" })).toBeVisible();
await expect(page.getByText("Inspectable from DevTools.")).toBeVisible();
@@ -3,7 +3,7 @@ import { useEffect, useMemo, useState } from "react";
import type { CapabilityDevtoolsSnapshot } from "../../../src/devtools";
import { Icon } from "./Icons";
type Tab = "timeline" | "state" | "diff" | "documents" | "protocol" | "runtime" | "authority";
export type CapabilityDevtoolsTab = "evidence" | "timeline" | "state" | "diff" | "documents" | "protocol" | "runtime" | "authority";
type Json = null | boolean | number | string | Json[] | { [key: string]: Json };
function JsonTree({ value, name = "root" }: { value: Json; name?: string }) {
@@ -55,7 +55,8 @@ function download(snapshot: CapabilityDevtoolsSnapshot) {
URL.revokeObjectURL(url);
}
const TABS: ReadonlyArray<{ id: Tab; glyph: string; label: string }> = [
const TABS: ReadonlyArray<{ id: CapabilityDevtoolsTab; glyph: string; label: string }> = [
{ id: "evidence", glyph: "◎", label: "Evidence" },
{ id: "timeline", glyph: "◫", label: "Timeline" },
{ id: "state", glyph: "{}", label: "State" },
{ id: "diff", glyph: "±", label: "Diff" },
@@ -85,8 +86,7 @@ function documentsOf(state: Json): Array<{ id: string; key: string; title: strin
});
}
export function DevtoolsInspector({ snapshot, onFork }: { snapshot: CapabilityDevtoolsSnapshot; onFork: (revision: number) => void }) {
const [tab, setTab] = useState<Tab>("timeline");
export function DevtoolsInspector({ snapshot, onFork, tab, onTabChange }: { snapshot: CapabilityDevtoolsSnapshot; onFork: (revision: number) => void; tab: CapabilityDevtoolsTab; onTabChange: (tab: CapabilityDevtoolsTab) => void }) {
const [query, setQuery] = useState("");
const latest = snapshot.revisions.at(-1)!;
const [revision, setRevision] = useState(latest.revision);
@@ -125,7 +125,7 @@ export function DevtoolsInspector({ snapshot, onFork }: { snapshot: CapabilityDe
</div>
<div className="pit-devtools-tabs" role="tablist" aria-label="Developer tools">
{TABS.map(({ id, glyph, label }) => (
<button key={id} type="button" role="tab" aria-selected={tab === id} className="pit-tab" onClick={() => setTab(id)}><span className="pit-tab-glyph" aria-hidden="true">{glyph}</span>{label}</button>
<button key={id} type="button" role="tab" aria-selected={tab === id} className="pit-tab" onClick={() => onTabChange(id)}><span className="pit-tab-glyph" aria-hidden="true">{glyph}</span>{label}</button>
))}
</div>
{tab === "timeline" ? (
@@ -9,7 +9,7 @@ import type {
CapabilityToolDisposition,
} from "../../../src/issue-thread/types";
import type { CapabilityDevtoolsSnapshot } from "../../../src/devtools";
import { DevtoolsInspector } from "./DevtoolsInspector";
import { DevtoolsInspector, type CapabilityDevtoolsTab } from "./DevtoolsInspector";
import { Icon } from "./Icons";
import { capabilitySemanticToolDescriptor } from "../../../src/semantic-tools/catalog";
import {
@@ -271,6 +271,7 @@ function Section({
}
export function EvidencePanel(props: EvidencePanelProps) {
const [devtoolsTab, setDevtoolsTab] = useState<CapabilityDevtoolsTab>("evidence");
const {
snapshot,
devtools,
@@ -331,11 +332,12 @@ export function EvidencePanel(props: EvidencePanelProps) {
{devtools === null ? (
<p className="pit-muted pit-devtools-loading">Loading company state…</p>
) : (
<DevtoolsInspector snapshot={devtools} onFork={onForkRevision} />
<DevtoolsInspector snapshot={devtools} onFork={onForkRevision} tab={devtoolsTab} onTabChange={setDevtoolsTab} />
)}
</>
) : null}
{devtoolsTab === "evidence" ? <div className="pit-evidence-browser">
<div className="pit-evidence-filter">
<label className="pit-card-meta" htmlFor="evidence-turn-selector">Evidence scope</label>
<select className="pit-select" id="evidence-turn-selector" value={selectedTurnId} onChange={(event) => onSelectTurn(event.target.value as string | "all")}>
@@ -548,6 +550,7 @@ export function EvidencePanel(props: EvidencePanelProps) {
</div>
))}
</Section>
</div> : null}
</aside>
);
}