(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 }) {
// "hide debug output" is a skip, not a dimmer — a hidden debug row
// still occupies no DOM node at all, same as it never streamed, rather
// than a CSS `display: none` that would keep it in the layout/DOM for
// no benefit.
if (row.level === "debug" && getHideDebugPref()) return null;
const cssClass = "level-" + row.level;
const icon = row.icon != null && row.icon !== "" && (
{row.icon}
);
if (row.body == null) {
return (
{icon}
{/* Explicit wrapper, not bare children — the row's grid places
`.row-content` in its content column by class, not by
auto-placement inference (an icon-less row's lone child would
otherwise auto-place into the icon column instead). */}
{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)}
)}
);
}