/* ============================================================
atoms.jsx — shared primitives + topo background
============================================================ */
// ---- deterministic topo contour generator ----
function _noise(a, seed) {
return (
Math.sin(a * 3 + seed) * 0.45 +
Math.sin(a * 5 + seed * 1.7) * 0.28 +
Math.sin(a * 2 + seed * 0.6) * 0.5 +
Math.sin(a * 7 + seed * 2.3) * 0.16
);
}
function _blob(cx, cy, r, seed, amp) {
const N = 80;
let d = "";
for (let i = 0; i <= N; i++) {
const t = (i / N) * Math.PI * 2;
const rr = r * (1 + amp * _noise(t, seed));
const x = cx + rr * Math.cos(t);
const y = cy + rr * Math.sin(t) * 0.82;
d += (i === 0 ? "M" : "L") + x.toFixed(1) + " " + y.toFixed(1) + " ";
}
return d + "Z";
}
function TopoBackground() {
const peaks = [
{ cx: 250, cy: 230, seed: 1.2, count: 11, base: 26, step: 34, peakAt: 3 },
{ cx: 1160, cy: 640, seed: 4.7, count: 13, base: 24, step: 32, peakAt: 4 },
{ cx: 760, cy: 120, seed: 8.1, count: 7, base: 30, step: 40, peakAt: 1 },
];
return (
{peaks.map((p, pi) =>
Array.from({ length: p.count }).map((_, i) => {
const r = p.base + i * p.step;
const amp = 0.05 + i * 0.012;
const strong = i === p.peakAt;
return (
);
})
)}
);
}
// ---- tone helpers ----
function toneOf(recovery) {
return (window.TFN.TONE_OF[recovery]) || "unknown";
}
function toneColor(tone) {
return "var(--tone-" + tone + ")";
}
// ---- small atoms ----
function Kicker({ children }) {
return
{children}
;
}
function Label({ children, accent, style }) {
return {children}
;
}
function ToneDot({ tone, size = 10 }) {
return (
);
}
// codebook chip: pass field + code
function CodeChip({ field, code, withDotTone }) {
const label = (window.TFN.CODEBOOK[field] && window.TFN.CODEBOOK[field][code]) || code;
return (
{withDotTone ? : null}
{label}
);
}
function Stamp({ tone, children }) {
return (
{children}
);
}
// section header used across the report
function SectionHead({ index, kicker, title, sub }) {
return (
{index ? {index} : null}
{kicker}
{title}
{sub ?
{sub}
: null}
);
}
Object.assign(window, {
TopoBackground, toneOf, toneColor,
Kicker, Label, ToneDot, CodeChip, Stamp, SectionHead,
});
/* ============================================================
trailmap.jsx — elevation-profile trail map + episode detail
x = progress through the session, y = risk / exposure.
The agent's journey climbs toward hazard.
============================================================ */
const ELEV = { stable: 0.12, detour: 0.44, iterative: 0.52, partial: 0.64, risk: 0.93, unknown: 0.30 };
const VBW = 1000, VBH = 360;
const PAD = { l: 116, r: 96, t: 48, b: 60 };
function _layout(episodes) {
const n = episodes.length;
const innerW = VBW - PAD.l - PAD.r;
const innerH = VBH - PAD.t - PAD.b;
const baseY = VBH - PAD.b;
return episodes.map((ep, i) => {
const tone = toneOf(ep.recovery_pattern);
const x = PAD.l + (n === 1 ? innerW / 2 : (i / (n - 1)) * innerW);
const jitter = ((i % 2) * 2 - 1) * 0.015;
const elev = Math.min(0.97, Math.max(0.06, ELEV[tone] + jitter));
const y = baseY - elev * innerH;
return { ep, tone, x, y, fx: (x / VBW) * 100, fy: (y / VBH) * 100, elev };
});
}
function _smoothPath(pts) {
if (pts.length === 1) return `M ${pts[0].x} ${pts[0].y}`;
let d = `M ${pts[0].x} ${pts[0].y}`;
for (let i = 0; i < pts.length - 1; i++) {
const p0 = pts[i - 1] || pts[i];
const p1 = pts[i];
const p2 = pts[i + 1];
const p3 = pts[i + 2] || p2;
const c1x = p1.x + (p2.x - p0.x) / 6;
const c1y = p1.y + (p2.y - p0.y) / 6;
const c2x = p2.x - (p3.x - p1.x) / 6;
const c2y = p2.y - (p3.y - p1.y) / 6;
d += ` C ${c1x.toFixed(1)} ${c1y.toFixed(1)}, ${c2x.toFixed(1)} ${c2y.toFixed(1)}, ${p2.x.toFixed(1)} ${p2.y.toFixed(1)}`;
}
return d;
}
function TrailMap({ episodes, selectedId, onSelect }) {
const pts = _layout(episodes);
const compact = episodes.length > 6;
const baseY = VBH - PAD.b;
const line = _smoothPath(pts);
const area = `${line} L ${pts[pts.length - 1].x} ${baseY} L ${pts[0].x} ${baseY} Z`;
const gridY = [0.25, 0.5, 0.75, 1].map((f) => baseY - f * (VBH - PAD.t - PAD.b));
return (
Hazard Exposure On-route
{/* elevation grid */}
{gridY.map((y, i) => (
))}
{/* hypsometric fill + ridge line */}
{/* drop stems + waypoint nodes (selectable) */}
{pts.map((p) => {
const sel = p.ep.episode_id === selectedId;
return (
onSelect(p.ep.episode_id)}>
);
})}
{/* HTML waypoint flags positioned over the SVG */}
{pts.map((p, i) => {
const sel = p.ep.episode_id === selectedId;
const above = p.fy > 46;
const edge = i === 0 ? " wp--first" : i === pts.length - 1 ? " wp--last" : "";
return (
onSelect(p.ep.episode_id)}
>
{p.ep.episode_id}
{(!compact || sel) ? {p.ep.title} : null}
{(!compact || sel) ? {p.ep.message_span.duration_label} : null}
);
})}
start · {episodes[0].message_span.start_time}
progress through session →
end · {episodes[episodes.length - 1].message_span.end_time}
);
}
function EpisodeRail({ episodes, selectedId, onSelect }) {
if (episodes.length <= 6) return null;
return (
Episodes
{episodes.map((ep) => {
const tone = toneOf(ep.recovery_pattern);
const selected = ep.episode_id === selectedId;
return (
onSelect(ep.episode_id)}
title={`${ep.episode_id}: ${ep.title}`}
>
{ep.episode_id}
{ep.title}
);
})}
);
}
// ---- Episode detail (used by both layouts) ----
function EpisodeDetail({ ep }) {
if (!ep) return null;
const tone = toneOf(ep.recovery_pattern);
const tm = window.TFN.TONE_META[tone];
return (
{ep.episode_id}
{ep.title}
{tm.label} · {ep.message_span.duration_label} · {ep.message_span.start_time}–{ep.message_span.end_time}
{[
["Intention", ep.initial_intention],
["Difficulty", ep.reported_difficulty],
["Reroute", ep.strategy_after],
].map(([k, v]) => (
))}
{ep.evidence_quotes && ep.evidence_quotes.length ? (
Evidence — agent's own words
{ep.evidence_quotes.map((q, i) => (
{q}
))}
) : null}
Analyst memo
{ep.analyst_memo}
);
}
// ---- Ledger (vertical) timeline variant ----
function LedgerTimeline({ episodes, selectedId, onSelect }) {
return (
{episodes.map((ep) => {
const tone = toneOf(ep.recovery_pattern);
const sel = ep.episode_id === selectedId;
return (
onSelect(ep.episode_id)}>
{ep.episode_id}
{ep.title}
{window.TFN.CODEBOOK.difficulty_type[ep.difficulty_type]} → {window.TFN.CODEBOOK.recovery_pattern[ep.recovery_pattern]}
{ep.message_span.duration_label}
);
})}
);
}
Object.assign(window, { TrailMap, EpisodeRail, EpisodeDetail, LedgerTimeline });
/* ============================================================
report.jsx — the field report: verdict, trail, analysis sections
============================================================ */
const HONESTY = {
resolved_with_confidence: { tone: "stable", note: "Clear, committed claim." },
resolved_with_caveat: { tone: "stable", note: "States its own limits." },
partially_resolved: { tone: "partial", note: "Honest partial." },
not_resolved: { tone: "partial", note: "Admits it's unresolved." },
needs_verification: { tone: "partial", note: "Flags a verification gap." },
uncertain_but_proceeding: { tone: "partial", note: "Proceeds under stated uncertainty." },
premature_success_claim: { tone: "risk", note: "Claim outruns the evidence." },
unknown: { tone: "unknown", note: "—" },
};
// download helper for the export buttons (no-op if the backend didn't supply text)
function dl(text, filename, mime) {
if (!text) return;
const blob = new Blob([text], { type: mime || "text/plain" });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url; a.download = filename; document.body.appendChild(a); a.click();
a.remove(); setTimeout(() => URL.revokeObjectURL(url), 1500);
}
function ReportHeader({ data }) {
return (
FIELD LOG № {data.agent_type_guess === "codex" ? "C-01" : "CC-04"}
Trace
{data.trace_title}
{[
["Agent", data.agent_type_guess.replace("_", " ")],
["Captured", data.captured],
["Scope", "narrative msgs only"],
["Messages", String(data.narrative_message_count)],
["Engine", data.engine],
["Redactions", String(data.redaction_count)],
].map(([k, v]) => (
{k}
{v}
))}
);
}
function ModelStatus({ data }) {
const notes = (data.privacy_notes || []).filter((note) =>
/^(Analysis produced|Model analysis|Model assist|Unknown analysis engine)/.test(String(note))
);
if (!notes.length) return null;
const fellBack = notes.some((note) =>
/unavailable|rule-based analysis was returned|deterministic analysis was returned|unknown analysis engine/i.test(note)
);
return (
{fellBack ? "!" : "✓"}
{fellBack
? "Model unavailable — showing the rule-based analysis instead."
: "This report was written by the model."} {" "}
{notes.join(" ")}
);
}
function Verdict({ data }) {
const v = data.verdict;
const tm = window.TFN.TONE_META[v.tone];
const honestyWord = v.honesty === "overclaimed" ? "Overclaimed close-out"
: v.honesty === "candid" ? "Candid close-out" : "Mixed close-out";
return (
Trail verdict
{v.headline}
{v.detail}
{honestyWord}
Recovery read
{tm.rating}
{tm.blurb}
{data.episodes.length} episodes
{data.duration_total} on trail
);
}
function Legend() {
const order = ["stable", "detour", "iterative", "partial", "risk", "unknown"];
const M = window.TFN.TONE_META;
return (
Waypoint key
{order.map((t) => (
{M[t].label} · {M[t].rating}
))}
);
}
function TrailSection({ data, variant, selectedId, setSelectedId }) {
const ep = data.episodes.find((e) => e.episode_id === selectedId) || data.episodes[0];
return (
{variant === "ledger"
?
: }
);
}
function DifficultyMap({ data }) {
const clusters = {};
data.episodes.forEach((e) => {
(clusters[e.difficulty_type] = clusters[e.difficulty_type] || []).push(e);
});
const CB = window.TFN.CODEBOOK.difficulty_type;
const entries = Object.entries(clusters).sort((a, b) => b[1].length - a[1].length);
return (
{entries.map(([type, eps]) => {
const quote = (eps.find((e) => e.evidence_quotes && e.evidence_quotes.length) || {}).evidence_quotes;
return (
{CB[type] || type}
{eps.map((e) => e.episode_id).join(" · ")}
{quote ?
{quote[0]} :
No short evidence quote.
}
);
})}
);
}
function DetourAnalysis({ data }) {
const groups = { yes: [], mixed: [], no: [] };
data.episodes.forEach((e) => { if (groups[e.productive_detour]) groups[e.productive_detour].push(e); });
const defs = [
["yes", "Productive detours", "Off-route, but a better line emerged.", "detour"],
["mixed", "Mixed", "A reroute with real upside and a loose end.", "partial"],
["no", "Wandering / workaround", "Movement without a new line on the problem.", "risk"],
];
return (
{defs.map(([key, title, blurb, tone]) => (
{title}
{groups[key].length}
{blurb}
{groups[key].length ? groups[key].map((e) => (
{e.episode_id}
)) :
None observed. }
))}
);
}
function RecoveryPattern({ data }) {
const p = data.overall_patterns;
const rows = [
["Difficulty style", p.difficulty_style],
["Detour style", p.detour_style],
["Recovery style", p.recovery_style],
["Standing caveat", p.risk_or_caveat],
];
return (
{rows.map(([k, v], i) => (
{String(i + 1).padStart(2, "0")}
{k}
{v}
))}
);
}
function OutcomeAudit({ data }) {
const CB = window.TFN.CODEBOOK.outcome_claim;
return (
{data.episodes.map((e) => {
const h = HONESTY[e.outcome_claim] || HONESTY.unknown;
return (
{e.episode_id}
{CB[e.outcome_claim] || e.outcome_claim}
{h.note}
{e.evidence_quotes && e.evidence_quotes.length ? (
{e.evidence_quotes[e.evidence_quotes.length - 1]}
) : null}
);
})}
);
}
function PrivacyExports({ data, onReset }) {
return (
{data.privacy_notes.map((n, i) => {n} )}
Take it with you
Export the redacted narrative and the structured findings. The raw trace never leaves your machine.
dl(data.exports && data.exports.narrative_md, (data.trace_title||"trace")+"-redacted.md", "text/markdown")}>↓ Redacted narrative .md
dl(data.exports && data.exports.report_md, (data.trace_title||"trace")+"-field-report.md", "text/markdown")}>↓ Field report .md
dl(data.exports && data.exports.episodes_json, (data.trace_title||"trace")+"-episodes.json", "application/json")}>↓ Episodes .json
← Analyze another trace
);
}
function ReportView({ data, variant, onReset }) {
const [selectedId, setSelectedId] = React.useState(
() => (data.verdict.tone === "risk"
? (data.episodes.find((e) => toneOf(e.recovery_pattern) === "risk") || data.episodes[0]).episode_id
: data.episodes[0].episode_id)
);
React.useEffect(() => {
setSelectedId(data.episodes[0].episode_id);
}, [data]);
return (
);
}
Object.assign(window, { ReportView });