The Fera UI stack is an unconsumed island: no window layer, no render-tree producer
Источник:
dev/fera-research/docs/oc2/fera-ui-stack-unconsumed-island.md(read-only; сгенерировано scripts/sync-docs.mjs)
The Fera UI stack is an unconsumed island: no window layer, no render-tree producer
Section titled “The Fera UI stack is an unconsumed island: no window layer, no render-tree producer”- kind: problem
- area: ui
- severity: high
- evidence:
~/dev/fera/fera/Cargo.toml:2-19(workspace members — no window/renderer crate)~/dev/fera/fera/core/crates/ui-contract/Cargo.toml:13(onlyfera-ui-engine-vellodepends on it)~/dev/fera/fera/core/crates/ui-elm/Cargo.toml(zero dependents)~/dev/fera/fera/core/crates/ui-engine-vello/src/lower.rs:118-119(“the actual surface is owned by the window layer”)~/dev/fera/fera/core/crates/ui-engine-vello/src/lower.rs:77-81(Text/Image/Spacer silently skipped)~/dev/fera/fera/core/crates/ui-engine-vello/src/lib.rs:8(“ADR-028 (Vello + Parley + winit + AccessKit substrate)”)~/dev/fera/fera/launcher/Cargo.toml(empty[dependencies]),launcher/src/main.rs(20 LOC)~/dev/fera/fera/tools/bench-scene/src/main.rs(15 LOC, prints"... generator skeleton")
Sum (Суть)
Section titled “Sum (Суть)”Measured facts, whole-repo greps over ~/dev/fera/fera (excluding target/):
- Nothing in the workspace depends on any
ui-*crate except one link. The onlyfera-ui-*dependency edge in the entire workspace isui-engine-vello/Cargo.toml:13 → fera-ui-contract.fera-ui-elmhas zero dependents. - No crate ever constructs a
RenderTree.grep -rn "RenderTree" --include=*.rsoutsideui-contract/srcandui-engine-vello/srcreturns zero hits. The tree is a type that only its own module and its test module name. VelloEngineandParleyTextEngineare never instantiated outside their own tests.- There is no window layer.
grep -rni "winit\|wgpu\|Surface\|swapchain\|create_window"over*.rs+*.tomlmatches only comments and doc strings — zero dependencies, zero code. Yetlower.rs:118-119documents “the actual surface is owned by the window layer, so this only validates the dimensions”, andlib.rs:8advertises “ADR-028 (Vello + Parley + winit + AccessKit substrate)”. The layer that ADR-028 names does not exist in this repo. - The launcher is a 20-LOC binary with an empty
[dependencies], while its own package description is “verifies + loads the core module per the signed trust manifest (ADR-013)”. It cannot load a module it does not depend on. tools/bench-sceneis a 15-LOCmain()that prints"fera-bench-scene: fera-scene-v0 generator skeleton"— while its own header comment declares the scene “FROZEN (OQ-006): 20 chats x 5k messages (100k total) … 50 concurrent 512x512@30fps stickers”. No fixture is emitted.
So the shape is: three well-documented crates (1836 LOC total) describing a seam, with no
window at either end. The Engine trait has one implementation, zero callers.
A concrete defect inside the island: paint() cannot fail and drops text silently
Section titled “A concrete defect inside the island: paint() cannot fail and drops text silently”ui-engine-vello/src/lower.rs:
fn lower(&mut self, tree: &RenderTree) { // :70 — returns () self.scene.reset(); for node in tree.nodes() { match &node.kind { NodeKind::PushClip(rect) => self.push_clip(*rect), NodeKind::PopClip => self.scene.pop_layer(), NodeKind::Rect { rect, fill } => self.fill_rect(*rect, *fill), NodeKind::Text(_) | NodeKind::Image { .. } | NodeKind::Spacer(_) => { // Text and images are lowered by the text/media // passes; a text-less engine pass skips them // rather than drawing placeholders. } // :77-81 } }}and
fn paint(&mut self, tree: &RenderTree, _size: Size) -> Result<(), Self::Error> { // :140 self.lower(tree); Ok(()) // unconditional}Consequences:
paint’s doc comment claims “[VelloError::Render]if lowering fails” (:138-139) — butlowerreturns()and cannot fail.VelloError::Renderis only ever constructed inresize(:127). The documented error is unreachable.- A tree containing only text, images and spacers lowers to a completely empty scene and
still returns
Ok(()). Under a Telegram workload (message list = text + avatars + spacers) that is the typical frame. Success is indistinguishable from “I drew nothing”, which is precisely the class of bug a golden-frame test exists to catch — and there are no golden-frame tests anywhere infera(the 3 tests inlower.rs:159-189only assert “did not panic”).
Options (with tradeoffs)
Section titled “Options (with tradeoffs)”- A. Accept the island as a deliberate spike and document it. Cheap, zero code. Cost: the doc comments that name ADR-028’s “winit + AccessKit substrate” and ADR-013’s “loads the core module” stay misleading, and any roadmap that assumes “the UI layer exists, we swap the engine” is wrong — both ends are missing.
- B. Write the missing window layer first (winit + wgpu surface), then benchmark. Fixes the 3-LOC half of the island. Cost: pulls in winit/wgpu immediately, blocks on a vello version decision (see the vello-version-split finding), and produces a window that can only draw rectangles because text/images are skipped.
- C. Fix the two cheap lies now, defer the window. Make
lower/paintsurface unhandled node kinds (error or counter), and correct the two doc comments. Cost: near-zero, kills the “success means nothing was drawn” trap.
Proposed solution
Section titled “Proposed solution”C now, then A-as-stated. Two independent, cheap steps:
paintmust not claim to have drawn a frame it could not lower. EitherlowerreturnsResultandpaintpropagates a realVelloError::Render, or — cheaper and more honest —VelloEnginetracks anunsupported_kinds: u32counter thatpaintreports. Given the workspace lints (clippy::todo/unimplemented= deny,unused_results= deny), the counter is the lower-friction option. This also gives the future golden-frame suite a natural invariant: frames are never silently empty.- Fix the two doc claims.
lower.rs:118-119andlib.rs:8name a window layer that does not exist in the repo. Either the window crate is tracked as a missing member of the workspace, or the comments stop referring to it as present-tense fact.
Then, separately, the integration question this raises is the real finding: chroix cannot be
dropped into fera-ui-engine-vello as a new Engine impl, because chroix’s seam is inverted
relative to fera’s (separate finding: seam-inversion). But given finding 1 above — nothing
consumes Engine at all — the honest framing is that there is no integration cost to pay yet,
and no integration pressure to justify either. The two projects are at the same maturity level
and can be sequenced deliberately rather than urgently.