// Issue Detail + Agent Execution panel (dialog + terminal variants)

// ---- Issue Detail ----
const IssueDetail = ({ lang, layout = 'drawer', panelStyle = 'dialog' }) => {
  const [id, setId] = React.useState(() => window.__currentIssue || 'ATL-412');
  const [switcherOpen, setSwitcherOpen] = React.useState(false);
  const iss = ISSUES.find(i => i.id === id) || ISSUES[0];
  const proj = PROJECTS.find(p => p.id === iss.proj);

  // Siblings: same project. Include archived only when viewing an archived issue.
  const siblings = React.useMemo(() => {
    const all = ISSUES.filter(i => i.proj === iss.proj);
    return iss.status === 'archived' ? all : all.filter(i => i.status !== 'archived');
  }, [iss.proj, iss.status]);
  const idx = siblings.findIndex(i => i.id === iss.id);
  const prev = idx > 0 ? siblings[idx - 1] : null;
  const next = idx >= 0 && idx < siblings.length - 1 ? siblings[idx + 1] : null;

  const jump = React.useCallback((target) => {
    if (!target) return;
    setId(target.id);
    window.__currentIssue = target.id;
    setSwitcherOpen(false);
  }, []);

  // Keyboard: J/K or ←/→ to navigate siblings, Esc closes switcher
  React.useEffect(() => {
    const onKey = (e) => {
      const tag = (e.target && e.target.tagName) || '';
      if (tag === 'INPUT' || tag === 'TEXTAREA' || e.target?.isContentEditable) return;
      if (e.key === 'Escape' && switcherOpen) { setSwitcherOpen(false); return; }
      if (e.key === 'j' || e.key === 'ArrowDown') { e.preventDefault(); jump(next); }
      else if (e.key === 'k' || e.key === 'ArrowUp') { e.preventDefault(); jump(prev); }
    };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, [prev, next, switcherOpen, jump]);

  // Click-outside for switcher
  React.useEffect(() => {
    if (!switcherOpen) return;
    const onDoc = (e) => {
      if (!e.target.closest('.issue-switcher')) setSwitcherOpen(false);
    };
    document.addEventListener('mousedown', onDoc);
    return () => document.removeEventListener('mousedown', onDoc);
  }, [switcherOpen]);

  const body = (
    <>
      <div className="issue-head">
        <div className="issue-id-row">
          <span style={{width:8,height:8,borderRadius:2,background:proj.color}} />
          <span>{proj.name}</span>
          <Icon name="chevRight" size={11} className="dim" />
          <span>{iss.id}</span>
          <span className="spacer" />
          <span className={`chip chip-${iss.status} dot`}>{iss.status[0].toUpperCase()+iss.status.slice(1)}</span>
          {iss.status === 'working' && iss.eta && (
            <span className="chip chip-working" style={{marginLeft:4}}>
              <span className="running-pulse" style={{width:5,height:5}}/> ETA {iss.eta}
            </span>
          )}
        </div>
        <div className="issue-title">{iss.title}</div>
        <div style={{display:'flex',gap:8,marginTop:12,alignItems:'center'}}>
          <AssigneeChip a={iss.assignee} />
          <span className="dim" style={{fontSize:11.5}}>·</span>
          <span style={{fontSize:11.5,color:'var(--fg-2)'}}>Opened by <strong style={{fontWeight:500}}>Rita Chen</strong> · 14:03 · #3 of 4 this week</span>
          <span className="spacer" />
          <button className="ibtn bordered"><Icon name="link" size={12} /> Copy link</button>
          <button className="ibtn bordered"><Icon name="dots" size={14} /></button>
        </div>
      </div>

      {layout === 'full' && (
        <div style={{padding:'0 32px',maxWidth:920,margin:'0 auto'}}>
          <div className="issue-side-inline">
            <SideKV label="Status"><span className={`chip chip-${iss.status} dot`}>{iss.status}</span></SideKV>
            <SideKV label="Priority"><span style={{color:iss.priority==='urgent'?'var(--status-blocked)':'var(--status-working)',fontWeight:500}}>{iss.priority}</span></SideKV>
            <SideKV label="Assignee"><AssigneeChip a={iss.assignee} size="sm" /></SideKV>
            <SideKV label="Engine"><span className="chip chip-claude">Claude sonnet-4.5</span></SideKV>
            <SideKV label="Skill"><span className="chip"><Icon name="sparkles" size={10} /> API endpoint scaffold</span></SideKV>
          </div>
        </div>
      )}

      <div className="issue-body">
        {iss.id === 'ATL-412' ? (<>
          <p>When a client requests <code>POST /v1/completions</code> with <code>stream: true</code>, the gateway currently buffers the entire upstream response before returning. For long-running generations this exceeds our edge worker's 30s wall clock, so requests are terminated mid-stream.</p>
          <h2>Acceptance criteria</h2>
          <ul>
            <li>Stream upstream SSE chunks to the client unmodified, including heartbeat comments</li>
            <li>Preserve rate-limit headers on the <em>first</em> event only</li>
            <li>Fallback to buffered mode when <code>stream: false</code></li>
            <li>Add integration tests covering 2xx, 4xx, and mid-stream 5xx scenarios</li>
          </ul>
          <h2>Execution plan</h2>
          <p>Agent drafted the following plan at 13:59:</p>
          <div style={{padding:'12px 14px',background:'var(--bg-2)',borderRadius:8,fontSize:12.5,lineHeight:1.6}}>
            <ol style={{margin:0,paddingLeft:18}}>
              <li>Replace <code>response.text()</code> with <code>response.body.getReader()</code> in <code>gateway/src/stream.ts</code></li>
              <li>Wire a <code>TransformStream</code> to pass through SSE chunks</li>
              <li>Update rate-limit middleware to emit headers only on initial chunk</li>
              <li>Add three integration tests in <code>gateway/test/stream.test.ts</code></li>
            </ol>
          </div>
          <h2>Links</h2>
          <div style={{display:'flex',flexDirection:'column',gap:6}}>
            <a style={{fontSize:12.5,color:'var(--accent)',display:'inline-flex',alignItems:'center',gap:6}}><Icon name="gitBranch" size={12}/> feature/atl-412-streaming</a>
            <a style={{fontSize:12.5,color:'var(--accent)',display:'inline-flex',alignItems:'center',gap:6}}><Icon name="mergeReq" size={12}/> PR #284 — ready for review</a>
            <a style={{fontSize:12.5,color:'var(--accent)',display:'inline-flex',alignItems:'center',gap:6}}><Icon name="link" size={12}/> Related: ATL-388, ATL-401</a>
          </div>
        </>) : (<>
          <p style={{color:'var(--fg-3)'}}>{iss.body || `${iss.title}. No detailed description yet — leave a comment below or @mention an operator to start work.`}</p>
          {iss.labels && iss.labels.length > 0 && (<>
            <h2>Labels</h2>
            <div style={{display:'flex',gap:6,flexWrap:'wrap'}}>
              {iss.labels.map(l => <span key={l} className="chip" style={{fontSize:11}}>{l}</span>)}
            </div>
          </>)}
          <h2>Activity</h2>
          <div style={{padding:'12px 14px',background:'var(--bg-2)',borderRadius:8,fontSize:12.5,lineHeight:1.7,color:'var(--fg-3)'}}>
            <div>· Opened {iss.updated || 'recently'} · status <strong style={{color:'var(--fg-1)'}}>{iss.status}</strong></div>
            {iss.assignee && <div>· Assigned to <strong style={{color:'var(--fg-1)'}}>{iss.assignee.name || iss.assignee.handle}</strong></div>}
            <div>· Priority <strong style={{color:'var(--fg-1)',textTransform:'capitalize'}}>{iss.priority}</strong></div>
          </div>
        </>)}
      </div>

      <AgentPanel panelStyle={panelStyle} />
    </>
  );

  // Split-layout sibling rail (left column) — collapsed by default, expands on hover
  const renderSiblingRail = () => (
    <aside className="issue-rail" aria-label="Sibling issues">
      {/* Collapsed strip */}
      <div className="issue-rail-collapsed" aria-hidden="true">
        <span className="issue-rail-pos mono">{idx+1}/{siblings.length}</span>
        <div className="issue-rail-dots">
          {siblings.map(s => (
            <button
              key={s.id}
              className={`issue-rail-dot ${s.id === iss.id ? 'on' : ''}`}
              onClick={() => jump(s)}
              title={`${s.id} · ${s.title}`}
              tabIndex={-1}
            />
          ))}
        </div>
        <span className="issue-rail-hint">Issues</span>
      </div>
      {/* Expanded panel */}
      <div className="issue-rail-expanded">
        <div className="issue-rail-head">
          <div style={{display:'flex',alignItems:'center',gap:8,minWidth:0}}>
            <span style={{width:10,height:10,borderRadius:2,background:proj.color,flexShrink:0}} />
            <span style={{fontSize:13,fontWeight:500,overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{proj.name}</span>
          </div>
          <span className="mono dim" style={{fontSize:11}}>{idx+1}/{siblings.length}</span>
        </div>
        <div className="issue-rail-sub">
          <span>Open issues in this project</span>
        </div>
        <div className="issue-rail-list">
          {siblings.map(s => {
            const active = s.id === iss.id;
            return (
              <button
                key={s.id}
                className={`issue-rail-row ${active?'on':''}`}
                onClick={() => jump(s)}
              >
                <span className={`priority-icon priority-${s.priority==='urgent'?'high':s.priority}`}><i/><i/><i/></span>
                <span className="mono" style={{fontSize:10.5,color:'var(--fg-4)',flexShrink:0,minWidth:46}}>{s.id}</span>
                <span className="issue-rail-title">{s.title}</span>
                <span className={`issue-rail-status chip-${s.status} dot`} />
              </button>
            );
          })}
        </div>
        <div className="issue-rail-foot">
          <span className="mono" style={{fontSize:10.5}}>↑↓</span>
          <span>navigate</span>
        </div>
      </div>
    </aside>
  );

  if (layout === 'full') {
    return <div className="fade-in issue-full" style={{overflow:'auto',height:'100%'}}>{body}</div>;
  }

  if (layout === 'split') {
    return (
      <div className="issue-split fade-in">
        {renderSiblingRail()}
        <div style={{flex:1,minWidth:0,overflow:'auto'}}>
          <div className="issue-main">{body}</div>
        </div>
      </div>
    );
  }

  return (
    <div className="issue-layout fade-in">
      <div className="issue-main">{body}</div>
      <div className="issue-side">
        <SideBlock label="Status"><span className={`chip chip-${iss.status} dot`}>{iss.status[0].toUpperCase()+iss.status.slice(1)}</span></SideBlock>
        <SideBlock label="Priority"><span className="row"><span className={`priority-icon priority-${iss.priority==='urgent'?'high':iss.priority}`}><i/><i/><i/></span><span style={{color:iss.priority==='urgent'?'var(--status-blocked)':'var(--status-working)',textTransform:'capitalize',fontSize:12.5,fontWeight:500}}>{iss.priority}</span></span></SideBlock>
        <SideBlock label="Assignee"><AssigneeChip a={iss.assignee} /></SideBlock>
        <SideBlock label="Engine"><span className="chip chip-claude"><span style={{width:6,height:6,borderRadius:1,background:'var(--engine-claude)'}}/> Claude sonnet-4.5</span></SideBlock>
        <SideBlock label="Skill"><span className="chip"><Icon name="sparkles" size={10} /> API endpoint scaffold</span></SideBlock>
        <SideBlock label="Labels">
          <div style={{display:'flex',gap:4,flexWrap:'wrap'}}>
            {iss.labels.map(l => <span key={l} className="chip">{l}</span>)}
          </div>
        </SideBlock>
        <SideBlock label="Project">
          <span className="row"><span style={{width:10,height:10,borderRadius:2,background:proj.color}}/><span style={{fontSize:12.5}}>{proj.name}</span></span>
        </SideBlock>
        <SideBlock label="Branch">
          <span className="mono" style={{fontSize:11.5,color:'var(--accent)'}}>feat/atl-412-streaming</span>
        </SideBlock>
        <SideBlock label="Cycle">
          <span className="mono" style={{fontSize:11.5}}>18m 32s · ETA 18m</span>
        </SideBlock>
        <SideBlock label="Timeline">
          <div style={{fontSize:11.5,color:'var(--fg-2)',display:'flex',flexDirection:'column',gap:6,lineHeight:1.4}}>
            <div><span className="mono dim" style={{marginRight:6}}>14:03</span>Opened by Rita</div>
            <div><span className="mono dim" style={{marginRight:6}}>14:04</span>Assigned to @claude/sonnet</div>
            <div><span className="mono dim" style={{marginRight:6}}>14:04</span>Skill: API endpoint scaffold</div>
            <div><span className="mono dim" style={{marginRight:6}}>14:05</span>Execution started</div>
            <div style={{color:'var(--accent)',fontWeight:500}}><span className="mono dim" style={{marginRight:6}}>14:18</span>In progress…</div>
          </div>
        </SideBlock>
      </div>
    </div>
  );
};

const SideBlock = ({ label, children }) => (
  <div className="side-block">
    <div className="side-label">{label}</div>
    <div className="side-value">{children}</div>
  </div>
);
const SideKV = ({ label, children }) => (
  <div>
    <div className="side-label">{label}</div>
    <div style={{fontSize:12.5,marginTop:4,textTransform:'capitalize'}}>{children}</div>
  </div>
);

// ---- Agent Execution Panel ----
const AgentPanel = ({ panelStyle }) => {
  const [tab, setTab] = useState('chat');
  return (
    <div className="agent-panel">
      <div className="agent-tabs">
        <div className={`agent-tab ${tab==='chat'?'active':''}`} onClick={() => setTab('chat')}>
          <Icon name="sparkles" size={12} /> Agent <span className="mono dim" style={{fontSize:10.5}}>sonnet</span>
        </div>
        <div className={`agent-tab ${tab==='diff'?'active':''}`} onClick={() => setTab('diff')}>
          <Icon name="diff" size={12} /> Diff <span className="mono dim" style={{fontSize:10.5}}>+42 −7</span>
        </div>
        <div className={`agent-tab ${tab==='terminal'?'active':''}`} onClick={() => setTab('terminal')}>
          <Icon name="terminal" size={12} /> Terminal
        </div>
        <div className={`agent-tab ${tab==='tests'?'active':''}`} onClick={() => setTab('tests')}>
          <Icon name="check" size={12} /> Tests <span className="mono dim" style={{fontSize:10.5}}>128/130</span>
        </div>
        <div className="agent-status">
          <span className="running-pulse" />
          <span>Running · 13m 24s</span>
          <button className="ibtn" style={{color:'var(--status-blocked)'}}><Icon name="stop" size={12} /></button>
        </div>
      </div>
      <div className="agent-body">
        {tab === 'chat' && (panelStyle === 'terminal' ? <AgentTerminal /> : <AgentStream />)}
        {tab === 'diff' && <DiffView />}
        {tab === 'terminal' && <AgentTerminal />}
        {tab === 'tests' && <TestsView />}
      </div>
      {tab === 'chat' && (
        <div className="agent-input">
          <Icon name="sparkles" size={13} className="dim" />
          <input placeholder="Send a follow-up… (@mention other agents, / for commands)" />
          <button className="ibtn bordered"><Icon name="mic" size={13} /></button>
          <button className="ibtn accent"><Icon name="send" size={13} /> Send</button>
        </div>
      )}
    </div>
  );
};

// Dialog-style stream: message bubbles + tool calls
const AgentStream = () => {
  const [showFinalTool, setShowFinalTool] = useState(false);
  useEffect(() => {
    const to = setTimeout(() => setShowFinalTool(true), 1400);
    return () => clearTimeout(to);
  }, []);
  return (
    <div className="stream">
      <StreamMsg who="user">
        <div className="msg-bubble user">
          Please implement streaming for /v1/completions per the plan. Preserve rate-limit headers only on first event.
        </div>
      </StreamMsg>
      <StreamMsg who="agent" engine="claude">
        <div className="msg-bubble">
          I'll start by reading the existing gateway stream module, then patch in a TransformStream-based passthrough. Running 4 steps.
        </div>
      </StreamMsg>
      <StreamMsg who="agent" engine="claude">
        <div className="tool-call">
          <div className="tool-head">
            <Icon name="file" size={12} className="dim"/>
            <span className="tool-name">Read</span>
            <span className="tool-args">apps/gateway/src/stream.ts · 0–240</span>
            <span className="tool-status" style={{color:'var(--status-done)'}}>✓ 8.2 KB</span>
          </div>
        </div>
      </StreamMsg>
      <StreamMsg who="agent" engine="claude">
        <div className="tool-call">
          <div className="tool-head">
            <Icon name="edit" size={12} className="dim"/>
            <span className="tool-name">Edit</span>
            <span className="tool-args">apps/gateway/src/stream.ts</span>
            <span className="tool-status" style={{color:'var(--status-done)'}}>✓ +28 −5</span>
          </div>
          <div className="tool-body">{`-  const body = await response.text();
-  return new Response(body, { headers });
+  const reader = response.body!.getReader();
+  const transform = new TransformStream({
+    transform(chunk, controller) {
+      controller.enqueue(chunk);
+    },
+  });
+  pipeSSE(reader, transform.writable);
+  return new Response(transform.readable, { headers });`}</div>
        </div>
      </StreamMsg>
      <StreamMsg who="agent" engine="claude">
        <div className="tool-call">
          <div className="tool-head">
            <Icon name="terminal" size={12} className="dim"/>
            <span className="tool-name">Bash</span>
            <span className="tool-args">pnpm --filter gateway test stream</span>
            <span className="tool-status" style={{color:'var(--status-working)',display:'inline-flex',alignItems:'center',gap:5}}>
              <span className="running-pulse" style={{width:5,height:5}}/> running
            </span>
          </div>
          <div className="tool-body">{`Test Files  2 passed
Tests       8 passed, 2 pending
Duration    2.1s
  ✓ stream.test.ts > proxies sse chunks
  ✓ stream.test.ts > preserves rate-limit on first event
  ⟳ stream.test.ts > falls back to buffered mode...`}</div>
        </div>
      </StreamMsg>
      {showFinalTool && (
        <StreamMsg who="agent" engine="claude">
          <div className="msg-bubble">
            Tests passing. Drafting PR description and opening #284. ETA 18m for review-ready state.
          </div>
        </StreamMsg>
      )}
    </div>
  );
};

const StreamMsg = ({ who, engine, children }) => {
  let avatar;
  if (who === 'user') {
    avatar = <div className="avatar" style={{width:22,height:22,fontSize:10}}>XM</div>;
  } else {
    const letter = engine === 'claude' ? 'C' : engine === 'codex' ? 'O' : 'G';
    avatar = <div className={`avatar agent ${engine}`} style={{width:22,height:22,fontSize:10,borderWidth:0}}>{letter}</div>;
  }
  return (
    <div className="stream-msg">
      <div>{avatar}</div>
      <div style={{display:'flex',flexDirection:'column',gap:4}}>{children}</div>
    </div>
  );
};

const AgentTerminal = () => (
  <div className="terminal" style={{borderRadius:8,margin:'-12px -16px -4px',padding:'14px 16px'}}>
    <div className="term-line"><span className="term-prompt">claude ⚡</span><span className="term-com">start --issue ATL-412 --skill endpoint-scaffold</span></div>
    <div className="term-line"><span className="term-dim">→</span><span className="term-dim">session a8f3…c104 · model claude-sonnet-4.5 · workspace ~/src/multicast-gateway</span></div>
    <div className="term-line"><span className="term-tool">[plan]</span><span>4 steps · est. 18m · confidence 0.82</span></div>
    <br/>
    <div className="term-line"><span className="term-tool">[tool]</span><span className="term-com">Read</span><span className="term-dim">apps/gateway/src/stream.ts 0..240</span><span className="term-ok">✓ 8.2 KB</span></div>
    <div className="term-line"><span className="term-tool">[tool]</span><span className="term-com">Grep</span><span className="term-dim">"response.body" in apps/gateway</span><span className="term-ok">✓ 12 matches in 4 files</span></div>
    <div className="term-line"><span className="term-tool">[tool]</span><span className="term-com">Edit</span><span className="term-dim">apps/gateway/src/stream.ts</span><span className="term-ok">✓</span> <span className="term-add">+28</span> <span className="term-del">-5</span></div>
    <div className="term-line"><span className="term-tool">[tool]</span><span className="term-com">Edit</span><span className="term-dim">apps/gateway/src/rate-limit.ts</span><span className="term-ok">✓</span> <span className="term-add">+14</span> <span className="term-del">-2</span></div>
    <br/>
    <div className="term-line"><span className="term-tool">[bash]</span><span>$ pnpm --filter gateway test stream</span></div>
    <div className="term-line"><span className="term-dim">  ✓ stream.test.ts &gt; proxies sse chunks <span className="term-dim">(14ms)</span></span></div>
    <div className="term-line"><span className="term-dim">  ✓ stream.test.ts &gt; preserves rate-limit on first event <span className="term-dim">(8ms)</span></span></div>
    <div className="term-line"><span className="term-dim">  ✓ stream.test.ts &gt; drops rate-limit on subsequent events <span className="term-dim">(6ms)</span></span></div>
    <div className="term-line"><span className="term-warn">  ⟳ stream.test.ts &gt; falls back to buffered mode on stream:false</span></div>
    <br/>
    <div className="term-line"><span className="term-tool">[usage]</span><span className="term-dim">in 28,412 · out 4,821 · cost $0.187 · elapsed 13m 24s</span></div>
    <div className="term-line"><span className="term-prompt">⏵</span><span style={{color:'var(--accent)'}}>│</span></div>
  </div>
);

const DiffView = () => (
  <div>
    <div className="diff">
      <div className="diff-head">
        <Icon name="file" size={13} className="dim" />
        <span className="diff-path mono">apps/gateway/src/stream.ts</span>
        <span className="diff-stats mono">
          <span style={{color:'var(--syntax-add-fg)'}}>+28</span> <span style={{color:'var(--syntax-del-fg)'}}>−5</span>
        </span>
      </div>
      <div className="diff-body">
        {[
          { hunk: '@@ -42,5 +42,28 @@ export async function proxy(req: Request) {' },
          { ln: '42', type: ' ', text: '  const response = await fetch(upstream, { headers, body });' },
          { ln: '43', type: ' ', text: '  const headers = sanitize(response.headers);' },
          { ln: '44', type: '-', text: '  const body = await response.text();' },
          { ln: '45', type: '-', text: '  return new Response(body, { headers });' },
          { ln: '44', type: '+', text: '  if (!req.body?.stream) {' },
          { ln: '45', type: '+', text: '    const body = await response.text();' },
          { ln: '46', type: '+', text: '    return new Response(body, { headers });' },
          { ln: '47', type: '+', text: '  }' },
          { ln: '48', type: '+', text: '  const reader = response.body!.getReader();' },
          { ln: '49', type: '+', text: '  const transform = new TransformStream<Uint8Array>({' },
          { ln: '50', type: '+', text: '    transform(chunk, controller) {' },
          { ln: '51', type: '+', text: '      controller.enqueue(chunk);' },
          { ln: '52', type: '+', text: '    },' },
          { ln: '53', type: '+', text: '  });' },
          { ln: '54', type: '+', text: '  pipeSSE(reader, transform.writable);' },
          { ln: '55', type: '+', text: '  return new Response(transform.readable, { headers });' },
        ].map((l, i) => (
          l.hunk
            ? <div key={i} className="diff-line hunk"><span className="diff-ln" /><span className="diff-ln" /><span>{l.hunk}</span></div>
            : <div key={i} className={`diff-line ${l.type==='+'?'add':l.type==='-'?'del':''}`}>
                <span className="diff-ln">{l.type!=='+'?l.ln:''}</span>
                <span className="diff-ln">{l.type!=='-'?l.ln:''}</span>
                <span>{l.type} {l.text.slice(2)}</span>
              </div>
        ))}
      </div>
    </div>

    <div className="diff">
      <div className="diff-head">
        <Icon name="file" size={13} className="dim" />
        <span className="diff-path mono">apps/gateway/test/stream.test.ts</span>
        <span className="diff-stats mono">
          <span style={{color:'var(--syntax-add-fg)'}}>+48</span> <span style={{color:'var(--syntax-del-fg)'}}>−0</span> · new file
        </span>
      </div>
      <div className="diff-body">
        {[
          { ln: '1', type: '+', text: 'import { describe, it, expect } from "vitest";' },
          { ln: '2', type: '+', text: 'import { proxy } from "../src/stream";' },
          { ln: '3', type: '+', text: '' },
          { ln: '4', type: '+', text: 'describe("proxy → /v1/completions", () => {' },
          { ln: '5', type: '+', text: '  it("proxies sse chunks unmodified", async () => {' },
          { ln: '6', type: '+', text: '    const res = await proxy(mockSSERequest());' },
          { ln: '7', type: '+', text: '    const chunks = await collectSSE(res);' },
          { ln: '8', type: '+', text: '    expect(chunks).toHaveLength(5);' },
          { ln: '9', type: '+', text: '  });' },
          { ln: '10', type: '+', text: '});' },
        ].map((l, i) => (
          <div key={i} className={`diff-line ${l.type==='+'?'add':''}`}>
            <span className="diff-ln"></span>
            <span className="diff-ln">{l.ln}</span>
            <span>{l.type} {l.text}</span>
          </div>
        ))}
      </div>
    </div>
  </div>
);

const TestsView = () => (
  <div style={{padding: '4px 0'}}>
    {[
      { name: 'proxies sse chunks unmodified', status: 'pass', ms: 14 },
      { name: 'preserves rate-limit on first event', status: 'pass', ms: 8 },
      { name: 'drops rate-limit on subsequent events', status: 'pass', ms: 6 },
      { name: 'falls back to buffered mode on stream:false', status: 'run', ms: null },
      { name: 'handles mid-stream 5xx', status: 'pending', ms: null },
      { name: 'heartbeat comments pass through', status: 'pending', ms: null },
    ].map((t, i) => (
      <div key={i} style={{display:'grid',gridTemplateColumns:'22px 1fr 80px 60px',gap:10,padding:'7px 2px',borderBottom:'1px solid var(--border-subtle)',alignItems:'center',fontSize:12.5}}>
        <div>
          {t.status === 'pass' && <span style={{color:'var(--status-done)'}}>✓</span>}
          {t.status === 'run' && <span className="running-pulse" style={{display:'inline-block'}}/>}
          {t.status === 'pending' && <span className="dim">○</span>}
        </div>
        <div className="mono" style={{fontSize:12}}>{t.name}</div>
        <div className="mono dim" style={{fontSize:11,textAlign:'right'}}>{t.ms ? `${t.ms}ms` : '—'}</div>
        <div style={{textAlign:'right'}}>
          <span className={`chip ${t.status==='pass'?'chip-done':t.status==='run'?'chip-working':''} dot`} style={{fontSize:10}}>
            {t.status}
          </span>
        </div>
      </div>
    ))}
  </div>
);

// ---- Project Detail — configuration-focused ----
// Projects are CONFIG for how issues in the project should be created, tagged, and routed.
// The issues list lives on the Issues page; we don't duplicate it here.

// Per-project label palette (derived from actual issue labels, then a stable color pick)
const LABEL_COLORS = {
  api: '#3D63FF', streaming: '#06B6D4', security: '#E5484D', bug: '#E5484D',
  edge: '#10A37F', billing: '#8B5CF6', webhooks: '#F59E0B', charts: '#06B6D4',
  infra: '#64748B', kv: '#10A37F', deploy: '#8B5CF6', scim: '#3D63FF',
  tax: '#F59E0B', perf: '#E5484D', design: '#EC4899', sso: '#3D63FF',
};
const labelColor = (name) => LABEL_COLORS[name.toLowerCase()] || '#64748B';

// Inline editable text cell — click to edit, blur to commit
const InlineField = ({ value, onChange, placeholder, multiline }) => {
  const [v, setV] = React.useState(value);
  const [editing, setEditing] = React.useState(false);
  React.useEffect(() => { setV(value); }, [value]);
  const commit = () => { setEditing(false); if (v !== value) onChange && onChange(v); };
  if (editing) {
    const Tag = multiline ? 'textarea' : 'input';
    return (
      <Tag
        className={multiline ? 'pd-field pd-field-multiline' : 'pd-field'}
        value={v}
        autoFocus
        placeholder={placeholder}
        onChange={e => setV(e.target.value)}
        onBlur={commit}
        onKeyDown={e => { if (e.key === 'Enter' && !multiline) { e.target.blur(); } if (e.key === 'Escape') { setV(value); setEditing(false); } }}
      />
    );
  }
  return (
    <div className={`pd-field-display ${!value ? 'empty' : ''}`} onClick={() => setEditing(true)}>
      {value || <span className="dim">{placeholder}</span>}
    </div>
  );
};

const ProjectDetail = ({ onNav }) => {
  const id = window.__currentProj || 'ATLAS';
  const proj = PROJECTS.find(p => p.id === id) || PROJECTS[0];
  const projIssues = ISSUES.filter(i => i.proj === id);
  const node = NODES.find(n => n.id === proj.nodeId);
  const nodeEngines = new Set((node?.engines || []).map(e => e.id));

  // Project-scoped config (local state — pretend-persist)
  const [name, setName] = React.useState(proj.name);
  const [slug, setSlug] = React.useState(proj.slug);
  const [desc, setDesc] = React.useState(proj.desc);
  const [repo, setRepo] = React.useState(`git@github.com:bks/${proj.slug}.git`);
  const [branch, setBranch] = React.useState('main');
  const defaultWorkdir = (NODES.find(n => n.id === proj.nodeId)?.kind === 'local')
    ? `~/code/${proj.slug}`
    : `/workspaces/${proj.slug}`;
  const [workdir, setWorkdir] = React.useState(defaultWorkdir);
  React.useEffect(() => { setWorkdir(defaultWorkdir); }, [id]);
  const [color, setColor] = React.useState(proj.color);

  // Derive an initial label set from this project's issues
  const initialLabels = React.useMemo(() => {
    const seen = new Set();
    const out = [];
    projIssues.forEach(i => (i.labels || []).forEach(l => { if (!seen.has(l)) { seen.add(l); out.push({ name: l, color: labelColor(l) }); } }));
    // Always seed a few defaults
    ['bug','feature','docs','chore'].forEach(l => { if (!seen.has(l)) { seen.add(l); out.push({ name: l, color: labelColor(l) }); } });
    return out;
  }, [id]);
  const [labels, setLabels] = React.useState(initialLabels);
  React.useEffect(() => { setLabels(initialLabels); }, [initialLabels]);
  const [newLabel, setNewLabel] = React.useState('');

  // Capability config: enabled + role per capability (engine derived from role)
  const defaultCaps = React.useMemo(() => {
    const map = {};
    CAPABILITIES.forEach(c => {
      map[c.id] = { enabled: true, operatorId: c.defaultOperator };
    });
    return map;
  }, [proj.id]);
  const [capCfg, setCapCfg] = React.useState(defaultCaps);
  React.useEffect(() => { setCapCfg(defaultCaps); }, [proj.id, defaultCaps]);
  const setCapEnabled = (cid, on) => setCapCfg(prev => ({ ...prev, [cid]: { ...prev[cid], enabled: on }}));
  const setCapRole = (cid, rid) => setCapCfg(prev => ({ ...prev, [cid]: { ...prev[cid], operatorId: rid }}));
  const [openRolePicker, setOpenRolePicker] = React.useState(null);

  // Project-custom capabilities — augment the global list per project
  const [customCaps, setCustomCaps] = React.useState([]);
  React.useEffect(() => { setCustomCaps([]); }, [proj.id]);
  const [addingCap, setAddingCap] = React.useState(false);
  const [draftCap, setDraftCap] = React.useState({ label: '', desc: '', icon: 'puzzle', operatorId: OPERATORS[0].id });
  const addCustomCap = () => {
    if (!draftCap.label.trim()) return;
    const id = 'cap-' + Math.random().toString(36).slice(2, 7);
    const cap = { id, label: draftCap.label.trim(), desc: draftCap.desc.trim() || 'Project-specific work type', icon: draftCap.icon, defaultOperator: draftCap.operatorId, custom: true };
    setCustomCaps(prev => [...prev, cap]);
    setCapCfg(prev => ({ ...prev, [id]: { enabled: true, operatorId: draftCap.operatorId } }));
    setDraftCap({ label: '', desc: '', icon: 'puzzle', operatorId: OPERATORS[0].id });
    setAddingCap(false);
  };
  const removeCustomCap = (cid) => {
    setCustomCaps(prev => prev.filter(c => c.id !== cid));
    setCapCfg(prev => { const n = { ...prev }; delete n[cid]; return n; });
  };
  const allCaps = [...CAPABILITIES, ...customCaps];

  // Automation rules — simple "when X then Y" list
  const [rules, setRules] = React.useState([
    { id: 'r1', when: 'title contains "bug"',    then: 'set capability to Bug fix',    on: true },
    { id: 'r2', when: 'label = security',        then: 'assign reviewer Ren Chao',     on: true },
    { id: 'r3', when: 'capability = Docs',       then: 'default engine Gemini',        on: false },
  ]);
  const toggleRule = (rid) => setRules(rules.map(r => r.id === rid ? { ...r, on: !r.on } : r));

  // Members derived from AGENTS_BY_ENGINE.human filtered by this project
  const members = (AGENTS_BY_ENGINE.human || []).filter(h => h.projects.includes(proj.id));

  const PD_TABS = [
    { id: 'issues',       icon: 'issue',       label: 'Issues',       desc: 'Issue tracker for this project' },
    { id: 'overview',     icon: 'project',     label: 'Overview',     desc: 'Name, slug, description, repo' },
    { id: 'capabilities', icon: 'puzzle',      label: 'Capabilities', desc: 'Work types & default operators' },
    { id: 'automation',   icon: 'bolt',        label: 'Automation',   desc: 'Rules that auto-tag or route issues' },
    { id: 'members',      icon: 'team',        label: 'Members',      desc: 'Who has access to this project' },
    { id: 'settings',     icon: 'settings',    label: 'Settings',     desc: 'Danger zone — archive, delete' },
  ];
  const [tab, setTab] = React.useState(null); // null = no modal; otherwise section id
  const [configOpen, setConfigOpen] = React.useState(false);
  const configRef = React.useRef(null);
  React.useEffect(() => { setTab(null); }, [proj.id]);
  React.useEffect(() => {
    if (!configOpen) return;
    const onDoc = (e) => { if (configRef.current && !configRef.current.contains(e.target)) setConfigOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setConfigOpen(false); };
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); };
  }, [configOpen]);
  const activeTab = PD_TABS.find(t => t.id === tab);

  // Palette for color picker
  const PALETTE = ['#3D63FF','#8B5CF6','#E8A317','#00A870','#E5484D','#10A37F','#06B6D4','#EC4899','#F59E0B','#64748B'];

  const removeLabel = (n) => setLabels(labels.filter(l => l.name !== n));
  const addLabel = () => {
    const v = newLabel.trim().toLowerCase();
    if (!v) return;
    if (labels.some(l => l.name === v)) { setNewLabel(''); return; }
    setLabels([...labels, { name: v, color: labelColor(v) }]);
    setNewLabel('');
  };

  const openIssueCount = projIssues.filter(i=>i.status!=='archived').length;
  const capsEnabledCount = Object.values(capCfg).filter(c=>c.enabled).length;
  const automationOnCount = rules.filter(r=>r.on).length;

  const [projSwitchOpen, setProjSwitchOpen] = React.useState(false);
  const projSwitchRef = React.useRef(null);
  React.useEffect(() => {
    if (!projSwitchOpen) return;
    const onDoc = (e) => { if (projSwitchRef.current && !projSwitchRef.current.contains(e.target)) setProjSwitchOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setProjSwitchOpen(false); };
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); };
  }, [projSwitchOpen]);

  return (
    <div className="fade-in pd-scroll project-detail" style={{height:'100%',overflow:'auto',display:'flex',flexDirection:'column'}}>
      {/* Slim toolbar — project switcher + section switcher + stats, like Issues page */}
      <div className="pd-toolbar">
        <div className="pd-proj-switch" ref={projSwitchRef}>
          <button className="pd-proj-pill" onClick={() => setProjSwitchOpen(o => !o)} aria-expanded={projSwitchOpen}>
            <span className="proj-icon" style={{background:color,width:22,height:22,fontSize:10,borderRadius:6}}>{proj.id.slice(0,2)}</span>
            <span className="pd-proj-pill-name">{name}</span>
            <span className="mono dim" style={{fontSize:11}}>{slug}</span>
            <Icon name="chevDown" size={11} className="dim" />
          </button>
          {projSwitchOpen && (
            <div className="pd-proj-pop" role="menu">
              <div className="pd-config-head">Switch project</div>
              {PROJECTS.map(p => (
                <button key={p.id} className={`pd-config-item ${p.id===proj.id?'on':''}`}
                  onClick={() => { window.__currentProj = p.id; setProjSwitchOpen(false); onNav && onNav('project-detail'); }}>
                  <span className="proj-icon" style={{background:p.color,width:22,height:22,fontSize:10,borderRadius:6}}>{p.id.slice(0,2)}</span>
                  <span style={{flex:1,minWidth:0}}>
                    <span className="pd-config-name">{p.name}</span>
                    <span className="pd-config-desc mono">{p.slug}</span>
                  </span>
                  {p.id===proj.id && <Icon name="check" size={12} className="pd-config-check" />}
                </button>
              ))}
            </div>
          )}
        </div>

        {/* Slot for embedded IssuesList toolbar (search + Options) — portaled in by IssuesList */}
        <span className="pd-toolbar-divider" />
        <div id="pd-toolbar-issues-slot" className="pd-toolbar-issues-slot" />

        <span className="spacer" />

        <div className="pd-config-wrap" ref={configRef}>
          <button
            className={`ibtn icon-only pd-settings-btn ${configOpen?'on':''}`}
            onClick={() => setConfigOpen(o => !o)}
            aria-expanded={configOpen}
            title="Project settings"
          >
            <Icon name="settings" size={14} />
          </button>
          {configOpen && (
            <div className="pd-config-pop pd-settings-pop" role="menu" aria-label="Project sections">
              <div className="pd-config-head">Configure project</div>
              {PD_TABS.filter(t => t.id !== 'issues').map(tb => (
                <button
                  key={tb.id}
                  className="pd-config-item"
                  onClick={() => { setTab(tb.id); setConfigOpen(false); }}
                  role="menuitem"
                >
                  <span className="pd-config-icon"><Icon name={tb.icon} size={13} /></span>
                  <span style={{flex:1,minWidth:0}}>
                    <span className="pd-config-name">{tb.label}</span>
                    <span className="pd-config-desc">{tb.desc}</span>
                  </span>
                  <Icon name="chevRight" size={11} className="pd-config-check" />
                </button>
              ))}
            </div>
          )}
        </div>
      </div>

      {/* Issues list is always the main content */}
      <div className="pd-issues-wrap">
        <IssuesList onNav={onNav} scopeProject={proj.id} embedded />
      </div>

      {/* Section modal: opens when tab is set to overview/capabilities/automation/members/settings */}
      {tab && tab !== 'issues' && (
        <div className="pd-modal-overlay" onMouseDown={(e) => { if (e.target === e.currentTarget) setTab(null); }}>
          <div className="pd-modal" role="dialog" aria-label={activeTab?.label}>
            <div className="pd-modal-head">
              <div className="pd-modal-title">
                <Icon name={activeTab?.icon} size={14} />
                <span>{activeTab?.label}</span>
              </div>
              <div className="pd-modal-sub">{activeTab?.desc}</div>
              <button className="ibtn ghost icon-only pd-modal-close" onClick={() => setTab(null)} aria-label="Close">
                <Icon name="close" size={14} />
              </button>
            </div>
            <div className="pd-modal-body">
              <div className={`pd-grid ${tab==='overview' ? 'pd-grid--two' : ''}`} style={{margin:0,padding:0,maxWidth:'none'}}>
                {tab === 'overview' && (<>
        <div className="pd-main">
        {/* General */}
        <section className="pd-section">
          <div className="pd-sec-head">
            <div>
              <div className="pd-sec-title">General</div>
              <div className="pd-sec-sub">Name, slug, description — visible to everyone on this project.</div>
            </div>
          </div>
          <div className="pd-form">
            <div className="pd-row">
              <label className="pd-label">Name</label>
              <InlineField value={name} onChange={setName} placeholder="Project name" />
            </div>
            <div className="pd-row">
              <label className="pd-label">Slug</label>
              <div className="mono" style={{fontSize:12}}>
                <InlineField value={slug} onChange={setSlug} placeholder="project-slug" />
              </div>
            </div>
            <div className="pd-row pd-row--top">
              <label className="pd-label">Description</label>
              <InlineField value={desc} onChange={setDesc} placeholder="What this project is for…" multiline />
            </div>
            <div className="pd-row">
              <label className="pd-label">Accent color</label>
              <div className="pd-color-row">
                {PALETTE.map(c => (
                  <button key={c} className={`pd-swatch ${color===c?'on':''}`} style={{background:c}} onClick={() => setColor(c)} title={c} />
                ))}
              </div>
            </div>
            <div className="pd-row">
              <label className="pd-label">Issue key</label>
              <div style={{display:'flex',alignItems:'center',gap:10}}>
                <span className="mono" style={{fontSize:12,color:'var(--fg-1)',background:'var(--bg-2)',padding:'3px 8px',borderRadius:4}}>{proj.id}-###</span>
                <span className="dim" style={{fontSize:11}}>immutable · derived from project id</span>
              </div>
            </div>
          </div>
        </section>

        {/* Git */}
        <section className="pd-section">
          <div className="pd-sec-head">
            <div>
              <div className="pd-sec-title">Repository</div>
              <div className="pd-sec-sub">Agents clone this repo into the working directory on the bound node.</div>
            </div>
          </div>
          <div className="pd-form">
            <div className="pd-row">
              <label className="pd-label">Remote</label>
              <div className="mono" style={{fontSize:12}}><InlineField value={repo} onChange={setRepo} placeholder="git@host:org/repo.git" /></div>
            </div>
            <div className="pd-row">
              <label className="pd-label">Default branch</label>
              <div className="mono" style={{fontSize:12}}><InlineField value={branch} onChange={setBranch} placeholder="main" /></div>
            </div>
            <div className="pd-row">
              <label className="pd-label">Working dir</label>
              <div className="pd-workdir">
                <Icon name="folder" size={13} className="dim" />
                <div className="mono" style={{fontSize:12,flex:1,minWidth:0}}><InlineField value={workdir} onChange={setWorkdir} placeholder={defaultWorkdir} /></div>
              </div>
            </div>
          </div>
        </section>

        {/* Labels */}
        <section className="pd-section">
          <div className="pd-sec-head">
            <div>
              <div className="pd-sec-title">Labels</div>
              <div className="pd-sec-sub">Tag issues in this project. Available in the label picker on every issue here.</div>
            </div>
            <div className="mono dim" style={{fontSize:11}}>{labels.length} labels</div>
          </div>
          <div className="pd-labels">
            {labels.map(l => (
              <span key={l.name} className="pd-label-chip" style={{background:`color-mix(in oklab, ${l.color} 14%, transparent)`,borderColor:`color-mix(in oklab, ${l.color} 30%, transparent)`,color:l.color}}>
                <span className="pd-label-dot" style={{background:l.color}} />
                {l.name}
                <button className="pd-label-x" onClick={() => removeLabel(l.name)} title="Remove"><Icon name="close" size={10} /></button>
              </span>
            ))}
            <div className="pd-label-add">
              <input
                placeholder="add label…"
                value={newLabel}
                onChange={e => setNewLabel(e.target.value)}
                onKeyDown={e => { if (e.key === 'Enter') addLabel(); }}
              />
              <button className="ibtn ghost" onClick={addLabel} disabled={!newLabel.trim()}><Icon name="plus" size={11} /></button>
            </div>
          </div>
        </section>
        </div>

        {/* Sidebar */}
        <aside className="pd-side">
          <section className="pd-section pd-side-card">
            <div className="pd-side-head">
              <span className="pd-side-eyebrow mono">Bound node</span>
              {node && <span className={`node-dot`} style={{width:7,height:7,borderRadius:'50%',background: node.status==='online'?'var(--status-done)':'var(--status-blocked)',display:'inline-block'}} />}
            </div>
            {node ? (
              <>
                <button className="pd-side-node" onClick={() => { window.__currentNode = node.id; onNav && onNav('node-detail'); }}>
                  <div className={`node-badge ${node.kind || ''}`} style={{width:32,height:32,borderRadius:8,display:'flex',alignItems:'center',justifyContent:'center',background:'var(--bg-2)',border:'1px solid var(--border-subtle)'}}>
                    <Icon name={node.kind==='local'?'terminal':'cloud'} size={14} />
                  </div>
                  <div style={{flex:1,minWidth:0,textAlign:'left'}}>
                    <div className="mono" style={{fontSize:12.5,fontWeight:500}}>{node.id}</div>
                    <div className="dim" style={{fontSize:11}}>{node.region}</div>
                  </div>
                  <Icon name="chevRight" size={11} className="dim" />
                </button>
                <div className="pd-side-engines">
                  <div className="pd-side-eyebrow mono" style={{marginBottom:6}}>Available engines</div>
                  <div className="pd-eng-row">
                    {(node?.engines || []).map(e => (
                      <span key={e.id} className={`engine-chip engine-${e.id}`}>
                        <span className="eng-dot" /> {e.id} <span className="mono dim" style={{fontSize:10,marginLeft:4}}>{e.version}</span>
                      </span>
                    ))}
                    {(!node?.engines || node.engines.length === 0) && <span className="dim" style={{fontSize:11}}>No engines on this node</span>}
                  </div>
                </div>
              </>
            ) : (
              <div className="dim" style={{padding:'8px 0',fontSize:12}}>No node bound. Pick one to start running issues.</div>
            )}
          </section>

          <section className="pd-section pd-side-card">
            <div className="pd-side-head">
              <span className="pd-side-eyebrow mono">Activity (7d)</span>
            </div>
            <div className="pd-side-stat-row">
              <div className="pd-side-stat">
                <div className="pd-side-stat-val">{projIssues.length}</div>
                <div className="pd-side-stat-lbl dim">Issues</div>
              </div>
              <div className="pd-side-stat">
                <div className="pd-side-stat-val">{Math.max(1, Math.round(projIssues.length * 1.4))}</div>
                <div className="pd-side-stat-lbl dim">Runs</div>
              </div>
              <div className="pd-side-stat">
                <div className="pd-side-stat-val">{members.length}</div>
                <div className="pd-side-stat-lbl dim">People</div>
              </div>
            </div>
          </section>

          <section className="pd-section pd-side-card pd-side-quick">
            <div className="pd-side-head">
              <span className="pd-side-eyebrow mono">Quick actions</span>
            </div>
            <button className="pd-side-action" onClick={() => setTab('capabilities')}>
              <Icon name="puzzle" size={13} className="dim" />
              <span>Configure capabilities</span>
              <span className="mono dim" style={{fontSize:11,marginLeft:'auto'}}>{capsEnabledCount}</span>
            </button>
            <button className="pd-side-action" onClick={() => setTab('automation')}>
              <Icon name="bolt" size={13} className="dim" />
              <span>Edit automation rules</span>
              <span className="mono dim" style={{fontSize:11,marginLeft:'auto'}}>{automationOnCount}/{rules.length}</span>
            </button>
            <button className="pd-side-action" onClick={() => setTab('members')}>
              <Icon name="team" size={13} className="dim" />
              <span>Manage members</span>
              <span className="mono dim" style={{fontSize:11,marginLeft:'auto'}}>{members.length}</span>
            </button>
            <button className="pd-side-action" onClick={() => setTab('settings')}>
              <Icon name="settings" size={13} className="dim" />
              <span>Project settings</span>
            </button>
          </section>
        </aside>

        </>)}

        {tab === 'capabilities' && (<>
        {/* Capabilities */}
        <section className="pd-section">
          <div className="pd-sec-head">
            <div>
              <div className="pd-sec-title">Issue capabilities</div>
              <div className="pd-sec-sub">Which types of work this project accepts, and the default engine for each.</div>
            </div>
          </div>
          <div className="pd-cap-list">
            {allCaps.map(c => {
              const cfg = capCfg[c.id] || { enabled: true, operatorId: c.defaultOperator };
              const role = OPERATORS.find(r => r.id === cfg.operatorId) || OPERATORS.find(r => r.id === c.defaultOperator) || OPERATORS[0];
              const compatibleOperators = c.custom
                ? OPERATORS   // custom caps can use any role
                : OPERATORS.filter(r => r.capabilities.includes(c.id));
              const engineAvailable = role && (nodeEngines.has(role.defaultEngine) || role.defaultEngine === 'human');
              return (
                <div key={c.id} className={`pd-cap-row ${cfg.enabled?'':'off'} ${c.custom?'custom':''}`}>
                  <button
                    className={`pd-toggle ${cfg.enabled?'on':''}`}
                    onClick={() => setCapEnabled(c.id, !cfg.enabled)}
                    aria-label={`Toggle ${c.label}`}
                  >
                    <span className="pd-toggle-dot" />
                  </button>
                  <div className="pd-cap-icon"><Icon name={c.icon} size={13} /></div>
                  <div className="pd-cap-text">
                    <div className="pd-cap-name">
                      {c.label}
                      {c.custom && <span className="pd-cap-tag-custom">custom</span>}
                    </div>
                    <div className="pd-cap-desc">{c.desc}</div>
                  </div>
                  <div className="pd-cap-right">
                    <div className="pd-op-picker-wrap">
                      <button
                        className={`pd-op-chip ${!engineAvailable?'warn':''}`}
                        onClick={() => cfg.enabled && setOpenRolePicker(openRolePicker === c.id ? null : c.id)}
                        disabled={!cfg.enabled}
                        title={role ? `${role.name} — ${role.desc}` : 'Pick a role'}
                      >
                        <span className="pd-op-swatch" style={{background: role?.color || '#888'}}>
                          <Icon name={role?.icon || 'puzzle'} size={10} />
                        </span>
                        <span className="pd-op-name">{role?.name || '—'}</span>
                        <span className="pd-op-sep">·</span>
                        <span className={`eng-tag engine-${role?.defaultEngine || 'human'}`}>{role?.defaultEngine || '—'}</span>
                        <Icon name="chevron" size={10} className="dim" />
                      </button>
                      {openRolePicker === c.id && (
                        <div className="pd-op-menu" onMouseLeave={() => setOpenRolePicker(null)}>
                          <div className="pd-op-menu-head">
                            <span>Workflows for {c.label.toLowerCase()}</span>
                            <button className="ibtn ghost" onClick={() => { onNav && onNav('operators'); setOpenRolePicker(null); }}>
                              <Icon name="settings" size={11}/> Manage
                            </button>
                          </div>
                          {compatibleOperators.map(r => {
                            const avail = nodeEngines.has(r.defaultEngine) || r.defaultEngine === 'human';
                            return (
                              <button
                                key={r.id}
                                className={`pd-op-opt ${cfg.operatorId===r.id?'on':''} ${!avail?'unavail':''}`}
                                onClick={() => { setCapRole(c.id, r.id); setOpenRolePicker(null); }}
                              >
                                <span className="pd-op-swatch" style={{background:r.color}}>
                                  <Icon name={r.icon} size={11} />
                                </span>
                                <div style={{flex:1,minWidth:0}}>
                                  <div className="pd-op-opt-name">
                                    {r.name}
                                    {!r.builtIn && <span className="pd-op-tag-custom">custom</span>}
                                  </div>
                                  <div className="pd-op-opt-desc">{r.desc}</div>
                                </div>
                                <span className={`eng-tag engine-${r.defaultEngine}`}>{r.defaultEngine}</span>
                                {!avail && <span className="dim mono" style={{fontSize:10}}>not on node</span>}
                              </button>
                            );
                          })}
                        </div>
                      )}
                    </div>
                    <button className="ibtn ghost pd-edit-prompt" onClick={() => { window.__currentOperator = role?.id; onNav && onNav('operators'); }} title="Edit prompt in Workflows">
                      <Icon name="file" size={11} /> Prompt
                    </button>
                    {c.custom && (
                      <button className="ibtn ghost pd-cap-remove" onClick={() => removeCustomCap(c.id)} title="Remove this custom capability">
                        <Icon name="x" size={11} />
                      </button>
                    )}
                  </div>
                </div>
              );
            })}
            {addingCap ? (
              <div className="pd-cap-add-form">
                <Icon name="plus" size={13} className="dim" />
                <input
                  className="pd-cap-input pd-cap-input-name"
                  placeholder="Capability name (e.g. Migration, Triage, Hotfix)"
                  value={draftCap.label}
                  onChange={(e) => setDraftCap(d => ({ ...d, label: e.target.value }))}
                  autoFocus
                />
                <input
                  className="pd-cap-input pd-cap-input-desc"
                  placeholder="One-line description (optional)"
                  value={draftCap.desc}
                  onChange={(e) => setDraftCap(d => ({ ...d, desc: e.target.value }))}
                />
                <select
                  className="pd-cap-input pd-cap-input-role"
                  value={draftCap.operatorId}
                  onChange={(e) => setDraftCap(d => ({ ...d, operatorId: e.target.value }))}
                >
                  {OPERATORS.map(r => <option key={r.id} value={r.id}>{r.name} · {r.defaultEngine}</option>)}
                </select>
                <div className="pd-cap-add-actions">
                  <button className="ibtn ghost" onClick={() => { setAddingCap(false); setDraftCap({ label: '', desc: '', icon: 'puzzle', operatorId: OPERATORS[0].id }); }}>Cancel</button>
                  <button className="ibtn primary" onClick={addCustomCap} disabled={!draftCap.label.trim()}>Add</button>
                </div>
              </div>
            ) : (
              <button className="pd-cap-add" onClick={() => setAddingCap(true)}>
                <Icon name="plus" size={12} />
                <span>Add custom capability</span>
                <span className="dim" style={{fontSize:10.5,marginLeft:'auto'}}>project-specific work type</span>
              </button>
            )}
          </div>
          <div className="pd-cap-foot">
            <Icon name="info" size={11} className="dim" />
            <span className="dim" style={{fontSize:11}}>Workflows are global. Editing a workflow's prompt updates every project that points to it.</span>
            <button className="ibtn ghost" style={{marginLeft:'auto'}} onClick={() => onNav && onNav('operators')}>
              <Icon name="settings" size={11} /> Manage workflows
            </button>
          </div>
        </section>

        </>)}

        {tab === 'automation' && (<>
        {/* Automation */}
        <section className="pd-section">
          <div className="pd-sec-head">
            <div>
              <div className="pd-sec-title">Automation</div>
              <div className="pd-sec-sub">Rules applied when an issue is created in this project.</div>
            </div>
            <button className="ibtn ghost"><Icon name="plus" size={11} /> Add rule</button>
          </div>
          <div className="pd-rules">
            {rules.map(r => (
              <div key={r.id} className={`pd-rule ${r.on?'':'off'}`}>
                <button className={`pd-toggle ${r.on?'on':''}`} onClick={() => toggleRule(r.id)}><span className="pd-toggle-dot"/></button>
                <span className="mono dim" style={{fontSize:11}}>when</span>
                <span className="pd-rule-clause">{r.when}</span>
                <span className="dim" style={{fontSize:11}}>→</span>
                <span className="mono dim" style={{fontSize:11}}>then</span>
                <span className="pd-rule-clause">{r.then}</span>
                <button className="ibtn ghost" style={{marginLeft:'auto',padding:'3px 6px'}}><Icon name="dots" size={12} /></button>
              </div>
            ))}
          </div>
        </section>

        </>)}

        {tab === 'members' && (<>
        {/* Members */}
        <section className="pd-section">
          <div className="pd-sec-head">
            <div>
              <div className="pd-sec-title">Members</div>
              <div className="pd-sec-sub">Humans on this project. Agents are configured per node — see capabilities above.</div>
            </div>
            <button className="ibtn ghost"><Icon name="plus" size={11} /> Invite</button>
          </div>
          <div className="pd-members">
            {members.map(m => (
              <div key={m.handle} className="pd-member">
                <div className="avatar" style={{background:m.color}}>{m.avatar}</div>
                <div style={{flex:1,minWidth:0}}>
                  <div style={{fontWeight:500,fontSize:13}}>{m.name}</div>
                  <div className="dim" style={{fontSize:11}}>{m.role} · <span className="mono">{m.handle}</span></div>
                </div>
                <span className="mono dim" style={{fontSize:11}}>{m.status}</span>
              </div>
            ))}
          </div>
        </section>

        </>)}

        {tab === 'settings' && (<>
        {/* Danger */}
        <section className="pd-section pd-danger">
          <div className="pd-sec-head">
            <div>
              <div className="pd-sec-title">Danger zone</div>
              <div className="pd-sec-sub">Irreversible actions. Issues are preserved on archive; deleted on delete.</div>
            </div>
          </div>
          <div className="pd-danger-row">
            <button className="ibtn bordered">Archive project</button>
            <button className="ibtn danger">Delete project</button>
          </div>
        </section>
        </>)}
              </div>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

// ---- Activity feed page ----
const ActivityPage = () => (
  <div className="fade-in" style={{display:'flex',flexDirection:'column',height:'100%'}}>
    <div className="page-head">
      <div>
        <div className="page-title">Activity</div>
        <div className="page-sub">Humans + agents · live feed across all projects</div>
      </div>
      <div className="page-actions">
        <button className="ibtn bordered">All <Icon name="chevDown" size={11}/></button>
        <button className="ibtn bordered">Live <span className="running-pulse" style={{marginLeft:4}}/></button>
      </div>
    </div>
    <div style={{flex:1,overflow:'auto',padding:'0 24px',maxWidth:900,margin:'0 auto',width:'100%'}}>
      <div className="timeline" style={{padding:'10px 0'}}>
        {ACTIVITY.concat(ACTIVITY).map((a, i) => (
          <div key={i} className="tl-row" style={{gridTemplateColumns:'60px 1fr'}}>
            <div className="tl-time">{a.t}</div>
            <div className="tl-body">
              <div className="tl-line">
                {a.tag === 'agent' ? (
                  <span className={`avatar agent ${a.engine}`} style={{width:20,height:20,fontSize:10,borderWidth:0}}>
                    {a.engine === 'claude' ? 'C' : a.engine === 'codex' ? 'O' : 'G'}
                  </span>
                ) : (
                  <span className="avatar" style={{width:20,height:20,fontSize:10}}>{a.who.split(' ').map(x=>x[0]).join('').slice(0,2)}</span>
                )}
                <strong>{a.who}</strong>
                <span style={{color:'var(--fg-3)'}}>{a.verb}</span>
                <span className={`tag tag-${a.tag}`}>{a.tag}</span>
                {a.target && <span className="mono" style={{fontSize:11,color:'var(--accent)',marginLeft:'auto'}}>{a.target}</span>}
              </div>
              <div className="tl-line" style={{color:'var(--fg-2)',fontSize:12.5}}>{a.what}</div>
            </div>
          </div>
        ))}
      </div>
    </div>
  </div>
);

Object.assign(window, { IssueDetail, AgentPanel, AgentStream, AgentTerminal, DiffView, TestsView, ProjectDetail, ActivityPage });
