2026 04 24_blitz_shaped_chrome_scoping - mark-ik/graphshell GitHub Wiki
Status: Scoping pass β no implementation committed Lane: Long-horizon alternative to the iced chrome track; informs when/if to abandon iced for an HTML/CSS-rendered chrome stack.
2026-04-28 update β premise shift: Iced reached wgpu 29 parity by vendoring (see GPUI plan progress entry and iced migration plan progress entry). The wgpu version split that this doc treats as a permanent architectural cost (Β§1, Β§3.5, Β§5 comparison table) no longer exists. "Removing iced also removes the version split" is no longer a unique benefit of Blitz-shaped β that win is already captured in iced.
This doc's case for Blitz-shaped therefore reduces to the non-GPU arguments: HTML/CSS as the source of truth for chrome, Firefox-grade selectors via Stylo, and architectural alignment with Servo's own renderer model. Those are still substantive but they're a chrome-semantics case, not a GPU-stack-alignment case. Any future activation of this plan should re-anchor Β§1 on those arguments rather than the wgpu split. Decision criteria in Β§8 remain valid β the "wgpu version split causes a real, blocking problem" trigger is now defensively unlikely to fire.
Context: The 2026-04-24 renderer-boot research turned up two architectural surprises that shift the cost/benefit of replacing iced:
- iced 0.14 is closed around
wgpu::Device(no public accessor, no injection hook). - iced uses wgpu 27 while Servo's fork is wgpu-29-era; the two
cannot share
wgpu::Texture/wgpu::Devicetypes directly.
These together mean iced will always be architecturally awkward for a Servo-hosting application β not just temporarily until iced catches up. That makes a chrome-renderer alternative more attractive than it appeared at the 2026-04-16 vision doc.
This doc fleshes out scope #2 from the 2026-04-24 question
"how difficult would Blitz-shaped chrome be?" β specifically the
"we own the assembly" variant that wires Stylo + Taffy + Parley +
our WebRender fork directly, without depending on the upstream
Blitz crate or DioxusLabs's anyrender abstraction.
Related:
- Rendering Architecture Vision: Blitz + anyrender + WebRender β original framing; this doc is its concrete follow-on.
- WASM portable renderer feasibility β proves Stylo + Taffy + WebRender-wgpu compose; same components, different target (chrome vs portable web).
-
Middlenet lane architecture spec
β already names blitz-dom + blitz-html + Stylo + Taffy + Parley
- WebRender for the HTML middlenet lane. Scope #2 overlaps this stack with chrome instead of content.
- Iced host migration execution plan
- Iced content-surface scoping
- WebRender wgpu renderer research
| Option | Description | Time | Decision risk |
|---|---|---|---|
| 1 β Adopt Blitz proper | Use blitz-dom + blitz-html + anyrender-webrender (which doesn't exist) for chrome |
3β6 months | Blitz API unstable, we don't control timing |
| 2 β Blitz-shaped (we own the assembly) | Wire Stylo + Taffy + Parley + WebRender ourselves; use blitz-dom if convenient, but no anyrender indirection |
2β3 months | Integration risk we control |
| 3 β Blitz only for HTML content | Add Blitz inside the middlenet HTML lane, keep iced chrome | ~1 month | Smallest commitment; doesn't unify GPU |
This doc covers option 2. The product framing: build a custom HTML/CSS-rendered chrome that paints into the same WebRender our graph canvas and (eventually) Servo content paint into. Replace iced's role as the chrome host. Keep iced files in the tree only as long as needed for graceful overlap.
Platform-tier scope (per the 2026-04-24 iced content-surface scoping Β§0): Blitz-shaped chrome is native-desktop-only (Windows, macOS, Linux). Mobile / web targets continue to use wry (system WebView) for fullnet content and middlenet for lighter content. The chrome rendering decision (iced vs Blitz-shaped) is therefore a desktop architecture decision; no other platform is affected.
Key reframing from 2026-04-24 research: the wgpu version split isn't a permanent architectural cost β it's specifically caused by iced 0.14 being on wgpu 27 while everything else in the graphshell ecosystem is already on wgpu 29:
| Crate | wgpu version |
|---|---|
webrender-wgpu (our fork) |
29 |
servo-wgpu (our Servo fork) |
29 |
egui-wgpu 0.34.1 (graphshell main) |
29 |
iced 0.14 |
27 β outlier |
Removing iced also removes the version split. The Blitz-shaped plan delivers wgpu unification by retiring the one component that's out of alignment, not by uplifting everything else to match it.
The sub-question that determines difficulty: how much of
upstream Blitz do we adopt vs. how much do we hand-assemble?
The answer here is "use Blitz's DOM tree where it's load-bearing
(blitz-dom); skip Blitz's paint and event layers; assemble our
own from Stylo + Taffy + Parley + WebRender."
| Component | Source | License | Notes |
|---|---|---|---|
| html5ever |
servo/html5ever (crates.io) |
MPL/Apache | HTML parser, already a transitive dep via Servo. |
| Stylo |
servo-style from our Servo fork |
MPL | CSS engine. Already in workspace via servo dep. Disable rayon for simplicity (single-threaded chrome layout is fine; document layout volume is small). |
| Taffy |
taffy (crates.io) |
MIT | Layout (flexbox + grid + block + table + abs/fixed). Pure Rust, no patches needed. |
| Parley |
linebender/parley (crates.io) |
Apache/MIT | Text shaping + line breaking. Same dep we already use indirectly through Vello-related work in graph-canvas. |
| WebRender | Our webrender-wgpu fork |
MPL | GPU display-list compositor. Already pinned in Cargo.toml patches. |
blitz-dom |
DioxusLabs/blitz (probably git, not crates.io) |
Apache/MIT | DOM tree representation Blitz uses internally. Optional β we could roll our own if it's not stable enough, but it's a natural anchor for "we use what Blitz already assembled here." |
| Component | Why skip |
|---|---|
blitz-html |
Convenience facade for blitz-dom + html5ever + Stylo. We'd inline its assembly to keep dependencies under control. |
anyrender (DioxusLabs trait crate) |
Two render targets only matters if we want backend pluggability. We commit to WebRender for chrome paint; skipping anyrender removes one trait hop. Vello-via-WebRender is already an option through WebRender's primitives. |
| Blitz's event layer | Blitz's event handling is geared toward rendering web pages, not driving native chrome. We'd implement event routing tailored to graphshell's Message-style state model directly. |
| Iced (post-cutover) | Once Blitz-shaped chrome reaches feature parity, iced retires. Until then, iced + Blitz-shaped overlap (chosen at startup like --iced vs --blitz). |
-
graphshell-coreβ portable shell state (FrameViewModel, FrameHostInput, HostIntent). Survives unchanged. -
graphshell-runtimeβ runtime kernel. Survives unchanged. -
graph-canvasβ graph rendering. Survives unchanged; paints into the same WebRender that the chrome paints into. -
graph-treeβ workbench/navigator. Survives unchanged. -
HostPortstraits + sanctioned-writes contracts. Survive; the new Blitz-shaped host implementsHostPortsthe same way iced does.
The portable crate boundary we built for the iced migration is the reason this is feasible. None of the cross-cutting work (intent routing, view-model projection, host-neutral runtime) is wasted. Only the host adapter changes.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β graphshell-app (new shell host crate) β
β - main loop (winit-driven; replaces iced::application) β
β - state container (FrameViewModel + Blitz DOM root) β
β - event router (winit::WindowEvent β HostEvent / DOM event) β
β - HostPorts impl (BlitzHostPorts) β
ββββββββββββββ¬ββββββββββββββββββββββββββββββββββ¬ββββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββ
β Chrome via Blitz-shaped β β Graph canvas via graph-canvas β
β - HTML + CSS source β β - ProjectedScene<NodeKey> β
β - blitz-dom tree β β - canvas_bridge derive β
β - Stylo computed styles β β β
β - Taffy layout β β β
β - Parley text shaping β β β
β - WebRender display list β β - WebRender display list β
ββββββββββββββββ¬ββββββββββββββββ βββββββββββββββ¬ββββββββββββββββββ
β β
ββββββββββββββ¬ββββββββββββββββββββ
βΌ
ββββββββββββββββββββββββββββββββ
β WebRender (one instance) β
β - one wgpu::Device β
β - one render thread β
ββββββββββββββ¬ββββββββββββββββββ
βΌ
one wgpu::Surface
One WebRender, one wgpu device, two display-list producers (chrome + graph canvas). Future: Servo content composes in too β same pipeline, no version split.
Today's iced view() is a Rust function that returns an Element. Tomorrow's Blitz-shaped equivalent would be:
-
A static HTML document loaded at startup that defines the chrome shape (toolbar, sidebar, status bar, toast container, modal slots). Looks like:
<body class="graphshell-chrome"> <header class="toolbar"> <input id="location" type="text" placeholder="Enter URLβ¦"> <span class="nav-hint" data-back="true" data-fwd="false"></span> </header> <main class="canvas-host"><!-- graph canvas mounts here --></main> <aside class="toast-stack" id="toasts"></aside> </body>
- A stylesheet that takes the lion's share of design decisions β themed via CSS custom properties.
-
Runtime DOM updates driven by
FrameViewModel: a small "view-model β DOM diff" routine each frame updates text content, attribute states, and class lists. Conceptually similar to a tiny React reconciler, but Rust-side and not user-extensible.
The "tiny reconciler" is the load-bearing piece. It's what makes the chrome reactive without a full virtual-DOM library.
Event routing is the area where this scope rolls its own work hardest. Current design:
- winit emits a window event (key, mouse, resizeβ¦).
- Hit-test against Taffy-laid DOM: which DOM element does this pointer event land on? (Taffy returns rectangles; we walk the tree to find the deepest match.)
-
Two paths:
- If the hit element is the
canvas-host, dispatch intograph-canvas's existing event path (same code we landed for iced'scanvas::Program::update). - Otherwise, the hit element gets a synthetic event sent to a registered Rust handler. Handlers map (element_id, event_kind) β
Message(orHostIntent). Looks like:event_router.on("#location", "submit", |value| { Message::LocationSubmitted(value.into_string()) });
- If the hit element is the
-
Messageβupdateruns the same way iced's update does. -
Update populates
FrameHostInput.host_intentsif needed; runtime tick runs. - View-model β DOM-diff updates the chrome shape next frame.
Crucially, this preserves the HostIntent contract we just landed. Blitz-shaped is a different chrome shape, not a different mutation path.
Each frame:
- Layout pass: Taffy walks the DOM, asks Stylo for computed styles for each node, computes a layout tree.
- Display-list build: walk the laid-out tree; for each node emit WebRender display-list items (rect, text run via Parley, image, etc.).
- Submit to WebRender: the same WebRender instance accepts the chrome's display list and the graph canvas's display list as separate pipelines, composed by WebRender at GPU time.
- Present: one swap, one frame, one wgpu device.
This is the same architecture Servo uses internally β we're just doing it for our chrome instead of for HTML content (it's the same problem, smaller scope).
The portable contract layer:
-
graphshell-coretypes (FrameViewModel,FrameHostInput,HostIntent,HostEvent,OverlayStrokePass, β¦) β unchanged. -
graphshell-runtimekernel β unchanged. -
graph-canvasandgraph-treecrates β unchanged (graph canvas now paints into WebRender via a new adapter instead of viaiced::widget::canvas, but the portable derive pipeline is identical). -
HostPortstrait suite β unchanged. New impls land for the Blitz-shaped host. - Sanctioned-writes contract tests β unchanged. Adds
BlitzHostfiles to the host-adapter allowlist.
What gets retired (after parity):
-
shell/desktop/ui/iced_app.rs,iced_host.rs,iced_host_ports.rs,iced_canvas_painter.rs,iced_graph_canvas.rs,iced_events.rs(5 files; ~1500 lines). -
icedandiced_*crate deps. - The wgpu version split β gone, since both chrome and Servo eventually share WebRender.
What gets retired (immediately, even before Blitz-shaped lands):
- Nothing forced. Iced and Blitz-shaped can coexist behind feature flags during overlap.
Eight slices, ordered to keep the project shippable at every checkpoint. Estimates assume one developer with AI assistance, comparable cadence to the iced work landed through 2026-04-24.
-
B0.1
Audit Stylo's standalone usability outside Servo's full pipeline.Researched 2026-04-24 (parallel-agent survey). Findings:- Stylo lives at https://github.com/servo/stylo, pulled into our
servo-wgpufork via git dep at commita556f4cbd15fc289039261661b049a5dc845cd80. - Exports 8 crates:
stylo,stylo_atoms,stylo_dom,stylo_traits,stylo_static_prefs,stylo_malloc_size_of,selectors(v0.37),servo_arc(v0.4.3). - Stylo does NOT pull in Servo's layout, constellation, or SpiderMonkey. Clean library boundary; designed for embedders.
- Works against trait-based DOM abstractions (
TNode,TElement) β not tied to html5ever; Blitz implements them againstblitz-dom. We can do the same. -
Correction to the 2026-04-14 feasibility doc: rayon is
required, not optional. There is no feature flag to disable
it. For native desktop chrome (our use case), this is fine β
rayon helps. For WASM (a future concern, not v1), we'd need
either a fork-and-patch or
wasm-bindgen-rayon + SharedArrayBuffer. Neither blocks the chrome project. - CSS feature support is Firefox-grade: modern selectors, custom
properties, flexbox/grid, container queries,
calc(), transitions. No JS-driven anything (we wouldn't want it anyway). - Servo bumps Stylo roughly monthly; the public trait API is stable across bumps. Upgrade story is mature.
-
Implementation effort: 1β2 weeks for a
TElementadapter over our chosen DOM type, 1β2 weeks to wire computed styles into Taffy.
- Stylo lives at https://github.com/servo/stylo, pulled into our
-
B0.2
AuditOptional adoption β Blitz pins the same Stylo version (blitz-dom's API stability and licensing.v0.16.0) andselectors(v0.37.0) we'd consume from Servo, so dependency-tree alignment is fine. Adopt-vs-roll-our-own is a Phase B1 decision, not a B0 blocker. -
B0.3
Confirm WebRender display-list API is reachable as a library.Researched 2026-04-24. Findings:- Working example exists in the fork:
webrender-wgpu/examples/wgpu_shared_device.rs.
Creates a wgpu device β hands to WebRender via
RendererBackend::WgpuSharedβ builds display list β renders to texture β reads back pixels. This is the proof-of-concept for standalone embedding. - Public API (in
webrender/src/lib.rs):create_webrender_instance_with_backend()constructor,RenderApiSenderchannel,DisplayListBuilderfor scene construction,Transaction::set_display_list/RenderApi::send_transactionfor submission,Notifierfor frame-ready callbacks. - WebRender spawns 3 internal threads (render, render-backend, scene-builder via Rayon); embedder doesn't manage them.
- Display list capabilities are sufficient for chrome: rectangles, gradients (linear/radial/conic), images, borders, box shadows, text runs (Parley-style pre-shaped glyphs), clips, 3D transforms, scroll frames, CSS filters.
-
wgpu version: 29.0. Matches
servo-wgpu(Servo's fork), matchesegui-wgpu0.34.1 (graphshell's main path). Iced 0.14 is the odd one out at wgpu 27. Removing iced also removes the wgpu version split β the graphshell-side ecosystem is otherwise already aligned on wgpu 29.
- Working example exists in the fork:
webrender-wgpu/examples/wgpu_shared_device.rs.
Creates a wgpu device β hands to WebRender via
-
B0.4
Build a 1-day spike to confirm the WebRender entry point works.Already done by upstream βwgpu_shared_device.rsis the spike. We can run it and verify it works in our environment as a 30-minute check, not a full spike.
Receipt: B0 essentially landed via 2026-04-24 research. The
remaining concrete deliverable is running wgpu_shared_device.rs
in our local environment to confirm the example builds + executes
on our machines and validate wgpu adapter selection. ~1 day of
work, not 1 week.
The 2026-04-24 WebRender survey turned up a real, in-progress shader pipeline migration that affects timing:
- The 2026-04-18 SPIR-V shader pipeline plan is mid-Phase 2 of 5. SPIR-V is becoming the canonical shader source; naga's GLSL frontend is being retired.
- Phases 3β5 (queued): switch wgpu consumer to artifact-backed shader identity, switch GL fallback, retire GLSL preprocessing.
-
Risk to chrome project: during Phase 3 (rough timing
~weeks 4β8 of chrome work), runtime shader identity changes from
(name, config)tuple β SPIR-V digest. Our display-list submission code is not affected (that contract is stable); only our WebRender device/pipeline initialization touches the shader-identity boundary. Concretely: 2β3 weeks of "shader adapter" work somewhere in the project's middle. - Mitigation: do not author custom WebRender shaders during the chrome project β author SPIR-V if any are needed. Track the shader pipeline plan weekly. If Phase 3 lands cleanly mid-project, no rebase work; if it lands roughly, budget ~2 weeks to absorb.
-
B1.1 New crate
crates/graphshell-blitz-host(or ablitz-hostfeature flag in graphshell, mirroringiced-host). Empty scaffolding:BlitzApp,BlitzHost,BlitzHostPorts. All ports stubbed identically to earlyIcedHostPorts. - B1.2 WebRender bootstrap β winit window, wgpu device, WebRender renderer, swap-chain. The shared singleton.
- B1.3 First chrome render: a hardcoded HTML string ("Graphshell β Blitz-shaped chrome") through Taffy β display list β WebRender. No interactivity, no Stylo yet.
-
B1.4 CLI flag
--blitz(mirroring--iced). Bin chooses between three hosts: egui (default, retiring), iced, blitz.
Receipt: graphshell --blitz opens a window showing static text rendered via the Blitz-shaped pipeline.
- B2.1 Stylo integration. Load a stylesheet, compute styles for the DOM tree, feed them into Taffy.
-
B2.2 Tiny reconciler: minimal "view-model β DOM" diff routine. Updates text content, class lists, and a small set of attributes. No vDOM, no patches; direct mutation against
blitz-dom(or our own DOM if we rolled it). -
B2.3 First reactive surface: render the toolbar from
FrameViewModel.toolbar.location. Editable text input requires interaction wiring (next phase) β for now it's read-only, mirroring iced's M5.4b state. -
B2.4 Toast stack (HTML/CSS instead of iced's
column![]). Renders fromFrameViewModeltoasts.
Receipt: Static CSS-styled chrome with toolbar text and toasts that update from runtime ticks. Visually equivalent to iced's chrome before editable toolbar landed.
- B3.1 Hit-testing. Walk the laid-out Taffy tree to map screen coords to DOM elements.
- B3.2 Event router. Map (element_selector, event_kind) β application message. Wire keyboard, pointer, scroll, focus.
-
B3.3 Editable text input β chrome equivalent of iced's
text_input. Driven by Parley for cursor + selection + IME. -
B3.4 Toolbar submit: type URL β
LocationSubmittedβHostIntent::CreateNodeAtUrl(existing path). End-to-end navigation works in Blitz-shaped chrome. - B3.5 Hotkeys: Ctrl+L focuses the location bar (mirrors iced's hotkey).
Receipt: Toolbar editing + URL submit + Ctrl+L works in --blitz mode the same way it does in --iced. End-to-end test mirroring iced's location_submitted_clears_draft_and_creates_node.
-
B4.1 Graph canvas adapter. The
<main class="canvas-host">element gets its layout rect, thengraph-canvas'sderive_sceneruns against that viewport, producing aProjectedScene<NodeKey>. New adapterrender/canvas_webrender_painter.rs(mirror ofcanvas_egui_painter.rsandiced_canvas_painter.rs) translatesSceneDrawItemβ WebRender display-list items. -
B4.2 Camera/pan/zoom owned by host (no iced
canvas::Program::State). TheBlitzAppkeeps aCanvasCameraand round-trips it intoruntime.canvas_camerasthe same way iced does β sameMessage::CameraChangedshape, just a different host loop. -
B4.3 Pointer/wheel events on the canvas-host element route into the existing canvas update path (event router dispatches to
graph_canvas_update_camerainstead of a chrome handler when the hit element is the canvas-host).
Receipt: Graph nodes render and are pan/zoomable inside the Blitz-shaped chrome. cargo run --features blitz-host -- --blitz shows a live spatial canvas.
-
B5.1 All
HostPortsimpls fleshed out forBlitzHostPorts: clipboard (arboard, same as iced), texture cache (now using WebRender's image API instead of an iced-specific handle), toast queue, present requests, accessibility stub. - B5.2 Sanctioned-writes test allowlist updated to include Blitz host files.
-
B5.3 Cross-host parity tests extended: replay-trace parity across three hosts (egui, iced, blitz), all three driving the same
runtime.tick.
Receipt: Blitz-shaped is a peer host to iced; all HostPorts are real; parity tests prove the runtime is genuinely host-neutral.
This is where Blitz-shaped earns its keep over iced β Servo content can now share the same WebRender + wgpu device.
-
B6.1 Servo's
WebViewAPI exposes per-view rendering output. Wire Servo to render into a WebRender pipeline rooted inside our chrome's display list (via<iframe>-style nested pipelines, which WebRender supports natively β Servo uses this internally). -
B6.2 ViewerSurfaceRegistry stays β it already keys on
NodeKey, not on a paint backend. TheNativeRenderingContextbacking now points at a WebRender pipeline ID rather than a wgpu texture handle. - B6.3 First end-to-end Servo content render in Blitz-shaped chrome. No screenshot loop needed; no wgpu version split.
- B6.4 Pointer/keyboard events for content panes route through the existing input path Servo expects.
Receipt: Loading https://example.com/ in a graph node renders Servo content inside the Blitz-shaped chrome through one WebRender, one wgpu device. The architectural unification the 2026-04-16 vision doc promised.
- B7.1 Soak-test Blitz-shaped against the iced acceptance checklist. Identify any iced-only conveniences worth porting.
-
B7.2 Default
graphshell(no flag) launches Blitz-shaped. Iced retained behind--icedfor parity comparison and bisecting; egui retired. -
B7.3 Delete
iced_*files fromshell/desktop/ui/once the parity period elapses (1β2 months of overlap).
Receipt: Default Graphshell experience is Blitz-shaped chrome. Iced files can be retired in a clean follow-on PR.
~15 weeks (3.5 months) end-to-end if everything goes smoothly. Realistic estimate with normal friction: 4β5 months.
Revised confidence (post-2026-04-24 research): ~75β80% probability of hitting parity in 6 months. Risk drivers:
- WebRender shader pipeline migration (Phases 3β5, queued) may force ~2 weeks of mid-project adaptation work.
- The "tiny reconciler" (view-model β DOM diff) is the most invent-it-ourselves piece; could expand if we underestimate.
- Text input + IME is the single largest widget cost β Parley handles shaping but cursor/selection/IME composition is ours.
The Stylo + WebRender risks the original draft worried about have both shrunk: Stylo is mature and production-proven via Blitz; WebRender has a working standalone embedding example and our fork is on a stable wgpu 29 with a clear migration plan we can track.
Compared to other paths:
| Path | Estimate to feature parity with current iced | Architectural endpoint |
|---|---|---|
| Continue iced + screenshot-loop Servo (current plan) | ~1β2 months for chrome polish + C3 prototype | Permanent wgpu version split; awkward forever |
| Iced + middlenet (M1) only | ~3 weeks; ships middlenet capability | Same wgpu split; no Servo content in iced |
| Blitz-shaped (this plan) | ~3.5β5 months | One WebRender, one wgpu device, no version split, Firefox-grade CSS for chrome |
| Full Blitz adoption (Option 1) | 6+ months, blocked on Blitz crates.io maturity | Same as Blitz-shaped, but we depend on upstream Blitz cadence |
Blitz-shaped is 2β3Γ the time of staying-on-iced but half the time of full Blitz adoption because we control the integration cadence.
Status (2026-04-24 research): Risk Largely de-risked.
Stylo is production-proven as a standalone library β Blitz
consumes it that way today, and the API boundary is clean (no
SpiderMonkey, no Servo layout). Native desktop chrome is the
natural fit (rayon helps; required, not optional, but we have
threads to spare).
Residual risk: WASM target requires fork-and-patch or
wasm-bindgen-rayon + SharedArrayBuffer. Not a v1 concern.
Status (2026-04-24 research): Risk Working example
exists. Our fork ships
wgpu_shared_device.rs β
a standalone embedding that creates a wgpu device, hands it to
WebRender via RendererBackend::WgpuShared, builds a display
list, renders to a texture, and reads back pixels. This is the
contract we'd be building against.
Residual risk: the WebRender shader pipeline is mid-Phase 2 of a 5-phase SPIR-V migration. Phase 3 (queued) changes how wgpu consumes shader artifacts; this is the "moving target" cost. Display-list submission API is stable through the migration. Only device/pipeline initialization touches the shader-identity boundary. Budget ~2 weeks mid-project for shader adapter work; do not author custom shaders during the chrome project.
Risk: "View-model β DOM diff" sounds simple but text shaping reuse, layout invalidation, animation hooks, and CSS transitions all become our problem. This is the part where "we own the assembly" extracts the most cost.
Mitigation: keep it dumb β full DOM rebuild + Stylo recompute + Taffy relayout each frame. Optimize only when profiling forces it. Chrome DOM is small (dozens of elements), so brute force is viable.
Risk: Editable text with selection, IME composition, accessibility β this is the single most expensive widget to write. Iced gives us this for free; Blitz-shaped means we own it.
Mitigation: Parley does shaping/breaking; cursor + selection logic is a few hundred lines but it's a known shape. IME requires winit IME hooks. Accessibility is a long-tail problem we'd defer behind a shim until parity.
Risk: We're choosing the path Blitz/anyrender/DioxusLabs is on, but doing it ourselves. If Blitz proper matures faster than expected, we'd want to converge on it; that means redoing bits of B1βB3.
Mitigation: B0.2 audits Blitz's stability. If the API looks like it'll stabilize in 6 months, consider waiting. If not, our hand-assembly is independent of their cadence.
Risk: Both hosts in tree means double maintenance. New Message variants and view-model fields need handling in both places. Slows iced + chrome work.
Mitigation: keep iced frozen at "shippable" state during Blitz-shaped development. The portable runtime contract makes this clean β nothing in iced's host adapter changes once the runtime API does.
-
Do we adopt
blitz-dom? API stability check in B0.2 decides. Adopting it gives us a battle-tested DOM tree representation; rolling our own is ~2 weeks of work but pure control. - What's the chrome stylesheet authoring story? A single static stylesheet at startup? Multiple themes via CSS variable swaps? A loose "user.css" in the config directory? Decide before B2.
- Do we want JS/WASM-based extension hooks for chrome? The original 2026-04-16 vision doc mentions Boa as a possible extension point. For v1 this is a hard no β we keep extensions in graphshell mods, not in chrome JS. Reconsider post-v1.
- Hot-reload story? CSS hot-reload is the killer demo for HTML/CSS chrome. Probably trivial β re-read stylesheet on change, force relayout. Add in Phase 2.
-
Tooling: web devtools for graphshell chrome? Could expose
chrome://devtools for the chrome itself, mirroring browser self-hosted devtools. Parking-lot for v2. - Cross-platform font/text quality. Parley + WebRender give us native font rendering. Need to compare against iced's text quality on Windows/macOS/Linux during Phase 2.
Start if any of these become true:
- iced + screenshot-loop Servo proves unworkable in practice (latency, IME, accessibility issues we can't fix upstream).
- Wgpu version split causes a real, blocking problem (bug we can't fix without sharing devices).
- A second developer joins and the chrome rewrite is parallelizable with iced maintenance.
- Blitz crate matures to crates.io with a usable API β we'd reconsider scope #1 vs #2 at that point.
Don't start yet if:
- Iced + middlenet (M1 from the content-surface scoping doc) gives Graphshell a shipping-quality content path.
- The team is one person (you) and the iced work is finally yielding an actually-running product. Don't blow up momentum chasing architectural cleanliness.
Blitz-shaped chrome is feasible and architecturally compelling. The portable contract layer we built for the iced migration makes it ~3.5β5 months of focused work, which is realistic but not free. The big win is the wgpu unification: one WebRender, one wgpu device, no version split between chrome and Servo content forever.
Right now, it's not the right next slice. The iced + middlenet path produces a shippable spatial-browser product faster, and it's compatible with a future Blitz-shaped move (the runtime kernel doesn't change). This doc documents the option so it's startable when conditions warrant β most likely after iced + middlenet ships and we have a polished UX target the Blitz-shaped chrome can imitate.
Recommended adjacent action now: revive the middlenet HTML lane work (per 2026-04-16_middlenet_lane_architecture_spec.md). That uses the same Stylo + Taffy + WebRender stack at smaller scope (HTML content rendering) and proves the integration end-to-end before any chrome bet. It's actually scope #3 from the original three-option breakdown, and it de-risks scope #2 for free.