@@ -88,9 +88,11 @@ const getDeviceIdFromEvent = (e: EventItem) => {
8888const WebhookInspector : React . FC < Props > = ( { userPath, selectedDeviceId = null , selectedBayId = null , clearSignal } ) => {
8989 const [ allEvents , setAllEvents ] = React . useState < EventItem [ ] > ( [ ] ) ;
9090 const [ connected , setConnected ] = React . useState ( false ) ;
91+ const [ isLoadingEvents , setIsLoadingEvents ] = React . useState ( false ) ;
9192 const [ selectedIndex , setSelectedIndex ] = React . useState < number | null > ( null ) ;
9293 const [ selectedEventTypes , setSelectedEventTypes ] = React . useState < Set < string > > ( new Set ( ) ) ;
9394 const [ showEventTypeDropdown , setShowEventTypeDropdown ] = React . useState ( false ) ;
95+ const [ newEventIds , setNewEventIds ] = React . useState < Set < string > > ( new Set ( ) ) ;
9496 const listRef = React . useRef < HTMLUListElement | null > ( null ) ;
9597 const listContainerRef = React . useRef < HTMLDivElement | null > ( null ) ;
9698 const dropdownRef = React . useRef < HTMLDivElement | null > ( null ) ;
@@ -106,6 +108,7 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedDeviceId = null,
106108 React . useEffect ( ( ) => {
107109 if ( ! userPath ) return ;
108110 let cancelled = false ;
111+ setIsLoadingEvents ( true ) ;
109112 ( async ( ) => {
110113 try {
111114 const r = await fetch ( `/api/webhook/${ encodeURIComponent ( userPath ) } /events` ) ;
@@ -126,6 +129,8 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedDeviceId = null,
126129 }
127130 } catch ( err ) {
128131 console . warn ( 'Failed to load events' , err ) ;
132+ } finally {
133+ if ( ! cancelled ) setIsLoadingEvents ( false ) ;
129134 }
130135 } ) ( ) ;
131136 return ( ) => { cancelled = true ; } ;
@@ -157,6 +162,19 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedDeviceId = null,
157162 console . log ( '[SSE] Parsed event:' , data . eventType , 'id:' , data . id ) ;
158163 const newItem : EventItem = { id : data . id , eventType : data . eventType , timestamp : data . timestamp , data : data . data , raw : data . raw , expanded : false } ;
159164
165+ // Mark this event as new for the glow animation
166+ if ( newItem . id ) {
167+ setNewEventIds ( prev => new Set ( prev ) . add ( newItem . id ! ) ) ;
168+ // Remove the "new" flag after 500ms
169+ setTimeout ( ( ) => {
170+ setNewEventIds ( prev => {
171+ const next = new Set ( prev ) ;
172+ next . delete ( newItem . id ! ) ;
173+ return next ;
174+ } ) ;
175+ } , 500 ) ;
176+ }
177+
160178 // Process SessionInfo events to extract activity/course information
161179 if ( data . eventType === 'TPS.SessionInfo' ) {
162180 console . log ( '[SSE] Processing SessionInfo event' ) ;
@@ -584,7 +602,14 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedDeviceId = null,
584602 < div ref = { listContainerRef } className = "webhook-inspector-list" tabIndex = { 0 } onKeyDown = { onListKeyDown } >
585603 < div className = "webhook-events-header" >
586604 < strong > Events</ strong >
587- < span className = { `webhook-events-status ${ connected ? 'live' : '' } ` } > { connected ? 'live' : 'disconnected' } </ span >
605+ < span className = { `webhook-events-status ${ connected ? 'live' : '' } ` } >
606+ { isLoadingEvents ? (
607+ < span className = "loading-spinner" >
608+ < span className = "loading-spinner-icon" > </ span >
609+ < span className = "loading-text" > loading</ span >
610+ </ span >
611+ ) : ( connected ? 'live' : 'disconnected' ) }
612+ </ span >
588613
589614 { /* Event Type Filter Dropdown */ }
590615 < div className = "event-type-filter" ref = { dropdownRef } >
@@ -627,7 +652,12 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedDeviceId = null,
627652 </ div >
628653 </ div >
629654 < ul className = "webhook-events-ul" ref = { listRef } >
630- { visibleEvents . length === 0 ? (
655+ { isLoadingEvents ? (
656+ < li className = "loading-events-message" >
657+ < div className = "loading-events-spinner" > </ div >
658+ < div > Loading events...</ div >
659+ </ li >
660+ ) : visibleEvents . length === 0 ? (
631661 < li className = "no-events" > No events yet.</ li >
632662 ) : (
633663 allEvents . map ( ( e , allIdx ) => {
@@ -641,8 +671,14 @@ const WebhookInspector: React.FC<Props> = ({ userPath, selectedDeviceId = null,
641671 const customerColor = getColorForId ( customerSessionId , customerSessionColors ) ;
642672 const activityColor = getColorForId ( activitySessionId , activitySessionColors ) ;
643673
674+ const isNew = e . id && newEventIds . has ( e . id ) ;
675+
644676 return (
645- < li key = { e . id || allIdx } className = { `webhook-event-item ${ selectedIndex === visibleIdx ? 'selected' : '' } ` } onClick = { ( ) => select ( visibleIdx ) } >
677+ < li
678+ key = { e . id || allIdx }
679+ className = { `webhook-event-item ${ selectedIndex === visibleIdx ? 'selected' : '' } ${ isNew ? 'new-event' : '' } ` }
680+ onClick = { ( ) => select ( visibleIdx ) }
681+ >
646682 < div className = "event-type" > { e . eventType } </ div >
647683 < div className = "event-meta" > { new Date ( e . timestamp ) . toLocaleString ( ) } </ div >
648684 < div className = "event-session-indicators" >
0 commit comments