// Renders one `TermRow` — flat `
` or expandable // `
`, driven straight off the wire shape // (`lib/termMsg.ts`'s `TermMsg`, mirroring hive-agent's `term_msg.rs`): // `level` picks the colour class, an empty `summary` + markdown `body` // is a flat row with just the body (assistant text), anything else with // a body is an expandable details row gated by the operator's // expand-tool-output preference. No separate classification step — // mara: "StreamRow should now match what the server sends in TermMsg." import { useEffect, useRef } from 'preact/hooks'; import type { TermRow } from '../lib/termMsg.js'; import { linkifyToNodes } from '../lib/linkify.js'; import { renderMarkdown } from '../lib/markdown.js'; import { getExpandDetailsPref } from '@hive/shared/prefs.js'; function MarkdownBody({ text }: { text: string }) { const ref = useRef(null); const html = renderMarkdown(text); useEffect(() => { // marked autolinks URLs but leaves them same-tab — open externally // so a click never navigates the terminal away. ref.current?.querySelectorAll('a[href]').forEach((a) => { a.setAttribute('target', '_blank'); a.setAttribute('rel', 'noopener noreferrer'); }); }, [html]); // eslint-disable-next-line react/no-danger -- sanitized by DOMPurify in renderMarkdown return
; } function DiffBody({ text }: { text: string }) { const lines = String(text).split('\n'); return (
      {lines.map((line, i) => {
        const cls = line.startsWith('+ ') ? 'diff-add' : line.startsWith('- ') ? 'diff-del' : 'diff-ctx';
        return (
          
            {line}
            {'\n'}
          
        );
      })}
    
); } export function Row({ row }: { row: TermRow }) { const cssClass = 'level-' + row.level; const icon = row.icon != null && row.icon !== '' && {row.icon}; if (row.body == null) { return (
{icon} {linkifyToNodes(row.summary)}
); } // Empty summary + markdown body → the body itself is the whole row // (assistant text), no summary prefix line, never collapsible. if (row.body_format === 'markdown' && row.summary === '') { return (
{icon}
); } return (
{icon} {row.summary} {row.body_format === 'diff' && } {row.body_format === 'markdown' && } {row.body_format == null &&
{linkifyToNodes(row.body)}
}
); }