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.
0. Name
Section titled “0. Name”- [Decided] The project is called Chroix: coined from dichroic (light/prism theme), written to read like French croix (“cross”).
- Availability:
chroixis free on crates.io (confirmed by the author). A repo inside thetheferaorg 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-ferafrom 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.
1. Goals and constraints
Section titled “1. Goals and constraints”- [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.
2. Layered architecture
Section titled “2. Layered architecture”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 layer3. 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
updatestay pure Elm: replayable, time-travel debuggable, easy to test (ADR-008 — untouched). viewdoes 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
dyndispatch; 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.
6. Layout — [Proposed]
Section titled “6. Layout — [Proposed]”- “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).
8. Animations — [Proposed]
Section titled “8. Animations — [Proposed]”- 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.
11. Dev tooling — [Proposed]
Section titled “11. Dev tooling — [Proposed]”- 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
dyndispatch (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 checkand 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 flatrepr(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.
16. Backends
Section titled “16. Backends”- 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_gpu0.3 +vello_cpu0.3 (the Sparse Strips line, wgpu 30, no compute shaders), NOT the legacy compute-shadervello0.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:
- vello_gpu + vello_cpu + headless — the baseline trio. Proves the contract, gives golden-frame tests, ships with the first alpha.
- tiny-skia — after Fera’s first alpha release. A second CPU renderer, mature and tiny; the “no GPU at all” story.
- GPUI — a foreign-architecture stress test of the seam (per-platform renderer split: Metal/DX/wgpu).
- wgpu-direct — a backend that talks to wgpu directly, without vello. For when we outgrow vello_gpu’s limits.
- 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.
- 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.
17. Public API stability — [Proposed]
Section titled “17. Public API stability — [Proposed]”- 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:
- Compiled templates (static/dynamic split) —
view!emitsstatic TEMPLATEdescriptors 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.) - 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.) - Flat dependency tables — the root
app!macro emitsstatic EDGES: &[(u32,u32)]+ topological order; the frame scheduler is a dumb loop over a prefetch-friendly array. (Svelte$:topo sort, Slint binding graph.) - Msg write-masks — derive over
enum Msgemits 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]”- 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.)
- 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). - 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.
20.5 Memory architecture — [Proposed]
Section titled “20.5 Memory architecture — [Proposed]”- 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.
20.6 GPU / frame pipeline — [Proposed]
Section titled “20.6 GPU / frame pipeline — [Proposed]”- 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.
20.7 What to build first — [Proposed]
Section titled “20.7 What to build first — [Proposed]”Order, grounded in the research:
- Display-list schema + versioning (§20.2, OQ-5) — everything else keys off it.
- Damage/bitset + pipeline stage caching — biggest perceived-latency win (hover = 0.1ms not 5ms), and it’s testable headless from day one.
- The view! macro system (§20.1) — biggest engineering piece; start with bitmask codegen (cheapest technique per unit of win).
- Bump arenas + slotmap identity (§20.5) — foundation for everything above.
- Double-buffer swap + envelope + fingerprint (§20.3) — before the first real swappable build.
- GPU-side lanes/atlas/tiles (§20.6) — after the headless backend proves the contract.
20.8 Dev-loop protection — [Proposed]
Section titled “20.8 Dev-loop protection — [Proposed]”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.
Open questions
Section titled “Open questions”- OQ-1
NameResolved: Chroix. Remaining: its place in The FERA Project repo forest (own repo inthefera, with Fera as the first consumer; no rush to create it) and a domain check. - OQ-2
Reactivity modelResolved 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 Elmupdateunchanged (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_generationis 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).