Skip to content
Merged
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
501 changes: 485 additions & 16 deletions crates/pseudoscript-emit/src/c4_render.rs

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions crates/pseudoscript-emit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ mod project;
mod render;
mod scene;

pub use c4_render::{BoundaryFrame, C4Layout, LaidOutEdge, LaidOutNode, PointI, layout_c4_scene};
pub use project::{EmitError, View, project, project_symbol};
pub use render::{Theme, layout_sequence_scene, render_svg, render_svg_themed};
pub use scene::{
Expand Down
5 changes: 4 additions & 1 deletion crates/pseudoscript-emit/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,10 @@ impl C4View {
/// A laid-out C4 view: an ordered set of placed nodes and routed edges.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct C4Scene {
/// Which C4 view this is.
/// Which C4 view this is. Serialised as `c4view`, not `view`: the [`Scene`]
/// enum is internally tagged on `view` (`c4`/`sequence`), so the inner field
/// must not reuse that key or a round-trip hits a duplicate-`view` error.
#[serde(rename = "c4view")]
pub view: C4View,
/// The view's boundary node FQN (`of`): the system for a container view, the
/// container for a component view. `None` for context.
Expand Down
29 changes: 22 additions & 7 deletions crates/pseudoscript-wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ use pseudoscript_doc::{
try_render_site_with,
};
use pseudoscript_emit::{
Scene, View, graph_of_source, layout_sequence_scene, project, project_symbol, render_svg,
Scene, View, graph_of_source, layout_c4_scene, layout_sequence_scene, project, project_symbol,
render_svg,
};
use pseudoscript_format::format as format_source;
use pseudoscript_model::{
Expand Down Expand Up @@ -329,14 +330,16 @@ pub fn symbol_svg(modules_json: &str, fqn: &str) -> Result<String, JsError> {
symbol_svg_impl(modules_json, fqn).map_err(|e| JsError::new(&e))
}

/// Positions a sequence [`Scene`] (as JSON) into absolute coordinates, returning
/// the layout as JSON. The host collapses the scene to a chosen depth first,
/// then hands it here; the layout engine owns all geometry. A non-sequence scene
/// is an error.
/// Positions a [`Scene`] (as JSON) into absolute coordinates, returning the
/// layout as JSON. The layout engine owns all geometry: a sequence scene yields
/// a positioned sequence layout (the host collapses it to a chosen depth first);
/// a C4 scene yields a [`pseudoscript_emit::C4Layout`] (placed cards + routed
/// edges + boundary frame), the same geometry the SVG draws. The two layout
/// shapes are distinguishable by their fields (`participants` vs `nodes`).
///
/// # Errors
///
/// Returns an error for invalid JSON or a non-sequence scene.
/// Returns an error for invalid JSON.
#[wasm_bindgen]
pub fn layout_scene(scene_json: &str) -> Result<String, JsError> {
layout_scene_impl(scene_json).map_err(|e| JsError::new(&e))
Expand Down Expand Up @@ -752,7 +755,7 @@ fn layout_scene_impl(scene_json: &str) -> Result<String, String> {
let scene: Scene = serde_json::from_str(scene_json).map_err(|e| e.to_string())?;
match scene {
Scene::Sequence(seq) => Ok(to_json(&layout_sequence_scene(&seq))),
Scene::C4(_) => Err("layout_scene expects a sequence scene".to_owned()),
Scene::C4(c4) => Ok(to_json(&layout_c4_scene(&c4))),
}
}

Expand Down Expand Up @@ -1283,6 +1286,18 @@ mod tests {
assert!(out.contains("m::F"), "{out}");
}

#[test]
fn layout_scene_lays_out_a_c4_scene() {
// The exact IDE path: a projected C4 scene serialised to JSON
// (`emit_scene_modules`) and handed back to `layout_scene`. It must
// positionally lay out (placed cards), not error as it once did.
let scene = project_view("//! m\npublic person P;\npublic system S;", "context", "")
.expect("context view projects");
let out = layout_scene_impl(&to_json(&scene)).expect("c4 scene lays out");
assert!(out.contains(r#""nodes""#), "C4 layout carries nodes: {out}");
assert!(out.contains("m::P") && out.contains("m::S"), "{out}");
}

#[test]
fn layout_scene_rejects_a_scene_missing_the_view_tag() {
// A scene object without the `view` discriminant cannot deserialize into
Expand Down
16 changes: 0 additions & 16 deletions web-ide/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion web-ide/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@
"@codemirror/search": "^6.7.0",
"@codemirror/state": "^6.6.0",
"@codemirror/view": "^6.43.0",
"@dagrejs/dagre": "3.0.0",
"@lezer/highlight": "^1.2.3",
"@lezer/markdown": "^1.6.4",
"@xyflow/svelte": "1.5.2",
Expand Down
7 changes: 6 additions & 1 deletion web-ide/src/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,14 @@ button { font: inherit; cursor: pointer; color: inherit; }
.svelte-flow__edge-text { fill: var(--ink-soft); font-family: var(--font-mono); font-size: 10px; }
.svelte-flow__edge-textbg { fill: var(--surface); }

/* kind-coloured card nodes (shared by C4 graph + timeline) */
/* kind-coloured card nodes (shared by C4 graph + timeline). The C4 graph sizes
each card to the layout engine's rect (width/height set on the node), so the
box honours those dimensions and clips overflow; the timeline's lifeline head
cards (width unset) size to content. */
.svelte-flow__node.c4-node {
width: auto;
box-sizing: border-box;
overflow: hidden;
padding: 0.55rem 0.9rem;
font-family: var(--font-mono);
font-size: 0.8rem;
Expand Down
Loading
Loading