Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

Chroix — UI Framework: Ideas & Open Questions

Источник: dev/fera/community/UI-FRAMEWORK-IDEAS-V2.md (read-only; сгенерировано scripts/sync-docs.mjs)

Chroix — UI Framework: Ideas & Open Questions

Section titled “Chroix — UI Framework: Ideas & Open Questions”

Status: idea / pre-RFC. Name: Chroix. Technical spec and plan will follow. Origin: grew out of the Elm-style UI crate for Fera (native Rust Telegram client). Reclassified into a standalone UI framework project with replaceable backends.

  • [Decided] The project is called Chroix: coined from dichroic (light/prism theme), written to read like French croix (“cross”).
  • Availability: chroix is free on crates.io (confirmed by the author). A repo inside the thefera org can be created later (names inside the org cannot be taken by others); only crates.io and the domain are contested namespaces.
  • [Proposed] Crate set: chroix (public API), chroix-contract (display-list schema + boundary types), chroix-vello (vello_gpu + vello_cpu), chroix-headless, chroix-femtovg (planned, third backend), chroix-native.
  • [Decided 2026-10-04] Each crate gets its own repo in the-fera from day one: chroix, chroix-contract, chroix-headless, chroix-vello, chroix-femtovg, chroix-native. Not a monorepo. Dependencies via git/path until crates.io.
  • Pronunciation (README line): Chroix /kʁwa/ — like French croix (“cross”). The author reads it as “cross” (Russian: «крест»).
  • [Proposed] Logo direction: two crossing beams (a ray split by a prism / a boundary being crossed), in the shared copper-rust palette; original artwork, no copying of existing logos.

Legend: [Decided] = explicit project decision. [Proposed] = idea on the table, not yet committed.


  • [Decided] Standalone UI framework project, not just a thin layer for Fera. Fera is the first and main consumer.
  • [Decided] Replaceable backends behind a stable public API.
  • [Decided] A real, stable, public API, and a baseline implementation shipped together with it.
  • [Decided] Both runtime performance and authoring ergonomics are first-class goals.
  • [Decided] Compile time may be sacrificed heavily. The project exists for Fera and for optimization.
  • [Decided] All ideas below are designed in from day one (types, contracts, tree model), even if implementation lands in layers.
  • [Proposed] Design all of it up front; implement the baseline in stages, so a first working frame exists early.
  • [Decided 2026-10-03] Chroix is built from scratch. The existing fera ui-* crates (ui-contract, ui-elm, ui-engine-vello) are reference material only — an example of what the first consumer needs — not a code base to evolve. Nothing is ported over by default; every piece of the new design must earn its place on its own.
Public API (declarative, Elm-style) <- stable, small, edition-versioned
Reactive core (state, selectors, effects)
Element tree -> Layout tree -> Display list <- cached pipeline stages
Contract crate (flat repr(C), versioned) <- the only thing crossing DLL boundaries
Backends: Vello (gpu/cpu), tiny-skia, headless, native-component layer

3. Reactivity: Elm outside, compiled signals inside — [Decided 2026-10-03]

Section titled “3. Reactivity: Elm outside, compiled signals inside — [Decided 2026-10-03]”
  • State and update stay pure Elm: replayable, time-travel debuggable, easy to test (ADR-008 — untouched).
  • view does not rerun wholesale. It reads derived values (selectors/lenses); a changed message recomputes one cell, not the screen.
  • The dependency graph is compiled (Svelte/Solid-style, §12): macros analyze the lens/selector usage of each component and emit flat dependency tables. This is the primary mechanism and the performance focus. There is no runtime read-tracking (no Salsa-style capture).
  • Explicit lenses are the authoring vocabulary the macros consume — the thing we write by hand, and the escape hatch where the graph is data-driven rather than static: keyed collections (e.g. the message list) declare keyed cells explicitly, still without automatic read-tracking.
  • Dev mode (§12) interprets the same declared graph over dyn dispatch; release compiles it. Golden-frame + graph-parity tests (OQ-8) guarantee identical behavior.

4. Pipeline of cacheable stages — [Proposed]

Section titled “4. Pipeline of cacheable stages — [Proposed]”
  • Element tree -> layout tree -> display list, like browsers/Flutter.
  • Each stage has its own dirty flags and stable node keys.
  • Untouched subtrees are never recomputed.

5. Display list as the backend contract — [Proposed]

Section titled “5. Display list as the backend contract — [Proposed]”
  • Backends receive a serializable, diffable display list with damage regions, not draw calls.
  • Makes Vello, tiny-skia, and headless truly interchangeable.
  • Side benefits: frame record/replay, screenshot and golden-frame tests, backend conformance tests.
  • Transport: arena or ring buffer, no per-frame allocation.
  • Recommended first step: define this contract before anything else; it fixes the backend API, testing story, and how real “replaceability” is.
  • “Constraints down, sizes up” single predictable O(n) pass (Flutter-style), with an intrinsic-size cache.
  • Flex/grid (e.g. taffy) as a pluggable mode, not the foundation.
  • Decide early who owns layout (see OQ-3); hit-testing and native components depend on it.

7. Frame scheduler with priority lanes — [Proposed]

Section titled “7. Frame scheduler with priority lanes — [Proposed]”
  • Input > animation > data.
  • View work is time-sliced; layout and text shaping can run off the main thread (fits the actor model).
  • Frame budgets become scheduler configuration: UI 24, message updates 45, stickers/animated parts 60-75.
  • Background/hidden content automatically throttled (ties into the power-saving mode).
  • Declarative: springs and curves as data, functions of time.
  • Transform/opacity animations run on the compositor side without going through update.
  • Critical for stickers and energy use.

9. Effects and subscriptions — [Proposed]

Section titled “9. Effects and subscriptions — [Proposed]”
  • Effects/subscriptions have lifetimes tied to the tree: node disappears, subscription closes.
  • Typed message ports to actors.

10. Semantics tree and native slots — [Proposed]

Section titled “10. Semantics tree and native slots — [Proposed]”
  • A parallel semantics tree (accessibility via AccessKit, focus, traversal order) built next to the render tree, not bolted on later.
  • Native slots (IME, SwiftUI/AppKit/GTK/Qt/WinUI inserts) as platform views with an explicit contract for z-order, clipping, and hit-testing.
  • Tree inspector.
  • Deterministic replay.
  • Hot swap of view modules via the epoch-based relink mechanism.

12. Compile-time strategy — [Decided: compile time is expendable]

Section titled “12. Compile-time strategy — [Decided: compile time is expendable]”

Ways to spend that budget (all [Proposed]):

  • Statically typed view tree (Xilem/SwiftUI-like): no dyn, no boxing; diffing becomes generated code that walks only the dynamic parts (Svelte/Solid-style).
  • Compile-time signal dependency graph via macros: runtime subscriptions collapse into flat tables.
  • Layout specialization: generated pass per node with known structure instead of a general interpreter.
  • Animations/easing in const: runtime does lookups.
  • Generated display-list schemas (build.rs) for the supported backend set.
  • Release build: fat LTO, PGO, codegen-units=1, BOLT.

Mitigations for the dev loop:

  • Two codegen modes behind one public API: dev mode on dyn dispatch (fast builds, hot reload) and release mode fully static. Golden-frame tests ensure identical output.
  • Isolate heavy macros in their own crates to keep cargo check and rust-analyzer responsive.

13. Module boundary — [Decided: almost everything is inside one module]

Section titled “13. Module boundary — [Decided: almost everything is inside one module]”
  • Nearly all framework work happens inside a single module; across the DLL boundary the framework mostly sends draw commands. Generic/monomorphization limits at module borders are therefore not a concern.
  • [Proposed] One contract crate describes everything that crosses a DLL: display list, input events, hit-test results, semantics tree, platform view requests. Flat repr(C) versioned types, versioned through the project’s edition scheme.
  • The reverse channel (events, hit-testing, platform-view requests, semantics) is also flat and versioned.
  • [Decided 2026-10-03] The default DLL boundary is fload (the project’s nylon-ring fork, Fera OQ-024): the pinned rustc gives a stable module contract without repr(C) for first-party code. The flat repr(C) contract crate stays as an opt-in boundary for external / cross-toolchain backends — backend authors choose it themselves.

14. Text and shaping across the boundary — [Decided: batching]

Section titled “14. Text and shaping across the boundary — [Decided: batching]”
  • Parley (text shaping) is a separate DLL; the boundary is optimized by batching. The framework knows the whole tree and the trait formats to other backends in advance.
  • [Decided 2026-10-03] Text is three DLLs, not one:
    • Text layout (shaping, line breaking, BiDi) — Parley, width-independent, fully batchable, cached by key.
    • Text rasterization (glyph outlines → pixels) — separate DLL (glifo for vello, swash for femtovg, noop for headless). The display list ships glyph runs; the backend owns rasterization, but through a shared glyph atlas protocol.
    • Font loading / management — separate DLL (fontkit / custom), owns font discovery, fallback resolution, variable font coords.
  • [Proposed] Split text work:
    • Shaping (text -> glyphs and metrics): width-independent, fully batchable, cached by key (content hash, style, font version).
    • Line breaking: width-dependent but cheap on top of shaping; run in-module or as a second batch.
  • Frame flow: (1) walk the tree and collect uncached text requests; (2) one call across the boundary (request buffer in, result arena out); (3) layout runs on ready metrics with no boundary calls.
  • Pre-shape off-screen messages in a background thread so scrolling hits warm caches.
  • Cache invalidation (font changes, display scale) needs a design up front.

15. State ownership with hot swap — [Proposed]

Section titled “15. State ownership with hot swap — [Proposed]”

If a view module is swapped, the Model must survive. Options:

  • Model lives in the host as a flat repr(C) block; the module only reads it.
  • Or the module provides explicit, versioned state serialization/migration.

Otherwise epoch relink has to carry migration logic.

  • Vello (gpu and cpu variants), tiny-skia, headless (for tests), native-component layer (SwiftUI/AppKit, GTK, Qt, WinUI).
  • [Decided 2026-10-03] Build Vello and headless together from the start: headless alone does not give the tests Vello gives (real pipeline, golden frames), so the two-ship rule (above) applies to the actual baseline pair.
  • [Decided 2026-10-03] The Vello backend is vello_gpu 0.3 + vello_cpu 0.3 (the Sparse Strips line, wgpu 30, no compute shaders), NOT the legacy compute-shader vello 0.10/0.11 (now research-only). One Scene API on both, swappable GPU/CPU for free.
  • [Decided 2026-10-03] femtovg is a planned third backend, not a switch: it covers what vello_gpu still lacks (mask layers, complex filters, full blend modes) and proves the backend seam is real. Built after the baseline pair works.
  • [Proposed 2026-10-04] GPUI as a future backend — after femtovg. GPUI’s per-platform renderer split (Metal/DX/wgpu) would stress-test the seam against a foreign architecture. Parked; not scheduled.

16.1 Backend roadmap — [Decided 2026-10-04]

Section titled “16.1 Backend roadmap — [Decided 2026-10-04]”

The order and the scope of backend work, as a product decision:

  1. vello_gpu + vello_cpu + headless — the baseline trio. Proves the contract, gives golden-frame tests, ships with the first alpha.
  2. tiny-skia — after Fera’s first alpha release. A second CPU renderer, mature and tiny; the “no GPU at all” story.
  3. GPUI — a foreign-architecture stress test of the seam (per-platform renderer split: Metal/DX/wgpu).
  4. wgpu-direct — a backend that talks to wgpu directly, without vello. For when we outgrow vello_gpu’s limits.
  5. Custom native backends — a separate project with its own name, not Chroix. Direct Vulkan / DirectX 12 / older D3D / Metal / CUDA (compute-driven 2D raster) / software (weak CPU, generic CPU, SIMD CPU, big-cache CPU, etc.) / GL / GLES 3.x / WebGPU / WebGL. This is the “write our own renderer” endgame, deliberately last. It is 2D only — not a wgpu replacement, a Chroix consumer.
  6. Backend playground — a single separate repo for testing exotic compilation paths: rustc_codegen_gcc (GCC backend), cranelift, and friends. Not a Chroix component — a lab for “does our stack even build on this”. Parked until the baseline trio works.

The seam is designed so each of these is a drop-in: the display-list contract (CHROIX-DISPLAY-LIST.md) does not change, only the backend does.

  • Small public surface; anything uncertain stays behind unstable features.
  • Edition scheme analogous to the plugin one: edition = [N, N-1], with translation layers for older editions if breaking changes are small.

18. Rust version update rule (community repo) — [Decided]

Section titled “18. Rust version update rule (community repo) — [Decided]”
  • We must test compatibility with all older releases.
  • If compatible: update freely.
  • If not: think carefully and ship as a breaking change with an explicit description of what broke, what still works, and a classification: is N-1 still alive, or is it incompatible with everything older.

19. Optimization and reuse — [Decided 2026-10-03: redundancy is the law]

Section titled “19. Optimization and reuse — [Decided 2026-10-03: redundancy is the law]”

Everything the framework does to avoid work, collected in one place on purpose — this section is redundant by design: it is the index, the owning sections stay authoritative.

  • Compiled invalidation (§3, OQ-2 resolved 2026-10-03): the dependency graph is compiled; dirty/clean is decided at compile time and emitted as flat tables. No runtime read-tracking anywhere.
  • Pipeline caching (§4): element → layout → display list, each stage with its own dirty flags + stable node keys; untouched subtrees are never recomputed.
  • Damage regions (§5): backends receive a diffable display list with damage regions; repaint only changed blocks/pixels where the platform allows partial present.
  • [Proposed] Frame caching: keep the last rasterized frame; a fully-clean frame is re-presented, not re-rendered.
  • Arena transport (§5): display lists move in arenas / ring buffers, no per-frame allocation.
  • [Proposed] Node reuse across frames: node structs live in pools and are rewritten in place frame-to-frame, not reallocated (complements §4).
  • [Proposed] Host-leased zero-copy buffers across the DLL boundary (inherits Fera ADR-005): anything crossing at frame frequency is host-leased and zero-copy; a boundary must never become a per-operation tax.
  • [Proposed] Static pointers + mutable DLL-side access: instead of passing structures across the boundary, pass a static pointer to a host-leased arena (a ring-buffer-like region) and give the DLL mutable access to it. The pointer itself is static; the contents change. This kills marshalling entirely for the hot path. The ring is owned by the host, leased to the DLL for the duration of one frame; lifetime rules are written down with the display-list spec (OQ-5).
  • Layout cache (§6): intrinsic-size cache behind the single “constraints down, sizes up” pass.
  • Text caches (§14): shaping cached by (content hash, style, font version); line-breaking on top of shaping; pre-shape off-screen content on a background thread so scrolling hits warm caches.
  • Compiled view tree (§12): statically typed tree, generated diff walks only the dynamic parts; dev interprets, release compiles (parity via OQ-8).
  • Frame budgets as scheduler configuration (§7): 24 / 45 / 60-75 fps lanes, background content throttled.
  • Release pipeline (§12): fat LTO, PGO, codegen-units=1, BOLT.

Rule: optimization is a contract requirement, not an emergent property. A design that regresses a frame budget or adds per-frame allocation fails review. The QA gates for this section are written together with the display-list spec (OQ-5 / OQ-9).

20. Research synthesis — where to spend compile time — [Proposed, from 2026-10-03 research]

Section titled “20. Research synthesis — where to spend compile time — [Proposed, from 2026-10-03 research]”

Raw material: CHROIX-OPTIM-RESEARCH.md (5 research agents, ~70 techniques). This section is the distilled design guidance; the dump stays as reference.

20.1 The macro system is ONE system — [Proposed]

Section titled “20.1 The macro system is ONE system — [Proposed]”

The core compile-time techniques are not a menu — they are one coherent view! macro system and must be designed together:

  1. Compiled templates (static/dynamic split) — view! emits static TEMPLATE descriptors into rodata; dynamic content is “holes” (signal indices). Diffing statics = pointer compare; runtime walks only the holes. (Prior art: Dioxus rsx→Template, Vue 3 block tree, Solid compiled statements.)
  2. Dirty bitmask codegen — the same macro collects signal reads via AST visitor and assigns each signal a bit in u64/u128; update blocks become if dirty & MASK != 0. No runtime subscription graph. (Svelte $$.dirty, Compose $changed:Int. Unclaimed niche in Rust UI.)
  3. Flat dependency tables — the root app! macro emits static EDGES: &[(u32,u32)] + topological order; the frame scheduler is a dumb loop over a prefetch-friendly array. (Svelte $: topo sort, Slint binding graph.)
  4. Msg write-masks — derive over enum Msg emits per-variant write masks; dispatch → raised bits → dirty checks by mask. Direct strengthening of the Elm-outside loop.

Interaction with the compiled-signal-graph decision (§3): this IS the implementation of that decision. The macro system is the graph compiler.

20.2 Display list schema via build.rs is not optional — [Proposed]

Section titled “20.2 Display list schema via build.rs is not optional — [Proposed]”

rkyv/flatbuffers-style generated encode/decode shared by both sides of the fload boundary; the boundary passes buffers, not structs. Same buffer consumed by Vello and headless — one contract, two backends. Version the schema; old backend rejects new list explicitly. This feeds OQ-5 directly.

20.3 DLL boundary — the three load-bearing patterns — [Proposed]

Section titled “20.3 DLL boundary — the three load-bearing patterns — [Proposed]”
  1. Double-buffer DLL + frame barrier beats EBR/hazard pointers for UI: the frame end IS the natural quiescent point (every 16ms). Host flips an atomic pointer at the frame boundary; old DLL unloads after drain. 1% of the complexity of lock-free reclamation. (Fera’s OQ-022/024 machinery stays for long-lived background threads; UI swaps use the frame barrier.)
  2. Versioned struct envelope (Vulkan sType/pNext): every boundary struct starts with {magic, size, abi_ver}; append-only fields. The only pattern proven at scale (Vulkan, Win32, COM).
  3. Layout fingerprint generated by build.rs (hash of names/offsets/sizes) embedded as an exported symbol; host verifies at LoadLibrary, rejects on mismatch. No repr(C) discipline-by-hand — generated checks instead.

20.4 Explicit rejections — [Decided 2026-10-03]

Section titled “20.4 Explicit rejections — [Decided 2026-10-03]”
  • abi_stable / stabby — vtable-based, heavy runtime, conflicts with link-time dispatch; both sides share one toolchain anyway (fload pins rustc).
  • NUMA-aware allocation — desktop UI, one socket; pointless.
  • Full EBR/hazard pointers for UI swaps — frame barrier is cheaper and sufficient (see 20.3).
  • React-style VDOM diffing — slotmap + bitset dirty fits retained UI better.
  • Runtime signal tracking (Salsa) — rejected with OQ-2; the compiled graph replaces it. (Salsa-style interning as a technique may still appear inside host-side services, but never as the UI invalidation mechanism.)
  • COW subtrees in hot path — Rc/Arc traffic breaks prefetch; structural sharing only at data boundaries (e.g. the chat-list model), never in widget trees.
  • Per-frame bump arenas (bumpalo), one per frame-rate lane, living on the host side of fload; DLLs receive &mut [u8] / builders — never own cross-frame pointers. Reset per frame = O(1).
  • Flat node pool + generational indices (slotmap): tree = Vec<Node> + u32 indices; stable NodeId doubles as display-list key and hit-test id. Bail-out on unchanged subtree = one u64 compare.
  • AoS tree + SoA scratch built per frame from the arena for SIMD passes (measure, cull). Threshold: SoA pays off only above ~1k nodes.
  • Interning (u32 symbols) for style keys/ids at theme-compile time; never intern per-frame content.
  • Memory-mapped theme blob (rkyv, zero-deserialize) — matches Fera’s KDL→rkyv pipeline; crosses the boundary as opaque bytes.
  • Multi-rate render lanes are the implementation of the 24/45/60-75 budgets: per-lane deadline, dirty bit, and cached layer; scheduler composites lanes, skipping clean ones. Idle = 0 frames, 0 GPU.
  • Damage tracking flows from the compiled graph for free — signals already know what changed; no runtime tracking needed to compute dirty rects. Headless backend verifies damage logic pixel-exact in CI.
  • Texture atlas for all UI raster (stickers/emoji/avatars); sticker loop frames VRAM-resident with quota eviction (top-N visible). One texture bind per UI.
  • Frame caching / layer cache: static lanes render to their own textures; a frame can be pure composite. Cursor blink = 20×20px rect.
  • Tile-based message list (WebRender-style 256² tiles): scroll = UV shift + render only newly-visible tiles; message edit = 1 tile.
  • Async text shaping pre-pass on a worker pool, shape-ahead 1 viewport; cache keyed by (content hash, width bucket, font version).
  • Display list as glyph-run + image-ref — text ships as positioned glyph ids against an incrementally-updated glyph atlas; 95% of glyphs are cache hits after the first screen.

Order, grounded in the research:

  1. Display-list schema + versioning (§20.2, OQ-5) — everything else keys off it.
  2. Damage/bitset + pipeline stage caching — biggest perceived-latency win (hover = 0.1ms not 5ms), and it’s testable headless from day one.
  3. The view! macro system (§20.1) — biggest engineering piece; start with bitmask codegen (cheapest technique per unit of win).
  4. Bump arenas + slotmap identity (§20.5) — foundation for everything above.
  5. Double-buffer swap + envelope + fingerprint (§20.3) — before the first real swappable build.
  6. GPU-side lanes/atlas/tiles (§20.6) — after the headless backend proves the contract.

Heavy monomorphization × codegen-units=1 × LTO would kill iteration, so: two build profiles behind one public API — dev = dynamic linking (bevy_dylib-style) + interpreted graph; release = CU=1 + LTO + PGO + BOLT + compiled graph. Golden-frame + graph-parity tests (OQ-8) guarantee they agree. Macro infrastructure stays light: venial/unsynn over full syn where possible, one proc-macro crate compiled once, swappable DLLs depend only on its output via a thin runtime crate. cargo-expand + trybuild + insta are mandatory tooling from day one. Note: linkme/inventory do not cross dylib boundaries (each DLL gets its own copy of the distributed slice) — plugin registration must be explicit at load time.


  • OQ-1 Name Resolved: Chroix. Remaining: its place in The FERA Project repo forest (own repo in thefera, with Fera as the first consumer; no rush to create it) and a domain check.
  • OQ-2 Reactivity model Resolved 2026-10-03: B + C together — compiled signal graph (Svelte/Solid-style, from §12) is the primary mechanism and performance focus; explicit lenses are the authoring vocabulary the macros consume, and the escape hatch for data-driven graphs (keyed cells for collections). No Salsa-style runtime read-tracking. Pure Elm update unchanged (ADR-008). Dev mode interprets the declared graph (dyn, fast builds); release compiles it; parity proven by OQ-8 golden-frame + graph-parity tests.
  • OQ-3 Who owns layout and text: the framework or the backend? Resolved 2026-10-03: the framework. Layout and text layout are framework-owned (Chroix core), not backend-owned. Backends receive a fully-laid-out display list with glyph runs and rects; they rasterize, they don’t lay out. This is what makes native text input slots and IME possible (the framework knows where text is).
  • OQ-4 Accessibility (AccessKit) and hot reload: AccessKit yes, hot reload no. AccessKit is in the tree model from day one (parallel semantics tree, §10). Hot reload of view modules is rejected — it conflicts with the compiled signal graph (§3) and the static-typing strategy (§12). Code changes require a rebuild; the dev loop is protected by fast builds (§20.8), not runtime code swap.
  • OQ-5 Display list format: schema, damage-region representation, versioning, serialization cost. Draft spec written 2026-10-03: CHROIX-DISPLAY-LIST.md — append-only flat buffer, versioned envelope (Vulkan sType/pNext), op stream + side tables, glyph runs (verified against Parley 0.11.1), damage as hint not requirement (verified against Vello 0.10 / wgpu 30: no sub-scene patching, no partial present — damage is data, backend decides what it’s worth).
  • OQ-6 State ownership under hot swap (host-owned flat block vs serialized migration).
  • OQ-7 Text cache key design and invalidation (fonts, scale). Resolved 2026-10-03: cache key = hash(text, style_spans, scale, quantize, width, font_set_generation). font_set_generation is a counter the framework owns (Parley has none), bumped whenever fonts are registered/unregistered. Layout is cached shaped-but-unbroken where possible; line breaking is a cheap second pass on width change.
  • OQ-8 Dev vs release codegen modes: resolved 2026-10-03 as “no dev mode”. There is no interpreted dev profile — the compiled graph is the only mode. The dev loop is protected by fast builds (§20.8: dynamic linking, light macro infra), not by a second runtime. Golden-frame + graph-parity tests verify that the compiled graph behaves identically across opt levels.
  • OQ-9 Implementation order for the baseline (suggested: core + display list + Vello + headless first, then the rest). Updated 2026-10-03: core + display list spec + headless backend first (proves the contract), then vello_gpu + vello_cpu (proves the GPU path), then femtovg (proves the seam is real).
  • OQ-10 Scope of the native-component layer per platform and its contract (z-order, clipping, hit-testing).