registry_migration_plan - mark-ik/graphshell GitHub Wiki
Status: In Progress
Goal: Execute the migration of hardcoded subsystems to the Registry Layer defined in 2026-02-22_registry_layer_plan.md.
Completed in thin, test-gated slices:
- Phase 1 callsite migration (runtime path promotion)
- Toolbar input resolution now routes through registry input bindings by default.
- Address-bar submit/omnibox/detail flows now route through registry protocol/action helpers by default (no diagnostics feature-gated fallback path).
- Files:
desktop/toolbar_routing.rs,desktop/webview_controller.rs.
- Phase 1.4 capability topology slice (path-only) [Complete]
- Diagnostics registry implementation moved to
registries/atomic/diagnostics.rs. - Temporary compatibility re-export at
desktop/registries/diagnostics.rshas been removed (cleanup complete as of 2026-02-23). - Module wiring added:
registries/mod.rs,registries/atomic/mod.rs, crate root registration inlib.rs.
- Diagnostics contract continuity
- Config-change channel emission path validated via deterministic scenario flow (
registry.diagnostics.config_changed).
Validation evidence for this checkpoint:
-
cargo test webview_controller:: -- --nocapture(pass) -
cargo test desktop::registries:: -- --nocapture(pass) -
cargo test desktop::tests::scenarios::registries -- --nocapture(pass) -
cargo check(pass)
The Registry Layer Architecture defines the destination. This plan defines the journey: specific refactoring steps to move existing logic from input/, app.rs, and render/ into the new registries without destabilizing the application.
The current repository layout has outgrown a flat, root-heavy structure. Registry migration should run in parallel with a topology refactor so architecture boundaries are visible in the filesystem (Data, Capabilities, Services, Shell) rather than implicit in module names.
The registry migration plan now formally includes a repository topology track.
Why this is in-scope for registry migration:
- Registries are becoming the capability API surface; their location should reflect that role.
-
GraphBrowserAppcurrently mixes model/state with shell concerns, making borrow boundaries and test boundaries harder to reason about. - A future multi-shell target (
desktop,mobile,cli) requires model/services layers that are shell-agnostic.
Decision:
- Keep registry feature migration and directory migration coupled, but execute in thin slices (no big-bang move).
src/model/
-
graph/(existing graph module) -
intent.rs(GraphIntent + reducer ownership split from app shell) -
session.rs(GraphBrowserApp core state after shell fields are carved out) selection.rs
src/registries/
-
mod.rs(RegistryRuntime) -
infrastructure/(diagnostics,mod_loader) -
atomic/(protocol,index,action,identity,agent) -
domain/layout/(layout,workbench_surface,graph_surface,viewer_surface) -
domain/presentation/(presentation,theme,physics_profile) -
domain/(lens,input)
src/mods/
-
mod.rs(mod manifest, dependency resolution, loading) -
native/(compile-time registered mods:verso,verse,default_themes, etc.) -
wasm/(dynamically loaded sandboxed mods)
src/services/
persistence/search/-
physics/(engine integration and long-running simulation utilities)
Mod-first principle: Registries define capability contracts (empty surfaces). Core seeds provide minimal defaults so the app is usable without any mods (graph organizer with local files). Mods populate registries with richer capabilities (web rendering, P2P networking, alternative viewers).
src/shell/desktop/
-
host/(window,headed_window,headless_window,event_loop, embedder glue) -
workbench/tiles/(alltile_*files) -
workbench/frame.rs(fromgui_frame.rs) -
lifecycle/(webview_controller,lifecycle_reconcile,webview_backpressure) -
ui/toolbar/,ui/panels/,ui/gui.rs
- Move one semantic area at a time; preserve behavior before cleanup.
- Prefer
pub(crate)re-exports during transition to avoid broad callsite churn. - Do not combine large path moves with logic rewrites in the same change-set.
- Every move slice must pass:
cargo test desktop::registries:: -- --nocapturecargo test desktop::tests::scenarios::registries -- --nocapturecargo check
- Once a slice is stable, delete compatibility re-exports quickly (no long-lived dual paths).
Since there are no active users, we prioritize code cleanliness over backward compatibility. We will replace subsystems directly rather than maintaining parallel legacy paths.
- Stand Up: Initialize The Register (central container) to hold registries.
- Seed: Register core-seed defaults (the minimum set that makes the app usable without any mods).
- Replace: Switch call sites to use the registry. Delete the old hardcoded logic immediately.
- Verify: Use Diagnostics to confirm registry hits.
- Semantic Gap Check: Before adding/changing registry boundaries, ask whether the boundary maps cleanly to technical, architectural, or design concerns.
Registries define contracts (empty capability surfaces with fallback defaults). Mods populate those surfaces with implementations. Two mod tiers exist:
-
Native Mods: Compiled into the binary, registered at startup via
inventory::submit!. Not sandboxed. Used for first-party capabilities too large or too tightly integrated for WASM (Verso, Verse, default themes/physics). -
WASM Mods: Dynamically loaded at runtime via
extism. Sandboxed, capability-restricted, quota-limited. Used for third-party extensions and optional capabilities.
Both tiers use the same ModManifest format declaring provides (registry entries the mod registers) and requires (registry contracts that must exist before the mod loads). The mod loader performs topological sort at startup to resolve dependency order.
The application must be fully functional as an offline graph-based document organizer without any mods loaded. This defines the "core seed" — the minimal registry population:
| Registry | Core Seed (no mods) | Verso Mod Adds | Verse Mod Adds |
|---|---|---|---|
| ProtocolRegistry |
file://, about:
|
http://, https://, data:
|
ipfs://, activitypub://
|
| ViewerRegistry |
viewer:plaintext, viewer:metadata
|
viewer:webview (Servo) |
— |
| ActionRegistry |
graph.*, view.*, workspace.*
|
navigation.*, webview.*
|
verse.share, verse.sync
|
| InputRegistry | Graph/workspace keybindings | Browser-style keybindings | — |
| ThemeRegistry |
theme:default, theme:dark
|
— | — |
| PhysicsProfileRegistry |
physics:liquid, physics:gas
|
— | — |
| LayoutRegistry |
layout:default, layout:grid
|
— | — |
| IdentityRegistry |
identity:local (generated keypair) |
— | P2P personas, DID providers |
| IndexRegistry | Local tantivy search | — | Federated search providers |
| KnowledgeRegistry | UDC defaults | — | Schema.org providers |
Without Verso: no webviews, no HTTP. Nodes display as metadata cards with title/URL/tags. The graph is a visual outliner / Zettelkasten. Without Verse: no P2P, no federated search. Fully offline. Local identity only.
Target: Replace input/mod.rs (hardcoded KeyboardActions) and render/mod.rs (enum-based GraphAction) with InputRegistry and ActionRegistry.
-
Task: Define
ActionIdconstants for all current capabilities.-
graph.node.create,graph.selection.delete,view.zoom.in, etc.
-
-
Task: Implement
ActionHandlertrait wrappers for existingGraphIntentemission logic. -
Task: Seed
ActionRegistrywith these defaults on startup.
-
Task: Extract keybindings from
input/mod.rsinto a data structure. -
Task: Seed
InputRegistrywith these default bindings mapping toActionIds.
-
Target:
desktop/gui.rs(main loop) anddesktop/tile_behavior.rs. -
Refactor:
- Replace
input::collect_actions(ctx)withapp.services.input.resolve(ctx). - Replace
match action { ... }dispatch blocks withapp.services.actions.execute(action_id).
- Replace
Validation:
- Verify all keyboard shortcuts still work.
- Verify Command Palette (which will now query
ActionRegistry) lists all commands.
-
Task: Move
desktop/registries/*intosrc/registries/{atomic,domain}with minimal path adaptation. -
Task: Keep temporary re-exports in
desktop::registriesfor one slice only. - Done Gate: All registry tests/scenarios green with old shim removed.
Target: Stand up the mod system as the primary extensibility mechanism. Define protocol and viewer contracts as registry surfaces. Package current Servo integration as the Verso native mod.
-
Task: Define
ModManifeststruct:mod_id,display_name,mod_type(Native | WASM),provides(list of registry entry IDs),requires(list of registry contract IDs),capabilities(network, filesystem, etc.). -
Task: Implement mod dependency resolver (topological sort on
requires→providesedges). -
Task: Implement native mod loader using
inventory::submit!for compile-time registration. -
Task: Register mod lifecycle diagnostics (
registry.mod.load_started,registry.mod.load_succeeded,registry.mod.load_failed,registry.mod.dependency_missing).
-
Task: Define
ProtocolHandlertrait as the contract surface (scheme → handler). -
Task: Define
ViewerHandlertrait as the contract surface (MIME/content → renderer). -
Task: Seed core defaults:
protocol:file,protocol:about,viewer:plaintext,viewer:metadata. - Task: Ensure the app is fully functional with only core seeds (graph + metadata display, no web rendering).
-
Task: Package current Servo/Wry integration as a native mod with manifest:
-
provides:protocol:http,protocol:https,protocol:data,viewer:webview -
requires:ProtocolRegistry,ViewerRegistry -
capabilities:network
-
-
Task: Refactor
webview_controller.rsso webview creation is gated onviewer:webviewbeing registered; if absent, nodes display as metadata-only. - Task: Ensure startup without Verso mod succeeds (offline graph organizer mode).
Validation:
- App starts and functions as graph organizer with Verso mod disabled.
- App starts and functions as browser with Verso mod enabled.
- Mod dependency resolution rejects a mod with unmet
requires.
Target: Resolve all three surface forms through a unified layout domain: workbench (tile tree), graph (file tree), and viewer document surfaces. Each surface registry controls structure, interaction policy, and rendering policy — everything about how a surface is arranged and interacted with before styling.
-
Task: Introduce
LayoutDomainRegistryas the coordinator for surface subregistries. -
Task: Keep
LayoutRegistryas the top-level domain entrypoint/fallback resolver. - Refactor: Lens resolution obtains a composed layout profile from layout domain, not a single mode only.
-
Task: Define
GraphSurfaceRegistrycovering the full scope of graph canvas behavior:-
Layout algorithms: Wrap
egui_graphs::LayoutForceDirectedin aLayoutAlgorithmtrait impl. Register IDs (graph_layout:force_directed,graph_layout:grid,graph_layout:tree). -
Interaction policy: Selection modes, zoom/pan ranges, node creation positions, edge creation rules. Extract from hardcoded
SettingsNavigation/SettingsInteractioninrender/mod.rs. -
Rendering policy: Node shapes/sizes, edge routing/style, label format, badge display rules. Extract from hardcoded
SettingsStyle. - Physics engine integration: Which force profiles are available, energy thresholds, auto-pause triggers. (Parameters come from Presentation Domain; engine execution is Layout Domain.)
-
Layout algorithms: Wrap
-
Refactor: Update
render/mod.rsto instantiate graph layout/interaction/style via surface-registry dispatch.
-
Task: Define
WorkbenchSurfaceRegistrycovering tile tree behavior:-
Layout policy:
SimplificationOptions, split direction defaults, tab wrapping rules. - Interaction policy: Drag-to-rearrange rules, resize constraints, drop zone behavior.
-
Rendering policy: Tab bar style, container labels (semantic:
Split ↔,Tab Group, etc.), title truncation.
-
Layout policy:
-
Refactor: Update
tile_behavior.rsto resolve policy profiles via workbench surface registry.
-
Task: Define
ViewerSurfaceRegistrycovering viewer viewport behavior:- Zoom/scaling defaults, reader mode, scroll policy.
- (Viewer selection — MIME routing — stays in atomic
ViewerRegistry; this handles how the selected viewer presents its viewport.)
- Refactor: Update viewer entrypoints to resolve surface policies via layout domain.
Note: The graph surface registry is comparable in scope to the workbench surface registry. Both the tile tree and the file tree are extensible surfaces where mods can register new policies, types, and actions.
Target: Resolve appearance and motion semantics after layout. Formalize knowledge classification as a distinct atomic registry.
-
Task: Define
ThemeDatastruct (colors, strokes, font sizes). -
Task: Create
DefaultThemematching current hardcoded values. -
Task: Register
theme:defaultinThemeRegistry. Core seed — available without mods.
-
Task: Extract
PhysicsProfilepresets (Liquid, Gas, Solid) fromapp.rsas named parameter sets. -
Task: Register them in
PhysicsProfileRegistryas presentation-domain semantic labels. -
Task: Remove
layout_modefromPhysicsProfile. Layout mode is independently resolved by the Layout Domain. A Lens composes both, but physics must not override layout. -
Refactor: Physics engine execution stays in the Layout Domain (
GraphSurfaceRegistry). The Presentation Domain provides which parameter set to use. The Lens resolves the profile from Presentation and passes it to the engine in Layout.
-
Task: Make
PresentationDomainRegistrythe domain coordinator forThemeRegistry+PhysicsProfileRegistry. -
Refactor: Update
render/mod.rsso theme/physics resolution occurs after layout profile selection.
-
Task: Formalize the existing
OntologyRegistry(UDC tagging,CompactCode, fuzzy search,get_color_hint) asKnowledgeRegistry— an atomic capability, not a domain coordinator. - Task: Register as core seed with UDC defaults. Mods can add Schema.org, Wikidata, or custom taxonomy providers.
- Refactor: Lens filters can reference knowledge tags; knowledge resolution is independent of both layout and presentation domains.
Target: Package P2P networking, federated identity, and distributed indexing as the Verse native mod.
-
Task: Define Verse mod manifest:
-
provides:protocol:ipfs,protocol:activitypub,index:federated,identity:did,action:verse.share,action:verse.sync -
requires:ProtocolRegistry,IndexRegistry,IdentityRegistry,ActionRegistry -
capabilities:network,identity
-
- Task: Implement Verse mod as a native mod that registers protocol handlers, index providers, identity providers, and actions into existing atomic registries on load.
-
Task: The Verse mod extends
IdentityRegistrywith P2P persona types (DID, Nostr keypairs). -
Note:
IdentityRegistryitself is a core atomic registry (local keypair generation works without Verse). Verse adds networked identity types.
-
Task: When Verse mod is not loaded, all
verse.*actions are absent from ActionRegistry. Command Palette does not show share/sync commands. -
Task: When Verse mod is loaded but offline, protocol resolution for
ipfs:///activitypub://fails gracefully with diagnostics and user-visible fallback messaging.
Validation:
- App starts and functions fully without Verse mod loaded.
- Loading Verse mod registers all declared entries into atomic registries.
- Unloading Verse mod removes provided entries; dependent workspaces fall back to defaults.
Target: Align filesystem structure with architecture boundaries after capability paths are stable.
- Move data-centric types from root-heavy modules into
src/model/*. - Split shell/runtime fields from
GraphBrowserAppinto shell-facing adapters where practical.
- Normalize
persistence,search, and physics-support logic undersrc/services/*.
- Re-home
desktop/*intosrc/shell/desktop/{host,workbench,lifecycle,ui}. - Group tile files under
workbench/tilesand keep diagnostics harness paths updated.
- Delete temporary re-exports and old module aliases.
- Ensure docs and plan references use canonical paths only.
-
Scaffold: Create/normalize
RegistryRuntimecontainer and capability boundaries.-
The Register:
- Container: Holds all Atomic and Domain registries.
-
Signal Bus: Manages inter-registry events (
IdentityChanged,ThemeChanged,ModLoaded,ModUnloaded). - Control Panel: Exposes configuration logic.
- Core Seeds: Populate minimal defaults for each registry so the app is functional without mods.
-
The Register:
- Phase 1 (Input/Action): Unblocks "Command Palette" and "Keybind Config".
-
Phase 1.4 (Capability Topology Slice): Move registries to
src/registries/*. - Phase 2 (Mod Infrastructure + Protocols/Viewers): Stands up the mod system. Packages Verso as a native mod. Establishes the "core seed floor" (app works without mods).
- Phase 3 (Layout Domain): Unblocks multi-surface consistency across workbench, graph, and viewers. Surface registries control structure + interaction + rendering policy.
- Phase 4 (Presentation Domain + Knowledge): Applies style and motion semantics to resolved layout. Formalizes UDC tagging as KnowledgeRegistry.
- Phase 5 (Verse Mod): Packages P2P as a native mod with registry prerequisites.
- Phase 6 (Topology Consolidation): model/services/shell finalization.
Ordering rationale: Mod infrastructure (Phase 2) must precede domain phases so that surface registries and presentation registries can be populated by mods from the start. Verso-as-mod is the forcing function — if the mod system can support Servo integration, it can support anything.
For each phase:
- Unit Test: Verify Registry returns expected default items.
-
Integration Test: Use
TestHarnessto assert that triggering an input results in the correctGraphIntentvia the registry path. -
Diagnostics: Check
registry.*.fallback_usedchannels are 0 (unless testing fallback).
-
Risk: Performance regression in Input/Action lookup (per-frame).
-
Mitigation:
InputRegistryshould use a fast lookup (Hash map of KeyChord). It is only queried on input events, not every frame.
-
Mitigation:
-
Risk: Circular dependencies between
RegisterandGraphBrowserApp.-
Mitigation: Strict
Contextpattern. Registries never hold&mut App, they receive it during method calls.
-
Mitigation: Strict
-
Risk: Semantic overlap between layout and presentation domains creates duplicate knobs.
-
Mitigation: Enforce sequencing (
layout -> presentation) and keep cross-domain coupling out of registry APIs. Physics engine execution is Layout; physics parameter presets are Presentation.
-
Mitigation: Enforce sequencing (
-
Risk: Large path moves hide logic regressions.
- Mitigation: Separate path-only commits from behavior commits; run scenario matrix after each slice.
-
Risk: Test harness path churn breaks migration velocity.
- Mitigation: Update harness imports in the same slice as each move and keep diagnostics contracts as continuity checks.
-
Risk: Mod loading order creates startup failures or silent capability gaps.
-
Mitigation: Topological sort on mod
requires/providesat startup. Missing dependency = mod load failure with diagnostics, not silent skip. Core seeds guarantee a functional floor.
-
Mitigation: Topological sort on mod
-
Risk: Verso-as-mod creates a hard coupling to Servo that the mod contract can't cleanly express.
- Mitigation: Verso is a native mod — compiled in, not sandboxed. The mod contract (manifest + registry population) is the architectural boundary, not an execution sandbox. If the mod API can't express Verso's needs, the mod API is wrong — fix the API, don't special-case Verso.
-
Risk: "Core seed floor" is too minimal to be useful, pushing all value into mods.
- Mitigation: Core seeds include graph manipulation, local file protocol, plaintext/metadata viewers, full keyboard/action pipeline, persistence, search. This is a complete offline document organizer. Mods add web rendering and networking, not basic functionality.