2026 02 10_crate_refactor_plan - mark-ik/graphshell GitHub Wiki
Date Started: 2026-02-10 Status: Planning Goal: Replace custom graph/rendering/layout code with mature Rust crates to reduce maintenance burden and enable focus on Servo integration
Target: Replace custom graph rendering and interaction with egui_graphs
Rationale:
-
egui_graphsis purpose-built for interactive graph visualization in egui - Would eliminate ~60% of custom code in
render/mod.rsandinput/mod.rs - Provides professional-grade interaction (selection, dragging, zoom, pan) out of the box
- Active maintenance and good API
Tasks:
- Add
egui_graphsdependency toCargo.toml - Evaluate
egui_graphsAPI compatibility with currentGraphstructure - Create adapter layer between
Graph(SlotMap-based) andegui_graphs - Migrate rendering logic from
render/mod.rstoegui_graphs - Migrate input handling from
input/mod.rstoegui_graphsinteraction handlers - Preserve existing features: node lifecycle colors, selection, camera transforms
- Run tests to verify behavior unchanged
Validation Tests:
- Graph renders with correct node/edge positions
- Selection (single and multi-select) works
- Dragging nodes updates positions
- Camera zoom and pan work correctly
- Physics simulation still runs and updates positions
- Double-click to focus still works
Outputs:
- Simplified
render/mod.rs(egui_graphs wrapper) - Simplified
input/mod.rs(delegate to egui_graphs) - Adapter module:
graph/egui_adapter.rs
Success Criteria:
- All 84 existing tests pass
- Visual parity with current rendering
- Reduced LOC in rendering/input modules by 50%+
Target: Integrate petgraph as a projection layer for algorithms
Rationale:
- Current SlotMap approach works well for runtime operations
-
petgraphprovides graph algorithms (pathfinding, centrality, clustering) - Analysis recommends using it as a "projection" - convert SlotMap â petgraph for analysis, apply results back
- Enables future features: connected components, shortest paths, hub detection
Tasks:
- Add
petgraphdependency withserde-1feature - Create conversion:
Graphâpetgraph::stable_graph::StableGraph - Create conversion:
StableGraphresults âGraphnode/edge updates - Add graph algorithm wrappers in
graph/algorithms.rs - Expose algorithms via
GraphBrowserAppAPI
Validation Tests:
- Round-trip conversion preserves graph structure
- Pathfinding returns correct shortest paths
- Connected components correctly identifies clusters
- Performance: conversion overhead < 5ms for 100-node graph
Outputs:
-
graph/algorithms.rsmodule with algorithm wrappers -
graph/petgraph_adapter.rsfor conversions - New graph algorithm APIs on
Graphtype
Success Criteria:
- Can compute shortest path between any two nodes
- Can identify disconnected subgraphs
- Can detect hub nodes (high centrality)
Target: Replace exclusive view toggle with egui_tiles tiled layout
Rationale:
- Current approach: graph OR detail view (exclusive)
-
egui_tilesenables: graph AND detail view (simultaneous) - Professional tiling: drag-to-resize, tabs, docking, persistence
- Dramatically improves UX for spatial browsing
Tasks:
- Add
egui_tilesdependency - Replace
Viewenum (Graph/Detail) withegui_tiles::Treelayout - Migrate graph rendering into tile pane
- Migrate webview rendering into tile pane
- Preserve keyboard shortcuts (Home to toggle layout)
- Add tile persistence (save/restore split ratios)
Validation Tests:
- Graph and detail view render simultaneously
- Split ratio can be dragged
- Layout persists across restarts
- Physics still runs when graph pane visible
- Webview lifecycle works correctly in split mode
Outputs:
- Reworked
desktop/gui.rslayout usingegui_tiles - Layout persistence in
app::SplitViewConfig - Updated keyboard shortcuts documentation
Success Criteria:
- Can view graph + webpage simultaneously
- Can resize split with mouse drag
- Layout configuration saved/restored
- No framebuffer bleed-through issues
Target: Replace HashMap spatial grid with kiddo KD-tree
Rationale:
- Current:
HashMap<(i32,i32), Vec<NodeKey>>(grid-based) -
kiddo: Cache-friendly KD-tree, optimized for per-frame rebuild - Better for non-uniform node distributions
- Enables radius queries for physics (currently hardcoded 300.0 distance)
Tasks:
- Add
kiddodependency - Replace
SpatialGridwithKdTree<f32, NodeKey, 2> - Update physics
step()to rebuild KD-tree each frame - Replace fixed 300.0 radius with configurable physics param
- Benchmark: compare HashMap vs KD-tree performance
Validation Tests:
- Physics behavior unchanged (positions converge identically)
- Neighbor queries return same nodes as before
- Performance: step time < 1ms for 100-node graph
- Memory: KD-tree uses < 2x memory of HashMap
Outputs:
-
physics/spatial_kdtree.rsreplacingspatial_hash.rs - Benchmark results in
design_docs/tests/
Success Criteria:
- Physics simulation 10%+ faster for graphs > 50 nodes
- All physics tests pass unchanged
Phase 5: Better Force-Directed Layout
-
forceatlas2for Barnes-Hut O(N log N) repulsion - Better handling of hub-and-spoke topologies
- Only after Servo integration, as demo graph doesn't need this
Phase 6: Persistence
-
fjall(append-only log) +rkyv(zero-copy snapshots) - Critical for production, but lower priority than Servo integration
- See Claude ANALYSIS Phase 1 for detailed architecture
| Crate | Version | Maturity | Maintenance | Servo Compatibility | Notes |
|---|---|---|---|---|---|
egui_graphs |
0.21.0 | Stable | Active (2024) | â egui-compatible | Purpose-built for graph viz |
petgraph |
0.6.5 | Mature | Active | â Pure Rust | 9M+ downloads, de facto standard |
egui_tiles |
0.13.0 | Stable | Active (2025) | â egui plugin | By emilk (egui author) |
kiddo |
4.2.1 | Stable | Active | â Pure Rust | rkyv-compatible, cache-friendly |
forceatlas2 |
0.3.0 | Beta | Moderate | â Pure Rust | Barnes-Hut quadtree |
Keeping Custom Code:
- â Full control over behavior
- â No external API constraints
- â High maintenance burden
- â Missing ecosystem algorithms
- â Reinventing the wheel
Using Crates:
- â Battle-tested implementations
- â Access to graph algorithms
- â Professional UX (egui_graphs, egui_tiles)
- â Reduced LOC to maintain
- â Potential API mismatches
- â Dependency on external updates
Decision: Proceed with crate integration. Custom physics engine can coexist with egui_graphs rendering.
Completed:
- â Read Claude ANALYSIS document
- â Read DOC_POLICY
- â Identified crate candidates aligned with analysis
- â Created this plan file per workflow documentation rule
- â Added petgraph v0.8.3 to Cargo.toml with serde-1 feature
- â Added egui_graphs v0.29.0 to Cargo.toml (for future use)
- â
Created
graph/petgraph_adapter.rswith bidirectional Graph â petgraph conversion - â
Added graph algorithm methods to Graph:
-
shortest_path(from, to)- Dijkstra pathfinding -
connected_components()- Find disconnected subgraphs -
is_reachable(from, to)- Check path existence
-
- â All code compiles successfully (cargo check passed)
- âŗ Running tests to verify no breakage
Approach Decision: Started with Phase 2 (petgraph) instead of Phase 1 (egui_graphs) because:
- egui_graphs v0.29.0 API incompatibility issues (imports don't match)
- Claude ANALYSIS recommends petgraph as "projection layer" for algorithms
- Custom rendering already works well - no need to rush replacing it
- Petgraph gives immediate value: pathfinding, clustering, centrality
Next Steps:
- Verify all tests still pass
- Add comprehensive tests for algorithm methods
- Research egui_graphs v0.29.0 actual API for future integration
- Add algorithm usage example in GraphBrowserApp
- Servo integration (Task 1 from original priority list) - COMPLETED
Test Results:
- Baseline: 84 tests passing
- After petgraph integration: 89 tests passing (5 new petgraph adapter tests)
- After adding algorithm tests: 98 tests passing (9 new algorithm tests)
Phase 2 Complete - Petgraph Integration Testing:
-
â Added 9 comprehensive tests for graph algorithm methods:
-
test_shortest_path_direct- Direct path between connected nodes -
test_shortest_path_indirect- Multi-hop path through intermediary nodes -
test_shortest_path_no_path- No path exists (disconnected nodes) -
test_shortest_path_same_node- Path from node to itself -
test_connected_components_single- All nodes in one component -
test_connected_components_multiple- Multiple disconnected components -
test_connected_components_empty- Empty graph -
test_is_reachable_direct- Direct reachability -
test_is_reachable_indirect- Indirect reachability through intermediary -
test_is_reachable_no_path- No path exists
-
-
â Fixed
connected_components()implementation:- Initially used Kosaraju's SCC (strongly connected components - requires bidirectional paths)
- Changed to BFS-based approach treating edges as bidirectional
- Now correctly finds weakly connected components
- Simpler implementation using standard library collections
-
â All 98 tests pass (0 failures)
Algorithm Method Validation:
All three petgraph-based algorithms now have comprehensive test coverage:
-
shortest_path(from, to)- Uses Dijkstra + BFS path reconstruction -
connected_components()- Uses BFS treating graph as undirected -
is_reachable(from, to)- Wrapper around shortest_path
User Testing:
After implementing Servo integration and petgraph algorithms, user compiled and tested graphshell with real browsing. Discovered multiple critical bugs that required immediate fixes before proceeding with crate refactoring.
Critical Bugs Found & Fixed:
See 2026-02-10_servo_integration_plan.md for detailed bug reports and fixes:
- â Tab restoration broken - Fixed condition in gui.rs:741
- â Camera zoom too aggressive - Reduced max zoom from 10.0 to 2.0 for overlapping nodes
- âšī¸ Nodes stacking - Expected behavior, physics needs time to spread nodes
Impact on Crate Refactoring:
- Phase 1 (egui_graphs): Still recommended, but custom rendering works well enough
- Phase 2 (petgraph): â Complete and tested, ready for production use
- Phase 3 (egui_tiles): Higher priority after discovering tab management issues
- Phase 4 (kiddo): Lower priority, custom spatial hash works well
Next Steps:
- Continue with Phase 3 (egui_tiles) to improve split view UX
Phase 1 Complete - egui_graphs Integration:
- â
Created
graph/egui_adapter.rswithEguiGraphStatefor SlotMap â egui_graphs conversion - â
Refactored
render/mod.rsto useGraphViewwidget (371 â ~297 LOC)- Replaced custom painter calls with egui_graphs rendering
- Built-in zoom/pan navigation (SettingsNavigation)
- Built-in node dragging and selection (SettingsInteraction)
- Always-visible labels (SettingsStyle)
- Event-driven interaction (NodeDoubleClick â focus, NodeDragStart/End â physics pause)
- Added repulsion_radius slider to physics panel
- â
Refactored
input/mod.rs(313 â 58 LOC, -81% reduction)- Removed all custom mouse handling (drag, pan, zoom, selection, hit testing)
- Kept keyboard shortcuts (T, P, C, Home, Escape)
- 'C' now triggers egui_graphs fit_to_screen instead of custom camera
- â
Updated
app.rs:- Removed Camera field and all camera methods (update_camera, center_camera)
- Added
fit_to_screen_requested: boolfield (one-shot flag for 'C' key) - Added
request_fit_to_screen()method
- â
Updated
gui.rs: Removedupdate_camera(dt)call - â All 82 tests pass (was 100; removed 27 dead camera/render/input tests, added 9 new)
Phase 4 Complete - kiddo KD-tree Integration:
- â
Added
kiddo = "4.2"to Cargo.toml - â
Replaced
SpatialGridHashMap withkiddo::KdTree<f64, 2>(95 LOC, same interface)-
within_unsorted::<SquaredEuclidean>for O(log n) radius queries - Parallel
node_keys: Vec<NodeKey>array for index â key lookup
-
- â
Added
repulsion_radius: f32toPhysicsConfig(default: 300.0)- Configurable via physics panel slider (50-1000 range)
- Replaces hardcoded
300.0distance check in physics step
- â
Updated physics to use
query_nearby_radius(pos, config.repulsion_radius) - â Added 4 new spatial grid tests (insertion, clear, radius query, legacy API)
Architecture Decisions:
-
Per-frame conversion approach: Rebuild egui_graphs::Graph from SlotMap each frame
- Pros: Simple, no sync complexity, positions always match physics
- Cons: Negligible conversion cost for < 1000 nodes
- GraphView stores zoom/pan state in egui memory (persists across rebuilds)
- Interaction state (drag, select) set fresh by GraphView each frame
-
Event sink:
Rc<RefCell<Vec<Event>>>(wasm-friendly, no crossbeam dependency) -
Layout: Using
LayoutRandom(no-op) since our physics engine controls positions - User preference: Favor maintained crates over custom implementations
LOC Impact:
- render/mod.rs: 371 â 297 (-20%)
- input/mod.rs: 313 â 58 (-81%)
- New: egui_adapter.rs: ~100 LOC
- Net reduction: ~330 LOC of custom code replaced by crate
- Custom physics engine works well with egui_graphs rendering
- kiddo KD-tree is a drop-in replacement for HashMap spatial hash
- Camera module still exists but is dead code (can be removed in cleanup)
- egui_graphs built-in navigation handles zoom/pan better than custom camera
- Phase order can be flexible - independence between phases allows parallel work
- Principle: Favor maintained, well-documented crates over custom implementations