2026 04 21_verso_shell_authority_refactor_plan - mark-ik/graphshell GitHub Wiki
Date: 2026-04-21
Status: Proposed refactor plan
Owner: Shell / Workbench / Viewer Platform
Scope: Promote verso from βemerging cross-engine helper crate + legacy native mod nameβ into the Shellβs explicit routing authority for engine choice, viewer choice, pane ownership, and backend escalation. Decompose the current mods/native/verso bundle so backend/provider code no longer masquerades as the authority.
The current tree has two different things called βVersoβ:
-
crates/versois now the emerging authority layer for cross-engine decisions. -
mods/native/versois a provider bundle containing Servo/Wry integration, Gemini/Gopher/Finger servers, and browser-adjacent runtime helpers.
That split is confusing because the name verso semantically fits the first role much better than the second:
-
verso://is already the appβs internal shell/workbench namespace. - viewer/backend choice is now increasingly shell policy rather than mod-local behavior.
-
middlenet-enginehas already started shedding cross-engine concerns intocrates/verso.
The architectural direction should therefore be:
versobecomes a Shell subsystem- backend/provider code stops owning the
versoname
Premises for this refactor were audited against the codebase on 2026-04-21 (see conversation log). Summary of the actual state β not all of it matches the plan's initial framing, so sequencing in Β§5/Β§7 below has been revised accordingly.
-
crates/verso/src/lib.rsexports the right shape (EngineChoice,EngineOverride,WebEnginePreference,HostCapabilities,VersoRequest,DispatchOutcome,ViewerRoutingDecision,dispatch_request,dispatch_prepared,select_viewer_for_content). Phase 0's "confirm public types" is ~80% done already. -
crates/middlenet-engine/src/engine.rsis already Middlenet-internal-only β no Servo/Wry/viewer-id awareness. DoD #2 is already true. -
mods/native/verso/mod.rsis cleanly a provider bundle (Wry lifecycle + Gemini/Gopher/Finger + storage); no routing authority code lives here. The rename is safe. -
crates/versoβmiddlenet-engineis one-way; no circular dependency risk. - "mod:verso" appears as a string literal in only 8 sites (infrastructure + tests). The manifest-id rename is cheap and can be done at any time.
The load-bearing caveat: verso is not yet the primary authority. At shell/desktop/workbench/tile_runtime.rs:81β108, preferred_viewer_id_for_content currently:
- calls
phase0_select_viewer_for_content()(registry baseline) first, - then calls
::verso::select_viewer_for_content(), - then falls back to the registry result if verso returned
None.
That shape is "verso as fallback consultant, registry as default." Risk 8.3 (registry/runtime divergence) is not a future risk β it is the current state. Viewer-id string literals appear 202Γ across 27 files; shell/desktop/runtime/registries/mod.rs alone carries 35 of them. Closing the inversion is the highest-leverage move in this refactor β and the reason Phases 3+5 have been merged and moved to the front of the ordered list below.
crates/verso should become the canonical authority for:
- content routing across Middlenet / Servo / Wry
- viewer selection for pane-backed content
- browser-backend preference interpretation
- per-pane engine ownership
- backend escalation and fallback policy
- shell-facing routing outcomes and diagnostics reasons
verso should not own:
- rendering implementation
- protocol parsing/adaptation
- transport stacks
- graph domain logic
- platform backend details
It is an orchestration layer, not a renderer or runtime backend.
middlenet-engine should continue narrowing toward:
- semantic adaptation
- Middlenet-internal lane choice
- Direct / Html / FaithfulSource / Unsupported
- prepared-document packaging for Middlenet-owned surfaces
It should not know how to escalate into Servo/Wry.
The current mods/native/verso bundle should stop being the architectural βVersoβ.
It should be renamed toward something that reflects what it actually provides:
mods/native/web_runtimemods/native/browser_backendsmods/native/web_backendsmods/native/verso_native
Recommended default: mods/native/web_runtime.
That bundle would provide:
- Servo webview/runtime integration
- Wry overlay/runtime integration
- local Gemini/Gopher/Finger server helpers
- browser-adjacent storage/runtime helpers
Those capabilities are inputs into verso, not the authority itself.
The current code already points at the right seam:
-
crates/verso/src/lib.rs- cross-engine dispatch and viewer routing are starting to live here.
-
crates/middlenet-engine/src/engine.rs- Middlenet lanes are now internal-only.
-
shell/desktop/workbench/tile_runtime.rs- pane-level effective viewer choice now calls into
verso.
- pane-level effective viewer choice now calls into
The biggest remaining places where βVerso the authorityβ and βVerso the modβ are still conflated:
mods/native/verso/mod.rsregistries/infrastructure/mod_activation.rsregistries/infrastructure/mod_loader.rsshell/desktop/runtime/registries/mod.rsshell/desktop/workbench/tile_behavior.rsshell/desktop/workbench/tile_compositor.rsshell/desktop/ui/workbench_host.rs- app settings/persistence that still encode backend choice directly as
viewer:webview/viewer:wry
This refactor is complete when the following are true:
-
versois the named shell authority for viewer/backend/engine routing. -
mods/native/versono longer exists under that name. - backend/provider code is renamed to a capability/provider-oriented name.
- pane routing and effective viewer choice go through
verso. - shell/runtime registry helpers do not hardcode web-backend policy independently of
verso. - Middlenet remains ignorant of Servo/Wry.
- docs and terminology stop describing βVersoβ as merely a mod.
- Public type shape in
crates/versomatches the target contract (see Β§1.1). The few items still missing β anVersoResolvedRoute/VersoPaneOwner/VersoRouteReasontrio β will be added under Phase 3 below, after consolidation pressure surfaces their real shape, not speculatively. -
crates/middlenet-engineis already Middlenet-internal-only. DoD #2 is satisfied without work.
This is the highest-leverage phase and is what actually turns verso from "fallback consultant" into "routing authority." Prior versions of this plan had this as Phase 3+5; it is moved to the front because the rename and the resolved-route types without this step would only reorganize a still-inverted flow.
Concrete edits:
-
shell/desktop/workbench/tile_runtime.rs:81β108β insidepreferred_viewer_id_for_content, call::verso::select_viewer_for_content()first. Only consultphase0_select_viewer_for_content()when verso returnsNone(content verso doesn't route: specialized non-web viewers β images, PDFs, etc.). -
shell/desktop/runtime/registries/mod.rsβ trim the registry's web-backend policy to capability description. Non-web viewer selection (images, PDFs, local files) stays owned by the registry; web and Middlenet content now flow entirely through verso. - Existing scattered
viewer:webview/viewer:wry/viewer:middlenetstring comparisons at call sites that were previously reading registry output become redundant for web content β they can be removed as the call sites migrate.
Acceptance: every browser-backed and Middlenet-backed routing decision has a verso invocation as its primary source, and the fallback path from verso into the registry never returns a web viewer id.
Rename:
-
mods/native/verso/βmods/native/web_runtime/
Then update:
mods/native/mod.rs- all
crate::mods::native::verso::*imports - mod activation tables
- manifest wiring
- docs/tests referencing the old path
Important distinction:
-
mod id can remain
mod:versotemporarily if migration cost needs to stay low (the manifest-id rename is queued as a later optional step since only 8 literal references exist). - file/module namespace should still be renamed first.
This phase is cheap (~10 files, 8 literal references) and independent of Phase 1 β it can land at any time after Phase 1 is stable, or before if an orchestrated rename window opens up.
Now that Phase 1 has consolidated the decision point, the shape of a "resolved route" is obvious from actual usage β not speculative. Introduce:
-
VersoResolvedRouteβ decision + reason + override provenance -
VersoPaneOwnerβ per-pane engine ownership handle -
VersoRouteReasonβ human-readable explanation for UX/debug surfaces
Thread these through pane/route state in the workbench. This reduces the remaining scattered raw-string checks (viewer:webview / viewer:wry / viewer:middlenet) to opaque handles behind the resolved-route type.
Introducing these types before Phase 1 would risk a god-object shape (Risk 8.1 inverted) because nothing constrains what they carry. Doing it after Phase 1 lets the consolidation dictate the fields.
Map user-facing concepts onto verso's decision types:
- "Compat mode" means "prefer Wry through
verso" - Default web backend setting maps to
WebEnginePreference - Overview/debug surfaces show: engine owner, viewer backend, route reason, override state
This phase overlaps in scope with the settings-persistence migration (see app/settings_persistence.rs track). Coordinate to land changes together; do not parallel-land without alignment.
Longer-term: persist semantic preference (WebEnginePreference) rather than raw viewer ids.
Update design_docs/TERMINOLOGY.md so "Verso" is described as:
- shell routing authority
- internal namespace owner
- viewer/pane/engine orchestration layer
And describe the renamed native mod as a backend/provider bundle, not the authority.
Priority docs to update:
design_docs/TERMINOLOGY.md- shell/workbench specs
- viewer backend docs
- Wry integration spec
- Middlenet lane docs
- mod architecture docs
Decide whether mod:verso should become mod:web-runtime. Only 8 literal references exist; it is a mechanical change that can ship after Phase 5 has normalized terminology. Gate on: whether any external consumer (settings, plugins, tests) has taken a hard dependency on the old id.
Primary authority crate:
crates/verso/src/lib.rs
Likely shell wrappers or helpers:
shell/desktop/workbench/tile_runtime.rsshell/desktop/workbench/tile_behavior.rsshell/desktop/ui/workbench_host.rsshell/desktop/runtime/registries/mod.rs
Rename module tree:
mods/native/verso/mod.rsmods/native/verso/wry_manager.rsmods/native/verso/wry_types.rsmods/native/verso/wry_viewer.rsmods/native/verso/wry_frame_source.rsmods/native/verso/client_storage/*mods/native/verso/gemini/*mods/native/verso/gopher/*mods/native/verso/finger/*
registries/infrastructure/mod_activation.rsregistries/infrastructure/mod_loader.rs- test helpers that disable
mod:verso
app/settings_persistence.rsapp/workspace_state.rs- any workbench UX that exposes browser-backend preference
Longer-term target:
- persist semantic preference (
WebEnginePreference) rather than raw viewer ids where possible
Revised 2026-04-21 after codebase verification. The principle: land the authority inversion first, because every other step becomes cleaner once verso owns the decision. Rename, type-introduction, and terminology updates are downstream consequences, not prerequisites.
- Edit
preferred_viewer_id_for_contentto callversofirst, consult registry only for non-web content. - Touches 1-2 files in hot path; keeps existing tests as a regression gate.
- No new types, no rename churn.
- Walk
shell/desktop/runtime/registries/mod.rsand strip web-backend selection from registry functions (leave capability description intact). - Remove now-unreachable
viewer:webviewfallback branches at call sites downstream.
- Mechanical rename; keeps manifest id
mod:versofor one migration window. - Can land any time after PR 1 is stable; PR 2 is not a prerequisite.
- Shape now obvious from PR 1/PR 2 usage.
- Thread through workbench pane state.
- Begin collapsing raw viewer-id string comparisons into resolved-route accessors.
- Map "Compat mode" + default web backend settings to
WebEnginePreference. - Expose engine owner / route reason / override state in overview/debug surfaces.
- Coordinate with the in-flight settings-persistence track.
- Update
TERMINOLOGY.md, shell/workbench specs, viewer backend docs, Wry integration spec, Middlenet lane docs, mod architecture docs.
- Decide
mod:versoβmod:web-runtimeonly after PR 3/PR 6 have normalized terminology; gate on external-consumer dependency audit.
Guardrail:
- keep it orchestration-only
- no rendering code
- no transport stacks
- no provider implementation logic
Guardrail:
- rename module path before changing manifest/capability identity
- separate naming migration from policy migration
Guardrail:
- every browser-backed content route should eventually be explainable through one
versodecision object
Guardrail:
- viewer ids remain backend handles, not policy truth
- policy truth should live in
versoroute/owner types
Today ViewerRoutingDecision at crates/verso/src/lib.rs:92β96 returns viewer_id: &'static str β a "viewer:webview" / "viewer:wry" / "viewer:middlenet" literal. Consumers are meant to treat these as opaque handles but every raw == string comparison downstream (tile_runtime.rs, registries/mod.rs, overview_plane.rs) reinforces string-as-authority habits.
Guardrail:
- Phase 4 should reshape
ViewerRoutingDecisionso consumers get a typed handle (e.g.,ViewerHandlewrapping the string internally) plusEngineChoiceand the reason, never a bare string. - Call sites doing
decision.viewer_id == "viewer:wry"are a red flag β they should be comparingdecision.engineor asking the resolved route a typed question.
This refactor is done when:
-
versois documented and used as shell routing authority. -
middlenet-enginecontains no cross-engine escalation logic. - provider/backend code no longer lives under the
mods/native/versomodule path. - workbench pane routing goes through
versotypes, not scattered viewer-id heuristics. - settings and debug surfaces describe backend choice in
versoterms. - terminology/docs no longer describe Verso primarily as a mod.
Revised 2026-04-21. Do the next slice in this order:
- Land PR 1: make
versoprimary attile_runtime.rs:81β108. This is the one edit that actually turns verso into authority. Everything else downstream gets cleaner. - Land PR 2: trim registry web-backend policy once PR 1 is stable.
- Rename
mods/native/versoβmods/native/web_runtime(PR 3). Cheap, independent of PR 1/PR 2 ordering after PR 1 has landed. - Introduce
VersoResolvedRoute/VersoPaneOwner/VersoRouteReason(PR 4) with their real shape now visible.
Terminology cleanup (PR 6) follows naturally once the code has taken the new shape. The manifest-id rename (PR 7) is a later, opt-in step.
-
shell/desktop/workbench/tile_runtime.rspreferred_viewer_id_for_contentnow calls::verso::select_viewer_for_content()first. The registry (phase0_select_viewer_for_content) is consulted only when verso returnsNoneβ i.e., for specialized non-web viewers (images, PDFs, local files) that verso legitimately doesn't route. - The legacy two-step shape is gone:
- No more pre-call to the registry.
- No more "if selected.viewer_id != 'viewer:webview'" fallback branch (the registry no longer gets a chance to propose a web viewer in parallel with verso).
-
versois now the primary authority for web + Middlenet content at the workbench pane-routing decision point.
Verification:
-
cargo check -p graphshellclean (only pre-existing warnings). -
cargo test -p versoβ 7 pass. -
cargo test -p graphshell --lib viewerβ 70 pass. - Full
graphshell --libsuite β 2166 pass / 0 fail / 3 ignored.
Not yet touched:
- PR 2: trim registry web-backend policy in
shell/desktop/runtime/registries/mod.rs.phase0_select_viewer_for_contentstill returns web viewer ids; consumers other thantile_runtime.rsstill reach the registry-first path. -
candidate_viewer_ids_for_node_paneattile_runtime.rs:510β546still hardcodes"viewer:webview"/"viewer:wry"for the candidate list. Phase 4 (resolved-route types) will fold this into a typed enumeration. -
tile_behavior.rs:545reads the registry for accessibility inspector diagnostics β not a routing decision, left alone.
-
shell/desktop/workbench/tile_runtime.rscandidate_viewer_ids_for_node_paneno longer hardcodes"viewer:webview"/"viewer:wry"for http(s) URLs. It queries::verso::select_viewer_for_contenttwice (once perWebEnginePreference) to enumerate web + Middlenet candidates; the registry is consulted only for specialized non-web viewers. - The registry's
select_for_uriwas left as-is. Routing callers no longer rely on its web-baseline output; the remaining web viewer returns are capability description consumed only by the accessibility inspector attile_behavior.rs:545(diagnostic, not routing). - Verification:
cargo test -p graphshell --lib viewer -- --test-threads=1β 70/70.
-
mods/native/verso/βmods/native/web_runtime/on disk. -
mods/native/mod.rsnow declarespub(crate) mod web_runtime;. - 29 bulk substitutions across 10 files for
mods::native::versoβmods::native::web_runtime. - 9 bare
verso::call sites for the mod's wry helpers (destroy_wry_overlay_for_node, etc.) rewritten toweb_runtime::(concentrated intile_runtime.rs,tile_compositor.rs,lifecycle_reconcile.rs), plus 4 test-onlyverso::reset_wry_manager_for_tests/last_wry_overlay_sync_for_node_for_testssites. -
"mod:verso"string literals (8 total) kept as-is for compatibility β Phase 6 (manifest-id rename) remains optional/deferred. - No changes to workspace-root
Cargo.toml(thecrates/versolibrary is a distinct thing from the renamed mod).
- Added to
crates/verso/src/lib.rs:-
VersoPaneOwnerβPolicy/UserPin/Unresolved -
VersoRouteReasonβMiddlenetLane(lane)/WebEnginePreferred(pref)/WebEngineFallback { preferred, used }/UserOverride/Unsupported -
VersoResolvedRouteβ decision + reason + owner, with typed accessors (is_wry(),is_middlenet(),engine(),viewer_id(),reason(),owner()) so consumers can stop string-comparing viewer ids. -
resolve_route_for_content(uri, mime_hint, host_caps, preference, owner)β preferred entry point; wrapsselect_viewer_for_contentand populates reason/owner.
-
- 5 new unit tests cover: Middlenet lane reason, web-preference match, web-preference fallback, owner propagation, unsupported-content None.
- Types are introduced only; wiring them into
NodePaneState/preferred_viewer_id_for_contentis deferred to a follow-on PR (previously framed as "full migration"). Risk 8.5 (ViewerRoutingDecision.viewer_idas string authority) remains until that migration lands; the new typed accessors are the mechanism for the replacement.
-
cargo test -p versoβ 12/12 (was 7 before PR 4; +5 new). -
cargo test -p graphshell --lib viewer -- --test-threads=1β 70/70. -
cargo test -p graphshell --lib -- --test-threads=1β 2164 pass / 2 known-flaky / 3 ignored. The two flakes (phase0_registry_cancellation_short_circuits_before_viewer_selectionandsave_named_frame_bundle_preserves_collapsed_runtime_semantic_tabs) pass when rerun in isolation; they depend on shared test state and are tracked under the archived 2026-04-19 flaky-test hygiene plan. Not regressions from PRs 2β4.
- PR 5: settings + UX migration (map "Compat mode" / default web backend to
WebEnginePreference; expose route reason in debug surfaces). - PR 6: terminology + docs cleanup (
TERMINOLOGY.md, shell/workbench specs, viewer backend docs). - PR 7 (optional): manifest-id rename
mod:versoβmod:web-runtime. - Follow-on to PR 4: thread
VersoResolvedRoutethroughNodePaneState; replace rawviewer_idstring comparisons at the 17 identified call sites; updateViewerRoutingDecisionto return a typed handle instead of&'static str.
-
app/settings_persistence.rsβ addedDefaultWebViewerBackend::web_engine_preference() -> ::verso::WebEnginePreferencemethod. The user-facing setting enum now exposes its verso mapping as a typed accessor; call sites stop open-coding the match. -
tile_runtime.rs preferred_viewer_id_for_contentβ uses the new accessor. The registry-side preferenceβverso conversion is gone from the call site. -
workbench_host.rs WorkbenchNodeViewerSummaryβ gainedverso_route: Option<::verso::VersoResolvedRoute>field.build_node_viewer_summarypopulates it by calling::verso::resolve_route_for_contentwith owner derived from whether a user override exists (UserPinif overridden, otherwisePolicy). Debug/overview surfaces can now read engine/reason/owner off a typed field instead of parsing viewer-id strings. - "Compat mode" per Β§Phase 4 of the plan does not yet exist in the codebase (grep confirmed: no
CompatMode/compat_modereferences). Introducing it is deferred; the pattern established here (user setting βWebEnginePreference) is the template for when it lands.
-
design_docs/TERMINOLOGY.mdβ "Verso" entry rewritten to frame verso as the shell's routing authority (crates/verso+ theverso://internal namespace), with explicit pointer that the legacy "Verso mod" is now theweb_runtimeprovider bundle feeding into it. -
design_docs/graphshell_docs/technical_architecture/ARCHITECTURAL_OVERVIEW.mdβ Viewer feature table row "Servo/Web" updated to referenceweb_runtime, and a new "Viewer β Routing Authority" row added pointing atcrates/versoas the decision-making layer. Viewer/Routing & Fallback row narrowed to describe non-web-only viewer registry responsibility.
-
"mod:verso"β"mod:web-runtime"at all 19 literal occurrences across 7 files (tests, mod_loader, mod_activation, runtime registry dispatcher, plus the web_runtime manifest itself). - No persisted state reads the manifest id, so renaming is safe from a backward-compatibility standpoint. The dispatcher at
shell/desktop/runtime/registries/mod.rs:1387now dispatches on"mod:web-runtime" | "verso"β the legacy"verso"short-form alias is kept intact. - One test-site adjustment: the new
verso_route: Nonefield needed to be added to sixWorkbenchNodeViewerSummarystruct-literal sites inoverview_plane.rstests; awk-script injection handled it cleanly.
-
cargo test -p verso --libβ 12/12. -
cargo test -p graphshell --lib viewer -- --test-threads=1β 70/70. -
cargo test -p graphshell --lib workbench_host:: -- --test-threads=1β 74/74. -
cargo test -p graphshell --lib mod_loader:: -- --test-threads=1β 19/19. -
cargo test -p graphshell --lib persistence_ops -- --test-threads=1β 26/26. -
cargo test -p graphshell --lib -- --test-threads=1β 2166 pass / 0 fail / 3 ignored. (An interim run during the PR 7 test-wiring pass reported 24 ordering-dependent flakes; a clean rerun after the struct-literal fixes showed the suite green.)
- β
versois documented and used as shell routing authority (PR 1 + PR 6 + this plan). - β
middlenet-enginecontains no cross-engine escalation logic (was already true pre-plan; verified in Β§1.1). - β
Provider/backend code no longer lives under the
mods/native/versomodule path (PR 3). β οΈ Workbench pane routing goes throughversotypes (PR 1 + PR 2). ~17 rawviewer_idstring comparisons remain at scattered call sites (tile_compositor.rs,node_pane_ui.rs,pane_ops.rs,overview_plane.rs,workbench_host.rsmiddlenet-availability check) β these are not routing decisions (they're capability / render-path checks), but eliminating them would further reduce Risk 8.4. Deferred to a follow-on "viewer-id type hardening" plan rather than included in this scope.β οΈ Settings and debug surfaces describe backend choice inversoterms (PR 5). Theverso_routefield is now available onWorkbenchNodeViewerSummary; actually rendering engine/reason/owner in the UI is a UX-layer follow-on.- β Terminology/docs no longer describe Verso primarily as a mod (PR 6).
- Reshape
ViewerRoutingDecision.viewer_id: &'static strinto a typed handle (Risk 8.5) β requires an audit of every== "viewer:*"site and a coordinated flip. Appropriate as a dedicated plan after this one archives. - Thread
VersoResolvedRoutethroughNodePaneStatesoresolved_viewer_idcan be replaced by the structured route. - Render
verso_route.reason()/.owner()in the workbench debug overview pane (UX work). - "Compat mode" introduction (per Β§Phase 4): a per-node setting that routes through
WebEnginePreference::Wry. New feature, separate plan.
Plan status: complete. Archived 2026-04-21.