Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ jobs:
name: Mutation Testing
needs: [test]
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*.swo
.DS_Store
.claude/worktrees/
.worktrees/

# WASM binary assets (built or downloaded, not committed)
rivet-cli/assets/wasm/*.wasm
Expand Down
17 changes: 17 additions & 0 deletions artifacts/features.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,23 @@ artifacts:
phase: future
baseline: v0.4.0

- id: FEAT-073
type: feature
title: JSON API with oEmbed and CORS
status: draft
description: >
REST JSON API under /api/v1/ with permissive CORS for Grafana
dashboard embedding. Includes health, stats, artifacts, diagnostics,
coverage endpoints plus an oEmbed provider at /oembed for rich
embeds of artifact detail pages.
tags: [api, serve, phase-3]
links:
- type: satisfies
target: REQ-007
fields:
phase: phase-3
baseline: v0.3.1

- id: FEAT-070
type: feature
title: Draft-aware validation severity
Expand Down
94 changes: 72 additions & 22 deletions etch/src/layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -777,7 +777,17 @@ fn route_edges<N, E>(
let node_pos: HashMap<&str, &LayoutNode> =
layout_nodes.iter().map(|n| (n.id.as_str(), n)).collect();

let mut edges: Vec<LayoutEdge> = Vec::new();
// Collect edge metadata and endpoints.
struct EdgeData {
src_id: String,
tgt_id: String,
info: EdgeInfo,
start: (f64, f64),
end: (f64, f64),
is_ortho: bool,
}

let mut edge_data: Vec<EdgeData> = Vec::new();

for edge_ref in graph.edge_references() {
let src_idx = edge_ref.source();
Expand Down Expand Up @@ -824,30 +834,63 @@ fn route_edges<N, E>(
});
let end = tgt_point.unwrap_or_else(|| (tgt_node.x + tgt_node.width / 2.0, tgt_node.y));

let points = match options.edge_routing {
EdgeRouting::Orthogonal => crate::ortho::route_orthogonal(
layout_nodes,
start,
end,
options.bend_penalty,
options.port_stub_length,
),
EdgeRouting::CubicBezier => {
if src_point.is_some() || tgt_point.is_some() {
vec![start, end]
} else {
compute_waypoints(src_node, tgt_node, options)
}
let is_ortho = matches!(options.edge_routing, EdgeRouting::Orthogonal);

edge_data.push(EdgeData {
src_id: src_id.clone(),
tgt_id: tgt_id.clone(),
info,
start,
end,
is_ortho,
});
}

// Batch-route orthogonal edges for nudging support.
let ortho_endpoints: Vec<((f64, f64), (f64, f64))> = edge_data
.iter()
.filter(|e| e.is_ortho)
.map(|e| (e.start, e.end))
.collect();

let ortho_paths = if !ortho_endpoints.is_empty() {
crate::ortho::route_orthogonal_batch(
layout_nodes,
&ortho_endpoints,
options.bend_penalty,
options.port_stub_length,
options.edge_separation,
)
} else {
Vec::new()
};

// Assign routed paths back to edges.
let mut ortho_idx = 0;
let mut edges: Vec<LayoutEdge> = Vec::new();

for ed in &edge_data {
let points = if ed.is_ortho {
let p = ortho_paths[ortho_idx].clone();
ortho_idx += 1;
p
} else {
let src_node = node_pos[ed.src_id.as_str()];
let tgt_node = node_pos[ed.tgt_id.as_str()];
if ed.info.source_port.is_some() || ed.info.target_port.is_some() {
vec![ed.start, ed.end]
} else {
compute_waypoints(src_node, tgt_node, options)
}
};

edges.push(LayoutEdge {
source_id: src_id.clone(),
target_id: tgt_id.clone(),
label: info.label,
source_id: ed.src_id.clone(),
target_id: ed.tgt_id.clone(),
label: ed.info.label.clone(),
points,
source_port: info.source_port,
target_port: info.target_port,
source_port: ed.info.source_port.clone(),
target_port: ed.info.target_port.clone(),
});
}

Expand Down Expand Up @@ -1724,8 +1767,15 @@ mod tests {
.find(|e| e.source_id == "A" && e.target_id == "C")
.expect("should find A->C edge");

// A->C spans ranks 0..2, so should have 3 waypoints (start, mid, end).
assert_eq!(long_edge.points.len(), 3);
// A->C spans ranks 0..2, so should have intermediate waypoints.
// The orthogonal router may add port stubs and extra bends, so we
// check for at least 3 points (start, intermediate(s), end) rather
// than an exact count.
assert!(
long_edge.points.len() >= 3,
"A->C should have at least 3 waypoints, got {}",
long_edge.points.len()
);
}

// -----------------------------------------------------------------------
Expand Down
Loading
Loading