Subagents take their instructions from the agent that started them.
@@ -457,6 +462,65 @@ function SubagentBar({
);
}
+/**
+ * Lets the rows of a long conversation skip rendering while off screen (`data-measured`, see
+ * the CSS), so showing the tab again or restyling it costs what is on screen, not the whole
+ * history. A row skips only once it was laid out: it keeps its real height, and scrolling up
+ * doesn't jump (WebKit has no scroll anchoring). A new width lays them out again once it
+ * settles; Find in Conversation (`enabled` false) needs every row laid out.
+ */
+function useOffscreenRows(ref: RefObject
, visible: boolean, enabled: boolean) {
+ // The width the marked rows were laid out at.
+ const width = useRef(0);
+ useEffect(() => {
+ const el = ref.current;
+ if (!el) return;
+ const unmark = () => {
+ for (const row of el.querySelectorAll(`:scope > .${styles.conversationRow}[data-measured]`)) {
+ row.removeAttribute("data-measured");
+ }
+ };
+ if (!enabled) return unmark();
+ if (!visible) return;
+ let frame = 0;
+ let timer: number | undefined;
+ const mark = () => {
+ cancelAnimationFrame(frame);
+ // The next frame lays the new rows out (and remembers their size); mark them after it.
+ frame = requestAnimationFrame(() => {
+ frame = requestAnimationFrame(() => {
+ for (const row of el.querySelectorAll(`:scope > .${styles.conversationRow}:not([data-measured])`)) {
+ row.setAttribute("data-measured", "");
+ }
+ });
+ });
+ };
+ const remeasure = () => {
+ if (el.clientWidth === width.current) return;
+ width.current = el.clientWidth;
+ unmark();
+ };
+ remeasure();
+ mark();
+ const rows = new MutationObserver(mark);
+ rows.observe(el, { childList: true });
+ const resize = new ResizeObserver(() => {
+ window.clearTimeout(timer);
+ timer = window.setTimeout(() => {
+ remeasure();
+ mark();
+ }, 200);
+ });
+ resize.observe(el);
+ return () => {
+ cancelAnimationFrame(frame);
+ window.clearTimeout(timer);
+ rows.disconnect();
+ resize.disconnect();
+ };
+ }, [ref, visible, enabled]);
+}
+
interface StreamProps {
task: AgentTask;
entries: AgentEntry[] | null;
@@ -540,6 +604,7 @@ function Stream({ task, entries, subagent, onOpenSubagent, finding, findFocus, o
}, [start]);
// Hidden behind another tab, it has no layout to scroll; shown again, it follows the end again.
const visible = useEditorShown();
+ useOffscreenRows(ref, visible, !finding);
/** A diagram that drew after the last streamed chunk grows the conversation: keep following. */
const onDiagramDrawn = useCallback(() => {
const el = ref.current;
@@ -1015,19 +1080,23 @@ const Entry = memo(function Entry({
case "thought":
return (
-
-
- {working ? (
-
- ) : (
-
- )}
- {working ? "Thinking" : "Reasoning"}
-
+
+ {working ? (
+
+ ) : (
+
+ )}
+ {working ? "Thinking" : "Reasoning"}
+
+ }
+ >
-
+
);
case "tool":
@@ -1383,14 +1452,18 @@ const ProcessRun = memo(function ProcessRun({
tools.length > 0 ? `${tools.length} ${tx(tools.length === 1 ? "tool call" : "tool calls")}` : null,
].filter(Boolean);
return (
-
-
-
- {working && }
- {parts.join(" · ")}
- {added > 0 && +{added}}
- {removed > 0 && −{removed}}
-
+
+
+ {working && }
+ {parts.join(" · ")}
+ {added > 0 && +{added}}
+ {removed > 0 && −{removed}}
+
+ }
+ >
{row.entries.map(({ index, entry }) =>
entry.type === "tool" ? (
@@ -1400,10 +1473,38 @@ const ProcessRun = memo(function ProcessRun({
),
)}
-
+
);
});
+/** Set while Find in Conversation is open: folded sections render their content, so it is found. */
+const FoldedContent = createContext(false);
+
+/**
+ * A collapsed section that renders its content only once opened: most of a long conversation
+ * stays folded, and keeping it out of the DOM keeps showing and restyling the tab cheap.
+ */
+function Fold({
+ className,
+ open,
+ summary,
+ children,
+}: {
+ className?: string;
+ open?: boolean;
+ summary: ReactElement;
+ children: ReactNode;
+}) {
+ const always = useContext(FoldedContent);
+ const [expanded, setExpanded] = useState(!!open);
+ return (
+ setExpanded(e.currentTarget.open)}>
+ {summary}
+ {(expanded || open || always) && children}
+
+ );
+}
+
const ToolEntry = memo(function ToolEntry({
task,
entry,
@@ -1443,13 +1544,18 @@ const ToolEntry = memo(function ToolEntry({
);
return (
-
-
-
- {head}
-
+
+
+ {head}
+
+ }
+ >
{entry.output}
-
+
);
});
@@ -1532,22 +1638,30 @@ function SubagentCard({
)}
{entry.prompt && (
-