-
Notifications
You must be signed in to change notification settings - Fork 0
refactor(ui): decompose MonitorView into maintainable modules #161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
streamer45
merged 7 commits into
video
from
devin/1773521888-monitor-view-decomposition
Mar 15, 2026
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
975e8ad
refactor(ui): extract utilities and styles from MonitorView
streamkit-devin 9fe01f5
style: fix formatting in nodeIssues.ts
streamkit-devin cbb7d90
refactor(ui): extract component modules from MonitorView
streamkit-devin ef109d1
style: fix formatting in MonitorView.tsx
streamkit-devin 99a73c9
refactor(ui): extract Legend component and useMonitorPreview hook
streamkit-devin 94ee114
refactor(ui): extract useAutoLayout and useNodeStatesSubscription hooks
streamkit-devin c700b5a
fix: address review items — memo comparator, background ternary, fitV…
streamkit-devin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // SPDX-FileCopyrightText: © 2025 StreamKit Contributors | ||
| // | ||
| // SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| import React from 'react'; | ||
|
|
||
| import { | ||
| ConnectionStatusContainer, | ||
| ConnectionStatusDot, | ||
| } from '@/components/monitor/MonitorView.styles'; | ||
|
|
||
| // Memoized ConnectionStatus component | ||
| export const ConnectionStatus = React.memo(({ connected }: { connected: boolean }) => ( | ||
| <ConnectionStatusContainer connected={connected}> | ||
| <ConnectionStatusDot connected={connected} /> | ||
| {connected ? 'Connected' : 'Disconnected'} | ||
| </ConnectionStatusContainer> | ||
| )); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,165 @@ | ||
| // SPDX-FileCopyrightText: © 2025 StreamKit Contributors | ||
| // | ||
| // SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| /** | ||
| * Left sidebar panel for the Monitor View. | ||
| * | ||
| * Contains the session list (with search) and, in staging mode, | ||
| * a "Nodes Library" tab for drag-and-drop node insertion. | ||
| */ | ||
|
|
||
| import React, { useState, useEffect } from 'react'; | ||
|
|
||
| import { | ||
| LeftPanelAside, | ||
| SessionsContainer, | ||
| SessionSearchInput, | ||
| SearchWrapper, | ||
| SessionListWrapper, | ||
| LoadingText, | ||
| SessionList, | ||
| EmptyStateText, | ||
| NodesLibraryContainer, | ||
| } from '@/components/monitor/MonitorView.styles'; | ||
| import { SessionItem } from '@/components/monitor/SessionItem'; | ||
| import NodePalette from '@/components/NodePalette'; | ||
| import { TabsContent, TabsList, TabsRoot, TabsTrigger } from '@/components/ui/Tabs'; | ||
| import type { NodeDefinition } from '@/types/types'; | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Props | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| interface LeftPanelProps { | ||
| isLoadingSessions: boolean; | ||
| sessions: { id: string; name: string | null; created_at: string }[]; | ||
| selectedSessionId: string | null; | ||
| onSessionClick: (id: string) => void; | ||
| onSessionDelete: (id: string) => void; | ||
| editMode: boolean; | ||
| nodeDefinitions: NodeDefinition[]; | ||
| onDragStart: (event: React.DragEvent, nodeType: string) => void; | ||
| pluginKinds: Set<string>; | ||
| pluginTypes: Map<string, 'wasm' | 'native'>; | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Component | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| export const LeftPanel = React.memo( | ||
| ({ | ||
| isLoadingSessions, | ||
| sessions, | ||
| selectedSessionId, | ||
| onSessionClick, | ||
| onSessionDelete, | ||
| editMode, | ||
| nodeDefinitions, | ||
| onDragStart, | ||
| pluginKinds, | ||
| pluginTypes, | ||
| }: LeftPanelProps) => { | ||
| const [activeTab, setActiveTab] = useState<'sessions' | 'add'>('sessions'); | ||
| const [searchQuery, setSearchQuery] = useState(''); | ||
|
|
||
| useEffect(() => { | ||
| if (!editMode && activeTab === 'add') { | ||
| setActiveTab('sessions'); | ||
| } | ||
| }, [editMode, activeTab]); | ||
|
|
||
| const filteredSessions = React.useMemo(() => { | ||
| if (!searchQuery.trim()) { | ||
| return sessions; | ||
| } | ||
| const query = searchQuery.toLowerCase(); | ||
| return sessions.filter( | ||
| (session) => | ||
| session.id.toLowerCase().includes(query) || | ||
| (session.name && session.name.toLowerCase().includes(query)) | ||
| ); | ||
| }, [sessions, searchQuery]); | ||
|
|
||
| return ( | ||
| <LeftPanelAside> | ||
| <TabsRoot | ||
| value={activeTab} | ||
| onValueChange={(value) => setActiveTab(value as 'sessions' | 'add')} | ||
| > | ||
| <TabsList> | ||
| <TabsTrigger value="sessions">Sessions</TabsTrigger> | ||
| {editMode && ( | ||
| <TabsTrigger value="add" disabled={!selectedSessionId}> | ||
| Nodes Library | ||
| </TabsTrigger> | ||
| )} | ||
| </TabsList> | ||
|
|
||
| <TabsContent value="sessions"> | ||
| <SessionsContainer data-testid="sessions-list"> | ||
| {isLoadingSessions ? ( | ||
| <LoadingText>Loading sessions...</LoadingText> | ||
| ) : sessions.length === 0 ? ( | ||
| <EmptyStateText>No active sessions</EmptyStateText> | ||
| ) : ( | ||
| <> | ||
| {sessions.length >= 5 && ( | ||
| <SearchWrapper> | ||
| <SessionSearchInput | ||
| type="text" | ||
| placeholder="Search sessions..." | ||
| value={searchQuery} | ||
| onChange={(e) => setSearchQuery(e.target.value)} | ||
| /> | ||
| </SearchWrapper> | ||
| )} | ||
| <SessionListWrapper> | ||
| {filteredSessions.length === 0 ? ( | ||
| <EmptyStateText>No matching sessions</EmptyStateText> | ||
| ) : ( | ||
| <SessionList> | ||
| {filteredSessions.map((session) => ( | ||
| <li key={session.id}> | ||
| <SessionItem | ||
| session={session} | ||
| isActive={selectedSessionId === session.id} | ||
| onClick={onSessionClick} | ||
| onDelete={onSessionDelete} | ||
| /> | ||
| </li> | ||
| ))} | ||
| </SessionList> | ||
| )} | ||
| </SessionListWrapper> | ||
| </> | ||
| )} | ||
| </SessionsContainer> | ||
| </TabsContent> | ||
|
|
||
| <TabsContent value="add"> | ||
| {editMode && ( | ||
| <NodesLibraryContainer> | ||
| {selectedSessionId ? ( | ||
| nodeDefinitions.length === 0 ? ( | ||
| <EmptyStateText>Loading node definitions…</EmptyStateText> | ||
| ) : ( | ||
| <NodePalette | ||
| nodeDefinitions={nodeDefinitions} | ||
| onDragStart={onDragStart} | ||
| pluginKinds={pluginKinds} | ||
| pluginTypes={pluginTypes} | ||
| /> | ||
| ) | ||
| ) : ( | ||
| <EmptyStateText>Select a session to add nodes</EmptyStateText> | ||
| )} | ||
| </NodesLibraryContainer> | ||
| )} | ||
| </TabsContent> | ||
| </TabsRoot> | ||
| </LeftPanelAside> | ||
| ); | ||
| } | ||
| ); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| // SPDX-FileCopyrightText: © 2025 StreamKit Contributors | ||
| // | ||
| // SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| /** | ||
| * Node-state legend overlay for the Monitor View canvas. | ||
| * Memoized to prevent re-renders during node drag. | ||
| */ | ||
|
|
||
| import React from 'react'; | ||
|
|
||
| import { | ||
| LegendContainer, | ||
| LegendTitle, | ||
| LegendItem, | ||
| LegendDot, | ||
| } from '@/components/monitor/MonitorView.styles'; | ||
|
|
||
| export const Legend = React.memo(() => ( | ||
| <LegendContainer> | ||
| <LegendTitle>Node States</LegendTitle> | ||
| <LegendItem> | ||
| <LegendDot color="var(--sk-status-initializing)" /> | ||
| <span>Initializing</span> | ||
| </LegendItem> | ||
| <LegendItem> | ||
| <LegendDot color="var(--sk-status-running)" /> | ||
| <span>Running</span> | ||
| </LegendItem> | ||
| <LegendItem> | ||
| <LegendDot color="var(--sk-status-recovering)" /> | ||
| <span>Recovering</span> | ||
| </LegendItem> | ||
| <LegendItem> | ||
| <LegendDot color="var(--sk-status-degraded)" /> | ||
| <span>Degraded</span> | ||
| </LegendItem> | ||
| <LegendItem> | ||
| <LegendDot color="var(--sk-status-failed)" /> | ||
| <span>Failed</span> | ||
| </LegendItem> | ||
| <LegendItem> | ||
| <LegendDot color="var(--sk-status-stopped)" /> | ||
| <span>Stopped</span> | ||
| </LegendItem> | ||
| </LegendContainer> | ||
| )); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Stale search filter hides sessions when session count drops below threshold
The search input is only rendered when
sessions.length >= 5(line 108), butsearchQuerystate persists even after the input is hidden. If the session count drops from ≥5 to <5 while a non-empty search query is active, thefilteredSessionsmemo at lines 73-83 continues to filter by the stale query. This causes the session list to show "No matching sessions" with no visible search input to clear the filter, making existing sessions invisible to the user.Was this helpful? React with 👍 or 👎 to provide feedback.
Debug
Playground