Feature/rpc monitoring and UI optimization#28
Conversation
…stats endpoint - RPC monitor: concurrent batch processing, latency tracking, store failed endpoints - Add BFS graph traversal (traverseRelations) for chain relations - Add GET /stats and GET /relations/:id/graph endpoints - Add MCP tools: get_stats, traverse_relations, get_rpc_monitor_by_id - Update unit, integration, and fuzz tests for all new behavior Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Performance: debounced search, DocumentFragment for dropdown, reduced node resolution - Performance: try local /export endpoint before GitHub raw (14MB) - Security: replace innerHTML with safe DOM-based text highlighting - UI: add stats bar, color legend, status badges, keyboard shortcuts (/ and arrow keys) - UI: refined glassmorphism, responsive mobile layout, background click to dismiss - Add rel=noopener to all external links Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces concurrent RPC monitoring with latency tracking, a BFS graph traversal helper (traverseRelations), two new REST API endpoints (GET /stats and GET /relations/:id/graph), three new MCP tools, and a comprehensive 3D visualization UI overhaul (glassmorphism styling, search with keyboard shortcuts, filter buttons, legend panel, and responsive mobile layout).
Changes:
- Backend:
rpcMonitor.jsnow processes chains in concurrent batches usingPromise.allSettledwith theRPC_CHECK_CONCURRENCYconfig, trackslatencyMsper endpoint, and recordsfailedEndpointsseparately.dataService.jsgains thetraverseRelationsBFS function.index.jsandmcp-tools.jsaddGET /statsandGET /relations/:id/graphendpoints and corresponding tools. - Frontend: Complete UI redesign with glassmorphism panels, new search UX (debouncing, DocumentFragment rendering, keyboard navigation with
/shortcut and arrow keys), color legend, status badges, and responsive mobile breakpoints. - Tests: Fuzz tests and unit tests added/updated to cover the new endpoints and behavior changes.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
rpcMonitor.js |
Adds failedEndpoints counter, latencyMs tracking, concurrent batch processing; removes early-exit-on-failure behavior |
dataService.js |
Adds traverseRelations BFS function |
index.js |
Adds GET /stats and GET /relations/:id/graph endpoints; adds failedEndpoints to per-chain RPC response |
mcp-tools.js |
Adds get_stats and traverse_relations tool definitions and handlers; adds latency to RPC monitor output |
public/index.html |
Restructures header with search redesign, legend panel, status badges, and loading sub-text |
public/style.css |
Complete redesign with glassmorphism, CSS variables for radius, responsive breakpoints, custom scrollbars |
public/app.js |
Adds debounce, DocumentFragment DOM updates, keyboard shortcuts, highlightText helper, and safe URL parsing |
tests/unit/rpcMonitor.test.js |
Fixes concurrent monitoring mock to use timer-based delays; updates endpoint-limiting test |
tests/unit/mcp-tools.test.js |
Adds tests for get_stats, traverse_relations; updates tool count to 13 |
tests/unit/dataService.test.js |
Adds comprehensive traverseRelations tests |
tests/integration/api.fuzz.test.js |
Adds fuzz tests for GET /stats and GET /relations/:id/graph |
You can also share your feedback on Copilot code review. Take the survey.
tests/integration/api.fuzz.test.js
Outdated
| const totalTestnets = chains.filter(c => c.tags?.includes('Testnet')).length; | ||
| const totalL2s = chains.filter(c => c.tags?.includes('L2')).length; | ||
| const totalBeacons = chains.filter(c => c.tags?.includes('Beacon')).length; | ||
| const totalMainnets = chains.filter(c => !c.tags?.includes('Testnet')).length; |
There was a problem hiding this comment.
The inline totalMainnets calculation in the fuzz test stub (line 319) uses !c.tags?.includes('Testnet'), which is consistent with the actual implementation. However, both the test and the real code share the same semantics bug — L2s and Beacons are counted as mainnets. If the real implementation is corrected, this stub will also need to be updated.
| const totalMainnets = chains.filter(c => !c.tags?.includes('Testnet')).length; | |
| const totalMainnets = chains.filter( | |
| c => !c.tags?.includes('Testnet') && !c.tags?.includes('L2') && !c.tags?.includes('Beacon') | |
| ).length; |
tests/unit/mcp-tools.test.js
Outdated
| expect(data.totalTestnets).toBe(1); | ||
| expect(data.totalL2s).toBe(1); | ||
| expect(data.totalBeacons).toBe(1); | ||
| expect(data.totalMainnets).toBe(3); |
There was a problem hiding this comment.
The test expects totalMainnets to be 3 (Ethereum, Polygon/L2, Gnosis Beacon) with 4 chains including 1 testnet. This confirms the current implementation counts L2 and Beacon chains under totalMainnets, which is misleading. The test should be updated when the semantics are corrected.
| expect(data.totalMainnets).toBe(3); |
public/style.css
Outdated
| padding: 2px 6px; | ||
| border-radius: 4px; | ||
| font-size: 0.7rem; | ||
| font-family: 'Inter', monospace; |
There was a problem hiding this comment.
The .search-hint class is applied to a <kbd> element (keyboard shortcut indicator) but its font-family is set to 'Inter', monospace, which means the Inter sans-serif font takes precedence over a monospace font. For keyboard shortcut indicators, it is conventional to use a monospace font. Consider using font-family: ui-monospace, monospace instead.
| font-family: 'Inter', monospace; | |
| font-family: ui-monospace, monospace; |
index.js
Outdated
| const monitorResults = getMonitoringResults(); | ||
|
|
||
| const totalChains = chains.length; | ||
| const totalMainnets = chains.filter(c => !c.tags?.includes('Testnet')).length; |
There was a problem hiding this comment.
The totalMainnets field is calculated as chains that do NOT have the 'Testnet' tag (!c.tags?.includes('Testnet')), which means L2 chains and Beacon chains are also included in this count. The API already separately exposes totalL2s and totalBeacons, so consumers would reasonably expect totalMainnets to be the count of chains that are neither L2, Beacon, nor Testnet. As written, a chain tagged L2 is counted in both totalL2s and totalMainnets, which is misleading. The calculation should be chains.filter(c => !c.tags?.includes('Testnet') && !c.tags?.includes('L2') && !c.tags?.includes('Beacon')).length to count only true mainnet chains.
| const totalMainnets = chains.filter(c => !c.tags?.includes('Testnet')).length; | |
| const totalMainnets = chains | |
| .filter( | |
| c => | |
| !c.tags?.includes('Testnet') && | |
| !c.tags?.includes('L2') && | |
| !c.tags?.includes('Beacon') | |
| ).length; |
mcp-tools.js
Outdated
| const monitorResults = getMonitoringResults(); | ||
|
|
||
| const totalChains = chains.length; | ||
| const totalMainnets = chains.filter(c => !c.tags?.includes('Testnet')).length; |
There was a problem hiding this comment.
Same issue as in index.js: totalMainnets is calculated as chains without the 'Testnet' tag, incorrectly including L2 and Beacon chains in the count. This should exclude L2 and Beacon tags to truly represent mainnet-only chains.
| const totalMainnets = chains.filter(c => !c.tags?.includes('Testnet')).length; | |
| const totalMainnets = chains.filter(c => !['Testnet', 'L2', 'Beacon'].some(tag => c.tags?.includes(tag))).length; |
| const relations = chain.relations || []; | ||
| for (const rel of relations) { | ||
| if (rel.chainId === undefined) continue; | ||
|
|
||
| edges.push({ | ||
| from: chainId, | ||
| to: rel.chainId, | ||
| kind: rel.kind, | ||
| source: rel.source | ||
| }); | ||
|
|
||
| if (!visited.has(rel.chainId)) { | ||
| queue.push({ chainId: rel.chainId, depth: depth + 1 }); | ||
| } |
There was a problem hiding this comment.
In traverseRelations, edges are added for every relation encountered during BFS traversal, regardless of whether the target chain has already been visited. Because the graph includes bidirectional relations (e.g., a parent chain has parentOf edges to children, and child chains have l2Of or testnetOf edges back to the parent), the resulting edges array will contain both directions (e.g., A→B and B→A) as well as edges to already-visited nodes. This means totalEdges will be higher than the number of unique relationships, and graph consumers may render duplicate connections. To fix this, track visited pairs (e.g., using a Set of ${from}-${to} strings) or only add an edge when the target node has NOT been visited before adding it to the queue.
- Fix totalMainnets to exclude L2 and Beacon chains (was only excluding Testnet) - Deduplicate bidirectional edges in traverseRelations BFS traversal - Use monospace font stack for keyboard shortcut hint Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
traverseRelations) for exploring chain relationships with configurable depthGET /statsfor aggregate statistics andGET /relations/:id/graphfor graph traversalChanges
Backend
rpcMonitor.js— concurrent batch processing withPromise.allSettled, latency tracking, failed endpoint countersdataService.js— addedtraverseRelations(startChainId, maxDepth)BFS functionindex.js— addedGET /statsandGET /relations/:id/graphendpointsmcp-tools.js— addedget_stats,traverse_relations,get_rpc_monitor_by_idtoolsFrontend (
/public)index.html— restructured with search dropdown, filter buttons, color legend, status badgesstyle.css— complete redesign with glassmorphism, responsive breakpoints, custom scrollbarsapp.js— performance optimizations (debounce, DocumentFragment, reduced polygon count), safe DOM text highlighting, keyboard shortcutsTests
tests/unit/rpcMonitor.test.js— fixed mocks for concurrent monitoring behaviortests/unit/dataService.test.js— fixed test ordering dependencyTest plan
npm test)/,Escape, arrow keys)GET /statsandGET /relations/:id/graphendpoints respond correctly