/* ============================================================ app.jsx — shell + landing, wired to the gradio.Server backend. Adapted from the designer's prototype: the demo's fake upload is replaced with a real file picker that calls /analyze_trace through @gradio/client; the tweaks panel is dropped and the theme is pinned to the dusk-survey dark mode. ============================================================ */ function BrandMark({ size = 34 }) { return ( ); } function TopBar() { return (
Trace Field Notes narrative analysis for coding-agent traces
narrative-only privacy-first
); } const ENGINES = [ ["qwen", "Quick analysis", "Qwen3.5 9B"], ["nemotron", "Deeper analysis", "Nemotron 3 Nano 30B-A3B"], ["deterministic", "Rule-based", "no model, always on"], ]; function Toggle({ on, set, label, sub, locked }) { return ( ); } function LandingView({ onAnalyze, onSample, error }) { const [staged, setStaged] = React.useState(null); // { name, file } const [redact, setRedact] = React.useState(true); const [userCtx, setUserCtx] = React.useState(true); const [engine, setEngine] = React.useState("qwen"); const [dragOver, setDragOver] = React.useState(false); const [copied, setCopied] = React.useState(false); const fileRef = React.useRef(null); const chosen = ENGINES.find((e) => e[0] === engine) || ENGINES[2]; const engineLabel = chosen[1] + ": " + chosen[2]; function onFiles(list) { const f = list && list[0]; if (f) setStaged({ name: f.name, file: f }); } function pick() { if (fileRef.current) fileRef.current.click(); } function run() { if (!staged) return; onAnalyze({ file: staged.file, include_user_context: userCtx, redact_secrets: redact, analysis_engine: engine, engineLabel }); } const AGENT_PROMPT = `Use this Space as a tool. 1. Read its /agents.md endpoint. 2. Find my latest local agent session log (Codex ~/.codex/sessions, Claude ~/.claude/projects). 3. Review and redact secrets before upload. 4. Upload the JSONL and request a narrative difficulty analysis. 5. Return the report. Do not publish the raw trace.`; return (
Field report · qualitative, not a leaderboard

See how your coding agent
got stuck, detoured, recovered & claimed success.

Upload a Codex, Claude Code, or Pi Agent session log. Trace Field Notes reads only the agent's narrated messages — what it planned, where it snagged, how it rerouted, and how honestly it called it done — and charts the session as a trail you can walk.

!

Agent traces can carry prompts, command output, local paths, screenshots, secrets, and private code. Review and redact before uploading or sharing. This app analyzes only visible narrative messages and ignores raw tool telemetry by default.

{error ? (
×

Analysis failed. {error}

) : null}
{/* LEFT: upload */}
onFiles(e.target.files)} />
{ e.preventDefault(); setDragOver(true); }} onDragLeave={() => setDragOver(false)} onDrop={(e) => { e.preventDefault(); setDragOver(false); onFiles(e.dataTransfer.files); }} onClick={pick} role="button" tabIndex={0} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") pick(); }} > {staged ? (
{staged.name} staged · click Analyze
) : (
Drop a .jsonl trace or click to choose · .json .txt .log accepted
)}
{}} locked label="Ignore tool contents" sub="locked for this release" />
{ENGINES.map(([key, name, detail]) => ( ))}

Quick and Deeper run a small model on the Space GPU. Rule-based needs no model and never fails.

{/* RIGHT: guide */}
{[ ["Codex", "~/.codex/sessions"], ["Claude Code", "~/.claude/projects"], ["Pi Agent", "~/.pi/agent/sessions"], ].map(([a, p]) => ( ))}
{a} {p}

Using Codex or Claude Code? Point it at this Space's agents.md. It finds your latest log, redacts it, uploads, and returns the report.

{AGENT_PROMPT}
{[ ["Elevation trail", "every snag as a waypoint"], ["Detour read", "exploration vs wandering"], ["Closeout audit", "honest, or overclaimed?"], ].map(([t, s]) => (
{t} {s}
))}
); } const PIPELINE = [ "Uploading the trace", "Extracting narrative messages", "Redacting likely secrets", "Charting difficulty episodes", "Classifying with the codebook", "Synthesizing field notes", ]; function Analyzing({ label }) { const [step, setStep] = React.useState(0); React.useEffect(() => { const id = setInterval(() => setStep((s) => (s + 1) % (PIPELINE.length + 1)), 700); return () => clearInterval(id); }, []); return (
Surveying the trace · {label}
); } function EmptyReport({ data, onReset }) { return (

The trace yielded {data.narrative_message_count} visible narrative messages, but none carried clear self-reported blockage, detour, or recovery language. That does not prove the session was trouble-free — only that the narrative did not say so. Try the redacted-narrative export to read it yourself.

); } function App() { const [stage, setStage] = React.useState("landing"); // landing | analyzing | report const [data, setData] = React.useState(null); const [engineLabel, setEngineLabel] = React.useState(""); const [error, setError] = React.useState(""); async function analyze({ file, include_user_context, redact_secrets, analysis_engine, engineLabel }) { setError(""); setEngineLabel(engineLabel || analysis_engine); setStage("analyzing"); window.scrollTo({ top: 0 }); try { const g = window.__gradio; if (!g) throw new Error("Client is still loading — reload the page and try again."); const client = await g.clientPromise; const res = await client.predict("/analyze_trace", { trace_file: g.handle_file(file), include_user_context: !!include_user_context, redact_secrets: !!redact_secrets, analysis_engine, }); const out = Array.isArray(res.data) ? res.data[0] : res.data; if (!out || typeof out !== "object") throw new Error("The analyzer returned an empty response."); setData(out); setStage("report"); } catch (e) { setError(String((e && e.message) || e)); setStage("landing"); } window.scrollTo({ top: 0 }); } function loadSample(key) { const base = key === "short" ? window.TFN.SHORT : window.TFN.LONG; setError(""); setEngineLabel(base.engine); setData(base); setStage("report"); window.scrollTo({ top: 0 }); } function reset() { setStage("landing"); setData(null); window.scrollTo({ top: 0 }); } const reportData = data ? Object.assign({}, data, { engine: engineLabel || data.engine }) : null; const hasEpisodes = reportData && reportData.episodes && reportData.episodes.length; return (
{stage === "landing" && } {stage === "analyzing" && } {stage === "report" && (
{hasEpisodes ? : }
Trace Field Notes Qualitative narrative analysis · we report what the agent said, not whether its code is correct.
)}
); } ReactDOM.createRoot(document.getElementById("root")).render();