2026 02 13_graph_tile_unification_plan - mark-ik/graphshell GitHub Wiki
- Goal: unify graph semantics and tile/workspace behavior without enforcing structural parity.
- Scope: UUID identity migration, Servo signal wiring, sync_to_graph replacement, mutation pipeline, pane/tab behavior.
- Dependencies: complete physics migration + selection consolidation first.
- Behavioral reference: GRAPHSHELL_AS_BROWSER.md
- Validation baseline: 2026-02-14_servo_architecture_constraints.md
- Servo delegate callbacks are processed through
Servo::spin_event_loop()dispatch flow. -
notify_url_changedexists in Servo API but is not overridden in current GraphshellRunningAppState. -
request_create_newis implemented in Graphshell and creates/activates a new WebView. - Current
sync_to_graphpolling path creates a new graph node on URL change; this is semantically wrong for same-tab navigation. -
notify_history_changedis currently handled only as UI invalidation (set_needs_update()), not graph history update.
- No assumption that graph and tile tree must be structurally identical.
- No unvalidated thread-model redesign.
- No Servo-core patch unless a proven API gap appears.
Problem
Current graph identity is URL-index-driven (url_to_node: HashMap<String, NodeKey>), which blocks duplicate URL tabs and conflates identity with mutable location.
Implementation
- Add stable
uuid::Uuidfield toNode. - Keep URL index as lookup aid, not identity source.
- Change URL index to support duplicates (
HashMap<String, Vec<NodeKey>>or equivalent). - Add UUID lookup index (
HashMap<Uuid, NodeKey>). - Extend snapshot/log persistence to include UUID-based identity.
- Do not add legacy snapshot migration paths; Graphshell is pre-user and UUID-only.
Tests
- Duplicate URL nodes coexist with distinct UUIDs.
- UUID survives snapshot roundtrip.
- URL lookup can return multiple candidates.
- Replay logic resolves by UUID, not URL.
Problem
Current behavior infers navigation from polling and URL diffs in sync_to_graph, creating structural mutations from observation.
Target semantics
-
notify_url_changed: update mapped node URL/history state in-place. -
request_create_new: create new node +Hyperlinkedge from parent mapping. -
notify_history_changed: store/update node history metadata.
Implementation
- Override
notify_url_changedinrunning_app_state.rs. - Extend
notify_history_changedhandling beyond UI invalidation. - Extend
request_create_newflow to emit graph-meaningful operation (directly or via mutation queue). - Keep title/load/focus/favicon handlers as they are unless needed for consistency.
Important correction
- Do not rely on the incorrect assumption that delegate callbacks run on a compositor callback thread.
- Any queue/reducer added here is for deterministic ordering and conflict handling, not because cross-thread callback dispatch is mandatory.
Tests
- Same-tab URL change does not create a new node.
- New-tab action creates exactly one node and one
Hyperlinkedge. - History callbacks update node history state.
Problem
sync_to_graph currently handles too much and contains semantically wrong structural creation logic.
Implementation
- Remove URL-change -> new-node creation path from
sync_to_graph. - Retain only reconciliation duties that are still needed (stale mapping cleanup, active highlight sync, compatibility glue).
- Move semantic structural mutations to signal-driven and/or reducer-driven paths.
- Keep tile remap reconciliation only where required by current runtime behavior.
Tests
- No node creation from URL polling path.
- Cleanup logic still removes stale mappings.
- Existing tile remap and favicon ingestion flows remain correct.
Problem
Graph, tile, keyboard, and Servo-sourced operations can conflict when applied ad-hoc in-frame.
Implementation
- Introduce/extend unified mutation intents (
GraphIntentor equivalent). - Collect operations from:
- Servo delegate event handlers,
- graph interactions,
- tile/tab interactions,
- keyboard actions.
- Apply at one sync boundary in deterministic order.
- Define conflict policy (for example: delete dominates metadata updates on same node in same batch).
Notes
- Transport mechanism (direct queue vs channel) should follow measured need and code simplicity.
- Deterministic mutation boundary is the requirement; specific transport is an implementation choice.
Tests
- Conflicting same-frame operations resolve deterministically.
- Any operation source can propagate updates to graph + tile + runtime mappings.
- Persistence logging reflects successfully applied mutations only.
Model
- Graph stores semantics and identity.
- Tile tree stores workspace layout and visibility.
- Runtime webviews are ephemeral instances.
Rules
- Every tile node reference must resolve to an existing graph node.
- Graph node may exist without a tile.
- Layout-only operations (reorder, resize, move between panes) should not implicitly mutate graph semantics.
- Semantic operations should be explicit (for example, explicit grouping command creates
UserGrouped).
Implementation
- Normalize lifecycle naming and semantics (
Active,Inactive,Closedpolicy, with clear state transitions). - Update tab rendering to reflect lifecycle and focus state.
- Keep close/delete semantics explicit in intent definitions (avoid implicit coupling).
Tests
- Tile references are pruned when graph nodes are deleted.
- Graph nodes can remain without tiles.
- Explicit semantic grouping creates expected edges; layout-only movement does not unless requested.
Status question to resolve first
ARCHITECTURAL_OVERVIEW.md and this plan previously diverged on whether UserGrouped already exists. Resolve code truth first, then implement any missing piece.
Implementation (if missing)
- Add
UserGroupedto edge enum and persistence type. - Add rendering distinction in graph adapter.
- Wire explicit creation command/gesture.
Tests
- Serialization roundtrip for
UserGrouped. - Distinct visual style.
- Explicit command creates edge.
Identity
- Duplicate URL tabs with unique UUIDs.
- UUID-based persistence roundtrip.
Navigation semantics
-
notify_url_changedupdates URL in-place. -
request_create_newcreates new node + edge. - No same-tab node inflation.
Mutation discipline
- Deterministic conflict resolution.
- No direct cross-subsystem mutation bypassing apply boundary.
Graph/tile consistency
- Tile node reference always valid.
- Graph node may exist without tile.
- Stale tile references pruned on restore/update.
- What is the minimal migration path that removes
sync_to_graphsemantic mutation without regressing current tile remap behavior? - What ordering guarantees are observed between
notify_url_changed,notify_page_title_changed, andnotify_history_changedunder redirects and SPA transitions? - Do we need an explicit buffering queue for performance/ordering in practice, or is current event-loop serialization sufficient with a local intent batch?
- What close-tab policy should be canonical for Graphshell phase scope: close-node, deactivate-node, or mode-dependent?
-
notify_url_changedis not implemented inports/graphshell/running_app_state.rs. -
request_create_newis implemented and creates/activates WebViews, but graph node creation still depends on polling-based sync path. -
notify_history_changedexists in Graphshell delegate impl but currently only invalidates UI. -
sync_to_graphinports/graphshell/desktop/webview_controller.rscreates a new node on URL change. - Current architecture documents contain stale contradictions (identity model, lifecycle naming, and
UserGroupedstatus); this plan assumes code truth must drive cleanup.
- 2026-02-13: initial unification plan drafted.
- 2026-02-14: refined to validated baseline; removed incorrect threading assumptions and converted unverified claims to explicit research questions.