No display-list producer exists: outside two test files, nothing in the chroix stack builds a buffer
Источник:
dev/fera-research/docs/oc2/no-display-list-producer-exists.md(read-only; сгенерировано scripts/sync-docs.mjs)
No display-list producer exists: outside two test files, nothing in the chroix stack builds a buffer
Section titled “No display-list producer exists: outside two test files, nothing in the chroix stack builds a buffer”- kind: problem
- area: ui
- severity: high
- evidence:
- Repo-wide grep over
chroix-core,chroix-macros,chroix-headless,chroix-vello,chroix-contractforfn layout|LayoutTree|taffy|flex|intrinsic|measure(→ zero matches grep -rn "DisplayList::new|push_op|\.encode()"over all five crates → hits only inchroix-headless/tests/raster.rs:25-29andchroix-vello/tests/cpu_backend.rs:39-44(and further test fns)~/dev/fera/chroix-project/CHROIX-DISPLAY-LIST.md:34-40— the pipeline:Model → view!() → Element tree → layout → Layout tree → build → Display list → backend → pixels~/dev/fera/chroix-project/CHROIX-DISPLAY-LIST.md:42-43— “The display list is the only thing a backend ever sees. It never sees the element tree, the layout tree, the model, or the signal graph.”~/dev/fera/chroix-project/UI-FRAMEWORK-IDEAS-V2.md:268(OQ-3) — “Layout and text layout are framework-owned (Chroix core) … Backends receive a fully-laid-out display list … they rasterize, they don’t lay out.”~/dev/fera/chroix/TODO.md:3-5— stage goal is “thechroixmeta-crate exports a stable public API over contract/core/macros, and headless renders a reference scene from a display list”~/dev/fera/chroix/TODO.md:112-115(2g) —fixture::reference_scene(): “one shared scene … that every backend renders from the same bytes”~/dev/fera/chroix/TODO.md:145-147— vello conformance run is a “next stage (not now)” item~/dev/fera/chroix-contract/src/encode.rs:20—encode()consumes an already-populatedDisplayList
- Repo-wide grep over
Sum (Суть)
Section titled “Sum (Суть)”This is the structural answer to the standing question — what blocks replacing fera’s vello engine with chroix. Everything else found so far (duplicated trait, inert damage, header divergence) is repairable inside existing crates. This one is not, because the producer half of the pipeline does not exist.
The corpus defines a four-stage pipeline (DISPLAY-LIST.md:34-40):
Model → view!() → Element tree → layout → Layout tree → build → Display list → backend → pixelsMeasured state of each stage:
| Stage | Exists? | Evidence |
|---|---|---|
| Model / reactive core | yes | chroix-core 493 LOC: cell arena, epochs, Lens, Template/Hole, App, frame()/frame_tagged() |
view! → element tree |
partly | chroix-macros genuinely emits TEMPLATE/READ_ROOTS/render() (V2 §20.1 flat static tables) |
| layout | NO | zero matches for layout/flex/taffy/intrinsic/measure across all five crates |
| build → display list | NO | the only DisplayList::new / push_op / encode() call sites in the entire stack are chroix-headless/tests/raster.rs:25-29 and chroix-vello/tests/cpu_backend.rs:39-44 |
| backend → pixels | partly | VelloCpu real; Headless FillRect-only; VelloGpu a skeleton returning Err(Error::GpuUnimplemented) |
So the display list is consumed correctly by two backends and produced only by tests. There is
no code path anywhere — in any crate, production or otherwise — that turns a component tree into
a buffer. encode() (encode.rs:20) takes an already-populated DisplayList and serializes
it; something upstream is supposed to be doing the populating, and that something is absent.
This inverts the usual reading of “the contract is done, the backends are done, so we’re most of the way.” By the corpus’s own stage list the framework is missing two of four stages, and the missing two are precisely the ones that make a UI framework a UI framework.
Why this dominates the integration question
Section titled “Why this dominates the integration question”The adoption plan for chroix at V2 §20.7:251-256 puts “display-list schema + versioning” first
because “everything else keys off it.” That reasoning is sound for the contract, but the
schema is the consumed half. For the framework to render anything, the missing
layout → build → display list stages must exist first. Concretely:
- A
RenderTree -> bytesencoder does not exist and cannot be written until the element/ layout stages exist (see the companion finding on the seam inversion). Adopting chroix from fera is therefore blocked not on an adapter but on building the framework’s middle. - OQ-3 assigns layout to the framework (
V2:268) — so this cannot be deferred to a backend, and it cannot be waved off as “the backend’s job”. It is core work, unstarted. - TODO.md’s stage goal is scoped to dodge this.
:3-5asks only that “headless renders a reference scene from a display list” — i.e.fixture::reference_scene()hand-writes ops in a test (which is exactly whattests/raster.rs:25-29does today). That goal is reachable with no layout and no producer, and reaching it would prove the contract while leaving the actual gap untouched.
The consequence: conformance, the entire point of the display list, is not demonstrable
Section titled “The consequence: conformance, the entire point of the display list, is not demonstrable”The display-list approach justifies itself with replaceable backends plus
“backend conformance tests” as a side benefit (DISPLAY-LIST.md:24-26, V2:58). Today:
- no two backends render the same bytes — the only scenes are per-crate test fixtures, so
schema_fingerprintcannot be compared (see the seam finding), and TODO.md 2g exists precisely to fix this; - neither backend implements more than a fraction of the op vocabulary (
Headless:FillRectonly,StrokeRectparsed-and-dropped, layers are bare counters;VelloGpu: nothing); - damage is inert end-to-end, so the one feature that distinguishes a display list from a draw-call list is untestable.
So the contract’s central claim — “one contract, two backends” (DISPLAY-LIST.md:30) — is
true only in the narrow sense that both crates call DisplayList::decode. It is untested as
a claim.
Options (with tradeoffs)
Section titled “Options (with tradeoffs)”- A. Build the missing middle (layout + display-list builder) as the next stage. Unblocks chroix standalone and makes fera adoption an adapter instead of a rewrite. Cost: the largest single piece of work in the project — V2 §12 explicitly allows heavy spend here, and OQ-3 places it in core. Delay, but unblock everything.
- B. Land the conformance fixture first (TODO.md 2g) and be explicit it is not a producer. Cheap, already planned, and it makes the contract trustworthy — but it must not be mistaken for progress on the framework. Cost: near-zero; risk of the illusion.
- C. Keep polishing the schema (Stages 1–4 as written) while the middle stays absent. Cost: the wire format gets rewritten twice (header 56→72, then again for conformance) with no consumer to validate it, and the project reaches “green everywhere” with no frame drawn.
Proposed solution
Section titled “Proposed solution”B immediately, A as the declared next stage, C explicitly not the default. In order:
- Do 2g, but label it for what it is.
fixture::reference_scene()is a contract conformance fixture, not a pipeline. Say so in the commit and the docs so a green suite is not reported as “chroix renders”. - Record the gap explicitly as the project’s next stage, in the same place as the other
design gaps, since it is larger than Stages 1–4 combined. Layout + display-list builder is
the missing 50% of
DISPLAY-LIST.md:34-40. - Keep the stage goal honest. Amend the framing at
TODO.md:3-5: the current goal proves the contract; the goal that matters is “renders a scene produced by the pipeline”, and that is not reachable until layout exists. - Then the earlier findings become tractable in the right order:
schema_fingerprintandBackendcan be compared once two backends render the same bytes (2g); damage becomes meaningful once a producer exists; the header question should be settled before the pipeline bakes the current layout in (see the header-layout finding).
Sizing this honestly: severity high, and it is the one finding here that is not a bug but an
absence. It also carries the good news — because nothing in chroix consumes the display list in
production and nothing in fera consumes Engine, this gap costs nothing yet in either project.
It is cheap to name now and the dominant cost line the moment either side wants real frames.