From 904d0063d6e3879af78f32b8b697aae040478a6c Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Fri, 9 Jan 2026 15:49:11 -0800 Subject: [PATCH] Fix lint issues and improve EventViewer component - Fix biome lint formatting issues - Add onActiveIndexChange callback for controlled state - Simplify WebsocketEventDetail to compute message internally - Fix exhaustive deps warning in handleRowClick Co-Authored-By: Claude Opus 4.5 --- src-web/components/HttpResponseTimeline.tsx | 47 +++++++++++++----- src-web/components/WebsocketResponsePane.tsx | 51 +++++--------------- src-web/components/core/AutoScroller.tsx | 2 +- src-web/components/core/EventViewer.tsx | 23 +++++++-- 4 files changed, 69 insertions(+), 54 deletions(-) diff --git a/src-web/components/HttpResponseTimeline.tsx b/src-web/components/HttpResponseTimeline.tsx index be459181..a9946703 100644 --- a/src-web/components/HttpResponseTimeline.tsx +++ b/src-web/components/HttpResponseTimeline.tsx @@ -81,13 +81,13 @@ function EventDetails({ const rawText = formatEventRaw(event.event); return (
- - +
); } @@ -114,7 +114,12 @@ function EventDetails({ if (e.type === 'send_url') { return (
- + @@ -129,7 +134,12 @@ function EventDetails({ if (e.type === 'receive_url') { return (
- + {e.version} @@ -144,7 +154,12 @@ function EventDetails({ if (e.type === 'redirect') { return (
- + @@ -162,7 +177,12 @@ function EventDetails({ if (e.type === 'setting') { return (
- + {e.name} {e.value} @@ -176,7 +196,12 @@ function EventDetails({ const direction = e.type === 'chunk_sent' ? 'Sent' : 'Received'; return (
- +
{formatBytes(e.bytes)}
); diff --git a/src-web/components/WebsocketResponsePane.tsx b/src-web/components/WebsocketResponsePane.tsx index 041fda32..76a704d6 100644 --- a/src-web/components/WebsocketResponsePane.tsx +++ b/src-web/components/WebsocketResponsePane.tsx @@ -31,7 +31,6 @@ interface Props { } export function WebsocketResponsePane({ activeRequest }: Props) { - const [activeEventIndex, setActiveEventIndex] = useState(null); const [showLarge, setShowLarge] = useStateWithDeps(false, [activeRequest.id]); const [showingLarge, setShowingLarge] = useState(false); const [hexDumps, setHexDumps] = useState>({}); @@ -40,26 +39,6 @@ export function WebsocketResponsePane({ activeRequest }: Props) { const connections = useAtomValue(activeWebsocketConnectionsAtom); const events = useWebsocketEvents(activeConnection?.id ?? null); - const activeEvent = useMemo( - () => (activeEventIndex != null ? events[activeEventIndex] : null), - [activeEventIndex, events], - ); - - const hexDump = - hexDumps[activeEventIndex ?? -1] ?? activeEvent?.messageType === 'binary'; - - const message = useMemo(() => { - if (hexDump) { - return activeEvent?.message ? hexy(activeEvent?.message) : ''; - } - return activeEvent?.message - ? new TextDecoder('utf-8').decode(Uint8Array.from(activeEvent.message)) - : ''; - }, [activeEvent?.message, hexDump]); - - const language = languageFromContentType(null, message); - const formattedMessage = useFormatText({ language, text: message, pretty: true }); - if (activeConnection == null) { return ( @@ -101,12 +80,8 @@ export function WebsocketResponsePane({ activeRequest }: Props) { renderDetail={({ event, index }) => ( setHexDumps({ ...hexDumps, [index]: v })} - message={message} - formattedMessage={formattedMessage} - language={language} showLarge={showLarge} showingLarge={showingLarge} setShowLarge={setShowLarge} @@ -133,11 +108,7 @@ function WebsocketEventRow({ : ''; const iconColor = - messageType === 'close' || messageType === 'open' - ? 'secondary' - : isServer - ? 'info' - : 'primary'; + messageType === 'close' || messageType === 'open' ? 'secondary' : isServer ? 'info' : 'primary'; const icon = messageType === 'close' || messageType === 'open' @@ -170,29 +141,31 @@ function WebsocketEventRow({ function WebsocketEventDetail({ event, - index, hexDump, setHexDump, - message, - formattedMessage, - language, showLarge, showingLarge, setShowLarge, setShowingLarge, }: { event: WebsocketEvent; - index: number; hexDump: boolean; setHexDump: (v: boolean) => void; - message: string; - formattedMessage: string | null; - language: string; showLarge: boolean; showingLarge: boolean; setShowLarge: (v: boolean) => void; setShowingLarge: (v: boolean) => void; }) { + const message = useMemo(() => { + if (hexDump) { + return event.message ? hexy(event.message) : ''; + } + return event.message ? new TextDecoder('utf-8').decode(Uint8Array.from(event.message)) : ''; + }, [event.message, hexDump]); + + const language = languageFromContentType(null, message); + const formattedMessage = useFormatText({ language, text: message, pretty: true }); + const title = event.messageType === 'close' ? 'Connection Closed' diff --git a/src-web/components/core/AutoScroller.tsx b/src-web/components/core/AutoScroller.tsx index 017ebd48..f0688192 100644 --- a/src-web/components/core/AutoScroller.tsx +++ b/src-web/components/core/AutoScroller.tsx @@ -1,4 +1,4 @@ -import { type Virtualizer, useVirtualizer } from '@tanstack/react-virtual'; +import { useVirtualizer, type Virtualizer } from '@tanstack/react-virtual'; import type { ReactElement, ReactNode, UIEvent } from 'react'; import { useCallback, useLayoutEffect, useRef, useState } from 'react'; import { IconButton } from './IconButton'; diff --git a/src-web/components/core/EventViewer.tsx b/src-web/components/core/EventViewer.tsx index 3053069e..ff3e8102 100644 --- a/src-web/components/core/EventViewer.tsx +++ b/src-web/components/core/EventViewer.tsx @@ -48,6 +48,9 @@ interface EventViewerProps { /** Message to show when no events */ emptyMessage?: string; + + /** Callback when active index changes (for controlled state in parent) */ + onActiveIndexChange?: (index: number | null) => void; } export function EventViewer({ @@ -63,8 +66,22 @@ export function EventViewer({ isLoading = false, loadingMessage = 'Loading events...', emptyMessage = 'No events recorded', + onActiveIndexChange, }: EventViewerProps) { - const [activeIndex, setActiveIndex] = useState(null); + const [activeIndex, setActiveIndexInternal] = useState(null); + + // Wrap setActiveIndex to notify parent + const setActiveIndex = useCallback( + (indexOrUpdater: number | null | ((prev: number | null) => number | null)) => { + setActiveIndexInternal((prev) => { + const newIndex = + typeof indexOrUpdater === 'function' ? indexOrUpdater(prev) : indexOrUpdater; + onActiveIndexChange?.(newIndex); + return newIndex; + }); + }, + [onActiveIndexChange], + ); const containerRef = useRef(null); const virtualizerRef = useRef | null>(null); @@ -101,7 +118,7 @@ export function EventViewer({ (index: number) => { setActiveIndex((prev) => (prev === index ? null : index)); }, - [], + [setActiveIndex], ); if (isLoading) { @@ -154,7 +171,7 @@ export function EventViewer({
- {renderDetail({ event: activeEvent, index: activeIndex! })} + {renderDetail({ event: activeEvent, index: activeIndex ?? 0 })}
)