/* ============================================================ 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
build small
); } const ENGINES = [ ["minicpm", "Quick analysis", "MiniCPM5 1B"], ["nemotron", "Deeper analysis", "Nemotron 3 Nano 30B-A3B"], ["deterministic", "Rule-based", "no model, always on"], ]; const EXEC_MODES = [ ["zerogpu", "GPU", "Space GPU · faster"], ["cpu", "CPU", "no GPU quota · slower"], ]; 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("minicpm"); const [execMode, setExecMode] = React.useState("zerogpu"); 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, execution_mode: execMode, 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 (

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, ignores raw tool telemetry by default, and scrubs secrets and personal data with pattern rules plus OpenAI's privacy-filter model.

{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 uses MiniCPM5 1B on the Space GPU. Deeper uses Nemotron 3 Nano 30B-A3B. Rule-based needs no model and never fails.

{EXEC_MODES.map(([key, name, detail]) => ( ))}

ZeroGPU is fast but spends your Space GPU quota. CPU needs no quota and still works if you've run out — just slower, so the progress bar will move more gradually.

{/* 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 fmtSeconds(s) { if (s == null || isNaN(s)) return "—"; const m = Math.floor(s / 60), sec = Math.round(s % 60); return m > 0 ? `${m}m ${sec}s` : `${sec}s`; } function Analyzing({ label, step, progress }) { const pct = progress && typeof progress.pct === "number" ? Math.max(0, Math.min(100, progress.pct)) : null; return (
Surveying the trace · {label} {pct != null && (
{pct}%{progress.stage ? " · " + progress.stage : ""} {progress.total != null ? (progress.processed != null && progress.processed < progress.total ? progress.processed + "/" + progress.total : progress.total) + " msgs · " : ""} {fmtSeconds(progress.elapsed)} elapsed {progress.eta != null && pct < 100 ? " · ~" + fmtSeconds(progress.eta) + " left" : ""}
)}
    {PIPELINE.map((s, i) => (
  • {i < step ? "✓" : i === step ? "…" : "·"}{s}
  • ))}
); } 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(""); const [step, setStep] = React.useState(0); const [progress, setProgress] = React.useState(null); async function analyze({ file, include_user_context, redact_secrets, analysis_engine, execution_mode, engineLabel }) { setError(""); setEngineLabel(engineLabel || analysis_engine); setStep(0); setProgress(null); 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 sub = client.submit("/analyze_trace", { trace_file: g.handle_file(file), include_user_context: !!include_user_context, redact_secrets: !!redact_secrets, analysis_engine, execution_mode, }); let result = null; for await (const msg of sub) { if (msg.type === "data") { const p = Array.isArray(msg.data) ? msg.data[0] : msg.data; if (p && typeof p === "object") { if (p.result) { result = p.result; } else if (typeof p.step === "number") { setStep(Math.min(p.step, PIPELINE.length - 1)); } if (typeof p.pct === "number") { setProgress({ pct: p.pct, elapsed: p.elapsed, eta: p.eta, total: p.total, processed: p.processed, stage: p.stage, }); } } } else if (msg.type === "status") { if (msg.stage === "error") throw new Error(msg.message || "The analyzer failed on the server."); if (msg.stage === "generating") setStep((s) => (s < 1 ? 1 : s)); } } if (!result || typeof result !== "object") throw new Error("The analyzer returned no result."); setStep(PIPELINE.length); setData(result); 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, { requested_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();