facett — reasoning (f0: why this overall shape)
Class:
reasoning. The why behind facett's overall architecture, and the whole-library alternatives we rejected. The descriptive shape isdesign.md. Per-function rationale lives in each function's-reasoning.md.
Why "components", not one big viewer
The constellation needs to view many unrelated shapes of data — Arrow tables, dep graphs, geo points, Spark DAGs, Iceberg snapshots, 3D scenes. A single monolithic viewer would grow a mode for each and share nothing but bugs. Instead each shape is its own crate implementing one tiny contract, and a FacetDeck composes them. The cost is a trait per component; the payoff is that korp, nornir, and anyone else assemble exactly the panes they need, and a new data shape is a new crate, not a patch to a giant.
Why source-agnostic (no data layer inside facett)
facett renders what it is handed — no connection, query, or fetch code. That boundary is deliberate: the moment a viewer knows how to fetch, it is coupled to a backend and can no longer be reused across knut / nornir / korp, and it can no longer be tested with a fixed in-memory fixture. Keeping data acquisition in the consumer (e.g. knut) is what lets a single component draw a real warehouse table in production and a three-row fake in a unit test with the same code.
Why two stacked contracts (Facet over Elm)
There are two different audiences: a host that just wants to place a pane and dump app state, and a component author who needs the thing testable. One trait cannot serve both without either burdening the host with Model/Msg/Effect or leaving the author with a mutating, untestable ui. So facett stacks them: Facet (title/ui/state_json) is the simple host surface; Elm (Model/Msg/pure view/update) is the strict internal surface; and the macro impl_facet_via_elm! writes the first from the second. The host never sees Msg; the author never hand-writes the bridge. Why the macro and not a blanket impl<T: Elm> Facet is argued in elm-reasoning.md.
Why headless-first (the whole thing exists to be tested without a screen)
The overriding goal was testing a GUI without a GUI. Every architectural choice serves it: one serializable Model (so a test can snapshot state), a pure view (so painting doesn't mutate), state_json on every Facet (so the introspection is uniform), and a harness that drives Msg scripts into a RenderReport. Robot testing and the nornir test-matrix are that same seam at scale. A library of GUI components that could only be checked by a human clicking would not be maintainable across dozens of components and parallel agents; headless-first is what makes the matrix green automatically.
Why one render kernel with the GPU behind the same draw()
Components write to Scene / Layout and call draw — never to a backend. The wgpu fast path slots in behind the identical draw() / Facet::ui, so no component changes when GPU lands and none can accidentally depend on a backend detail. Splitting the render decision out of every component and into one shared kernel is what keeps 40+ crates from each re-deciding CPU-vs-GPU. The 2-category design and its trade-offs are in render-reasoning.md.
How we got here (the road, briefly)
facett was assembled by copying the best existing egui components in — nornir's dep-graph and warehouse table, knut's pipeline and pop viewers, osm-viewer's wgpu map — then generalising each into a source-agnostic Facet and improving it here. The order was always copy-in + fix (build + test in facett) before any cut-over of the source repo, and only quiescent sources (knut) could be cut early; active repos (nornir, znippy, osm) stayed copy-only until their owners agreed. The later, larger move was migrating every component onto the Elm contract (FC-2 + FC-9) in waves, which is what made the headless matrix trustworthy — that story is elm-reasoning.md.