// Renders one `StreamRow` — flat `
` or expandable // `
`, matching @hive/shared/terminal.css's // existing row-kind classes exactly (see docs/terminal-rendering.md). // Reuses that stylesheet as-is (imported once by LiveStream.tsx) — the // taxonomy's visual language isn't what mara asked to change, the // component *model* underneath it is. import { useEffect, useRef } from 'preact/hooks'; import type { StreamRow } from '../lib/streamRow.js'; import { linkifyToNodes } from '../lib/linkify.js'; import { renderMarkdown } from '../lib/markdown.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: StreamRow }) { if (row.details) { return (
{row.icon != null && row.icon !== '' && {row.icon}} {row.text} {row.diffBody != null && } {row.markdownBody != null && } {row.plainBody != null &&
{linkifyToNodes(row.plainBody)}
}
); } return (
{row.icon != null && row.icon !== '' && {row.icon}} {row.text != null && linkifyToNodes(row.text)} {row.meta?.map((m) => ( {m.text} ))} {row.childText != null &&
{row.childText.text}
} {row.markdownBody != null && }
); }