2026 04 18_egui_graphs_retirement_plan - mark-ik/graphshell GitHub Wiki
Status: Archived 2026-04-19 โ scope complete. egui_graphs is
retired from the live path; graph-canvas is the sole graph scene /
interaction / camera authority. Live-path follow-ons moved to their own
plans:
- Steps 2โ6 layout portfolio โ ../../../../graphshell_docs/implementation_strategy/graph/2026-04-19_step5_spatial_pattern_layouts_plan.md
- Persistent rapier adapter โ ../../../../checkpoint_2026-04-20/graphshell_docs/implementation_strategy/graph/2026-04-19_persistent_rapier_adapter_plan.md (archived 2026-04-20)
- Pluggable-mods registry โ ../../../../graphshell_docs/implementation_strategy/graph/2026-04-19_layouts_as_pluggable_mods_plan.md
- ยง5.6 plumbing re-lands (background pan / wheel pan / pinch / inertia / Fit / overlays) โ ../../../../checkpoint_2026-04-20/graphshell_docs/implementation_strategy/shell/2026-04-19_graph_canvas_overlays_and_camera_relands_plan.md (archived 2026-04-20)
- Flaky test hygiene โ ../../../../checkpoint_2026-04-20/graphshell_docs/implementation_strategy/testing/2026-04-19_flaky_test_hygiene_plan.md (archived 2026-04-20)
- Step 7 WASM layout adapter โ ../../../../graphshell_docs/implementation_strategy/graph/2026-04-03_wasm_layout_runtime_plan.md
- Graph-canvas input / accessibility follow-on โ ../../../../graphshell_docs/implementation_strategy/shell/2026-04-19_graph_canvas_input_accessibility_followon_plan.md
Original scope: Complete M2 of the iced host migration by retiring
egui_graphs as a live dependency, leaving a clean baseline in which
graph-canvas is the sole graph scene/interaction/camera authority.
Parent plan: ../../../../graphshell_docs/implementation_strategy/shell/2026-04-14_iced_host_migration_execution_plan.md
Driving directive: "ensure redundancy doesn't carry over and that we don't have any weird legacy issues or compensations. let's just get a clean, good baseline 'this should work' out of retiring egui_graphs" โ user, 2026-04-18.
Earlier drafts assumed three interlocked slices (MetadataFrame, overlays, physics). Verification showed most of that surface is already dead on the live path. The revised baseline scope is:
- Rework the
LayoutCalculatortrait to operate on petgraph (crate::graph::Graph) or a position snapshot instead ofegui_graphs::Graph<...> - Migrate
graph/layouts/graphshell_force_directed.rs,barnes_hut_force_directed.rs,active.rs - Replace
graph/physics.rsre-exports ofegui_graphs::FruchtermanReingold*with internal-owned equivalents - Delete
render::sync_graph_positions_from_layout(physics writes directly to petgraph; no copy step needed) - Delete
app.workspace.graph_runtime.egui_stateandegui_state_dirty
All of the following are unreachable from the live graph pane today (see ยง2):
-
render/canvas_camera.rsโ entire file retires (its dead-code public surface has no live callers) -
render/canvas_overlays.rsโ alldraw_*functions retire (none live) -
render/canvas_input.rsโcollect_lasso_action,collect_graph_actions,collect_graph_keyboard_traversal_actionretire -
render/mod.rsโgraph_view_metadata_id,graphshell_owned_navigation_settings,canvas_interaction_settings,canvas_style_settings,pointer_canvas_pos(when its MetadataFrame dep is gone), and any surrounding dead helpers -
model/graph/egui_adapter.rsโ whole module deletes -
render/canvas_visuals::apply_search_node_visualsโ test-only; delete with its tests
The dead functions above support features that, after ยง2 analysis, meet the
Firefox/standards/ergonomic/modular bar with only a data-source rework (i.e.,
swap egui_state.graph reads for petgraph + CanvasCamera):
- Background pan (primary-drag pans the camera)
- Wheel pan (plain wheel translates camera โ infinite-canvas convention, not webpage convention)
-
Ctrl/Cmd + wheel zoom (also captures trackpad pinch via synthetic
ctrlKey) - Middle-click drag โ free pan (Figma/Miro idiom, appropriate for infinite canvas)
- Pan inertia (kept; reinforces the spatial/physical feel of the force-directed space)
-
Fit / FitSelection / FitGraphlet (bounds computed from petgraph,
applied to
CanvasCamera) - Frame affinity backdrops (decorative visual layer over world space)
- Scene region backdrops (Arrange/Simulate mode chrome)
- Highlighted edge overlay (hovered/selected edge visual)
All routed through CanvasCamera as single camera authority. No MetadataFrame.
The baseline re-lands should extend canvas_bridge rather than egui-specific
code where feasible, so the iced host later inherits them for free:
- Pan/zoom/fit: live in
CanvasCamera+ host-neutral helpers - Overlay draws: emit extra
ProjectedSceneoverlay items (whichcanvas_egui_painteralready paints) rather than host-specific painter calls - Middle-click and ctrl+wheel input translation: in
canvas_bridge::collect_canvas_events
- Remove
egui_graphsfromCargo.toml - Cargo.lock regen
- Run full workspace build + tests
- Lasso redesign (accessibility, right-drag conflict with context menu)
- Tab traversal redesign (spatial navigation, ARIA roles, announcements)
- Hover tooltips (focus-triggered variant, ARIA, keyboard dismissal)
- Keyboard zoom shortcut rebinding (
Ctrl+=/Ctrl+-/Ctrl+0)
These require real design work and cannot be "ported as-is". Tracked in the follow-on plan.
- render_graph_canvas_in_ui (render/mod.rs:475) โ graph-canvas only, zero egui_graphs
-
render_graph_info_in_ui (render/mod.rs:401) โ
graph_info::draw_graph_infoโ touchesegui_stateonly for dirty flag + one scene-gatherset_locationsync -
sync_graph_positions_from_layout
(render/mod.rs:599) โ reads from
egui_state.graph, writes to petgraph - render_graph_pane_overlay (shell/desktop/workbench/tile_behavior.rs:1094) โ chrome only (Split Graph button); hint text reads "Lens, depth, fit, and physics moved to the graph host" confirming original intent for migration
No live callers found for:
-
canvas_camera::handle_custom_navigation/apply_pending_camera_command/apply_pending_keyboard_zoom_request/apply_pending_wheel_zoom/apply_background_pan/apply_background_pan_inertia -
canvas_input::collect_lasso_action/collect_graph_actions/collect_graph_keyboard_traversal_action -
canvas_overlays::draw_frame_affinity_backdrops/draw_highlighted_edge_overlay/draw_hovered_node_tooltip/draw_hovered_edge_tooltip/draw_scene_runtime_backdrops/draw_scene_simulate_overlays/draw_scene_region_action_overlay -
render_graph_in_ui_collect_actions(only mentioned in a comment) -
graph_view_metadata_idโ called only from its own tests -
apply_search_node_visualsโ called only from tests
The functions are pub(super) or pub(crate), so Rust's dead-code lint
doesn't flag them. That's why the drift survived.
The graph canvas is infinite-canvas territory, not webpage territory. The correct conventions (stored in agent memory as feedback_graph_canvas_navigation_defaults.md):
- Plain wheel โ pan (like Firefox inside infinite-canvas documents)
- Ctrl/Cmd + wheel โ zoom (also trackpad pinch)
- Middle-click drag โ free pan (Figma/Miro idiom)
- Pan inertia on release โ keep (reinforces spatial/physical feel)
- Build current tree; capture baseline test count
- Retire physics off
egui_graphs::Graph(ยง1.A) - Dead-code sweep (ยง1.B)
- Re-land plumbing-only features on
CanvasCamera+ host-neutral seam (ยง1.C, ยง1.D) - Drop
egui_graphsdependency (ยง1.E) - Run full workspace build + tests; verify no new failures
- Plan created.
-
Scope verified against live path. Revised plan: physics retirement + dead code sweep + plumbing-only re-lands; follow-on plan for features needing design work. Saved pan/zoom defaults to agent memory.
-
Dead-code sweep landed (ยง1.B). Deleted:
render/canvas_camera.rs,render/canvas_overlays.rs,render/canvas_input.rs(three files, all unreachable from the live path). Fromrender/mod.rs: removed the threemoddecls, their production and test imports,graph_view_metadata_id,pointer_canvas_pos,apply_active_scene_region_drag,graphshell_owned_navigation_settings,canvas_interaction_settings,canvas_style_settings,use egui_graphs::{...}, and ~20 tests that depended on deleted symbols. Fromrender/graph_info.rs: removed the dead-on-live-pathrequested_layout_algorithm_idandshould_apply_layout_algorithm. Fromrender/canvas_visuals.rs: removedapply_search_node_visuals(test-only caller set) and its three tests. Keptcanvas_lasso_binding_label(promoted topub(crate)) โ it's live viagraph_info.rs:557. -
cargo check --lib --testsgreen. Only warnings remain (mostly deprecated egui methods, unused variables; none from this change). -
Remaining
egui_graphssurface after this sweep:-
graph/physics.rsre-exportsFruchtermanReingold*etc. -
graph/layouts/{graphshell_force_directed,barnes_hut_force_directed,active}.rsoperate onegui_graphs::Graph<...> -
model/graph/egui_adapter.rsdefinesEguiGraphState/EguiGraph -
app.workspace.graph_runtime.egui_state,egui_state_dirtyfields -
render/sync_graph_positions_from_layoutreadsegui_state.graphand writes to petgraph -
graph_info.rsscene-gatherset_locationsync +egui_state_dirtyflag writes -
physics.rs::apply_position_deltasstill mirrors writes toegui_state - Isolated: no camera/overlay/input code still touches egui_graphs
-
The remaining work is physics migration. All egui_graphs dependence now
sits in the physics/layout pipeline and its carrier state (EguiGraphState).
Decision (2026-04-19): physics moves into graph-canvas, not into
Graphshell-app-local graph/layouts/. Options considered:
-
Option A โ Vendor FR inRejected as legacy compensation. Keeps physics parochial to one host; forces either a re-extract or a duplicate port when iced-host lands (M5).graph/layouts/. -
Option B โ UseRejected: new dep with different API shape; perturbs existing tuning; ownership cost without structural yield.fdg-simor similar. -
Option C โ Rewrite in
graph-canvas. Accepted. Right structural home (mobile/WASM-clean by construction, iced inherits for free, aligns with existingscene_physicsandsimulatemodules, matches the 2026-02-24 physics plan's updated home). See ../graph/2026-02-24_physics_engine_extensibility_plan.md ยง2026-04-19.
Lands in a new module crates/graph-canvas/src/layout/mod.rs:
use std::collections::HashMap;
use std::hash::Hash;
use euclid::default::Vector2D;
use serde::{Deserialize, Serialize};
use crate::scene::CanvasSceneInput;
pub trait Layout<N: Clone + Eq + Hash> {
type State: Default + Clone + Serialize + for<'de> Deserialize<'de>;
/// Advance one frame. Returns position deltas for the host to apply
/// to its own position store. Does not mutate the scene input.
fn step(
&mut self,
scene: &CanvasSceneInput<N>,
state: &mut Self::State,
dt: f32,
extras: &LayoutExtras<N>,
) -> HashMap<N, Vector2D<f32>>;
/// Whether this layout has settled (delta norm below threshold).
/// Hosts use this to auto-pause the tick.
fn is_converged(&self, state: &Self::State) -> bool { false }
}
/// Out-of-band inputs for layout composition.
///
/// - `pinned` โ nodes whose positions must not be moved
/// - `domain_buckets` โ precomputed registrable-domain groups for clustering
/// - `semantic_similarity` โ precomputed pair similarities for semantic forces
/// - `regions` โ frame-affinity / scene region pulls
pub struct LayoutExtras<N> {
pub pinned: std::collections::HashSet<N>,
pub domain_buckets: HashMap<String, Vec<N>>,
pub semantic_similarity: HashMap<(N, N), f32>,
pub regions: Vec<crate::scene_region::SceneRegion>,
}Key shape choices:
-
Delta-returning, not mutating โ matches
scene_physicsconvention. Hosts apply deltas to their own position lane (petgraph for graphshell, other carriers for future hosts). -
dt: f32โ explicit timestep; nostd::timedep; WASM-clean. -
extrasโ out-of-band inputs per the 2026-04-03 semantic-clustering follow-on plan (semantic vectors computed out-of-band, not per-frame). -
Generic over
Nโ same NodeKey generic as the rest of the crate; no petgraph dependency in graph-canvas.
// crates/graph-canvas/src/layout/active.rs
pub enum ActiveLayout<N: Clone + Eq + Hash> {
ForceDirected(force_directed::ForceDirected<N>),
BarnesHut(barnes_hut::BarnesHut<N>),
Radial(radial::Radial<N>),
Timeline(timeline::Timeline<N>),
Phyllotaxis(phyllotaxis::Phyllotaxis<N>),
Grid(grid::Grid<N>),
// future: Penrose, LSystem, SemanticEmbedding, Wasm, Rapier
}
#[derive(Serialize, Deserialize)]
pub enum ActiveLayoutState { /* per-variant state */ }
impl<N: Clone + Eq + Hash> Layout<N> for ActiveLayout<N> {
type State = ActiveLayoutState;
fn step(&mut self, scene, state, dt, extras) -> HashMap<N, Vector2D<f32>> {
match self { ActiveLayout::ForceDirected(l) => l.step(...), ... }
}
}Graphshell's graph::physics becomes a thin re-export/shim module:
// graph/physics.rs (post-migration)
pub use graph_canvas::layout::{ActiveLayout, ActiveLayoutState, Layout, LayoutExtras};
pub use graph_canvas::layout::force_directed::{
ForceDirected as GraphPhysicsLayout,
ForceDirectedState as GraphPhysicsState,
};
// Graphshell-owned policy stays
pub struct GraphPhysicsTuning { ... }
pub struct GraphPhysicsExtensionConfig { ... }Each step is a clean stopping point. Baseline-safe iteration.
Step 1 โ Trait + FR (the MVP). Land:
-
graph-canvas::layout::Layouttrait +LayoutExtras -
graph-canvas::layout::force_directed::{ForceDirected, ForceDirectedState}โ vendored fromegui_graphs::FruchtermanReingoldWithCenterGravity(MIT), rewritten to return deltas instead of mutating. ~300 LOC. - Bridge:
graph::physicsre-exports; frame tick in graphshell readsCanvasSceneInput, callsstep(), applies deltas to petgraph. - Delete
sync_graph_positions_from_layout,egui_state,egui_state_dirty,egui_adapter.rs. - Drop
egui_graphsfromCargo.toml.
At this point: egui_graphs is gone, parity is preserved, iced host adapter will inherit physics cleanly. Roughly the 1-session budget described in the previous plan.
Step 2 โ Barnes-Hut. Vendor FruchtermanReingoldWithExtras +
quadtree. ~250 LOC. Behind a barnes-hut feature flag if we want to
keep the core graph-canvas lean.
Step 3 โ Physics extras as Layout composition passes. Degree
repulsion, domain clustering, semantic clustering, hub pull, frame
affinity. Each is a small Layout impl that consumes the same scene + the
precomputed inputs in LayoutExtras. Composition via CompositeLayout
(run pass N's deltas, accumulate, then run pass N+1 against the mutated
snapshot โ simple layered composition).
Step 4 โ Static positional layouts. From the plan's catalogue, these are pure math with no iterative state:
-
layout::radial::Radialโ BFS from a focal node; ring n at graph-theoretic distance n; angular spacing by degree. ~80 LOC. -
layout::timeline::Timelineโ x bycreated_at/last_visited, y by UDC cluster or domain group. ~100 LOC. -
layout::phyllotaxis::Phyllotaxisโ Fibonacci spiral, priority-keyed. Five-line placement formula + ordering state. ~60 LOC. -
layout::grid::Gridโ rectilinear snap by node count sqrt or by explicit row/col. ~50 LOC. -
layout::kanban::Kanbanโ column-bucket by status tag. ~80 LOC.
All of these are Layout implementers that return deltas toward their
target positions (so they can compose with a damping pass for
animate-in, or return the full "position โ current" delta for instant
placement).
Step 5 โ Geometric layouts (lower priority, higher value/line ratio).
-
layout::penrose::Penroseโ recursive rhombus subdivision, P2/P3 variants. ~150 LOC of pure geometry. No crate; golden-ratio transforms. -
layout::l_system::LSystemโ Hilbert curve + Koch + dragon as built-in grammars; ~100 LOC.l-system-fractalscrate if we want external grammars. -
layout::semantic_embedding::SemanticEmbeddingโ UMAP-style projection of existing UDC / semantic-vector data; reads fromLayoutExtras. The projection itself is out-of-band; theLayoutimpl just reads the precomputed 2D coordinates and returns deltas toward them.
Step 6 โ rapier2d Layout adapter. Bridge the already-landed
RapierSceneWorld to the Layout trait: each step() calls
world.step(), reads body translations, returns deltas. Makes the
scene-physics path a peer of other layouts rather than a parallel pipeline.
Step 7 โ WASM layout adapter. Host-side WasmLayoutAdapter
implementing Layout; delegates step() to a guest compute_layout
function via extism or wasmtime. Guest ABI: msgpack-serialized scene +
state in, deltas out. Versioned as layout-wasm-api:1.
Strong recommendation: Step 1 only next session. That's the egui_graphs-retirement MVP:
- One new module (
graph-canvas::layout) with the trait,LayoutExtras, and FR vendored. - One bridge edit in
graph::physics+ frame tick. - Delete carriers + dep.
- Verify
cargo check --lib --tests+cargo test --libpass count.
Steps 2โ7 become their own subplans (or ride on the existing follow-ons at 2026-04-03_layout_variant_follow_on_plan.md and 2026-04-03_wasm_layout_runtime_plan.md).
- Add
crates/graph-canvas/src/layout/mod.rswith theLayouttrait,LayoutExtras<N>, and re-exports for submodules. - Add
crates/graph-canvas/src/layout/force_directed.rswith vendored FR + center gravity math. State isForceDirectedState(displacement accumulator, damping, last_avg_displacement for convergence).step()readsCanvasSceneInput, computes repulsive/attractive/gravity forces, returns per-node deltas. Pin respectsLayoutExtras::pinned. - Add
ActiveLayout<N>enum +ActiveLayoutStateincrates/graph-canvas/src/layout/active.rs. Initially onlyForceDirectedvariant. -
Cargo.tomlfor graph-canvas: add serde + euclid (already present), plus any dependencies the vendored math needs. No new deps expected. - In
graphshell/graph/physics.rs: replacepub use egui_graphs::...withpub use graph_canvas::layout::{ActiveLayout, ActiveLayoutState, force_directed::*, ...}. KeepGraphPhysicsTuningandGraphPhysicsExtensionConfigGraphshell-owned. - Rewrite
graph/layouts/{graphshell_force_directed,barnes_hut_force_directed,active}.rsto target the new types. Keep the trait surface backward compatible at the Graphshell re-export point so downstream code doesn't churn. - Update
graph::physics::apply_position_deltasto write only to petgraph (noegui_statemirror). - Delete
render::sync_graph_positions_from_layoutand its three call sites (shell/desktop/workbench/tile_render_pass.rs:155, tile_render_pass.rs:225, node_pane_ui.rs:98). The physics loop now writes to petgraph directly; the frame tick callsActiveLayout::step()and applies deltas inline. - Delete
egui_stateandegui_state_dirtyfields fromapp.workspace.graph_runtime(app/workspace_state.rs). - Remove every
egui_state = Some(...)initialization,egui_state_dirty = trueflag write,egui_state.as_ref()/.as_mut()read. The compiler will enumerate them. - Delete
model/graph/egui_adapter.rs+ itsmoddeclaration. - Remove
egui_graphsfromCargo.toml. RegenerateCargo.lock. -
cargo check --lib --testsโ fix fallout โcargo test --lib. - Compare test count against this session's baseline; fix regressions.
These can happen in the same next session or a third session, whichever the user prefers:
- Background pan (primary drag) โ
CanvasCamera.pan - Plain wheel โ pan (not zoom) โ per memory feedback_graph_canvas_navigation_defaults.md
- Ctrl/Cmd + wheel โ zoom (also captures trackpad pinch)
- Middle-click drag โ free pan
- Pan inertia (keep; reinforces force-directed physicality)
- Fit / FitSelection / FitGraphlet โ bounds from petgraph, applied to
CanvasCamera - Frame affinity backdrops โ emit as
ProjectedSceneoverlay items - Scene region backdrops (Arrange/Simulate) โ same
- Highlighted edge overlay โ same
All routed through canvas_bridge so iced inherits them for free.
Files deleted:
render/canvas_camera.rsrender/canvas_overlays.rsrender/canvas_input.rs
Files edited:
-
render/mod.rsโ imports, helpers, tests cleaned up -
render/graph_info.rsโ dead layout-gate functions removed -
render/canvas_visuals.rsโapply_search_node_visualsremoved with tests
Compile state: cargo check --lib --tests passes (119 warnings, no errors).
Not yet done (physics/adapter): graph/physics.rs, graph/layouts/*.rs,
model/graph/egui_adapter.rs, app/workspace_state.rs::egui_state,
render/mod.rs::sync_graph_positions_from_layout, Cargo.toml are
untouched.
Executed the 14-step MVP checklist in ยง5.5:
- Added
crates/graph-canvas/src/layout/{mod.rs,force_directed.rs}with theLayout<N>trait,LayoutExtras<N>, and vendored FR + center gravity (delta-returning, flat state). 6 FR unit tests pass. -
graph::physicsnow re-exportsForceDirected/ForceDirectedStateasGraphPhysicsLayout/GraphPhysicsState. Tuning function updated for flat-state fields (c_repulse,c_attract,damping,c_gravity). -
render::canvas_bridge::run_graph_canvas_frameticks FR in-band: builds scene, runsLayout::step()when physics is running and the user isn't dragging, applies deltas straight to petgraph. Nosync_graph_positions_from_layoutanymore. - Deleted:
graph/layouts/subtree (active.rs, graphshell_force_directed.rs, barnes_hut_force_directed.rs, physics_scenarios.rs),model/graph/egui_adapter.rs(withGraphNodeChromeThememoved inline intoshell/desktop/runtime/registries/theme.rs),render::sync_graph_positions_from_layoutand its 3 call sites,GraphViewRuntimeState::egui_state+egui_state_dirtyfields and ~62 flag writes across 12 files,GraphViewState::egui_stateper-view cache. -
egui_graphsdropped fromCargo.toml.
Live-path effect: force-directed now actually ticks on the graph canvas.
Before Step 1, LayoutAlgorithm::execute for ForceDirectedLayout was a
no-op and the egui_graphs loop was never reached. Retirement both removes
the dead dependency and restores the missing motion.
cargo test --lib: 2144 passed / 0 failed / 3 ignored (serial);
cargo test -p graph-canvas --lib: 139 passed.
Following the staged order in ยง5.3:
crates/graph-canvas/src/layout/barnes_hut.rs. O(n log n) quadtree-based
repulsion with ฮธ = 0.5 default; same attraction + center-gravity as FR;
shares ForceDirectedState for drop-in swappability at scale.
crates/graph-canvas/src/layout/extras.rs adds DegreeRepulsion,
DomainClustering, SemanticClustering, HubPull, FrameAffinity. Each is
a Layout<N> that reads the scene plus slots on LayoutExtras:
-
pinned: HashSet<N>โ nodes that do not receive deltas. -
domain_by_node: HashMap<N, String>โ precomputed registrable-domain groupings for domain clustering. -
semantic_similarity: HashMap<(N, N), f32>โ precomputed pairwise similarity scores for semantic clustering. -
frame_regions: Vec<FrameRegion<N>>โ anchor + members per frame; the layout computes the member centroid each step.
Graphshell-side call sites (graph::physics::apply_*_forces,
graph::frame_affinity::apply_frame_affinity_forces) keep their external
signatures but delegate to the graph-canvas impls. semantic_pair_similarity
and registrable_domain_key helpers stay in graph::physics for the bridge
to call when building LayoutExtras slots each pass.
crates/graph-canvas/src/layout/static_layouts.rs:
-
Gridโ row-major withceil(sqrt(n))columns, configurable gap/origin. -
Radialโ BFS rings from a focal node; ringnat radiusn ร ring_spacing; unreachable nodes on an outer ring. -
Phyllotaxisโ Fibonacci spiral (golden angle) with inward/outward orientations for priority-queue vs recency-ring semantics.
All three share StaticLayoutState with a damping field โ 1.0 snaps
instantly, 0.2 eases in over ~20 frames.
Deferred: Timeline and Kanban โ they need host-specific metadata
(time coordinates, status tags) which would expand LayoutExtras with a
currently-unused slot. Adding them is mechanical once a consumer asks.
crates/graph-canvas/src/layout/rapier_adapter.rs behind the simulate
feature. RapierLayout builds a fresh RapierSceneWorld each step from the
current scene (ball collider per node, spring joints per edge, static bodies
for pinned nodes), steps once with configurable gravity, reads back deltas.
Known limitation: rebuild-per-step loses cross-frame momentum. A persistent variant that reuses one world and syncs positions in/out each step is a deferred optimization; the current behavior matches the pre-M2 scene-physics runtime which was also per-frame snapshot-based.
-
cargo test -p graph-canvas --lib: 154 passed / 0 failed (149 with default features + 5 static-layout tests; +4 Barnes-Hut, +6 extras in prior runs counted in this total). -
cargo test -p graph-canvas --features simulate --lib: 178 passed / 0 failed. -
cargo test --lib -- --test-threads=1(graphshell workspace): 2144 passed / 0 failed / 3 ignored. - Two tests flake under parallel execution in ways unrelated to this session's changes (corridor graphlet mask and radial sector probe). Flagged for a future test-hygiene pass; not regressions.
- Step 5: Penrose aperiodic tiling, L-system fractal path, UMAP-style semantic embedding โ design-heavy; trait-implementer work once decisions on grammar choice and similarity space land.
-
Step 7: WASM
Layoutadapter โ needs versioned guest ABI; tracked in ../graph/2026-04-03_wasm_layout_runtime_plan.md. -
Timeline / Kanban static layouts โ pending a
LayoutExtrasslot for time/tag metadata. -
Persistent rapier adapter โ reuse one
RapierSceneWorldacross frames for real momentum. - Plumbing-only feature re-lands from ยง5.6 (background pan, wheel pan, pinch zoom, middle-click pan, inertia, Fit commands, overlays).