/** Shared coach source badge, response, debug copy, and server picks. */ import { animate } from "motion"; import { useEffect, useRef } from "preact/hooks"; import { getDebugPaste, type CoachResponse, type ServerPick } from "../api"; import { prefersReducedMotion, springs } from "../motion/springs"; import { navigate } from "../router"; import { Button } from "./Button"; import { Pressable } from "./Pressable"; import { useToast } from "./Toast"; export function sourceLabel(source: CoachResponse["source"]): "Rules" | "Model" { return source === "model" ? "Model" : "Rules"; } export function ServerPicks({ picks }: { picks: ServerPick[] }) { if (!picks.length) return null; return (

From your data

{picks.map((pick) => ( navigate(`/log?remedy=${encodeURIComponent(pick.remedy_key)}`)} > {pick.remedy_key} n={pick.n} · {Math.round(pick.p_helped * 100)}% helped ))}
); } export function CoachResultCard({ result }: { result: CoachResponse }) { const ref = useRef(null); const toast = useToast(); useEffect(() => { if (!ref.current) return; if (prefersReducedMotion()) animate(ref.current, { opacity: [0, 1] }, { duration: 0.18 }); else animate(ref.current, { opacity: [0, 1], y: [12, 0] }, springs.snap); }, [result.trace_id]); const copyDebug = async () => { try { await navigator.clipboard.writeText(await getDebugPaste()); toast.show("Debug copied"); } catch (err) { toast.show(err instanceof Error ? err.message : "Copy failed", "error"); } }; const looksLikeTemplate = /LOOP:\s*
{looksLikeTemplate ? "Rules" : sourceLabel(result.source)} {result.flags.length ? {result.flags.join(" · ")} : null}

{displayText}

); }