facett-releaseview — a release process as THREE converging graphs
Status: DESIGN + SHIPPED (2026-07-17). Component: facett-releaseview (ReleaseView). Demo: the releaseview tab (facett-demo::releaseview_tab). Concept diagram: ../assets/release-convergence-planes.svg.
The idea
A release is a convergence. The same constellation dependency DAG exists in three states that a healthy release drives to agree:
- LOCAL — the working-tree / manifest versions (what you release from).
- SPARRING — the Gate-B holger local-rehearsal versions (the safe preview).
- CRATES_IO — the published, immutable versions (the target).
All three equal ⇒ converged (green). Divergence is not an error — it is a map of where to fix / release:
| condition | ConvergeStatus |
colour | meaning |
local == sparring == cratesio |
Converged |
green | done |
cratesio < local |
NeedsRelease |
red | crates.io behind local → publish |
sparring != cratesio |
LockDesync |
red | resolved ≠ published → lock/registry desync |
sparring != local |
BumpDrift |
amber | rehearsal hoisted a bump local hasn't |
| (live event) | Publishing |
accent-pulse | a publish is in flight |
Why it is NOT a new graph engine (Law #1 — REUSE)
facett-releaseview invents a component, not a renderer. It reuses:
facett-graph::DepGraphView— for its toposortcolumns()layout (exactly howfacett-pipelinecomposes it; the ReleaseView paints its own themed boxes on top).facett-core— theElm/Facetcontract,theme(ui),a11y::node, theNavigablepan/zoom camera, andeffects::glow_rect.- The status palette knob — the marker colours ride the active
Theme(th.point/th.accent) so the view re-themes with every palette.
Engine-agnostic (one-way, like the runa adapter)
ReleaseView::from_model(ReleaseConvergence) takes the neutral model in and renders. There is no dwarves / release-engine dependency — types flow one way, exactly the RunaPipelineAdapter pattern (.nornir/design/pipeline-adapter).
ReleaseConvergence { // the neutral, serde model
title,
nodes: Vec<CrateConvergence { // one crate across the three graphs
krate, local_ver, sparring_ver, cratesio_ver, status: ConvergeStatus }>,
edges: Vec<DepEdge { from, to }>, // dep DAG, by node index
}
derive_status(local, sparring, cratesio) -> ConvergeStatus is the pure kernel of the table above (total, no I/O, no engine types; unit-tested over all four version-derived states). Publishing is a live-event state, never derived.
The realtime seam
ReleaseConvergence::apply(ReleaseEvent) (surfaced through Msg::Event) flips one crate's state per call:
PublishStart{krate}→PublishingPublished{krate,version}→ set crates.io version + re-derive (→Converged)SparringResolved{krate,version}/LocalBumped{krate,version}→ re-derive
So a live release cascade animates the convergence node-by-node. The demo tab drives a scripted cascade (skade's bump, then publish dwarves → holger → nornir) and loops it.
Two renders (GPU-hero philosophy, CPU-honest)
Mirroring facett's "beautiful hero + always-works fallback":
ViewMode::Planes3D— the hero. The three states as three receding depth planes (LOCAL front → SPARRING → CRATES_IO back). A converged crate is drawn once where the planes coincide (green ✓); a diverged crate splits across the planes it differs on, joined by a severity-coloured beam + glow. Plane separation ∝1 − converged_fraction, so as crates converge the planes snap together. The three plane labels are AccessKit nodes (a robot can read "3 planes present").ViewMode::TriBadge2D— the fallback. One flat DAG; each node shows three version chips L / S / C, a diverging chip flagged in the node's severity colour.
Both renders are pure CPU egui-painter draws — wasm-clean, headless- snapshottable, no wgpu required to land green. That is deliberate: the render tests (tests/render.rs) run anywhere, and the demo tab is green on the web build.
DEFERRED (documented TODO — the wgpu CodeVault upgrade)
The 3-plane render is today a CPU pseudo-3D paint (isometric plane offsets, no depth buffer). The hero upgrade is to lower ReleaseConvergence onto facett-graph3d::CodeVault (the Wreck-It-Ralph 3D graph, wgpu, a real depth buffer): render the three states as three CodeVault depth planes, a crate node splits between planes when its versions diverge (+ the red/amber glitch glow the CodeVault already paints), and the planes snap together as crates converge. The seam is exactly the same neutral model — only the view changes, never the model or the (future) dwarves adapter. Not built here (kept the first landing green + CPU-portable); tracked as the release-3 follow-up.
dwarves-side follow-up (the one-way feed)
The release engine feeds ReleaseConvergence from:
- its
graph_diff→ thenodes+edgestopology, - the three version sources → each node's
local_ver(manifests),sparring_ver(the Gate-B holger rehearsal resolve),cratesio_ver(a registry probe, User-Agent set — see the "release keep-finish" note).
That adapter lives on the dwarves side (or in a releaseview feature like the runa adapter), never here: facett-releaseview stays a pure viewer.
Tests (RED-when-broken)
tests/derive.rs— the status kernel across all five states + the roll-ups (status_counts,converged_fraction) +applycascades.tests/render.rs— model→render through a real kittest AccessKit harness: diverged reads red / converged reads green (colour mapping + per-node status), the 3 planes present (3D), the 3 L/S/C chips present (2D), no error atom, and a live cascade converges the graph.facett-demo— thereleaseviewtab is in the deck, the LEGEND, the mega-matrix (component × data-state × palette), the find-them-all error-atom gate, and the D5b2 robot live-drive coverage.