Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ anchor: '@file'
created: 2026-10-16T23:05:41Z
norm: '1'
sig: ce515648b5bedef2
body_hash: 433c39db9d6384fd
raw_hash: ea74d2e9c0710659
vouched: 2026-10-22T22:05:34Z
body_hash: 91ade5f09dcd49cf
raw_hash: 2c9b0b22266f1342
vouched: 2026-10-27T14:07:44Z
lines: 1-912
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ anchor: function itemHeight
created: 2026-10-22T22:05:29Z
norm: '1'
sig: 3c40d8dd5f73026a
body_hash: 446268a0d25debb4
raw_hash: e696d2f48fa62bb6
body_hash: 9b7f5ad1d6fa2633
raw_hash: c051d6140c4c9315
vouched: 2026-10-27T14:07:44Z
lines: 742-831
---

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
id: 01M5YV02MCCK1PGFNC7FJ58XPJ
anchor: function toolGroupOpen
created: 2026-10-27T14:07:31Z
norm: '1'
sig: 609e7680e155462e
body_hash: 97cc55c00fcde4b1
raw_hash: 34d7822a7841af58
lines: 859-861
---

itemHeight and the timeline render must both decide a tool group's openness with toolGroupOpen, or measured heights drift from what renders
2 changes: 1 addition & 1 deletion packages/channel/src/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ async function history(harness: Harness, chat: ConversationId): Promise<EntryRec
entries.push(...page.items);
cursor = page.next;
} while (cursor);
return entries.reverse();
return entries.toReversed();
}

/**
Expand Down
23 changes: 20 additions & 3 deletions packages/ui/src/components/timeline/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ function itemHeight(
let working = content - BUBBLE_PAD_X * 2;
let header = turn.final ? workingHeaderHeight(turn.recap, working) : 0;
let expanded = !turn.final || openWorking.has(group.id);
h += workingSectionHeight(turn.working, blocks, working, open, header, expanded);
h += workingSectionHeight(turn.working, blocks, working, open, header, expanded, !turn.final);
children++;
}

Expand Down Expand Up @@ -837,20 +837,34 @@ function workingSectionHeight(
open: Set<string>,
header: number,
expanded: boolean,
live: boolean,
): number {
let h = BUBBLE_PAD_Y * 2 + BUBBLE_BORDER * 2 + header;
if (!expanded) return h;
let content = 0;
let count = 0;
for (let item of items) {
content += workingHeight(item, blocks, width, open);
content += workingHeight(item, blocks, width, open, live);
count++;
}
if (count > 1) content += (count - 1) * CONTENT_GAP;
if (header && count) content += CONTENT_GAP;
return h + content;
}

/**
* While a turn runs, its tool groups are open so the work stays visible; once it finishes they
* fold. `open` holds the groups someone toggled away from that default, keyed by toolGroupKey.
*/
function toolGroupOpen(id: string, open: ReadonlySet<string>, live: boolean): boolean {
return open.has(toolGroupKey(id, live)) !== live;
}

/** A separate key while live, so closing a running group doesn't open it once the turn folds. */
function toolGroupKey(id: string, live: boolean): string {
return live ? id + ":live" : id;
}

/** The folded turn's label row, below its recap when there is one. */
function workingHeaderHeight(recap: string | undefined, width: number): number {
if (!recap) return WORKING_SUMMARY_HEIGHT;
Expand All @@ -864,6 +878,7 @@ function workingHeight(
blocks: Map<string, Block[]>,
width: number,
open: Set<string>,
live: boolean,
): number {
let b = blocks.get(item.id) || [];
if (item.type === "text") {
Expand All @@ -873,7 +888,7 @@ function workingHeight(
}

let h = TOOL_GROUP_ROW_HEIGHT + TOOL_GROUP_PAD_Y * 2;
if (!open.has(item.id) || !b.length) return h;
if (!toolGroupOpen(item.id, open, live) || !b.length) return h;
h += measureRow(b, width - TOOL_LIST_INDENT).height;
h += b.length * CONTENT_GAP;
return h;
Expand Down Expand Up @@ -910,6 +925,8 @@ export {
TOOL_GROUP_PAD_Y,
TOOL_GROUP_ROW_HEIGHT,
TOOL_LIST_INDENT,
toolGroupKey,
toolGroupOpen,
TURN_GAP,
useBlockMap,
WORKING_SUMMARY_HEIGHT,
Expand Down
6 changes: 4 additions & 2 deletions packages/ui/src/components/timeline/timeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ import {
TOOL_GROUP_PAD_Y,
TOOL_GROUP_ROW_HEIGHT,
TOOL_LIST_INDENT,
toolGroupKey,
toolGroupOpen,
TURN_GAP,
useBlockMap,
WORKING_SUMMARY_HEIGHT,
Expand Down Expand Up @@ -469,7 +471,7 @@ function AgentWorkingContextView(
style={{ gap: CONTENT_GAP }}
>
{turn.working.map((w, index) => {
let open = w.type === "tools" && openGroups.has(w.id);
let open = w.type === "tools" && toolGroupOpen(w.id, openGroups, live);
return (
<AgentWorkingItemView
key={w.id}
Expand All @@ -478,7 +480,7 @@ function AgentWorkingContextView(
width={width}
open={open}
expanded={open}
onToggle={onToggleGroup}
onToggle={id => onToggleGroup(toolGroupKey(id, live))}
live={live}
index={index}
/>
Expand Down