Frontend Components¶
Reference guide for React components in the Substrate frontend.
Layout Components¶
DashboardLayout¶
File: components/layout/DashboardLayout.tsx
The root authenticated shell. Mounts global data hooks (useSyncs) so sync polling never stops. Validates persisted sync IDs on mount and initializes the sync set if empty.
Children: - TopBar - Sidebar (desktop only) - MobileNav (mobile only) - ModalRoot - SwapToast - Outlet (renders current page)
Sidebar¶
File: components/layout/Sidebar.tsx
Collapsible icon rail for desktop navigation. Opens modals for Graph, Sources, Enrichment, Search, and coming-soon items. Shows a badge with the count of currently loaded snapshots.
Props: None (reads from uiStore and syncSetStore)
TopBar¶
File: components/layout/TopBar.tsx
Fixed header containing: - Substrate brand mark - Search input (disabled until graph loaded) - Live visible node/edge counts - Health heartbeat pill (polls /api/graph/stats every 10s) - Last graph load round-trip time
MobileNav¶
File: components/layout/MobileNav.tsx
Bottom tab bar for mobile with icons for Graph, Sources, Enrichment, Search, and Account.
Graph Components¶
GraphCanvas¶
File: components/graph/GraphCanvas.tsx
The core graph visualization component. Lazily imports Cytoscape and initializes the instance.
Key behaviors: - Performance flags: textureOnViewport, hideEdgesOnViewport, pixelRatio: 1 - Groups nodes by source_id into compound parent nodes - Filters by legend type filter - Falls back from cose to grid layout when >200 nodes - Keyboard shortcuts: Ctrl/Cmd+0 (fit), ± (zoom), Escape (deselect), L (relayout) - One-way viewport sync: writes zoom/pan to store, never reads back
Dependencies: stores/graph.ts, lib/cytoscapeLoader.ts
DynamicLegend¶
File: components/graph/DynamicLegend.tsx
Interactive legend showing the top 12 node types by count. Clicking toggles visibility in the canvas.
SignalsOverlay¶
File: components/graph/SignalsOverlay.tsx
Floating overlay showing the last 3 graph signal events (type, nodeId truncated, timestamp).
ViolationBadge¶
File: components/graph/ViolationBadge.tsx
Floating badge showing the current violation count.
NodeDetailPanel¶
File: components/panels/NodeDetailPanel.tsx
Slide-over panel when a node is selected.
Features: - Fetches node detail from /api/graph/nodes/:id - Fetches/caches LLM summary from /api/graph/nodes/:id/summary - Shows file metadata (language, lines, size, imports) - Snapshot picker with divergence badge - Summary with regenerate button - Navigable neighbors list with edge type glyphs - Deep-link to Sources modal
Modals¶
All modals are routed through ModalRoot.tsx, which mounts only the active modal.
ModalRoot¶
File: components/modals/ModalRoot.tsx
Maps activeModal (ModalName) to the correct component. Renders nothing when no modal is open. Explicitly ignores nodeDetail because GraphPage mounts NodeDetailPanel directly.
type ModalName =
| 'sources'
| 'enrichment'
| 'search'
| 'graph'
| 'user'
| 'policies'
| 'adrs'
| 'drift'
| 'query'
| 'nodeDetail'
| null;
Note: nodeDetail is not rendered by ModalRoot; GraphPage handles it inline as a slide-over panel.
SourcesModal¶
File: components/modals/SourcesModal.tsx
Two-pane layout for source and sync management.
Structure: - Left: SourcesSidebar (list + add input) - Right: SourceDetailPane (header, schedule, snapshots)
State: - selectedSourceId - selectedSyncIds (checkbox-based bulk selection) - expandedSyncId (for issues inline)
SearchModal¶
File: components/modals/SearchModal.tsx
Semantic search interface with query input, category filter, and domain filter. Calls /api/graph/search.
UserModal¶
File: components/modals/UserModal.tsx
Tabbed modal: - Account: Avatar, username, email, role badge, Sign Out - Settings: Light/dark theme picker
GraphModal¶
File: components/modals/GraphModal.tsx
Graph configuration modal. Currently exposes Leiden community-detection tuning.
EnrichmentModal¶
File: components/modals/EnrichmentModal.tsx
Placeholder explaining that enrichment now runs inline during sync.
Inline Notices¶
SyncAlreadyActiveNotice¶
File: components/SyncAlreadyActiveNotice.tsx
Inline banner shown when a 409 sync_already_active response is returned. Includes an optional "View it" button to jump to the running sync.
Sources Sub-Components¶
SourcesSidebar¶
File: components/modals/sources/SourcesSidebar.tsx
Lists all sources with loaded and running status chips. Contains AddSourceInput and handles checkbox selection for bulk operations.
SourceListItem¶
File: components/modals/sources/SourceListItem.tsx
Individual source row. Navigate-on-click, checkbox, status chips.
SourceDetailPane¶
File: components/modals/sources/SourceDetailPane.tsx
Combines DetailHeader, ScheduleStrip, and SnapshotList for the active source.
SnapshotList¶
File: components/modals/sources/SnapshotList.tsx
Infinite-scroll list of sync runs using useSourceSyncs. Supports auto-scroll to a deep-linked snapshot.
SnapshotRow¶
File: components/modals/sources/SnapshotRow.tsx
Wrapper that renders SnapshotRowSummary and conditionally mounts SnapshotIssuesInline when expanded.
SnapshotRowSummary¶
File: components/modals/sources/SnapshotRowSummary.tsx
Displays: - Checkbox for bulk selection - Relative timestamp - Status chip (pending, running, completed, failed, cancelled, cleaned) - Progress bar (numeric % during file phases, indeterminate spinner during embedding) - Expand chevron
SnapshotIssuesInline¶
File: components/modals/sources/SnapshotIssuesInline.tsx
Fetches and lists sync issues. Shows a Retry button. Caps rendering at 100 issues.
AddSourceInput¶
File: components/modals/sources/AddSourceInput.tsx
Parses GitHub URLs (owner/repo), deduplicates against existing sources, and calls createSource.
UnifiedToolbar¶
File: components/modals/sources/UnifiedToolbar.tsx
Context-aware bulk-action toolbar.
Snapshot mode (when syncs selected): - Load, Unload, Clean, Purge
Source mode (when sources selected): - Sync, Stop (if running), Set Schedule, Purge Sources
UI Primitives¶
Modal¶
File: components/ui/Modal.tsx
Base modal wrapper with backdrop, close button, and size variants.
dialog.tsx¶
File: components/ui/dialog.tsx
Headless dialog primitives from @base-ui/react.
input.tsx¶
File: components/ui/input.tsx
Styled text input component.
button.tsx¶
File: components/ui/button.tsx
Styled button with variants (primary, secondary, ghost, danger).
badge.tsx¶
File: components/ui/badge.tsx
Status/badge component.
select.tsx¶
File: components/ui/select.tsx
Styled select dropdown from @base-ui/react.
Component Patterns¶
One-Way Data Flow¶
Graph canvas events write to Zustand, but Zustand does not write back to Cytoscape:
// GraphCanvas.tsx
cy.on('zoom pan', () => {
setZoom(cy.zoom());
setPan(cy.pan());
});
// NEVER: cy.zoom(store.zoom) — would create a feedback loop
Modal Isolation¶
Only the active modal renders. This prevents heavy hooks like useSyncs and useSources from running when unrelated modals are open.
// ModalRoot.tsx
export function ModalRoot() {
const { activeModal } = useUIStore();
switch (activeModal) {
case 'sources': return <SourcesModal />;
case 'search': return <SearchModal />;
// ...
default: return null;
}
}
Sync Polling at Layout Level¶
useSyncs() is called inside DashboardLayout, not inside SourcesModal. This ensures sync-state detection works even when all modals are closed.
Source Grouping on Canvas¶
Nodes with the same source_id are rendered as children of a Cytoscape compound parent: