From 2e2b2f8de7a9218c087ab2d1cb840a246a8b9ca6 Mon Sep 17 00:00:00 2001 From: Adam Firestone Date: Mon, 10 Aug 2026 15:14:34 -0500 Subject: [PATCH] fix(web): suspend end-scroll maintenance when expanding command output rows Route the command-output disclosure through the same settling path as turn folds and work-group toggles. Expanding a row at pinned bottom no longer lets LegendList schedule a compensating end scroll against a stale render window, which left the rows below blank until the next re-render. The library-patch port from round 1 stays: it fixes a real adjacent bug but was not the cause here. Codex (gpt-5.6-sol) via Claude Code --- .../src/components/chat/MessagesTimeline.tsx | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/apps/web/src/components/chat/MessagesTimeline.tsx b/apps/web/src/components/chat/MessagesTimeline.tsx index a3530005902a..3af949baea4d 100644 --- a/apps/web/src/components/chat/MessagesTimeline.tsx +++ b/apps/web/src/components/chat/MessagesTimeline.tsx @@ -143,6 +143,7 @@ interface TimelineRowSharedState { onOpenTurnDiff: (turnId: TurnId, filePath?: string) => void; onToggleTurnFold: (turnId: TurnId) => void; onToggleWorkGroup: (groupId: string, anchorKey: string) => void; + onToggleWorkEntryDisclosure: (anchorKey: string) => void; agentPanelModel: AgentPanelModel; onOpenAgents: () => void; } @@ -516,6 +517,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ onOpenTurnDiff, onToggleTurnFold, onToggleWorkGroup, + onToggleWorkEntryDisclosure: suspendEndScrollMaintenanceForDisclosure, agentPanelModel, onOpenAgents, }), @@ -532,6 +534,7 @@ export const MessagesTimeline = memo(function MessagesTimeline({ onOpenTurnDiff, onToggleTurnFold, onToggleWorkGroup, + suspendEndScrollMaintenanceForDisclosure, agentPanelModel, onOpenAgents, ], @@ -945,7 +948,9 @@ const TimelineRowContent = memo(function TimelineRowContent({ row }: { row: Time data-message-id={row.kind === "message" ? row.message.id : undefined} data-message-role={row.kind === "message" ? row.message.role : undefined} > - {row.kind === "work" ? : null} + {row.kind === "work" ? ( + + ) : null} {row.kind === "work-toggle" ? : null} {row.kind === "turn-fold" ? : null} {row.kind === "message" && row.message.role === "user" ? : null} @@ -1346,8 +1351,10 @@ function WorkingTimer({ createdAt }: { createdAt: string }) { /** Renders one or more already-derived work log rows. Overflow expansion is modeled as LegendList data. */ const WorkGroupSection = memo(function WorkGroupSection({ + rowId, groupedEntries, }: { + rowId: string; groupedEntries: Extract["groupedEntries"]; }) { const { workspaceRoot } = use(TimelineRowCtx); @@ -1373,6 +1380,7 @@ const WorkGroupSection = memo(function WorkGroupSection({ {nonEmptyEntries.map((workEntry) => ( @@ -2469,22 +2477,25 @@ const AgentSpawnCtaRow = memo(function AgentSpawnCtaRow(props: { workEntry: Time }); const SimpleWorkEntryRow = memo(function SimpleWorkEntryRow(props: { + rowId: string; workEntry: TimelineWorkEntry; workspaceRoot: string | undefined; }) { - const { workEntry, workspaceRoot } = props; + const { rowId, workEntry, workspaceRoot } = props; // Before any hooks: spawn CTA rows render their own component. if (workEntry.agentSpawn) { return ; } - return ; + return ; }); const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { + rowId: string; workEntry: TimelineWorkEntry; workspaceRoot: string | undefined; }) { - const { workEntry, workspaceRoot } = props; + const { rowId, workEntry, workspaceRoot } = props; + const { onToggleWorkEntryDisclosure } = use(TimelineRowCtx); const activity = use(TimelineRowActivityCtx); const [expanded, setExpanded] = useState(false); const iconConfig = workToneIcon(workEntry.tone); @@ -2524,16 +2535,20 @@ const PlainWorkEntryRow = memo(function PlainWorkEntryRow(props: { const showSuccessIndicator = workEntryIndicatesToolSuccess(workEntry) || (turnSettled && workEntryIndicatesToolNeutralStatus(workEntry)); + const toggleExpanded = () => { + onToggleWorkEntryDisclosure(rowId); + setExpanded((value) => !value); + }; const rowToggleProps = canExpand ? { role: "button" as const, tabIndex: 0 as const, "aria-label": displayText, - onClick: () => setExpanded((v) => !v), + onClick: toggleExpanded, onKeyDown: (e: KeyboardEvent) => { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); - setExpanded((v) => !v); + toggleExpanded(); } }, }