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

nordisk/facett

facett-helix/README.md

facett-helix — the TIME-HELIX DAG view

A plan / dependency DAG rendered as a helix that advances ALONG a time axis. Time is one axis; the DAG winds forward through time as a modern rounded helix (not a spin-in-place z-rotation). Nodes are plan items — idea / task / test / crate — each tied to a system (its angular lane / strand). Edges are soft rounded rubberband splines. Coils can be wound onto a pin, many coils coexist in one scene with inter-helix links, and an animated traverse flies a time-cursor along the axis. GPU-rendered with emissive bloom.

The model (what a host builds)

Scene { coils: Vec<Coil>, links: Vec<InterEdge>, tension }   // a multi-helix scene
Coil  { dag: PlanDag, origin, axis, wind }                   // one helix + placement
PlanDag { nodes: Vec<Node> }
Node  { id, label, t, system, status, kind, deps, events }
Dep   { on, level }        // SCIP-style dependency depth (1 = direct, 2.. = transitive)
Event { t, kind, label }   // a per-node timeline marker the traverse sweeps through
Edge  { from, to, level }  // intra-coil, derived from deps
InterEdge { from: HelixRef, to: HelixRef, level }            // cross-coil system links

The "add ideas" API (what the owner asked for)

use facett_helix::model::{PlanDag, Kind, Status, EventKind};

let mut dag = PlanDag::new();
let idea = dag.add_idea("helix view", "facett", 0.0, Kind::Idea);   // -> NodeId
let task = dag.add_idea("layout math", "facett", 1.0, Kind::Task);
let test = dag.add_idea("spline test", "facett", 2.0, Kind::Test);
dag.add_dep(task, idea);           // task depends on idea            (level 1, direct)
dag.add_dep_at(test, idea, 2);     // test transitively depends on idea (level 2)
dag.set_status(test, Status::Active);
dag.add_event(task, 1.5, EventKind::Cve, "RUSTSEC-…");

// …or the fluent builder:
let mut b = PlanDag::builder();
let a = b.idea("a", "core", 0.0, Kind::Idea);
let c = b.idea("c", "core", 1.0, Kind::Task);
b.dep(c, a);
let dag = b.build();

Wrap it in a view: HelixView::from_dag("plan", dag) (single coil) or build a Scene of many Coils + Scene::add_link(...) for cross-coil system links.

Helix layout math (layout.rs, pure + deterministic)

A node's coil-local point is [along, r·cos θ, r·sin θ] (time axis = local X):

The coil's Frame (an orthonormal basis around axis, translated to origin) maps that local point into world space, so coils point anywhere and many coexist.

Edges follow the helix: control points are sampled by interpolating u, the lane offset and the radius while re-evaluating the winding term at every sample, so a link spirals along the tube instead of cutting a straight chord. The controls run through a Catmull-Rom chain (rounded, passes through every control point). With tension > 0 the spline becomes an elastic rubberband: interior controls are displaced transverse to the chord by a bell-shaped, tension-scaled amount driven by an injected easing curve (the view feeds facett_core::effects::easing::elastic), so the edge wobbles and settles — and is a pure function of the clock (determinism).

Inter-helix links bridge two coils' world points with a bowed arc, rendered with the same elastic spline.

The traverse (the key ask: advance THROUGH time, not spin)

The view holds a time-cursor u ∈ [0,1]; the camera pans the world time-axis (X) to keep the cursor centred (the "now" plane). Msg::ToggleTraverse(true) auto-advances it from the clock; Msg::Seek(u) pins it. A secondary spin only rotates the tube's cross-section — the primary animation moves the camera along the axis. With an injected clock (set_clock / Msg::Tick) the cursor, the spin and the elastic wobble are all pure functions of it (FC-7), so the frame + AccessKit are reproducible.

Portability contract (why this is nornir-free, how it moves to ordning)

model.rs + layout.rs depend on nothing but serde + std — no egui, no facett, no nornir, no modgunn. They are the seam that git mvs into ordning:

Adapters (hosts populate the generic model)

These live in the host repos (keeping facett-helix host-free). Sketches:

nornir funnel → PlanDag (agent/nornir-helix-feed, follow-up)

Map nornir's funnel decompose-DAG (src/funnel) onto the model:

// for each funnel node, in decompose order:
let id = dag.add_idea(node.title, node.anchor_crate /*RAG-anchored file/crate = system*/,
                      order_index as f64 /* t */,
                      match node.role { Decompose => Kind::Idea, Implement => Kind::Task,
                                        Prove => Kind::Test, _ => Kind::Task });
for upstream in node.requires { dag.add_dep(id, ids[upstream]); }
// route the "add idea" path straight through PlanDag::add_idea.

modgunn → Scene (one coil per crate)

Map a SCIP/cargo dependency graph: one Coil per crate, version-time on the axis, sub-modules as lanes, dependency depth as Dep.level (the view's level filter = "direct deps only"), CVEs/version bumps as Events the traverse sweeps, and inter-helix links for cross-crate edges:

let h = scene.add_coil(Coil::new(crate_dag).at(stacked_offset(i)));   // crate i's coil
// inter-crate dep B → A (system-level):
scene.add_link(HelixRef { helix: hb, node: b_node }, HelixRef { helix: ha, node: a_node },
               dep_depth);
// a CVE landing on a crate at its vuln-introducing version-time:
crate_dag.add_event(node, vuln_t, EventKind::Cve, advisory_id);

FC compliance