From cd30c2d9b5daa13352953038b3b5f16e10dc412b Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Wed, 25 Mar 2026 19:12:08 +0100 Subject: [PATCH] fix: match log/timeline height, DESC sort with scroll-to-top Give logCard the same max-height and flex layout as timelineCard so both columns are equal height. Revert .toReversed() so events stay in DESC order (newest at top). Override EventFeed's auto-scroll-to- bottom with a requestAnimationFrame that resets scrollTop to 0 after mount, keeping newest entries visible at the top of both panels. Co-Authored-By: Claude Opus 4.6 (1M context) --- ui/src/pages/AgentHealth/AgentHealth.tsx | 18 ++++++++++++++--- .../AgentInstance/AgentInstance.module.css | 3 +++ ui/src/pages/AgentInstance/AgentInstance.tsx | 20 +++++++++++++++---- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/ui/src/pages/AgentHealth/AgentHealth.tsx b/ui/src/pages/AgentHealth/AgentHealth.tsx index 96c5aa93..adfc778c 100644 --- a/ui/src/pages/AgentHealth/AgentHealth.tsx +++ b/ui/src/pages/AgentHealth/AgentHealth.tsx @@ -1,4 +1,4 @@ -import { useState, useMemo } from 'react'; +import { useState, useMemo, useRef, useEffect } from 'react'; import { useParams, Link } from 'react-router'; import { StatCard, StatusDot, Badge, MonoText, ProgressBar, @@ -224,6 +224,18 @@ export default function AgentHealth() { const { appId } = useParams(); const { data: agents } = useAgents(undefined, appId); const { data: events } = useAgentEvents(appId); + const timelineRef = useRef(null); + + // Override EventFeed's auto-scroll-to-bottom so newest (DESC) events stay visible at top + useEffect(() => { + const el = timelineRef.current; + if (!el) return; + const timer = requestAnimationFrame(() => { + const list = el.querySelector('[aria-label="Event feed"]') as HTMLElement | null; + if (list) list.scrollTop = 0; + }); + return () => cancelAnimationFrame(timer); + }, [events]); const [selectedInstance, setSelectedInstance] = useState(null); const [panelOpen, setPanelOpen] = useState(false); @@ -256,7 +268,7 @@ export default function AgentHealth() { : ('running' as const), message: `${e.agentId}: ${e.eventType}${e.detail ? ' \u2014 ' + e.detail : ''}`, timestamp: new Date(e.timestamp), - })).toReversed(), + })), [events], ); @@ -500,7 +512,7 @@ export default function AgentHealth() { {/* EventFeed */} {feedEvents.length > 0 && ( -
+
Timeline {feedEvents.length} events diff --git a/ui/src/pages/AgentInstance/AgentInstance.module.css b/ui/src/pages/AgentInstance/AgentInstance.module.css index f1ec0d5b..2027ec17 100644 --- a/ui/src/pages/AgentInstance/AgentInstance.module.css +++ b/ui/src/pages/AgentInstance/AgentInstance.module.css @@ -132,6 +132,9 @@ border-radius: var(--radius-lg); box-shadow: var(--shadow-card); overflow: hidden; + display: flex; + flex-direction: column; + max-height: 420px; } .logHeader { diff --git a/ui/src/pages/AgentInstance/AgentInstance.tsx b/ui/src/pages/AgentInstance/AgentInstance.tsx index 2004ec5f..338edbaa 100644 --- a/ui/src/pages/AgentInstance/AgentInstance.tsx +++ b/ui/src/pages/AgentInstance/AgentInstance.tsx @@ -1,4 +1,4 @@ -import { useMemo, useState } from 'react'; +import { useMemo, useState, useRef, useEffect } from 'react'; import { useParams, Link } from 'react-router'; import { StatCard, StatusDot, Badge, LineChart, AreaChart, BarChart, @@ -36,10 +36,23 @@ export default function AgentInstance() { const timeFrom = timeRange.start.toISOString(); const timeTo = timeRange.end.toISOString(); + const timelineRef = useRef(null); + const { data: agents, isLoading } = useAgents(undefined, appId); const { data: events } = useAgentEvents(appId, instanceId); const { data: timeseries } = useStatsTimeseries(timeFrom, timeTo, undefined, appId); + // Override EventFeed's auto-scroll-to-bottom so newest (DESC) events stay visible at top + useEffect(() => { + const el = timelineRef.current; + if (!el) return; + const timer = requestAnimationFrame(() => { + const list = el.querySelector('[aria-label="Event feed"]') as HTMLElement | null; + if (list) list.scrollTop = 0; + }); + return () => cancelAnimationFrame(timer); + }, [events]); + const agent = useMemo( () => (agents || []).find((a: any) => a.id === instanceId) as any, [agents, instanceId], @@ -90,8 +103,7 @@ export default function AgentInstance() { : ('running' as const), message: `${e.eventType}${e.detail ? ' \u2014 ' + e.detail : ''}`, timestamp: new Date(e.timestamp), - })) - .toReversed(), + })), [events, instanceId], ); @@ -436,7 +448,7 @@ export default function AgentInstance() { )}
-
+
Timeline {feedEvents.length} events