2026 04 25_servo_into_verso_plan - mark-ik/graphshell GitHub Wiki
Status: Active execution plan β sliced S1 β S5
Lane: Move Servo's role behind a verso/servo-engine feature so
graphshell can be selectively compiled with or without Servo, mirroring
the 2026-04-25 wry-into-verso refactor.
Related:
-
VERSO_AS_PEER.md β
the spec verso has been working toward; calls out Servo (as
viewer:webview) and wry (asviewer:wry) as the two engines verso owns. - Iced host migration execution plan β Phase A2 (wry impl β verso) landed 2026-04-25, establishing the pattern this plan follows for Servo.
- Iced content-surface scoping β Β§0 platform-tier framing: native = Servo, mobile/web = middlenet/wry. Servo gating is the prerequisite for non-native builds.
graphshell main becomes selectively compileable across three independent engine axes:
| Cargo feature | Engine | Default? |
|---|---|---|
verso/servo-engine (via graphshell servo-engine) |
viewer:webview (Servo, texture mode) |
yes (matches today) |
verso/wry-engine (via graphshell wry) |
viewer:wry (system WebView, overlay mode) |
yes (matches today) |
| middlenet (always-on, lightweight) |
viewer:middlenet lanes |
yes |
The three are independent; a build without servo-engine should
still compile and run β chrome + middlenet + wry only. This unlocks:
- Mobile / WASM target paths (Servo isn't viable there)
- Faster CI iteration loops for chrome-focused work
- A test surface for graceful degradation when one engine is unavailable
Workspace crates β β all servo-free:
-
graphshell-core,graphshell-runtime,graph-canvas,graph-tree,middlenet-*,verso,verso-host,iced-middlenet-viewer,iced-graph-canvas-viewer,iced-wry-viewer. None depend on theservocrate. Only docstring mentions ofservo::WebViewIdexist ingraphshell-core/src/content.rs.
Graphshell main β β Servo is unconditional:
-
Cargo.toml:142 β
servo = { path = "...", default-features = false }is a non-optional direct dep. - ~75
.rsfiles in graphshell main directlyuse servo::*types. - Many graphshell features cascade into Servo features
(
gamepad = ["servo/gamepad"],webgpu = ["servo/webgpu"], lines 70β87 of Cargo.toml).
So gating Servo is the same magnitude of refactor as the iced host migration was β broad mechanical sweep + a handful of architectural decisions about what graphshell-without-Servo does.
Mirror the wry-engine pattern from Phase A1 (2026-04-25):
- Add
servo-engine = ["dep:servo", ...]to verso's[features]. - Add
servoCargo dep to verso,optional = true, identical features to graphshell main today. - New
verso::servo_enginemodule re-exports theservocrate so downstream consumers depend onverso/servo-enginerather thanservodirectly. - Verso compiles with the feature on; nothing in graphshell main changes yet.
Receipt: cargo check -p verso --features servo-engine clean.
The bulk of the mechanical work, split into S2a (Cargo wiring, done) and S2b (file-level sweep, in progress).
- Both
servodeps inCargo.toml(cross-platform line 142, Windows target-conditional line 263) markedoptional = true. - New feature
servo-engine = ["dep:servo", "verso/servo-engine"]. - All previously-forwarded servo features now require
servo-enginebefore forwarding into theservocrate:gamepad,webgpu,webxr,js_jit,crown,debugmozjs,jitspew,js_backtrace,vello,webgl_backtrace,tracing,media-gstreamer,native-bluetooth,profilemozjs,refcell_backtrace. -
defaultfeature set keepsservo-engineon, so existing builds are bit-identical to pre-S2a behavior.
Receipt (S2a): cargo check (default features) clean β verified
2026-04-25, completes in 4m 17s, no regressions, two pre-existing
egui deprecation warnings only.
Module-level pass (2026-04-25 evening): gated Servo-coupled
submodules at their parent mod.rs level. The structural cuts:
-
shell/desktop/mod.rs: gatehost+render_backend(entirely Servo-coupled). -
shell/desktop/lifecycle/mod.rs: gatelifecycle_reconcile,semantic_event_pipeline,webview_backpressure,webview_controller,webview_status_sync(keptlifecycle_intentsopen). -
shell/desktop/workbench/mod.rs: gatecompositor_adapter,tile_render_pass,tile_runtime,tile_view_ops, + consumer cascades:graph_tree_dual_write,tile_behavior,tile_compositor,tile_invariants,tile_post_render,graph_tree_projection,semantic_tabs,ux_probes. -
shell/desktop/runtime/protocols/mod.rs: gateresource,router,servo,urlinfo(keptregistryopen). -
shell/desktop/runtime/registries/mod.rs: gateworkbench_surface+workflow;ServoUrlaliased tourl::Urlwhen servo-engine is off. -
shell/desktop/ui/mod.rs: gatedialog,dialog_panels,egui_host_ports,gui,gui_frame,gui_orchestration,host_ports,nav_targeting,persistence_ops,thumbnail_pipeline,toolbar,toolbar_routing,workbench_host, plus the consumer cascadegui_state,finalize_actions,graph_search_flow,graph_search_ui,overview_plane,shell_layout_pass. The in-tree iced launch path (iced_appetc.) now requires bothiced-hostANDservo-engine(see "iced launch path coupling" below). -
lib.rs: gatemod render(egui rendering layer);mod parser,mod prefs,mod resourcesbehind servo-engine; provide a tiny stubmod prefs { ... }exposingFileAccessPolicysograph_app.rsstill compiles. -
shell/desktop/runtime/cli.rs: split intomain()(host-neutral prelude + iced-host branch + no-servo exit warning) andrun_servo_launch_path()(Servo+egui boot, gated). -
shell/desktop/runtime/tracing.rs: gatefrom_winitLogTarget impls (use host::event_loop::AppEvent). -
shell/desktop/runtime/diagnostics.rs: gate compositor_adapter import. -
panic_hook.rs: gateservo::optsuse; SIGSEGV path becomes no-op without servo-engine. -
graph_app.rs: bug fix β disambiguateuse ::graph_cartography::*(workspace crate, vs. localmod graph_cartographyshim).
Receipt (module-level pass):
- β
Default build (
cargo check, servo-engine + wry on) clean, no regressions, ~18s incremental. - β³
cargo check --no-default-features --features wry: down to 75 errors from 142, all body-level cascades.
iced launch path coupling: the in-tree iced launch path
(shell::desktop::ui::iced_app, iced_host, iced_host_ports,
etc.) consumes host_ports::* traits, render_backend::Backend*
types, compositor_adapter-re-exported PortableRect, and
servo::WebViewId directly. Achieving a true no-Servo iced
launch path requires extracting these to host-neutral locations
(graphshell-core for vocab; graphshell-runtime for traits) which
is S3 architectural work, not S2b mechanical sweep. For now,
the iced-host launch path inside graphshell main is gated with
cfg(all(feature = "iced-host", feature = "servo-engine")).
The standalone iced demo crates (crates/iced-{middlenet,graph-canvas, wry}-viewer) are unaffected β they remain fully no-Servo and
demonstrate the Phase B portable surface.
S2c body-level pass (completed 2026-04-25):
The deferred no-Servo Wry body-level cascade is now closed. A fresh
S2c baseline after the host-port extraction stood at 74 errors / 24
warnings for cargo check --no-default-features --features wry.
The pass fixed false Servo coupling and added no-Servo shims only where
the build already owned no live Servo state:
- 74 -> 64:
ux_treemetadata/telemetry imports moved to host-neutral sources; Tool-pane match arms made exhaustive whendiagnosticsis off. - 64 -> 56: pure local-file URL/access-policy helpers moved out of
Servo-gated
tile_behaviorinto ungatedworkbench::local_file_access. - 56 -> 53:
tag_panelstopped depending on Servo-gatedrender::semantic_tagsfor pure tag label/suggestion helpers. - 53 -> 41: persisted command-palette action taxonomy now uses
graphshell_core::actions; the no-Servoprefsstub gained the stylesheet source reader needed by settings persistence. - 41 -> 32: persisted workspace rename now updates the JSON
namefield directly; diagnostics gained no-Servo focus/compositor replay and content-budget fallbacks. - 32 -> 1:
workflowregistry compiled unconditionally; no-Servoworkbench_surfaceshim backed by the domain registry satisfied theRegistryRuntimefields. - 1 -> 0: no-Servo
workbench_surface::dispatch_intentadded as a no-op sink matching the Servo-path signature.
Validation receipts:
- β
cargo check --no-default-features --features wryβ clean, warnings only (graphshelllib generated 24 warnings; 18.54s incremental). - β
cargo check -p graphshell --libβ clean default-feature guardrail, warnings only (graphshelllib generated 2 warnings after trimming the staletile_behaviorre-export; latest incremental 10.72s).
Historical S2b cascade inventory, now resolved by S2c:
-
shell/desktop/runtime/registries/mod.rsbody (~30 refs toWorkbenchSurfaceRegistry,WorkflowRegistry, etc.) β needs per-line gating in fields, function signatures, match arms. -
shell/desktop/runtime/diagnostics.rsbody (CompositorReplaySample,replay_samples_snapshot). -
app/persistence_facade.rs,app/settings_persistence.rs(useprefs::read_user_stylesheet_source,crate::render::*,ui::persistence_ops). -
app/workspace_state.rs(usescrate::render::*). -
registries/atomic/viewer.rs,registries/viewers/{directory, image_viewer, middlenet, plaintext}.rs(5 files: useworkbench::tile_behavior). -
shell/desktop/workbench/ux_tree.rsbody (gated types in function signatures). -
shell/desktop/ui/tag_panel.rs(usescrate::render).
These were body-level uses of gated types. The S2c fix kept the Servo/default path on the real modules and used empty/no-op fallbacks only for no-Servo paths where no Servo producer exists.
cargo check --no-default-features --features iced-host,wry against
post-S2a tree surfaces 141 errors across 58 unique files
(close to the pre-survey "~75 files" estimate). Inventory below
freezes the sweep target so future sessions can resume mid-sweep
without re-running the full check.
Categories (by gating strategy):
Cluster A β gate-as-whole-module candidates (Servo coupling is structural; entire file is a Servo embedder/compositor adapter):
-
shell/desktop/host/*(~17 files):accelerated_gl_media,embedder,event_loop,geometry,headed_window(+clip_extraction,embedder_controls,input_routing,xr),headless_window,host_app,keyutils,running_app_state(+webview_delegate),webdriver_runtime,window(+projection,runtime). -
shell/desktop/lifecycle/:lifecycle_reconcile,webview_backpressure,webview_controller,webview_status_sync,semantic_event_pipeline. -
shell/desktop/render_backend/:mod,shared_wgpu_context,wgpu_backend. -
shell/desktop/runtime/protocols/:resource,router,servo,urlinfo(Servo URL-scheme protocol handlers). -
shell/desktop/workbench/:compositor_adapter,tile_render_pass,tile_runtime,tile_view_ops. -
shell/desktop/ui/thumbnail_pipeline.rs(Servo screenshot pipeline).
Cluster B β partial-gate candidates (file is host-neutral overall but pulls Servo types at specific boundaries):
-
shell/desktop/runtime/registries/mod.rs:99β single import. -
shell/desktop/runtime/cli.rs:75β single import in startup path. -
shell/desktop/ui/dialog.rs,dialog_panels.rs. -
shell/desktop/ui/egui_host_ports.rsβ egui-host bridge (egui being retired; can stay servo-coupled at file level since iced-host is the post-retirement path). -
shell/desktop/ui/iced_host_ports.rs,iced_host.rsβ iced-host bridge; must work both with and withoutservo-enginesince this is the litmus-test path. Partial gating required. -
shell/desktop/ui/host_ports.rsβ generic host bridge, partial. -
shell/desktop/ui/gui.rs(+accessibility,gui_frame,gui_orchestration,pre_frame_flow,semantic_lifecycle_flow,toolbar_phase_flow) β egui-host main loop, similar reasoning to egui_host_ports.rs. -
shell/desktop/ui/nav_targeting.rs,persistence_ops.rs. - Crate-root:
panic_hook.rs,prefs.rs,parser.rs,graph_resources.rs.
Sweep approach (recommendation for next session):
- Start with Cluster A: gate at the
mod.rsdeclaration site (e.g., add#[cfg(feature = "servo-engine")] pub mod host;inshell/desktop/mod.rs). One edit per cluster, ~7 mod-level edits removes ~40 of 58 file errors at once. - Then Cluster B: edit-by-edit gating of import lines and call
sites, focusing on iced_host* first since those are the path
that must stay alive without
servo-engine. - Imports become
verso::servo_engine::*rather thanservo::*, so the route through verso is consistent with the wry pattern established in Phase A2.
Sweep blocker (surfaced 2026-04-25): the workspace's parallel
webrender-wgpu checkout has a compile error in
webrender_build/src/compiled_artifacts.rs:23 (unresolved import crate::glsl). This blocks cargo check from progressing far
enough to surface graphshell-main errors when the cache is cold for
a given feature combo. Next session must either fix or stash that
checkout's working tree before the sweep can iterate. The 58-file
inventory above was captured before the blocker manifested, so the
sweep can proceed against this list without re-running the check.
Receipt (S2b, deferred): cargo check --no-default-features --features iced-host,wry clean (servo-engine off, iced + wry only)
once the sweep lands. cargo check with default features
(servo-engine on) must remain clean throughout.
What does the binary actually do when servo-engine is off?
Decision matrix:
| Subsystem | With servo-engine
|
Without servo-engine
|
|---|---|---|
| Chrome + canvas + iced/egui host | works | works |
| middlenet content rendering | works | works |
| wry overlay (fullnet) | works (when wry feature on) |
works (when wry feature on) |
viewer:webview (Servo) |
works | unavailable; routes to wry / middlenet / unsupported |
GPU context (HostGpuPort, iced-owned) |
works (iced device; Servo textures imported via import_content_texture) |
works (iced device only; no Servo texture producer) |
Content accessibility (ContentAccessibilityProducer) |
Active (Servo impl) |
EngineUnavailable stub; viewer labels degrade to URL + title |
| Webview backpressure | works | reduced to wry-only path |
Workbench compositor ViewerSurfaceBacking::NativeRenderingContext
|
works | always None; callback-fallback or wry-only |
Key code areas that need restructuring (not just gating):
-
HostCapabilitiesdefaults: todayHostCapabilities::defaulthassupports_servo: falsealready (per verso/src/lib.rs:58). The live wiring sets it totruewhen graphshell boots Servo. Withoutservo-engine, the live wiring staysfalseand verso's dispatch routes accordingly. No breaking change here. -
ViewerSurfaceRegistry::backingtyping: currently can beNativeRenderingContext(Rc<dyn RenderingContextCore>)whereRenderingContextCoreis a trait Servo provides. Without Servo, the trait still exists (we own the trait? need to verify) but no impls are imported. The variant becomes uninhabitable β but the enum compiles fine; just no producers. -
shared_wgpu_context.rs: holdsservo::wgpu::Device+servo::wgpu::Queue. Gate the whole file behindservo-engine. GPU context ownership is now defined byverso::host_gpu_port::HostGpuPort(Lane 2, 2026-04-27): iced is the host-owned device; Servo imports textures into it rather than owning the device root. Noverso::wgpure-export or stub is needed β the iced host'sHostGpuPortimpl fills the role.
For S3 first pass: gate liberally, document the architectural
follow-ons, don't refactor the trait surface. Goal is a working
no-servo build, not the cleanest possible no-servo architecture.
The trait extraction (e.g., move RenderingContextCore into verso
as a portable trait) is a separable later slice.
Receipt: with servo-engine off, the binary opens an iced
window with chrome + canvas + wry overlay. Submitting an https://
URL falls back to wry (or returns "engine not available" via
verso's existing dispatch) instead of attempting Servo.
-
cli.rs::main()currently initializes Servo unconditionally. Wrap Servo init in#[cfg(feature = "servo-engine")]; provide a no-servo branch that proceeds to graphshell startup without Servo. - The iced-host launch path (already gated on
iced-hostfeature) is independent; it'll work with or withoutservo-engine. - Egui-host launch path needs the same conditional Servo init treatment, since today it expects Servo to exist.
Receipt: --no-default-features --features iced-host runs
graphshell --iced against a Servo-free binary. --no-default-features --features iced-host,wry adds wry overlays.
- Add a CI build configuration (or document one if CI isn't
automated yet):
cargo check --no-default-features --features iced-host,wryshould be part of the check matrix to prevent regressions where a non-servo-enginechange breaks the no-Servo build. - Update PROJECT_DESCRIPTION.md's rendering-architecture paragraph to reflect tri-engine selectivity.
- Update VERSO_AS_PEER.md to note Servo + wry are both behind verso features, not just registered viewers.
- Update the iced-host migration plan with a Phase A2 sibling entry for Servo (S2/S3 receipts).
Receipt: documentation changes land in the same session; the "three independent engine axes" picture is canonically captured.
-
Does
ViewerSurfaceBacking::NativeRenderingContextsurvive without Servo? If we wantservo-engine-off builds to still support some "native rendering context" (e.g., a future non-Servo wgpu producer), theRenderingContextCoretrait needs to live in verso (not be re-exported from servo). For first-pass, gate the variant entirely β re-introduce when a non-Servo producer arrives. -
Webview lifecycle vocabulary:
WebviewBackpressureStateand thewebview_backpressuremodule assume Servo's webview creation cadence. Need to identify what's Servo-specific vs. what's host-neutral state machine. Probably gate the whole module behindservo-enginefor first pass. - Accesskit bridge: Servo provides accesskit tree updates per webview. Without Servo, the bridge has no producers but still has consumers (chrome accesskit). Stub the producer side.
-
GPU context ownership β
resolved (Lane 2, 2026-04-27):
verso::host_gpu_port::HostGpuPortdefines iced as the host-owned GPU context. Servo-produced textures are one import source viaimport_content_texture; Servo does not own the device. Withoutservo-engine, the host GPU context (iced's device/queue) still exists; there are simply no Servo-produced textures to import.AbsentContentAccessibilityProducerinverso::content_accessibility_producerhandles the no-engine accessibility path β it returnsEngineUnavailableand the viewer falls back to URL + title labels.
Status as of 2026-04-25:
- β
cargo check -p verso --features servo-engineβ clean (S1). - β
cargo check(default features) β clean (S2a; servo-engine + wry on, no regressions, 4m 17s). - β
cargo check --no-default-features --features wryβ clean after S2c body-level pass; warnings only (24 graphshell warnings). - β
cargo check --no-default-features --features iced-host,wryβ clean as a library compile (24 warnings, all unused-import noise, 0 errors; 1m 54s cold). Caveat: the in-tree iced launch path (iced_app,iced_host,iced_graph_canvas,iced_events,iced_middlenet_viewer) is still gated oncfg(all(iced-host, servo-engine))per S3b.1, so this receipt proves the iced-host bridge surface (iced_host_ports,CachedTexture, runtime ports) compiles without Servo β not that the binary launches via iced. Closing the gap (truly launchable iced without Servo) is the canonical S3b GraphshellRuntime extraction. - β³
cargo check --no-default-features --features iced-hostβ not yet attempted (dropswrytoo; expect new errors only if any ungated code assumed wry was present). - β³
cargo check --no-default-features --features servo-engine,iced-hostβ not yet attempted. - All matrix entries to be documented post-S5.
Compile-matrix runner: scripts/dev/engine-feature-matrix.sh
(and .ps1 sibling) runs the three checks above in sequence and emits a
one-line PASS/FAIL summary per combo. Wire this into CI or a pre-push hook
to prevent silent regressions of the no-Servo paths.
-
2026-04-27 (Lane 5a: first no-Servo iced launch):
cargo run --no-default-features --features iced-host -- --icednow opens a usable iced window. Three root blockers removed: (1)build_scene_input/scene_mode_to_canvas/view_id_to_canvasextracted fromrender/canvas_bridge(gated onservo-engine) into new ungatedapp/canvas_scene.rs;iced_graph_canvas.rsupdated to call the new location. (2)gui/frame_inbox.rscontent moved to ungatedshell/desktop/ui/gui_frame_inbox.rs;gui/frame_inbox.rsstubbed as a re-export shim;gui_state.rsimports updated to the new path. (3)gui_state::GraphshellRuntimeungated:viewer_surfaces/viewer_surface_host/omnibar_provider_suggestion_driverfields gated onservo-engine;ToolbarAuthorityMutstruct+impl gated onservo-engine;persistence_opsandcompositor_adaptercall sites ingui_state.rsgated onservo-engine;iced_host.rsviewer_surfaces.bump_content_generationgated onservo-engine.finalize_actions.rsungated with a no-servo no-op branch (webview queues are always empty on the iced-only path).mod.rsiced-module gates changed fromall(iced-host, servo-engine)toiced-host;cli.rsiced branch gate changed tocfg(feature = "iced-host"). Receipts:cargo check --no-default-features --features iced-hostclean (0 errors); default servo-engine build clean; binary ran for 5+ seconds without crash (window opened, event loop running). URL navigation returns "engine not available" as expected. -
2026-04-27 (Lane 2: verso host-port contracts): added
verso::host_gpu_port(HostGpuPort,HostGpuCapabilities) andverso::content_accessibility_producer(ContentAccessibilityProducer,ContentAccessibilityProducerState,AbsentContentAccessibilityProducer).HostGpuPortresolves the "Servo shared device" question from Β§3/Β§4: iced is the host-owned GPU context; Servo-produced textures are imported viaimport_content_texturerather than owning the device. The typedContentAccessibilityProducerState::EngineUnavailable+ the absent stub replace the "Servo accesskit bridge: stubbed" row with a concrete degraded-mode path. Plan wording updated in Β§3 decision matrix, Β§3 "Key code areas" item 3, Β§4 item 4, and the S3b.1 execution log entry. No new Cargo deps required β verso's port modules use only primitive andstdtypes. -
2026-04-25 (S1): Added
servo-enginefeature + optionalservodep +verso::servo_enginere-export module tocrates/verso. Verso compiles standalone with the feature on. -
2026-04-25 (S2a): Made graphshell main's
servodeps (both cross-platform line 142 and Windows-target-specific line 263) optional. Addedservo-engine = ["dep:servo", "verso/servo-engine"]. Cascaded all 16 servo-forwarded features to requireservo-enginefirst. Default feature set keepsservo-engineon; default build verified clean (4m 17s). -
2026-04-25 (S2b survey): Surveyed
cargo check --no-default-features --features iced-host,wryerrors; catalogued 141 errors across 58 files into Cluster A (whole-module gate candidates) and Cluster B (partial-gate candidates). See Β§3 S2b for the full inventory. Discovered concurrentwebrender-wgpuworking-tree breakage that blocks further cargo-check iteration; flagged as sweep prerequisite. -
2026-04-25 (S2b module-level pass): webrender-wgpu blocker cleared; ran the module-level gating pass across
lib.rs,shell/desktop/{mod,lifecycle/mod,workbench/mod,ui/mod, runtime/{mod,cli,tracing,diagnostics,registries/mod,protocols/mod}}, pluspanic_hook.rsandgraph_app.rs. Down from 142 β 75 errors againstcargo check --no-default-features --features wry; default build (servo-engine on) remains clean. Remaining 75 are body-level cascades that S3a (host_ports trait extraction) should supersede; deferred to S2c post-S3. -
2026-04-25 (S3a host-port trait extraction): moved the host-port trait surface into
graphshell-runtime:HostInputPort,HostSurfacePort,HostPaintPort,HostTexturePort,HostAccessibilityPort, plusBackendViewportInPixelsand the new host-neutralViewerSurfaceId.HostSurfacePortgained an associatedBackendContexttype so iced (= ()) and egui (= glow::Context) can ship without trait-signature churn. Tree-update injection was split out into a Servo-specific extension traitServoAccessibilityInjectionPortthat lives in graphshell-main (gated onservo-engine) since the egui-host's accesskit anchor derivation isservo::WebViewId-shaped today; the portableHostAccessibilityPortretains onlyrequest_focus. Shell-sidehost_ports.rsis now a thin re-export shim, so existing call sites work unchanged.iced_host_ports.rsno longer importsrender_backendorcompositor_adapter(it imports from graphshell-runtime + graphshell-core directly); the type-level painter stubs that did consume those gated modules are themselves gated onservo-engine. Default build clean. No-servo error count holds at 74 (S3a doesn't reduce body-level cascade count; that's S2c work). The architectural seam is the point: future iced launch path decoupling (S3b) can proceed without re-doing port plumbing. -
2026-04-25 (S3b.1 IcedWgpuContext gate + iced_host_ports ungating): smaller incremental S3b slice.
IcedWgpuContext(a stopgap holdingservo::wgpu::{Device,Queue}) is gated onservo-engineso iced-only builds don't carry Servo's wgpu surface. End-state captured byverso::host_gpu_port::HostGpuPort(Lane 2, 2026-04-27): iced provides the host GPU context as its own device/queue; Servo-produced textures are one import source viaimport_content_texturerather than the device owner.IcedWgpuContextremains gated until the icedHostGpuPortimpl lands (Lane 5b).CachedTexturerelocated fromiced_host.rsintoiced_host_ports.rsso the ports module has no shell-side gated deps;iced_host_portsis now ungated fromservo-engineinui/mod.rsand ships under justiced-host. The remaining iced launch path (iced_app,iced_host,iced_graph_canvas,iced_events,iced_middlenet_viewer) still consumesgui_state::GraphshellRuntimeand stays gated oncfg(all(iced-host, servo-engine))until the GraphshellRuntime extraction (S3b proper) lands. -
2026-04-25 (S2c body-level no-Servo Wry pass): closed the
cargo check --no-default-features --features wrycascade from a fresh 74-error baseline to green. Fixes were localized to false Servo coupling (ux_tree, command-surface telemetry, action taxonomy), pure helper relocation (workbench::local_file_access, tag-panel helpers), JSON/prefs no-Servo fallbacks, diagnostics no-Servo placeholders, and a no-Servoworkbench_surfaceshim for the registry runtime. Final receipts: no-Servo Wry check clean with 24 graphshell warnings; defaultcargo check -p graphshell --libclean with 2 graphshell warnings after stale re-export cleanup. -
2026-04-26 (S3b retry/cooldown core extraction): continued the canonical runtime-crate slice path.
WebviewAttachRetryState(the host-neutral retry/cooldown core named in the webview_creation_backpressure audit) moved intographshell-runtime::webview_backpressurewith a puremin*2^step-clamp cooldown delay, dropping thebackondependency from the runtime-side numerics. Shell-sideWebviewCreationBackpressureStatenow composes the runtime type alongside the Servo-typed pending probe andInstantdeadline β matching the audit's recommended split (probe identity + deadline stay shell-side because they bind toWebViewIdandstd::time::Instant). Receipts: graphshell-runtime tests 26 β 33 pass (8 new tests on the extracted core); shell webview_backpressure tests still pass (7); engine-feature matrix all 3/3 PASS. See the canonical plan's Source-side audit progress log 2026-04-26 entry for full details. -
2026-04-27 (gl_compat gating cascade): completed slice 1 of the GL-retirement ordering by gating the GL-callback machinery behind
gl_compat, so the wgpu-only build path is now compileable.glowis now anoptional = truedep (Cargo.toml:244), activated bygl_compat = ["dep:glow"]. Gated asgl_compat-only:BackendGraphicsContext/BackendFramebufferHandle/BackendParentRenderCallback(gl_backend.rs); the entireBackendContentBridge*selection machinery + tests + env-var helpers (render_backend/mod.rs); thecustom_pass_from_backend_viewport/register_custom_paint_callbackstubs (wgpu_backend.rs); the content callback registry static + types + accessor + register/unregister/compose family (compositor_adapter.rs ~10 functions); theContentPassPainter::register_content_callback_on_layertrait method and its egui impl;register_content_callback_from_render_context,content_callback_from_parent_render,registered_content_pass_callback. Thecfg(not(feature = "gl_compat"))variant ofrun_content_callback_with_guardrailswas retired (had no callers without the gated registry). The GL-callback fallback arm incompose_webview_content_pass_with_painterand the unregister calls in the wgpu success path are now conditionally compiled. Two-forkedEguiHostPorts: HostSurfacePortimpl:gl_compat-on usesBackendContext = BackendGraphicsContextand forwards toCompositorAdapter::register_content_callback;gl_compat-off usesBackendContext = ()with no-op register/unregister methods (the registry doesn't exist, so callbacks are silently dropped).retire_node_content_resourcesandretire_stale_content_resourcesskip the registry path under no-gl_compat but still clean the native-texture registry. New matrix entry (slot 4 inscripts/dev/engine-feature-matrix.{sh,ps1}):--no-default-features --features servo-engine,gamepad,js_jit,max_log_level,webgpu,webxr,diagnostics,wry,ux-probes,ux-bridgeβ production default minusgl_compat. Receipts: engine-feature matrix all 4/4 PASS (default, no-default wry, no-default iced-host wry, no-default servo-engine no-gl_compat); 7 render_backend bridge tests pass; 38 compositor_adapter tests pass; 40 graphshell-runtime tests pass. Slice 2 (default-offgl_compat) remains runtime-blocked β the static gating is honest but the wgpu-only path needs end-to-end smoke validation that webview composition succeeds without the GL fallback re-registering callbacks; that's a runtime-validation receipt, not a static-code one. -
2026-04-27 (GL legacy survey + Phase B dead-code removal): surveyed live GL-era surface in graphshell main against the
gl_to_wgpu_plan.mdPhase B/F retirement framing, then landed the static-code part of Phase B. Findings:gleamis not a direct dep (transitive via Servo).egui_glowis not present (the egui stack isegui-wgpualready).glow = "0.17.0"is unconditional but consumed only byshell/desktop/render_backend/gl_backend.rs.surfmanis direct-dep but used only byshell/desktop/host/accelerated_gl_media.rs(Servo media plumbing, not compositor legacy). The compositor side has ~38cfg(feature = "gl_compat")gates plus the content-callback registry shape that still threadsBackendGraphicsContext = glow::Contextthrough. Phase B retirement landed: deletedBackendContentBridge::SharedWgpuTexturevariant +BackendSharedWgpuImporttype alias +select_content_bridge_wgpu_from_render_contextfactory β pure dead architectural scaffolding (the actual wgpu shared-texture path bypassesBackendContentBridgeentirely and goes throughupsert_native_content_texturedirectly). CollapsedBackendContentBridgeSelectionto inlinecallback: BackendParentRenderCallback, removed the unreachableelsebranch inregister_content_callback_from_render_context, and re-framed the doc comment to explicit "GL parent-render callback used by the GL-compat composition path." Receipts: 7 render_backend bridge tests still pass; engine-feature matrix all 3/3 PASS. Remaining slices in the GL-retirement ordering (NOT landed today): (i) gateBackendGraphicsContext/BackendFramebufferHandle/BackendParentRenderCallbackand the content-callback registry machinery behindgl_compat, makingglowoptional β needs careful cascade across ~30compositor_adapter.rsuse sites and runtime validation that the wgpu-only path works without unregister/register fallback plumbing; (ii) flipgl_compatto off-by-default (runtime-validation gated); (iii) Phase F retirement of the 38 GL-state guardrails (depends on (ii) being stable).accelerated_gl_media.rsstays β it's Servo media plumbing, not compositor legacy. -
2026-04-27 (S3b viewer_surfaces Step 2: RenderingContextProducer trait): reviewed Servo's
RenderingContextCore(servo-wgpu/components/shared/paint/rendering_context_core.rs) to pick between (a) re-extracting the Servo trait into runtime, (b) opaque host-neutral handle, (c) parameterizing over the host context type, and (d) deferring entirely. Key findings: Servo's core trait pullsembedder_traits::RefreshDriver,webrender_api::units,surfman,gleam/glowβ too heavy for graphshell-runtime. Compositor's actual consumption fromViewerSurfaceBackingis narrow:size(),resize(),present(), plus GL-compatmake_current()/prepare_for_rendering(). Servo webview construction (webview_backpressure.rs:328) consumes the fullRc<dyn RenderingContextCore>directly, not via the producer trait. Decision: minimalRenderingContextProducertrait ingraphshell-runtime::rendering_context_producerwith primitive-typed surface only (nodpi, nowebrender_api, nosurfman); shell-sideServoRenderingContextProduceradapter wrapsRc<dyn RenderingContextCore>and forwards. Wgpu-first scoping: trait surface trimmed tosize_in_pixels,resize,present. GLmake_current/prepare_for_renderingwere considered but dropped β graphshell is on wgpu (Servo lives atservo-wgpu; renderer iswebrender-wgpu), and the GL-compat fallback path is gated behind the deprecatedgl_compatfeature inside the shell'sOffscreenRenderingContextconsumers. That path is path-specific (handled incompositor_adapter::paint_offscreen_content_pass), not producer-level.ViewerSurfaceBackingdeliberately UNCHANGED β its current Servo coupling is fine becausecompositor_adapter.rsis gated onservo-engineanyway, and Servo webview construction needs the original concrete trait. The reshape (changingNativeRenderingContextto holdRc<dyn RenderingContextProducer>) is a follow-on slice triggered when iced-host actually plugs in its own producer; today's slice establishes the contract iced will target. Adapter lives atshell/desktop/render_backend/servo_rendering_context_producer.rs. Receipts: graphshell-runtime tests 37 β 40 (3 new trait tests: resize observation, present count, object-safety); engine-feature matrix all 3/3 PASS. -
2026-04-27 (S3b viewer_surfaces Step 1: handle/frame-path types): followed the audit's two-step plan for
viewer_surfaces. Step 1 extracts the host-neutral lifecycle types:ContentSurfaceHandle<T>(parameterized over the host's texture-token type, with the pureis_wgpu()check) andViewerSurfaceFramePathnow live ingraphshell-runtime::content_surface. Shell-sidecompositor_adapter.rskeeps apub(crate) type ContentSurfaceHandle = graphshell_runtime::ContentSurfaceHandle<BackendTextureToken>alias plus a freecontent_surface_handle_for_node(NodeKey)function (the staticcompositor_native_texture_registry()lookup is shell-owned).ViewerSurfaceFramePathis now a re-export. Thecontent_generation: u64counter onViewerSurfaceis already host-neutral and stays as a field β no struct bundling yet (deferred to Step 2 alongside the portableRenderingContextProducertrait).ViewerSurfaceBacking(ServoRenderingContextCore+OffscreenRenderingContext) stays shell-side both steps per the audit. Receipts: graphshell-runtime tests 35 β 37 (two new content_surface tests foris_wgpuand frame-path distinctness); engine-feature matrix all 3/3 PASS. -
2026-04-27 (S3b frame_inbox extraction): continued the canonical runtime-crate slice path with the next portable-but-shell-owned input flagged by the audit.
FrameInboxState(the typedmpsc::Receiver-bag plusdrain_flag/drain_allhelpers and the fourtake_*per-frame consumers) moved intographshell-runtime::frame_inbox, with the two drain-coalescing tests migrated alongside it. Shell-sideshell/desktop/ui/gui/frame_inbox.rsis now a thin wiring shim: apub(crate) type GuiFrameInbox = FrameInboxStatealias plusspawn_gui_frame_inbox(&mut ControlPanel, Arc<dyn SignalRouter>) -> GuiFrameInboxfree function that owns the ControlPanel-driven subscription wiring (signal types are already graphshell-core, so the spawn body stays portable except for the&mut ControlPanelparameter). The control-panel spawn test stays shell-side. Two call sites updated (gui.rs:419,gui_state.rs:769) fromGuiFrameInbox::spawn(...)tospawn_gui_frame_inbox(...). Receipts: graphshell-runtime tests 33 β 35 (two drain tests added); shell-sideframe_inboxtest still passes; engine-feature matrix all 3/3 PASS. -
2026-04-26 (no-Servo warning cleanup + matrix runner): cleaned all 24 graphshell-lib unused-import warnings under
--no-default-features --features iced-host,wry. Pattern: imports consumed only by Servo-gated modules (render/*,host/*, gated UI modules) get#[allow(unused_imports)]on the re-export line (matches the pre-existing convention in graph_app.rs lines 139/157/195); imports consumed only bycfg(feature = "diagnostics")orcfg(test)callers get a parallel#[cfg(...)]use line. Files touched:graph_app.rs(6 re-exports),app/workbench_layout_policy.rs,panic_hook.rs,shell/desktop/runtime/cli.rs,shell/desktop/runtime/tracing.rs,shell/desktop/ui/{command_palette_state, command_surface_telemetry, host_ports, omnibar_state, portable_time}.rs,shell/desktop/workbench/{tile_kind, ux_tree}.rs,mods/mod.rs,registries/atomic/lens/mod.rs. Receipts: graphshell-lib warnings now 0 / 0 / 2 across the no-default-wry / no-default-iced-host,wry / default matrix entries (default's two are unchanged egui deprecations). Addedscripts/dev/engine-feature-matrix.{sh,ps1}that runs all three combos and emits a PASS/FAIL summary; verified end-to-end (3/3 PASS). -
2026-04-25 (iced-host,wry compile baseline): ran
cargo check --no-default-features --features iced-host,wryexpecting either gated-launch-path residue or runtime-ownership errors per the Β§3 framing. Result: clean. 24 warnings (all unused imports / one unused macro), 0 errors, 1m 54s cold. The iced launch path is still gated oncfg(all(iced-host, servo-engine))(per S3b.1), so this receipt covers the iced-host bridge surface (iced_host_ports,CachedTexture, runtime ports) β the library compiles without Servo and with iced-host's bridge code on. Defaultcargo check -p graphshell --libre-verified clean (2 warnings, pre-existing egui deprecations). Implication for sequencing: the next compile-wall is no longer the rate-limiter. Whatever S3/S4 means now is about making the no-Servo path launchable, which routes through the canonical GraphshellRuntime extraction (slice-by-slice), not more gating. -
2026-04-27 (gl_compat retirement β Phase F): completed the gl_compat retirement in a single commit (
b7b70f4b). The runtime-validation blocker noted in the gating cascade entry did not need a separate smoke step β the wgpu-only compositor was stable at prototype scale; the deprecation window was collapsed. Deleted:gl_compatfeature fromCargo.toml;glowdep (wasoptional);shell/desktop/render_backend/gl_backend.rs(GL state guardrail helpers, 139 lines);BackendContentBridgeMode/Selection/Capabilities, 9 selection/capability helpers,GRAPHSHELL_BACKEND_BRIDGE_MODEenv-var, and 6 bridge tests fromrender_backend/mod.rs(~280 lines);custom_pass_from_backend_viewport/register_custom_paint_callbackGL-shaped stubs fromwgpu_backend.rs; fromcompositor_adapter.rs: content-callback registry (COMPOSITOR_CONTENT_CALLBACKSstatic + register/unregister/compose family, ~250 lines), 13 GL state guardrail functions (capture/restore, chaos perturbation, scissor isolation, ~200 lines), 17 GL-only tests,BridgeProbeContext+COMPOSITOR_REPLAY_SEQUENCE.ViewerSurfaceBackingcollapsed from two-variant enum to single-field tuple struct (CompatGlOffscreenvariant deleted).EguiHostPorts: HostSurfacePorttwo-fork collapsed to the unconditionalBackendContext = ()shape.GlStateSnapshotretained as a frozen-default struct (diagnostics export carriesbefore/aftersnapshot fields). Matrix slot 4 (the "no-gl_compat" combo) removed βgl_compatno longer exists; 3/3 is the canonical matrix. Phase F closed. Receipts: engine-feature matrix 3/3 PASS; compositor_adapter and render_backend tests pass.
Canonical roadmap: 2026-04-24_graphshell_runtime_crate_plan.md is the authoritative plan for this work. It predates the servo-into-verso lane by a day and has already executed Slice 1 (toast/clipboard ports + finalize helpers + frame-vocabulary re-exports) plus the AppStateβFrameViewModel projection-helper follow-ons for focus/settings/accessibility/graph-search/dialogs/ toolbar/omnibar/command-palette/transient-outputs. ~18 unit tests live inside
graphshell-runtimeagainst tiny portable inputs.
Important framing correction. The earlier draft of this
section recommended "extract GraphshellRuntime wholesale" or
"split into GraphshellRuntimeCore + extension." Both options
violate the canonical plan's explicit guardrail:
"Do not move
GraphshellRuntimewholesale next... Do not hide shell ownership behind a giant trait just to move code. If a projection helper needs most ofGraphshellRuntime, it is not ready for the runtime crate."
The correct approach is the slice-based incremental extraction already underway: each slice moves one portable-but-shell-owned input from the canonical plan's inventory (graph_runtime frame caches, toolbar state/drafts, command-palette state, app settings, graph-search match collection, dialog objects, thumbnail capture set) into a runtime-crate-owned type with its own focused unit tests. The shell side keeps owning the GraphBrowserApp / audit/diagnostics adapters; only the portable inputs migrate.
The "iced launch path compiles without servo-engine" goal will
come as a natural consequence once enough of the inventory has
moved that gui_state::GraphshellRuntime's remaining fields are
either portable or feature-gated. Do not try to short-circuit
this with a wholesale extraction.
Cross-lane coordination: S3a's host-port trait extraction
(this plan, 2026-04-25 entry) is additive to the canonical
roadmap β Slice 1 covered toast/clipboard ports; S3a extended
the same graphshell-runtime::ports module with the broader
host-port surface (input, surface, paint, texture, accessibility).
Both lanes write to the same crate; neither blocks the other.
This lane lands the architectural claim that's been implicit in the recent refactors: graphshell is a chrome + spatial canvas; the content engines are pluggable. Phase A2 proved verso can own a heavy engine (wry); this lane proves it can own all of them.
Estimated effort: 3β5 sessions of focused work. Status as of
2026-04-25 end-of-day: S1, S2a, S2b module-level + body-level
(S2c), S3a host-port extraction, S3b.1 IcedWgpuContext gate, and
the iced-host,wry compile baseline all landed today. The
compile-wall portion of the lane is effectively closed: default,
no-default wry, and no-default iced-host,wry library checks
are all green. What remains:
- S3b proper β canonical GraphshellRuntime slice-by-slice extraction (see 2026-04-24_graphshell_runtime_crate_plan.md). This is the path that converts "compiles without Servo" into "launches without Servo" by ungating the iced launch path one portable input at a time. Do not short-circuit with a wholesale extraction.
-
S4 β startup path gating:
cli.rs::main()no-servo branch beyond the current "exit warning" stub once S3b's runtime is launchable. -
S5 β CI matrix doc + cross-doc updates (
PROJECT_DESCRIPTION,VERSO_AS_PEER, iced-host migration plan Phase A2 sibling).
Sidequests that would smooth the runway (none blocking): warning
cleanup in no-Servo Wry (24 unused imports), promote duplicated tag
helper logic to a shared home if drift appears, add a documented
compile-matrix command list so the green targets don't regress
silently, narrow tests around no-Servo shims (especially diagnostics
and workbench_surface::dispatch_intent), and revisit
local_file_access's home if non-workbench consumers appear.