Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facet-wrapped/tests/inventory.rs

//! **The Streamlit inventory** (handover 2026-09-29, lane A item 6): every `facett-*`
//! crate of the workspace is either wrapped (a dependency of this crate, so a registry
//! entry mounts it in the Streamlit component) or named in [`NOT_WRAPPED`] with the
//! reason it is not. A new crate that is neither turns this red, and so does a stale
//! reason for a crate that has since been wrapped.
//!
//! ```bash
//! cargo test -p facet-wrapped --test inventory -- --nocapture
//! ```

use std::collections::BTreeSet;
use std::path::PathBuf;

/// The crates that are NOT in the registry, and why. Five kinds of reason:
/// * **not a component** — a library, a host or tooling; it has no pane to mount;
/// * **overlay** — chrome that sits over an app (a window, a menu, a toast), not a pane;
/// * **input** — the component's product is an action or a value the host must receive;
///   the wrapper is render-and-state only (a Streamlit component that returns values is
///   a separate seam, not built);
/// * **live source** — the component reads its source on demand (a repository, a database
///   connection); a static payload would be a different component;
/// * **no data seam yet** — a data view that could be wrapped; the registry law
///   (`the_fixture_only_set_only_shrinks`) admits only entries that take live data, so
///   each needs a payload contract first. This is the to-do list.
const NOT_WRAPPED: &[(&str, &str)] = &[
    // ── not a component ──────────────────────────────────────────────────────────
    ("facett-app", "not a component: the app framework (scene, router, workspace) a host is built on"),
    ("facett-arrow", "not a component: Arrow RecordBatch -> Scene adapter; its output renders through graph2d"),
    ("facett-build-thing", "not a component: facett's build recipe (the pip/wheel rows)"),
    ("facett-ops", "not a component: the `cargo ops` verbs that are not builds (publish-pip, streamlit, deploy-pages …)"),
    ("facett-demo", "not a component: the native showcase app that mounts every component"),
    ("facett-py", "not a component: the pyo3 wheel, a separate workspace"),
    ("facett-wasm-demo", "not a component: the native/wasm demo HOST that mounts panels"),
    ("facett-osm", "not a component: the OSM GeoParquet/WKB reader (parquet); osm2d/osm3d take its output as payload"),
    ("facett-flowsim", "not a component: the particle-advection engine graph surfaces draw with (no Facet of its own)"),
    // ── overlay ─────────────────────────────────────────────────────────────────────
    ("facett-about", "overlay: the About Window over an app (reads nornir-appshell's receipt of the host binary)"),
    ("facett-confirm", "overlay: the destructive-action confirmation gate, a modal over the host's action"),
    ("facett-menu", "overlay: context menus and the command palette of the host app"),
    ("facett-toast", "overlay: timed notifications stacked over the host app"),
    ("facett-filedialog", "overlay: the in-app file dialog; its product is a path the host acts on"),
    ("facett-zoneui", "overlay: the zone/jera framework widgets of the suite apps (nornir-zone bound)"),
    // ── input ───────────────────────────────────────────────────────────────────────
    ("facett-form", "input: CRUD form whose product is the submitted values"),
    ("facett-search", "input: query-language search bar whose product is the parsed query"),
    ("facett-markdown", "input: markdown editor whose product is the edited text"),
    ("facett-sdp", "input: SDP pipeline editor whose product is the edited flows"),
    ("facett-brokkr", "input: brokkr.toml pipeline builder whose product is the authored pipeline"),
    ("facett-auth-ui", "input: device-code sign-in; the host authenticates, not the component"),
    ("facett-file", "input: attachment drop zone whose product is the uploaded files"),
    ("facett-deploykey", "input: deploy-key pane that generates a key pair (nornir-git) for the host"),
    ("facett-claude", "input: chat-with-Claude pane; its backend (nornir-claude) is the host's"),
    ("facett-video", "input: video player transport over a host-supplied decoder"),
    // ── no data seam yet (the to-do list) ────────────────────────────────────────
    ("facett-flow", "renderer dependency: pipeline, plandag and brokkrview use its GPU lane; the standalone graph still needs a payload contract and registry entry"),
    ("facett-dbexplorer", "live source: a navigator that expands by querying its connections for Arrow batches on demand; a static payload would freeze the one thing it does"),
    ("facett-git", "live source: browses a repository through gix (refs, log, tree, blobs read on demand); a payload snapshot would be a different component, and gix is not wasm-checked"),
    ("facett-korp", "input: korp's Ingest pane — a collector roster whose product is the Run action korp executes"),
    ("facett-map3d-flight", "not a component: the flight-sim engine layer (floating origin, CDLOD terrain) over map3d; osm3d is the wrapped surface"),
    ("facett-warehousedeck", "not a component: a FacetDeck wall that hosts wrapped panes; a Streamlit page composes those panes itself"),
];

fn root() -> PathBuf {
    PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent().unwrap().to_path_buf()
}

/// `facett-*` crates of the repo: the root manifest's `members = [...]` plus its
/// `exclude = [...]` (facett-py is its own workspace, still a facett crate).
fn members() -> BTreeSet<String> {
    let text = std::fs::read_to_string(root().join("Cargo.toml")).unwrap();
    let lines = text.lines().filter(|l| l.starts_with("members") || l.starts_with("exclude"));
    lines.flat_map(|l| l.split('"')).filter(|s| s.starts_with("facett-")).map(str::to_string).collect()
}

/// `facett-*` crates this crate depends on (the wrapped ones).
fn wrapped() -> BTreeSet<String> {
    let text = std::fs::read_to_string(root().join("facet-wrapped/Cargo.toml")).unwrap();
    text.lines()
        .filter(|l| l.starts_with("facett-") && l.contains("path ="))
        .map(|l| l.split_whitespace().next().unwrap().to_string())
        .filter(|n| n != "facett-core" && n != "facett-flow")
        .collect()
}

/// `facett-*` crates wrapped only in a GROUP bundle — the optional dependencies of
/// `facet-wrapped-group` (see `facet_wrapped::groups`).
fn feature_only() -> BTreeSet<String> {
    let text = std::fs::read_to_string(root().join("facet-wrapped-group/Cargo.toml")).unwrap();
    text.lines()
        .filter(|l| l.starts_with("facett-") && l.contains("path =") && l.contains("optional = true"))
        .map(|l| l.split_whitespace().next().unwrap().to_string())
        .collect()
}

/// No grouped entry is in the core bundle, and every core entry's group is `core`.
#[test]
fn the_core_bundle_holds_no_grouped_entry() {
    let reg = facet_wrapped::Registry::with_builtins();
    for (name, group) in facet_wrapped::groups::GROUP_ENTRIES {
        assert!(reg.get(name).is_none(), "{name} ({group}) must not be registered in the core bundle");
        assert!(!reg.manifest_json().as_array().unwrap().iter().any(|e| e["name"] == *name));
    }
    for n in reg.names() {
        assert_eq!(facet_wrapped::groups::group_of(n), facet_wrapped::groups::CORE, "{n}");
    }
}

#[test]
fn every_facett_crate_is_wrapped_or_says_why_not() {
    let members = members();
    assert!(members.len() > 60, "read the workspace members: {}", members.len());
    let wrapped: BTreeSet<String> = wrapped().union(&feature_only()).cloned().collect();
    let listed: BTreeSet<String> = NOT_WRAPPED.iter().map(|(n, _)| (*n).to_string()).collect();
    let mut unaccounted: Vec<&String> =
        members.iter().filter(|m| *m != "facett-core" && !wrapped.contains(*m) && !listed.contains(*m)).collect();
    unaccounted.sort();
    assert!(unaccounted.is_empty(), "neither wrapped nor listed with a reason: {unaccounted:?}");
    let stale: Vec<&String> = listed.iter().filter(|n| wrapped.contains(*n) || !members.contains(*n)).collect();
    assert!(stale.is_empty(), "listed as not wrapped, but wrapped or gone: {stale:?}");
    for (n, why) in NOT_WRAPPED {
        assert!(!why.trim().is_empty(), "{n} has no reason");
    }
    let reg = facet_wrapped::Registry::with_builtins();
    let feat = feature_only();
    eprintln!(
        "inventory: {} facett-* crates (+ facett-core) — {} wrapped ({} of them only in a feature bundle: {:?}) behind {} registry entries in THIS build, {} not wrapped ({} of them 'no data seam yet')",
        members.len() - 1,
        wrapped.len(),
        feat.len(),
        feat,
        reg.len(),
        NOT_WRAPPED.len(),
        NOT_WRAPPED.iter().filter(|(_, w)| w.starts_with("no data seam")).count()
    );
}