Checkpoint source 7 workflows and in-progress diagram authoring redesign
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s

Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
This commit is contained in:
Codex
2026-09-10 10:06:55 -07:00
parent 4b6b5fc197
commit e2ecfea69b
162 changed files with 17595 additions and 926 deletions
@@ -0,0 +1,328 @@
use super::*;
use crate::app::architecture_authoring::{Event, kinds};
fn setup(layer: Layer) -> App {
let mut app = App::with_project(Project::empty());
app.layer = layer;
app.active_diagram = None;
let _ = app.update(Message::AddDiagram);
app.zoom = 0.75;
app.pan = iced::Vector::new(37., -19.);
app
}
fn send(app: &mut App, event: Event) {
let _ = app.update(Message::Architecture(event));
}
#[test]
fn architecture_creation_names_before_commit_preserves_camera_and_undoes_once() {
let mut app = setup(Layer::Oa);
let before = app.document.bundle().clone();
let model = app.project().clone();
let camera = (app.zoom, app.pan);
send(&mut app, Event::Start(Kind::Entity, None));
assert_eq!(app.project(), &model);
let context = app.route_context().unwrap();
send(
&mut app,
Event::Click(context, Position { x: 219., y: 117. }),
);
assert_eq!(app.project(), &model);
assert!(app.architecture.as_ref().unwrap().inline());
send(&mut app, Event::Name(" Response team 雨 ".into()));
send(&mut app, Event::Submit);
assert!(app.architecture.is_none());
assert_eq!((app.zoom, app.pan), camera);
let element = &app.project().elements[&app.selected];
assert_eq!(element.name, " Response team 雨 ");
assert_eq!(element.parent, None);
let node =
&app.project().diagrams[&app.active_diagram.unwrap()].nodes[&app.selected_node.unwrap()];
assert_eq!(node.position, Position { x: 219., y: 117. });
let accepted = app.document.bundle().clone();
let _ = app.update(Message::Undo);
assert_eq!(app.document.bundle(), &before);
let _ = app.update(Message::Redo);
assert_eq!(app.document.bundle(), &accepted);
}
#[test]
fn architecture_context_choices_are_small_and_system_is_singleton() {
let mut app = setup(Layer::Oa);
assert_eq!(kinds(&app), vec![Kind::Entity, Kind::Actor, Kind::Activity]);
app = setup(Layer::Sa);
assert_eq!(
kinds(&app),
vec![Kind::System, Kind::SystemActor, Kind::Function]
);
send(
&mut app,
Event::Start(Kind::System, Some(Position { x: 60., y: 60. })),
);
send(&mut app, Event::Name("Pump controller".into()));
send(&mut app, Event::Submit);
assert_eq!(kinds(&app), vec![Kind::SystemActor, Kind::Function]);
}
#[test]
fn architecture_name_survives_rejected_click_resize_navigation_and_escape_is_explicit() {
let mut app = setup(Layer::Oa);
let before = app.document.bundle().clone();
send(&mut app, Event::Start(Kind::Activity, None));
send(&mut app, Event::Name("Diagnose pressure".into()));
send(&mut app, Event::Start(Kind::Entity, None));
assert!(
matches!(&app.architecture.as_ref().unwrap().saved.intent, architecture_authoring::Intent::Create { name, kind: Kind::Activity, .. } if name == "Diagnose pressure")
);
send(&mut app, Event::Submit);
let mut stale = app.route_context().unwrap();
stale.revision += 1;
send(&mut app, Event::Click(stale, Position { x: 1., y: 2. }));
let _ = app.update(Message::Page(Page::Home));
let _ = app.update(Message::WindowResized(iced::Size::new(1100., 700.)));
assert_eq!(app.page, Page::Diagram);
assert!(app.architecture.is_some());
assert_eq!(app.document.bundle(), &before);
let _ = app.update(Message::Escape);
assert!(app.architecture.is_none());
assert_eq!(app.document.bundle(), &before);
}
#[test]
fn architecture_existing_inventory_is_contextual_and_placement_never_implies_allocation() {
let mut app = setup(Layer::Oa);
let entity = app
.project()
.make_element(Kind::Entity, Layer::Oa, "Response team", None)
.unwrap();
let entity_id = entity.id;
let work = app
.project()
.make_element(Kind::Activity, Layer::Oa, "Report status", None)
.unwrap();
let work_id = work.id;
let data = app
.project()
.make_element(Kind::DataType, Layer::Oa, "Pressure", None)
.unwrap();
let data_id = data.id;
assert!(app.execute(Command::Batch(vec![
Command::CreateElement(entity),
Command::CreateElement(work),
Command::CreateElement(data)
])));
send(&mut app, Event::Existing(Some(Position { x: 80., y: 80. })));
let ids: Vec<_> = architecture_authoring::candidates(&app)
.iter()
.map(|row| row.0)
.collect();
assert!(ids.contains(&entity_id) && ids.contains(&work_id));
assert!(!ids.contains(&data_id));
send(&mut app, Event::Pick(entity_id));
let semantic = app.project().elements.clone();
let before = app.document.bundle().clone();
let camera = (app.zoom, app.pan);
send(
&mut app,
Event::Existing(Some(Position { x: 90., y: 100. })),
);
assert!(
!architecture_authoring::candidates(&app)
.iter()
.any(|row| row.0 == entity_id)
);
send(&mut app, Event::ShowPlaced(true));
assert!(
architecture_authoring::candidates(&app)
.iter()
.any(|row| row.0 == entity_id && row.2)
);
send(&mut app, Event::Pick(work_id));
assert_eq!(app.project().elements, semantic);
assert!(app.project().relations.is_empty());
assert_eq!((app.zoom, app.pan), camera);
let _ = app.update(Message::Undo);
assert_eq!(app.document.bundle(), &before);
}
#[test]
fn architecture_checkpoint_retains_exact_name_point_and_fresh_binding_without_commands() {
for (name, placing) in [(" Team 雨 ", true), (" ", false)] {
let mut app = setup(Layer::Oa);
send(&mut app, Event::Start(Kind::Entity, None));
send(&mut app, Event::Name(name.into()));
if placing {
send(&mut app, Event::Submit);
}
app.architecture.as_mut().unwrap().saved.point = Some(Position { x: 127.5, y: 215. });
app.architecture.as_mut().unwrap().saved.placing = placing;
let snapshot = crate::recovery::Snapshot::capture(
app.canvas_document,
&app.document,
&app.source_workspace,
app.draft.as_ref(),
&app.behavior,
None,
app.selected,
)
.unwrap()
.with_architecture(app.architecture.as_ref());
let bytes = serde_json::to_vec(&snapshot).unwrap();
let decoded: crate::recovery::Snapshot = serde_json::from_slice(&bytes).unwrap();
let accepted = app.document.bundle().clone();
let mut restored = setup(Layer::Sa);
let _ = restored.update(Message::CheckpointLoaded(Ok(Arc::new(decoded))));
assert_eq!(restored.document.bundle(), &accepted);
let draft = &restored.architecture.as_ref().unwrap().saved;
assert!(
matches!(&draft.intent, architecture_authoring::Intent::Create { name: actual, .. } if actual == name)
);
assert_eq!(draft.placing, placing);
assert_eq!(draft.point, Some(Position { x: 127.5, y: 215. }));
if placing {
let context = restored.route_context().unwrap();
send(
&mut restored,
Event::Click(context, Position { x: 20., y: 30. }),
);
assert!(restored.architecture.as_ref().unwrap().inline());
send(&mut restored, Event::Submit);
assert!(restored.architecture.is_none());
assert_eq!(restored.project().elements[&restored.selected].name, name);
} else {
send(&mut restored, Event::Submit);
assert!(restored.architecture.is_some());
assert_eq!(restored.document.bundle(), &accepted);
}
let mut value: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
value["architecture"]["base"] = json!("stale");
assert!(
serde_json::from_value::<crate::recovery::Snapshot>(value)
.unwrap()
.restore()
.is_err()
);
}
}
#[test]
fn architecture_canvas_left_click_uses_world_coordinate_once_at_zoom_and_pan() {
use iced::widget::canvas::Program;
let mut app = setup(Layer::Oa);
send(&mut app, Event::Start(Kind::Actor, None));
send(&mut app, Event::Name("Control centre".into()));
send(&mut app, Event::Submit);
let bounds = iced::Rectangle {
x: 260.,
y: 180.,
width: 600.,
height: 380.,
};
let local = iced::Point::new(190., 100.);
let expected = Position {
x: (local.x - app.pan.x) / app.zoom,
y: (local.y - app.pan.y) / app.zoom,
};
let canvas = crate::diagram::DiagramCanvas {
project: app.project(),
view_index: None,
diagram: app.active_diagram.unwrap(),
selected: app.selected,
zoom: app.zoom,
pan: app.pan,
context: app.route_context().unwrap(),
selected_bend: None,
editable: true,
placing: true,
connection: None,
};
let mut state = crate::diagram::State::default();
let action = canvas
.update(
&mut state,
&iced::widget::canvas::Event::Mouse(iced::mouse::Event::ButtonPressed(
iced::mouse::Button::Left,
)),
bounds,
iced::mouse::Cursor::Available(iced::Point::new(
bounds.x + local.x,
bounds.y + local.y,
)),
)
.unwrap();
let message = action
.into_inner()
.0
.expect("named placement emits a command");
let _ = app.update(message);
assert_eq!(
app.architecture.as_ref().unwrap().saved.point,
Some(expected)
);
send(&mut app, Event::Submit);
let node =
&app.project().diagrams[&app.active_diagram.unwrap()].nodes[&app.selected_node.unwrap()];
assert_eq!(node.position, expected);
}
#[test]
fn architecture_cancelled_gesture_cannot_commit_a_later_named_draft() {
let mut app = setup(Layer::Oa);
send(&mut app, Event::Start(Kind::Entity, None));
send(&mut app, Event::Name("First".into()));
send(&mut app, Event::Submit);
let stale = app.route_context().unwrap();
send(&mut app, Event::Cancel);
send(&mut app, Event::Start(Kind::Actor, None));
send(&mut app, Event::Name("Second".into()));
send(&mut app, Event::Submit);
let before = app.document.bundle().clone();
send(&mut app, Event::Click(stale, Position { x: 1., y: 1. }));
assert_eq!(app.document.bundle(), &before);
assert!(app.architecture.is_some());
}
#[test]
fn basic_transition_explicit_overview_focus_wins_over_an_already_populated_architecture() {
use crate::workflow::{Event as W, Workflow};
let mut p = Project::empty();
let work = p
.add_element(Kind::Activity, Layer::Oa, "Report status", None)
.unwrap();
let architecture = p.ensure_diagram(Layer::Sa);
p.diagrams.get_mut(&architecture).unwrap().projection = ProjectionKind::Allocation;
let mut first = Workflow::transition(&p, work).unwrap();
let Workflow::Transition(t) = &first else {
unreachable!()
};
let target = t
.plan
.changes
.iter()
.find(|c| c.source == work)
.unwrap()
.target;
let mut p = first.update(&p, W::Apply).unwrap().unwrap();
let mut overview = p.diagrams[&architecture].clone();
overview.id = Id::new_v4();
overview.alias = "chosen_overview".into();
overview.name = "Chosen system overview".into();
overview.projection = ProjectionKind::Overview;
overview.nodes.clear();
let chosen = overview.id;
p.diagrams.insert(chosen, overview);
let placement = p.place(chosen, target, Position { x: 823., y: 417. });
let mut app = App::with_project(p);
app.selected = work;
app.active_diagram = Some(architecture);
app.refresh_draft();
let _ = app.update(Message::StartTransition);
let _ = app.update(Message::Workflow(W::Destination(Choice {
id: chosen,
label: "Chosen system overview".into(),
})));
let before = app.project().diagrams.clone();
let _ = app.update(Message::Workflow(W::Apply));
assert!(app.workflow.is_none(), "{}", app.status);
assert_eq!(app.active_diagram, Some(chosen));
assert_eq!(app.selected, target);
assert_eq!(app.selected_node, Some(placement));
assert_eq!(app.project().diagrams, before);
}