feat: UI improvements with icons, back navigation, and overview cards (#7)
CI / Format (push) Successful in 3s
CI / Clippy (push) Successful in 3m59s
CI / Security Audit (push) Successful in 1m44s
CI / Tests (push) Successful in 5m2s
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Has been skipped
CI / Deploy Dashboard (push) Successful in 2s
CI / Deploy Docs (push) Has been skipped
CI / Deploy MCP (push) Has been skipped

This commit was merged in pull request #7.
This commit is contained in:
2026-03-09 17:09:40 +00:00
parent 46bf9de549
commit 0065c7c4b2
14 changed files with 778 additions and 171 deletions
@@ -1,4 +1,6 @@
use dioxus::prelude::*;
use dioxus_free_icons::icons::bs_icons::*;
use dioxus_free_icons::Icon;
use crate::components::code_inspector::CodeInspector;
use crate::components::file_tree::{build_file_tree, FileTree};
@@ -8,6 +10,36 @@ use crate::infrastructure::graph::{fetch_graph, search_nodes, trigger_graph_buil
#[component]
pub fn GraphExplorerPage(repo_id: String) -> Element {
rsx! {
div { class: "back-nav",
button {
class: "btn btn-ghost btn-back",
onclick: move |_| { navigator().go_back(); },
Icon { icon: BsArrowLeft, width: 16, height: 16 }
"Back"
}
}
PageHeader {
title: "Code Knowledge Graph",
description: "Interactive visualization of code structure and relationships",
}
GraphExplorerBody { repo_id: repo_id }
}
}
/// Inline variant without back button and page header — for embedding in other pages.
#[component]
pub fn GraphExplorerInline(repo_id: String) -> Element {
rsx! {
GraphExplorerBody { repo_id: repo_id }
}
}
/// Shared graph explorer body used by both the full page and inline variants.
#[component]
fn GraphExplorerBody(repo_id: String) -> Element {
let repo_id_clone = repo_id.clone();
let mut graph_data = use_resource(move || {
let rid = repo_id_clone.clone();
@@ -21,22 +53,15 @@ pub fn GraphExplorerPage(repo_id: String) -> Element {
let mut building = use_signal(|| false);
let mut toasts = use_context::<Toasts>();
// Selected node state
let mut selected_node = use_signal(|| Option::<serde_json::Value>::None);
let mut inspector_open = use_signal(|| false);
// Search state
let mut search_query = use_signal(String::new);
let mut search_results = use_signal(Vec::<serde_json::Value>::new);
let mut file_filter = use_signal(String::new);
// Store serialized graph JSON in signals so use_effect can react to them
let mut nodes_json = use_signal(String::new);
let mut edges_json = use_signal(String::new);
let mut graph_ready = use_signal(|| false);
// When resource resolves, serialize the data into signals
let graph_data_read = graph_data.read();
if let Some(Some(data)) = &*graph_data_read {
if !data.data.nodes.is_empty() && !graph_ready() {
@@ -48,7 +73,6 @@ pub fn GraphExplorerPage(repo_id: String) -> Element {
}
}
// Derive stats and file tree
let (node_count, edge_count, community_count, languages, file_tree_data) =
if let Some(Some(data)) = &*graph_data_read {
let build = data.data.build.clone().unwrap_or_default();
@@ -80,11 +104,8 @@ pub fn GraphExplorerPage(repo_id: String) -> Element {
};
let has_graph_data = matches!(&*graph_data_read, Some(Some(d)) if !d.data.nodes.is_empty());
// Drop the read guard before rendering
drop(graph_data_read);
// use_effect runs AFTER DOM commit — this is when #graph-canvas exists
use_effect(move || {
let ready = graph_ready();
if !ready {
@@ -96,7 +117,6 @@ pub fn GraphExplorerPage(repo_id: String) -> Element {
return;
}
spawn(async move {
// Register the click callback + load graph with a small delay for DOM paint
let js = format!(
r#"
window.__onNodeClick = function(nodeJson) {{
@@ -109,8 +129,6 @@ pub fn GraphExplorerPage(repo_id: String) -> Element {
setTimeout(function() {{
if (window.__loadGraph) {{
window.__loadGraph({nj}, {ej});
}} else {{
console.error('[graph-viz] __loadGraph not found — vis-network may not be loaded');
}}
}}, 300);
"#
@@ -119,7 +137,6 @@ pub fn GraphExplorerPage(repo_id: String) -> Element {
});
});
// Extract selected node fields
let sel = selected_node();
let sel_file = sel
.as_ref()
@@ -146,11 +163,6 @@ pub fn GraphExplorerPage(repo_id: String) -> Element {
.unwrap_or(0) as u32;
rsx! {
PageHeader {
title: "Code Knowledge Graph",
description: "Interactive visualization of code structure and relationships",
}
if repo_id.is_empty() {
div { class: "card",
p { "Select a repository to view its code graph." }