/* global React */

// ─── S21 Intent Mirror ─────────────────────────────────
// A dedicated, full-bleed screen. No CCM shell.
// Serif display + mono body. Warm sand accent.

function S21_IntentMirror() {
  const [openNode, setOpenNode] = React.useState("ws-broadcaster");
  const [pinnedAssump, setPinnedAssump] = React.useState({ 1: true });

  const nodes = [
    // CLIENT cluster
    { id: "use-replay",   x: 60,  y: 90,  label: "useReplay",        type: "react hook",         state: "new" },
    { id: "scrubber",     x: 60,  y: 200, label: "<Scrubber>",        type: "react component",    state: "new" },
    { id: "replay-store", x: 60,  y: 310, label: "replayStore",       type: "zustand store",      state: "new" },
    // TRANSPORT
    { id: "sse-client",   x: 290, y: 200, label: "sseClient",         type: "adapter",            state: "modified", delta: "+42 −8" },
    // SERVER cluster
    { id: "events-route", x: 510, y: 90,  label: "/events stream",    type: "hono route",         state: "modified", delta: "+88 −12" },
    { id: "ws-broadcaster",x: 510, y: 200, label: "wsBroadcaster",    type: "service",            state: "new" },
    { id: "session-store",x: 510, y: 310, label: "sessionStore",      type: "service",            state: "unchanged" },
    // DATA
    { id: "redis",        x: 740, y: 200, label: "Redis · pubsub",    type: "infra",              state: "unchanged" },
    { id: "events-table", x: 740, y: 310, label: "events",            type: "sqlite table",       state: "modified", delta: "+1 col" },
  ];

  const edges = [
    { from: "scrubber",     to: "use-replay",     label: "calls",         state: "new" },
    { from: "use-replay",   to: "replay-store",   label: "subscribes",    state: "new" },
    { from: "use-replay",   to: "sse-client",     label: "opens",         state: "new" },
    { from: "sse-client",   to: "events-route",   label: "streams from",  state: "modified" },
    { from: "events-route", to: "ws-broadcaster", label: "broadcasts via",state: "new" },
    { from: "ws-broadcaster", to: "redis",        label: "publishes to",  state: "new" },
    { from: "ws-broadcaster", to: "session-store",label: "reads",         state: "unchanged" },
    { from: "session-store",to: "events-table",   label: "queries",       state: "modified" },
  ];

  const clusters = [
    { label: "CLIENT",    x: 30,  y: 60,  w: 200, h: 290 },
    { label: "TRANSPORT", x: 260, y: 170, w: 200, h: 70  },
    { label: "SERVER",    x: 480, y: 60,  w: 200, h: 290 },
    { label: "DATA",      x: 710, y: 170, w: 180, h: 180 },
  ];

  const tasks = [
    { name: "Add wsBroadcaster service skeleton",          file: "src/server/services/ws-broadcaster.ts",   phase: "Phase 1 · Infra",   tools: [["edit", 1], ["bash", 1]], status: "done" },
    { name: "Wire Redis pubsub channel naming",            file: "src/server/services/ws-broadcaster.ts",   phase: "Phase 1 · Infra",   tools: [["edit", 1]],              status: "done" },
    { name: "Migrate events table — add broadcast_id col", file: "drizzle/0004_broadcast.sql",              phase: "Phase 1 · Infra",   tools: [["write", 1], ["bash", 1]], status: "done" },
    { name: "Refactor /events route to use broadcaster",   file: "src/server/routes/events.ts",             phase: "Phase 2 · Server",  tools: [["edit", 2], ["read", 1]], status: "running" },
    { name: "Add backpressure handling on slow clients",   file: "src/server/services/ws-broadcaster.ts",   phase: "Phase 2 · Server",  tools: [["edit", 1]],              status: "pending" },
    { name: "Build replayStore (zustand)",                 file: "src/client/stores/replay-store.ts",       phase: "Phase 3 · Client",  tools: [["write", 1]],              status: "pending" },
    { name: "Implement useReplay hook",                    file: "src/client/hooks/use-replay.ts",          phase: "Phase 3 · Client",  tools: [["write", 1], ["edit", 1]], status: "pending" },
    { name: "Build <Scrubber> component",                   file: "src/client/components/scrubber.tsx",      phase: "Phase 3 · Client",  tools: [["write", 1]],              status: "pending" },
    { name: "Wire <Scrubber> into session detail page",    file: "src/app/sessions/[id]/replay.tsx",        phase: "Phase 3 · Client",  tools: [["edit", 1]],              status: "pending" },
    { name: "Add tests for broadcaster + store",           file: "src/server/services/__tests__",           phase: "Phase 4 · Tests",   tools: [["write", 2], ["bash", 1]], status: "pending" },
    { name: "Run full type-check + integration suite",     file: "—",                                       phase: "Phase 4 · Tests",   tools: [["bash", 2]],              status: "pending" },
  ];

  const nodeColor = (s) => s === "new" ? "var(--im-new)" : s === "modified" ? "var(--im-mod)" : "var(--im-still)";
  const nodeBg    = (s) => s === "new" ? "rgba(120,180,140,.08)" : s === "modified" ? "rgba(212,165,116,.08)" : "transparent";

  const getNode = (id) => nodes.find(n => n.id === id);
  const sel = openNode ? getNode(openNode) : null;

  // Edge path: orthogonal route between node centers
  const edgePath = (a, b) => {
    const ax = a.x + 70, ay = a.y + 30, bx = b.x + 70, by = b.y + 30;
    // Decide whether to go horizontally or vertically first based on dx vs dy
    const midX = (ax + bx) / 2;
    if (Math.abs(ax - bx) > 40) {
      return `M ${a.x + 140} ${ay} L ${midX} ${ay} L ${midX} ${by} L ${b.x} ${by}`;
    }
    return `M ${ax} ${a.y + 60} L ${ax} ${(ay + by) / 2} L ${bx} ${(ay + by) / 2} L ${bx} ${b.y}`;
  };

  return (
    <div className="im-root">
      <style>{`
        .im-root {
          --im-bg: #0a0b0d;
          --im-bg-2: #101114;
          --im-bg-3: #16181c;
          --im-ink-0: #e8e6e1;
          --im-ink-1: #b8b5ad;
          --im-ink-2: #7a766f;
          --im-ink-3: #4a4843;
          --im-border: #232529;
          --im-border-2: #30333a;
          --im-accent: #d4a574;
          --im-accent-soft: #d4a57415;
          --im-accent-line: #d4a57440;
          --im-new: #78b48c;
          --im-mod: #d4a574;
          --im-still: #4a4843;
          --im-err: #c97070;
          --font-display: "Fraunces", "Tiempos", "Source Serif Pro", Georgia, serif;
          --font-mono: "JetBrains Mono", "IBM Plex Mono", ui-monospace, Menlo, monospace;

          background: var(--im-bg);
          color: var(--im-ink-0);
          min-height: 100%;
          font-family: var(--font-mono);
          font-size: 12.5px;
          line-height: 1.55;
          padding-bottom: 80px;
        }
        .im-root *, .im-root *::before, .im-root *::after { box-sizing: border-box; }
        .im-section {
          position: relative;
          border: 1px solid var(--im-border);
          margin: 28px 40px;
          padding: 36px 44px 32px;
          background: var(--im-bg);
        }
        .im-section-label {
          position: absolute;
          top: -10px; left: 32px;
          padding: 0 12px;
          background: var(--im-bg);
          font-family: var(--font-mono);
          font-size: 10px;
          letter-spacing: .32em;
          color: var(--im-ink-2);
          font-weight: 500;
        }
        .im-section-label .idx { color: var(--im-accent); margin-right: 10px; }

        /* Meta strip */
        .im-meta { display: flex; gap: 22px; font-size: 10.5px; color: var(--im-ink-2); letter-spacing: .05em; margin-bottom: 22px; }
        .im-meta b { color: var(--im-ink-1); font-weight: 500; }
        .im-meta .dot { color: var(--im-ink-3); }

        /* Intent */
        .im-goal {
          font-family: var(--font-display);
          font-size: 30px;
          font-weight: 400;
          line-height: 1.25;
          color: var(--im-ink-0);
          letter-spacing: -.01em;
          max-width: 980px;
          margin: 0 0 28px;
        }
        .im-goal em { font-style: normal; color: var(--im-accent); background: linear-gradient(transparent 80%, var(--im-accent-soft) 80%); padding: 0 2px; }

        .im-cols { display: grid; grid-template-columns: 1.1fr 1.3fr 1fr; gap: 36px; }
        .im-col-h { font-size: 10px; letter-spacing: .2em; color: var(--im-ink-2); margin-bottom: 12px; font-weight: 500; }
        .im-bullet { display: flex; gap: 10px; padding: 5px 0; cursor: pointer; align-items: flex-start; }
        .im-bullet:hover { color: var(--im-ink-0); }
        .im-bullet .b { flex: none; color: var(--im-ink-3); width: 16px; font-size: 11px; }
        .im-bullet.dim { color: var(--im-ink-2); }
        .im-bullet.dim .b { color: var(--im-ink-3); }
        .im-pin {
          flex: none; width: 14px; height: 14px;
          border: 1px solid var(--im-ink-3); border-radius: 2px;
          display: grid; place-items: center;
          font-size: 9px; color: transparent;
          margin-top: 2px;
        }
        .im-bullet.pinned .im-pin { background: var(--im-accent); border-color: var(--im-accent); color: var(--im-bg); }
        .im-bullet.pinned { color: var(--im-ink-0); }
        .im-bullet.pinned::before { content: ""; position: absolute; left: -10px; }

        /* Architecture */
        .im-canvas-wrap { position: relative; }
        .im-toolbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: 14px; }
        .im-legend { display: flex; gap: 18px; font-size: 10px; color: var(--im-ink-2); letter-spacing: .04em; }
        .im-legend .sw { display: inline-flex; align-items: center; gap: 6px; }
        .im-legend .swatch { width: 10px; height: 10px; border: 1px solid currentColor; border-radius: 1px; }
        .im-tools { display: flex; gap: 4px; }
        .im-tools button {
          background: transparent; border: 1px solid var(--im-border-2);
          color: var(--im-ink-2); padding: 4px 10px;
          font-family: var(--font-mono); font-size: 10px;
          letter-spacing: .06em; cursor: pointer; border-radius: 0;
        }
        .im-tools button:hover { color: var(--im-ink-0); border-color: var(--im-ink-2); }

        .im-canvas {
          position: relative;
          width: 100%;
          height: 460px;
          background: var(--im-bg);
          background-image: radial-gradient(circle, var(--im-border) 1px, transparent 1px);
          background-size: 16px 16px;
          background-position: 8px 8px;
          border: 1px solid var(--im-border);
          overflow: hidden;
        }

        .im-cluster {
          position: absolute;
          border: 1px dashed var(--im-border-2);
        }
        .im-cluster-label {
          position: absolute;
          top: -8px; left: 12px;
          padding: 0 8px;
          background: var(--im-bg);
          font-size: 9px;
          letter-spacing: .28em;
          color: var(--im-ink-3);
        }

        .im-node {
          position: absolute;
          width: 140px; height: 60px;
          background: var(--im-bg);
          border: 1px solid var(--im-still);
          padding: 8px 10px;
          cursor: pointer;
          transition: transform .15s ease, box-shadow .15s ease;
          font-family: var(--font-mono);
          display: flex; flex-direction: column; justify-content: center;
        }
        .im-node:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(0,0,0,.4); z-index: 3; }
        .im-node.new      { border-color: var(--im-new); background: linear-gradient(180deg, rgba(120,180,140,.10), transparent 70%); }
        .im-node.modified { border-color: var(--im-mod); background: linear-gradient(180deg, rgba(212,165,116,.10), transparent 70%); }
        .im-node.selected { box-shadow: 0 0 0 1px var(--im-accent), 0 10px 28px rgba(0,0,0,.5); z-index: 4; }
        .im-node .flag {
          position: absolute; top: 6px; right: 8px;
          font-size: 8.5px; letter-spacing: .1em;
          color: var(--im-ink-2);
        }
        .im-node.new .flag      { color: var(--im-new); }
        .im-node.modified .flag { color: var(--im-mod); }
        .im-node .label { font-size: 12.5px; color: var(--im-ink-0); font-weight: 500; margin-top: 8px; }
        .im-node .type { font-size: 9px; letter-spacing: .08em; color: var(--im-ink-2); margin-top: 4px; text-transform: uppercase; }

        /* Edge labels */
        .im-edge-label {
          position: absolute;
          font-size: 9px; letter-spacing: .04em;
          color: var(--im-ink-2);
          background: var(--im-bg);
          padding: 1px 5px;
          pointer-events: none;
          white-space: nowrap;
        }
        .im-edge-label.new { color: var(--im-new); }
        .im-edge-label.modified { color: var(--im-mod); }

        /* Detail panel */
        .im-detail-scrim {
          position: absolute; inset: 0;
          background: rgba(10,11,13,.55);
          backdrop-filter: blur(1px);
          z-index: 5;
        }
        .im-detail {
          position: absolute; top: 0; right: 0; bottom: 0;
          width: 360px;
          background: var(--im-bg-2);
          border-left: 1px solid var(--im-border-2);
          z-index: 6;
          padding: 24px 24px 20px;
          overflow-y: auto;
          font-family: var(--font-mono);
        }
        .im-detail .tag {
          font-size: 9.5px; letter-spacing: .24em; color: var(--im-accent);
          margin-bottom: 14px;
        }
        .im-detail h2 {
          font-family: var(--font-display);
          font-weight: 400;
          font-size: 26px;
          line-height: 1.2;
          margin: 0 0 6px;
          color: var(--im-ink-0);
          letter-spacing: -.01em;
        }
        .im-detail .path { font-size: 10.5px; color: var(--im-ink-2); margin-bottom: 22px; word-break: break-all; }
        .im-detail h4 {
          font-size: 9.5px; letter-spacing: .22em; color: var(--im-ink-2);
          font-weight: 500;
          margin: 22px 0 8px; text-transform: uppercase;
        }
        .im-detail p { font-size: 12px; color: var(--im-ink-1); line-height: 1.6; margin: 0; }
        .im-detail .contract {
          background: var(--im-bg-3);
          border: 1px solid var(--im-border);
          padding: 12px 14px;
          font-size: 11px;
          color: var(--im-ink-1);
          margin-top: 4px;
          line-height: 1.7;
          white-space: pre;
          overflow-x: auto;
        }
        .im-detail .contract .m { color: var(--im-accent); }
        .im-detail .contract .t { color: #87a8c5; }
        .im-detail .contract .k { color: var(--im-ink-2); }
        .im-detail ul.fx { margin: 4px 0 0; padding: 0; list-style: none; }
        .im-detail ul.fx li {
          font-size: 11px; color: var(--im-ink-1);
          padding: 4px 0 4px 18px;
          position: relative;
        }
        .im-detail ul.fx li::before {
          content: "→"; position: absolute; left: 0; color: var(--im-accent);
        }
        .im-detail-actions {
          display: flex; gap: 8px; margin-top: 28px;
          padding-top: 18px; border-top: 1px solid var(--im-border);
        }
        .im-detail-actions button {
          flex: 1; padding: 9px 12px;
          background: transparent;
          border: 1px solid var(--im-border-2);
          color: var(--im-ink-1);
          font-family: var(--font-mono);
          font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase;
          cursor: pointer;
        }
        .im-detail-actions button:hover:not(:disabled) { color: var(--im-ink-0); border-color: var(--im-ink-2); }
        .im-detail-actions button:disabled { color: var(--im-ink-3); border-color: var(--im-border); cursor: default; }
        .im-detail-close {
          position: absolute; top: 16px; right: 16px;
          width: 22px; height: 22px;
          background: transparent; border: 1px solid var(--im-border-2);
          color: var(--im-ink-2); cursor: pointer;
          font-size: 12px; line-height: 1;
        }
        .im-detail-close:hover { color: var(--im-ink-0); }

        /* Execution */
        .im-exec-head { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 22px; }
        .im-exec-stats { font-size: 10.5px; color: var(--im-ink-2); letter-spacing: .04em; }
        .im-exec-stats b { color: var(--im-ink-1); font-weight: 500; }
        .im-task {
          display: grid;
          grid-template-columns: 36px 1fr 200px 100px;
          gap: 18px;
          padding: 14px 0;
          align-items: center;
          position: relative;
          border-bottom: 1px solid var(--im-border);
        }
        .im-task:last-child { border-bottom: none; }
        .im-task:hover { background: var(--im-bg-2); margin: 0 -16px; padding-left: 16px; padding-right: 16px; }
        .im-task .marker {
          position: relative;
          width: 10px; height: 10px;
          border-radius: 50%;
          background: var(--im-ink-3);
          margin-left: 12px;
        }
        .im-task .marker::before {
          content: ""; position: absolute;
          top: -18px; left: 50%; transform: translateX(-50%);
          width: 1px; height: 18px; background: var(--im-border);
        }
        .im-task:first-child .marker::before { display: none; }
        .im-task.done .marker     { background: var(--im-new); }
        .im-task.running .marker  { background: var(--im-mod); box-shadow: 0 0 0 0 var(--im-accent); animation: imPulse 1.6s infinite; }
        .im-task.blocked .marker  { background: var(--im-err); }
        @keyframes imPulse {
          0% { box-shadow: 0 0 0 0 rgba(212,165,116,.6); }
          100% { box-shadow: 0 0 0 10px rgba(212,165,116,0); }
        }
        .im-task .task-name { font-size: 12.5px; color: var(--im-ink-0); }
        .im-task.done .task-name { color: var(--im-ink-2); }
        .im-task .task-meta { font-size: 10px; color: var(--im-ink-2); margin-top: 3px; letter-spacing: .03em; }
        .im-task .task-meta .file { color: var(--im-ink-1); }
        .im-task .task-meta .phase { color: var(--im-accent); margin-left: 10px; }
        .im-chips { display: flex; gap: 6px; flex-wrap: wrap; }
        .im-chip {
          font-size: 9.5px; letter-spacing: .06em;
          padding: 2px 7px;
          border: 1px solid var(--im-border-2);
          color: var(--im-ink-1);
          text-transform: lowercase;
        }
        .im-chip b { color: var(--im-accent); font-weight: 500; margin-left: 4px; }
        .im-status {
          text-align: right;
          font-size: 10px;
          letter-spacing: .22em;
          text-transform: uppercase;
        }
        .im-status.done { color: var(--im-new); }
        .im-status.running { color: var(--im-mod); }
        .im-status.pending { color: var(--im-ink-3); }
        .im-status.blocked { color: var(--im-err); }

        /* Sticky action bar */
        .im-actionbar {
          position: fixed; bottom: 0; left: 0; right: 0;
          background: var(--im-bg);
          border-top: 1px solid var(--im-border-2);
          padding: 14px 40px;
          display: flex; align-items: center; justify-content: space-between;
          z-index: 20;
          backdrop-filter: blur(8px);
          background: rgba(10,11,13,.92);
        }
        .im-hint { font-size: 10.5px; color: var(--im-ink-2); letter-spacing: .03em; }
        .im-hint kbd {
          background: var(--im-bg-3); border: 1px solid var(--im-border-2);
          padding: 1px 5px; border-radius: 2px;
          color: var(--im-ink-1); font-family: var(--font-mono); font-size: 9.5px;
          margin: 0 3px;
        }
        .im-actions { display: flex; gap: 10px; }
        .im-actions button {
          font-family: var(--font-mono);
          font-size: 11px;
          letter-spacing: .14em;
          text-transform: uppercase;
          padding: 10px 20px;
          cursor: pointer;
          border: 1px solid var(--im-border-2);
          background: transparent;
          color: var(--im-ink-1);
        }
        .im-actions button.reject { border-color: #5a2c2c; color: var(--im-err); }
        .im-actions button.reject:hover { background: rgba(201,112,112,.08); }
        .im-actions button.approve {
          background: var(--im-accent);
          border-color: var(--im-accent);
          color: var(--im-bg);
          font-weight: 500;
          padding: 10px 28px;
          box-shadow: 0 0 0 0 var(--im-accent-line), 0 2px 14px rgba(212,165,116,.18);
        }
        .im-actions button.approve:hover { background: #e0b483; }
        .im-actions button.ghost:hover { color: var(--im-ink-0); border-color: var(--im-ink-2); }

        /* Top mini-header (non-shell) */
        .im-top {
          padding: 18px 40px 0;
          display: flex; justify-content: space-between; align-items: baseline;
        }
        .im-top .brand {
          font-family: var(--font-display);
          font-size: 14px;
          color: var(--im-ink-0);
          letter-spacing: .02em;
        }
        .im-top .brand i {
          font-style: normal;
          color: var(--im-accent);
          font-size: 18px;
          line-height: 0;
          vertical-align: middle;
          margin-right: 6px;
        }
        .im-top .breadcrumb {
          font-size: 10.5px; color: var(--im-ink-2); letter-spacing: .04em;
        }
        .im-top .breadcrumb span { color: var(--im-ink-0); }
      `}</style>

      {/* Mini brand bar */}
      <div className="im-top">
        <div className="brand"><i>◆</i>intent mirror</div>
        <div className="breadcrumb">session 8af3 · <span>replay-broadcaster</span> · pending approval</div>
      </div>

      {/* ── SECTION 1 · INTENT ───────────────────── */}
      <section className="im-section">
        <div className="im-section-label"><span className="idx">01</span>INTENT</div>

        <div className="im-meta">
          <span><b>session</b> 8af3</span><span className="dot">·</span>
          <span><b>scope</b> 14 files across 3 services</span><span className="dot">·</span>
          <span><b>est</b> 55 min · ~92 tool calls</span><span className="dot">:</span>
          <span><b>plan v3</b> · revised 6m ago</span>
        </div>

        <h1 className="im-goal">
          Replace the polling-based event stream with a <em>Redis-backed broadcaster</em> so multiple clients can replay the same session timeline without each one opening its own /events subscription.
        </h1>

        <div className="im-cols">
          <div>
            <div className="im-col-h">SUCCESS CRITERIA</div>
            <div className="im-bullet"><span className="b">01</span><span>One <span style={{ color: "var(--im-accent)" }}>/events</span> stream serves N clients without duplicate tool-call records.</span></div>
            <div className="im-bullet"><span className="b">02</span><span>Replay scrubber stays in sync across two browser tabs (drift &lt; 1 event).</span></div>
            <div className="im-bullet"><span className="b">03</span><span>Backpressure handled — slow clients don't stall the broadcaster queue.</span></div>
            <div className="im-bullet"><span className="b">04</span><span>Full type-check + integration tests pass; existing /events consumers unaffected.</span></div>
          </div>

          <div>
            <div className="im-col-h">ASSUMPTIONS</div>
            {[
              { i: 1, t: "Redis is already running and reachable via REDIS_URL env var." },
              { i: 2, t: "Single Redis instance is sufficient — no clustering needed for v1." },
              { i: 3, t: "Existing sessionStore queries are fast enough to not need caching." },
              { i: 4, t: "Clients reconnect on disconnect — broadcaster doesn't need to replay buffered events." },
              { i: 5, t: "events table can take a new broadcast_id column without migration drama." },
            ].map(a => (
              <div key={a.i}
                   className={`im-bullet ${pinnedAssump[a.i] ? "pinned" : ""}`}
                   onClick={(e) => { e.stopPropagation(); setPinnedAssump({ ...pinnedAssump, [a.i]: !pinnedAssump[a.i] }); }}>
                <span className="im-pin">✓</span>
                <span>{a.t}</span>
              </div>
            ))}
          </div>

          <div>
            <div className="im-col-h">OUT OF SCOPE</div>
            <div className="im-bullet dim"><span className="b">—</span><span>Persisting broadcaster state across server restarts.</span></div>
            <div className="im-bullet dim"><span className="b">—</span><span>Authenticating SSE connections (handled upstream by /auth).</span></div>
            <div className="im-bullet dim"><span className="b">—</span><span>Rate-limiting per-client; assume trusted internal users for v1.</span></div>
            <div className="im-bullet dim"><span className="b">—</span><span>Backfilling broadcast_id for historical events.</span></div>
          </div>
        </div>
      </section>

      {/* ── SECTION 2 · ARCHITECTURE ─────────────── */}
      <section className="im-section">
        <div className="im-section-label"><span className="idx">02</span>ARCHITECTURE</div>

        <div className="im-toolbar">
          <div className="im-legend">
            <span className="sw"><span className="swatch" style={{ background: "rgba(120,180,140,.2)", borderColor: "var(--im-new)" }}/><span>NEW</span></span>
            <span className="sw"><span className="swatch" style={{ background: "rgba(212,165,116,.2)", borderColor: "var(--im-mod)" }}/><span>MODIFIED</span></span>
            <span className="sw"><span className="swatch" style={{ background: "transparent", borderColor: "var(--im-still)" }}/><span>UNCHANGED</span></span>
          </div>
          <div className="im-tools">
            <button>RE-LAYOUT</button>
            <button>FIT</button>
            <button>−</button>
            <button>+</button>
          </div>
        </div>

        <div className="im-canvas-wrap">
          <div className="im-canvas">
            {/* Clusters */}
            {clusters.map(c => (
              <div key={c.label} className="im-cluster" style={{ left: c.x, top: c.y, width: c.w, height: c.h }}>
                <div className="im-cluster-label">{c.label}</div>
              </div>
            ))}

            {/* Edges */}
            <svg style={{ position: "absolute", inset: 0, width: "100%", height: "100%", pointerEvents: "none" }}>
              <defs>
                <marker id="arrow-new" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                  <path d="M0,0 L10,5 L0,10 z" fill="var(--im-new)"/>
                </marker>
                <marker id="arrow-mod" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                  <path d="M0,0 L10,5 L0,10 z" fill="var(--im-mod)"/>
                </marker>
                <marker id="arrow-still" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
                  <path d="M0,0 L10,5 L0,10 z" fill="var(--im-still)"/>
                </marker>
              </defs>
              {edges.map((e, i) => {
                const a = getNode(e.from), b = getNode(e.to);
                if (!a || !b) return null;
                const stroke = e.state === "new" ? "var(--im-new)" : e.state === "modified" ? "var(--im-mod)" : "var(--im-still)";
                return (
                  <path key={i}
                        d={edgePath(a, b)}
                        fill="none"
                        stroke={stroke}
                        strokeWidth="1.4"
                        strokeDasharray={e.state === "new" ? "5 3" : "none"}
                        markerEnd={`url(#arrow-${e.state === "new" ? "new" : e.state === "modified" ? "mod" : "still"})`}/>
                );
              })}
            </svg>

            {/* Edge labels (positioned at midpoints) */}
            {edges.map((e, i) => {
              const a = getNode(e.from), b = getNode(e.to);
              if (!a || !b) return null;
              const ax = a.x + 70, ay = a.y + 30, bx = b.x + 70, by = b.y + 30;
              const mx = (ax + bx) / 2 - 30;
              const my = (ay + by) / 2 - 6;
              return (
                <span key={i} className={`im-edge-label ${e.state}`} style={{ left: mx, top: my }}>{e.label}</span>
              );
            })}

            {/* Nodes */}
            {nodes.map(n => (
              <div key={n.id}
                   className={`im-node ${n.state} ${openNode === n.id ? "selected" : ""}`}
                   style={{ left: n.x, top: n.y }}
                   onClick={() => setOpenNode(n.id)}>
                <span className="flag">
                  {n.state === "new" && "NEW"}
                  {n.state === "modified" && (n.delta || "MOD")}
                  {n.state === "unchanged" && "—"}
                </span>
                <div className="label">{n.label}</div>
                <div className="type">{n.type}</div>
              </div>
            ))}

            {/* Detail panel */}
            {sel && (
              <>
                <div className="im-detail-scrim" onClick={() => setOpenNode(null)}/>
                <div className="im-detail">
                  <button className="im-detail-close" onClick={() => setOpenNode(null)}>×</button>
                  <div className="tag">{sel.state.toUpperCase()} · {sel.type.toUpperCase()}</div>
                  <h2>{sel.label}</h2>
                  <div className="path">src/server/services/ws-broadcaster.ts</div>

                  <h4>RESPONSIBILITY</h4>
                  <p>
                    Single source of truth for broadcasting session events to subscribed clients.
                    Bridges the per-session event stream to a Redis pubsub channel so any number of /events
                    consumers (live or replay) see the same ordering.
                  </p>

                  <h4>CONTRACT</h4>
                  <div className="contract">
{`class `}<span className="m">WsBroadcaster</span>{` {
  `}<span className="m">subscribe</span>{`(sessionId: `}<span className="t">string</span>{`):
    `}<span className="t">AsyncIterable&lt;Event&gt;</span>{`
  `}<span className="m">publish</span>{`(sessionId: `}<span className="t">string</span>{`,
    event: `}<span className="t">Event</span>{`): `}<span className="t">Promise&lt;void&gt;</span>{`
  `}<span className="m">close</span>{`(sessionId: `}<span className="t">string</span>{`): `}<span className="t">void</span>{`
}`}
                  </div>

                  <h4>SIDE EFFECTS</h4>
                  <ul className="fx">
                    <li>publishes to Redis channel <span className="path">ccm:events:&lt;sessionId&gt;</span></li>
                    <li>writes broadcast_id into events table on each publish</li>
                    <li>holds an in-memory subscriber map; cleans up on close()</li>
                  </ul>

                  <div className="im-detail-actions">
                    <button>Comment</button>
                    <button disabled>Edit contract</button>
                  </div>
                </div>
              </>
            )}
          </div>
        </div>
      </section>

      {/* ── SECTION 3 · EXECUTION ────────────────── */}
      <section className="im-section">
        <div className="im-section-label"><span className="idx">03</span>EXECUTION</div>

        <div className="im-exec-head">
          <h3 style={{ fontFamily: "var(--font-display)", fontWeight: 400, fontSize: 22, margin: 0, color: "var(--im-ink-0)", letterSpacing: "-.01em" }}>
            Eleven steps across four phases.
          </h3>
          <div className="im-exec-stats">
            <b>11</b> steps · <b>~92</b> tool calls · est <b>55 min</b> · <span style={{ color: "var(--im-accent)" }}>3 done · 1 running</span>
          </div>
        </div>

        <div>
          {tasks.map((t, i) => (
            <div key={i} className={`im-task ${t.status}`}>
              <div className="marker"/>
              <div>
                <div className="task-name">{t.name}</div>
                <div className="task-meta">
                  <span className="file">{t.file}</span>
                  <span className="phase">{t.phase}</span>
                </div>
              </div>
              <div className="im-chips">
                {t.tools.map(([tool, n], j) => (
                  <span key={j} className="im-chip">{tool}<b>×{n}</b></span>
                ))}
              </div>
              <div className={`im-status ${t.status}`}>{t.status}</div>
            </div>
          ))}
        </div>
      </section>

      {/* ── STICKY ACTION BAR ────────────────────── */}
      <div className="im-actionbar">
        <div className="im-hint">
          <span style={{ color: "var(--im-ink-1)" }}>4 phases · agent pauses after each</span>
          <span style={{ margin: "0 14px", color: "var(--im-ink-3)" }}>·</span>
          <kbd>⌘</kbd><kbd>↵</kbd> approve <span style={{ margin: "0 10px", color: "var(--im-ink-3)" }}>·</span>
          <kbd>⌘</kbd><kbd>R</kbd> replan <span style={{ margin: "0 10px", color: "var(--im-ink-3)" }}>·</span>
          <kbd>C</kbd> comment
        </div>
        <div className="im-actions">
          <button className="ghost">Comment</button>
          <button className="reject">Reject & replan</button>
          <button className="approve">Approve plan</button>
        </div>
      </div>
    </div>
  );
}

window.S21_IntentMirror = S21_IntentMirror;
