2026 04 10_graph_tree_implementation_plan - mark-ik/graphshell GitHub Wiki
Date: 2026-04-10 Status: Active β Phases 0β6 complete, Phase 7 remaining (strategy planned) Scope: Phased migration from egui_tiles to GraphTree, including Navigator projection collapse, arrangement-edge consumption, and UxTree integration.
Related:
-
../../technical_architecture/graph_tree_spec.mdβ GraphTree crate API design -
2026-04-11_graph_tree_egui_tiles_decoupling_follow_on_plan.mdβ post-extraction authority migration, review findings, andegui_tilesdecoupling sequence -
../../research/2026-04-10_ui_framework_alternatives_and_graph_tree_discovery.mdβ research backing -
../../technical_architecture/graphlet_model.mdβ graphlet semantics (consumed, not replaced) -
../navigator/NAVIGATOR.mdβ Navigator domain spec (projection collapses into GraphTree) -
../navigator/navigator_interaction_contract.mdβ click grammar (carried forward unchanged) -
../navigator/navigator_backlog_pack.mdβ Navigator items absorbed/unblocked -
graphlet_projection_binding_spec.mdβ binding semantics (consumed) -
workbench_frame_tile_interaction_spec.mdβ mutation routing (preserved) -
../../research/2026-02-27_egui_stack_assessment.mdβ "when to replace" criteria -
../PLANNING_REGISTER.mdβ lane registration -
WORKBENCH.mdβ Workbench domain authority (preserved) -
../shell/shell_composition_model_spec.mdβ WorkbenchArea slot (preserved) -
../subsystem_ux_semantics/ux_tree_and_probe_spec.mdβ UxTree projection contract
The egui stack assessment (Feb 2026) said: replace egui_tiles when "adapter code becomes larger than the value the widget provides" or "core UX keeps being distorted to fit widget assumptions."
2026-04-11 note:
- the
graph-treecrate extraction has now landed, - but the desktop shell still mirrors
egui_tilesback intoGraphTreeduring the migration phase, - so the next stage is no longer "extract the crate" but "complete the
authority migration and decouple semantic truth from
egui_tiles."
Treat the 2026-04-11 follow-on plan as the active execution note for that post-extraction stage.
Current state:
-
~25.5k lines of workbench code in
shell/desktop/workbench/ -
53 files reference
egui_tilesacross the codebase -
tile_view_ops.rsalone is 2,443 lines of adapter/mutation code -
tile_behavior.rsis 1,681 lines ofBehaviortrait overrides -
GraphshellTileBehavior,GraphletBinding,TileCoordinator,FrameLayoutpersistence, focus routing β all layered on top of a data structure that speaks rectangles, not graphlets - Navigator maintains a parallel projection over the same membership truth, requiring synchronization that GraphTree eliminates
- The arrangement-graph-projection plan (shipped 2026-03-21) already moved membership truth to graph edges β but the tile tree still uses egui_tiles as the realization layer, creating a semantic translation gap
The adapter code now exceeds the value. The UX is distorted (Navigator vs. Workbench scope split exists because egui_tiles can't express what the graph already knows). The criteria are met.
| Item | Source | How GraphTree addresses it |
|---|---|---|
| Navigator section collapse | NAVIGATOR.md Β§8 | Sections become ProjectionLens variants over one tree |
| Navigator/Workbench scope split | chrome_scope_split_plan | Eliminated β one tree, pick your lens |
| Navigator arrangement projection | NV15 (backlog) |
visible_rows() with Arrangement lens replaces bespoke projection |
| Navigator expansion state | NV05 (backlog) |
expanded: HashSet<N> is a first-class field, session-persisted |
| Navigator click grammar | NV06 / interaction contract | Carried forward unchanged; NavAction maps 1:1 |
| Navigator reveal rule | NV11 (backlog) |
Reveal(N) in NavAction with lens-aware ancestor expansion |
| Navigator recents | NV14 (backlog) |
Recency projection lens over the same tree |
| Navigator/Workbench sync | NV23 / WB25 (backlog) | Eliminated β no sync needed when there's one tree |
| Graphlet binding | graphlet_projection_binding_spec | Per-graphlet GraphletBinding inside the tree |
| EdgeProjectionSpec consumption | graphlet_projection_binding_spec Β§3 |
GraphletRef reads selectors from graph truth |
| Frame graph representation | frame_graph_representation_spec | Stable GraphletId + tile-center coords for canvas minimap |
| Containment/domain grouping | graph_relation_families | Containment lens derives hierarchy from domain/url-path edges |
| Tile tree invariants | egui_stack_assessment Β§5.3 |
apply_nav() enforces invariants by construction |
| Minimum pane sizes | ux_integration_research G-DO-2 |
LayoutOverride per member, enforced by taffy |
| Node vs. tile identity confusion | ux_integration_research G-IA-1 |
Lifecycle makes the distinction explicit (Cold = in graph, not in pane) |
| UxTree projection | ux_tree_and_probe_spec |
emit_ux_tree() produces UxNodeDescriptor tree directly |
| Compositor bridge | frame_assembly_and_compositor_spec |
compute_layout() provides pane rects; compositor queries tree, not egui_tiles |
| WorkbenchIntent routing | SYSTEM_REGISTER Β§9.2 |
NavAction β apply_nav() β TreeIntent β host converts to WorkbenchIntent
|
| Arrangement-edge extensibility | graph_relation_families | String-keyed edge families, not closed enums |
| Extension/PWA portability | portable_web_core_host_envelopes | Framework-agnostic tree serializes as JSON; renders as DOM in extension hosts |
| ID | Item | Status after GraphTree |
|---|---|---|
| NV04 | Section Mapping Audit | Replaced by ProjectionLens variants |
| NV05 | Expansion State Contract | Built into GraphTree.expanded
|
| NV10 | Projection Refresh Triggers | Tree rebuilds from graph deltas via Attach/Detach/SetLifecycle
|
| NV14 | Recents Contract |
Recency lens |
| NV15 | Arrangement Projection |
Arrangement lens + visible_rows()
|
| NV23 | Workbench-Navigator Sync | Eliminated β shared tree |
Items NOT absorbed (remain Navigator-owned):
| ID | Item | Why separate |
|---|---|---|
| NV16 | Search/Filter Model | Navigator search is a query over graph truth, not a tree operation |
| NV20 | Accessibility/Keyboard | Framework-specific; handled by GraphTreeRenderer adapter |
| NV21 | Diagnostics Pack | Diagnostics channels are subsystem-owned, not tree-owned |
| File | Lines | Current role | Migration |
|---|---|---|---|
tile_view_ops.rs |
2,443 | ~40 tree mutation functions | Replaced by apply_nav() with typed NavAction
|
tile_behavior.rs |
1,681 |
Behavior trait overrides for egui_tiles |
Replaced by GraphTreeRenderer impl for egui |
tile_kind.rs |
135 |
TileKind enum (Pane/Graph/Node/Tool) |
Replaced by MemberEntry<NodeKey> with lifecycle + provenance |
tile_grouping.rs |
166 | Frame/graphlet group tracking | Replaced by GraphletRef inside tree |
semantic_tabs.rs |
248 | Tab ordering/chrome | Replaced by TabEntry from compute_layout()
|
tile_invariants.rs |
149 | Tile tree invariant checks | Built into apply_nav() postconditions |
persistence_ops.rs |
3,586 |
FrameLayout serialization |
Replaced by GraphTree serde (phased migration for backward compat) |
navigator_context.rs |
142 | Navigator scope/context | Replaced by ProjectionLens
|
| File | Lines | Current role | Migration |
|---|---|---|---|
tile_compositor.rs |
2,857 | Per-tile render scheduling | Reads compute_layout().pane_rects instead of iterating egui_tiles |
tile_render_pass.rs |
1,236 | Per-tile render dispatch | Dispatches from LayoutResult pane rects |
tile_post_render.rs |
1,205 | Post-render overlays | Uses tree for overlay positioning |
tile_runtime.rs |
1,015 | Runtime tile state | Thins to lifecycle bridge (SetLifecycle calls) |
compositor_adapter.rs |
2,461 | GL/wgpu compositor adapter | Unchanged β reads rects from tree instead of egui_tiles |
pane_model.rs |
703 | Pane state model | Merges into MemberEntry or thins to viewer-specific state |
interaction_policy.rs |
121 | Interaction policy | Unchanged β policy sits above tree |
ux_tree.rs |
3,408 | UxTree projection | Calls emit_ux_tree() instead of walking egui_tiles |
ux_bridge.rs |
1,129 | UxTree egui bridge | Adapts UxNodeDescriptor to egui AccessKit |
ux_probes.rs |
1,343 | UxTree invariant probes | Probes read from tree's structural API |
workspace_state.rs |
690 | Workspace persistence | Serializes GraphTree instead of Tree<TileKind>
|
workbench_commands.rs |
761 | Workbench command dispatch | Routes commands to NavAction instead of direct tree mutation |
The tile tree is currently threaded through the frame pipeline as a parameter:
gui_frame.rs β gui_orchestration.rs β tile_render_pass.rs β workbench_host.rs.
GraphTree must slot into this same pipeline, maintaining the same ownership
and borrowing discipline.
| File | Lines | Current role | Migration |
|---|---|---|---|
gui_frame.rs |
~600 | Passes tiles_tree to render/composition |
Thread GraphTree instead |
gui_orchestration.rs |
~1,200 | Frame phase orchestration | Thread GraphTree through phases |
gui_frame/frame_persistence.rs |
~500 | Snapshot save/restore/prune | Use GraphTree serialization |
gui/workbench_intent_interceptor.rs |
~400 | Intercepts intents β tile mutations | Route through NavAction
|
gui/intent_translation.rs |
~300 | Intent β tile_view_ops calls | Route through NavAction
|
| File | Lines | Current role | Migration |
|---|---|---|---|
app/intent_phases.rs |
~1,500 | Phase handlers dispatching to tile_view_ops | Dispatch to apply_nav()
|
lifecycle/lifecycle_reconcile.rs |
~800 | Node pane lifecycle coordination | Bridge via SetLifecycle NavAction |
runtime/registries/workbench_surface.rs |
~1,500 | Workbench surface registry; queries tiles_tree | Query GraphTree API |
runtime/registries/workbench_surface/focus_routing.rs |
~300 | FocusCycleRegion dispatch | Use CycleFocus / CycleFocusRegion NavAction |
tile_behavior/pending_intents.rs |
~200 | Pending intent queue | Route through NavAction batching |
app/startup_persistence.rs |
~300 | Load persisted layout on startup | Use GraphTree deserialization |
These render pane content and are mostly independent of tree structure:
| File | Lines | Migration |
|---|---|---|
tile_behavior/node_pane_ui.rs |
~1,000 | No β content rendering is tree-agnostic |
tile_behavior/tool_pane_ui.rs |
~400 | No β content rendering is tree-agnostic |
tile_behavior/tab_chrome.rs |
~200 | Adapt β tab title/icon rendering uses new TabEntry
|
~30 additional files with egui_tiles:: imports or Tree<TileKind> type
references that update to GraphTree<NodeKey>. Mechanical changes.
Goal: graph-tree crate compiles with all types, no logic.
- Create
graph-tree/workspace member with zero framework deps - Implement all core types from
graph_tree_spec.mdΒ§6.2β6.6:GraphTree<N>,MemberEntry,Lifecycle,Provenance,TreeTopology,GraphletRef,GraphletBinding,ProjectionLens,LayoutMode - Implement
Rect,MemberIdtrait,ViewIdtrait - Serde derives on all types
- Unit tests for type construction and serialization round-trip
Files created: graph-tree/Cargo.toml, graph-tree/src/{lib,tree,topology,member,graphlet,lens,layout,nav,query,ux,serde_compat}.rs
Done gate: cargo test -p graph-tree passes; types serialize/deserialize.
Goal: apply_nav() handles all NavAction variants with invariant enforcement.
- Implement
TreeTopologymutation methods (attach/detach/reparent/reorder) - Implement provenance-based placement rules (traversalβchild, manualβsibling, derivedβsibling, anchorβroot)
- Implement
apply_nav()for allNavActionvariants - Implement
visible_walk()with expansion state - Implement tree query functions (ancestors, descendants, siblings, depth)
- Property tests: tree invariants hold after any sequence of NavActions
- Implement
derive_topology()for petgraph feature
Done gate: Property tests pass; all NavAction variants covered; tree never enters invalid state (orphan nodes, cycles, missing parents).
Goal: compute_layout() produces correct rects for all layout modes.
- Implement taffy tree builder from GraphTree topology
- Implement
TreeStyleTabslayout (tree rows + single active pane rect) - Implement
FlatTabslayout (tab order + single active pane rect) - Implement
SplitPaneslayout (taffy-computed rects per active member) - Implement
LayoutOverrideapplication (min/max, flex) - Unit tests: layout produces non-overlapping rects; tree rows respect expansion
Done gate: Layout tests pass for all three modes; taffy integration works.
Goal: emit_ux_tree() produces correct UxNodeDescriptor tree.
- Implement
UxNodeDescriptorbuilder from GraphTree state - Map
LayoutModeto appropriate UxRole hierarchy (TreeView/TabList/SplitContainer) - Verify against
ux_tree_and_probe_spec.mdcompleteness contract - Unit tests: every visible member produces at least one UxNode
Done gate: UxNode tree is structurally complete for all layout modes.
Goal: GraphTree renders inside Graphshell's WorkbenchArea, replacing egui_tiles.
This is the big integration phase. It proceeds in sub-steps:
- Implement
GraphTreeRenderertrait for egui -
render_tree_tabs(): tree-style tab sidebar with lifecycle badges, expansion, provenance indicators -
render_flat_tabs(): traditional tab bar (fallback mode) -
render_pane_chrome(): split pane borders and resize handles - Wire into
shell_layout_pass.rsWorkbenchArea slot
- Replace egui_tiles iteration in
tile_compositor.rswithcompute_layout().pane_rects - Replace egui_tiles iteration in
tile_render_pass.rs - Verify compositor adapter still receives correct rects and render modes
- Replace direct
tile_view_ops.rscalls withNavActiondispatch - Wire
workbench_commands.rsto emitNavActionβapply_nav()βTreeIntent - Convert
TreeIntenttoWorkbenchIntentat the host boundary - Verify all existing workbench keyboard shortcuts still work
- Replace
navigator_context.rsscope management withProjectionLens - Wire Navigator sidebar to read
visible_rows()from GraphTree - Implement lens switching UI (Traversal / Arrangement / Containment / Semantic / Recency / All)
- Verify click grammar works identically to current Navigator
Done gate: Graphshell renders with GraphTree; all existing tile operations work; Navigator sidebar shows correct projection; compositor renders content in correct rects.
Goal: Frame layout save/restore works with GraphTree serialization.
- Implement
GraphTreeβ JSON serialization (already covered by serde derives) - Write migration function:
FrameLayout(old) βGraphTree<NodeKey>(new) - Backward-compatible loading: detect old format, migrate on load
- Forward persistence: save as GraphTree JSON
- Test: load old layout, verify correct tree structure, save, reload
Done gate: Existing saved layouts load correctly; new saves use GraphTree format.
Goal: Linked graphlet bindings auto-update tree membership from graph truth.
- Implement
EdgeProjectionSpecconsumption from graph truth - Implement
Linkedbinding: graph membership changes β tree roster updates - Implement
Forkedbinding detection (manual override of linked membership) - Implement roster delta emission for reconciliation UI
- Implement containment lens derivation from
ContainmentRelationedges - Test: add/remove node from graphlet β tree updates automatically
- Test: manual override β binding forks with reason
Done gate: Linked graphlets stay synchronized; forks are detectable and carry reason metadata.
Goal: Remove egui_tiles dependency entirely.
- Remove
egui_tilesfromCargo.toml - Delete
tile_view_ops.rs(replaced byapply_nav()) - Delete
tile_kind.rs(replaced byMemberEntry) - Delete
tile_grouping.rs(replaced byGraphletRef) - Delete
tile_invariants.rs(replaced byapply_nav()postconditions) - Delete
semantic_tabs.rs(replaced byTabEntry) - Thin
tile_behavior.rstoGraphTreeRendereradapter - Update all 53 files with
egui_tilesimports - Final test pass:
cargo testgreen,cargo clippyclean
Done gate: Zero egui_tiles references remain; all tests pass.
GraphTree should be registered in PLANNING_REGISTER Β§1D as:
lane:graph-tree β GraphTree crate: framework-agnostic graphlet-native tile tree
replacing egui_tiles, collapsing Navigator/Workbench projection gap
Section: C (UX / Interaction / Graph Capability) or B (Core Platform)
Dependencies:
- Arrangement-graph-projection plan (shipped β provides the arrangement-edge model)
- Relation families (design β provides containment/traversal/semantic edge vocabulary)
- Shell composition model (implemented β provides WorkbenchArea slot)
Dependents (unblocked by GraphTree):
- NV15 Navigator Arrangement Projection
- NV23 Workbench-Navigator Contract Sync
- Constellation projection plan (can consume richer tree)
- Extension/PWA tile tree (framework-agnostic tree serializes to JSON)
| Risk | Mitigation |
|---|---|
| Scope creep into full reconciliation UI | Phase 6 implements binding mechanics only; reconciliation UI (fork/rebase/unlink chooser) is Phase 7+ |
| Compositor regression | Phase 4b runs compositor tests against both old and new pane rects before switching |
| Persistence migration breaks saved layouts | Phase 5 detects old format and migrates; never overwrites until verified |
| Performance regression (taffy vs proportional) | Benchmark layout computation in Phase 2; taffy is fast but verify |
| Navigator behavior regression | Phase 4d tests click grammar against existing interaction contract acceptance criteria |
| 53-file import churn | Phase 7 is mechanical; can be done as a single well-tested PR |
- All existing tile operations work (split, close, tab switch, drag-drop, focus cycle)
- Navigator sidebar shows correct tree with lifecycle badges
- Lens switching shows different projections of same membership
- Graph canvas frame minimap renders correct frame bounding boxes
- UxTree probes pass with GraphTree-sourced structure
- Saved layouts round-trip through new persistence format
- Linked graphlet membership updates when graph changes
- Cold members visible in Navigator with correct badges
-
cargo testgreen,cargo clippyclean, zeroegui_tilesreferences