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
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:
@@ -5,6 +5,10 @@ use super::*;
|
||||
use iced::widget::{Column, column, text_input};
|
||||
use std::{cell::RefCell, collections::BTreeSet};
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "scenario_operation_tests.rs"]
|
||||
mod operation_tests;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub enum Tool {
|
||||
Lifeline,
|
||||
@@ -125,6 +129,9 @@ pub enum Event {
|
||||
Receiver(Choice),
|
||||
Call(Choice),
|
||||
Style(MessageStyle),
|
||||
OperationIntent(bool),
|
||||
Operation(Choice),
|
||||
ClearOperation,
|
||||
Parent(Choice),
|
||||
Expression(String),
|
||||
SecondGuard(String),
|
||||
@@ -175,6 +182,10 @@ struct Draft {
|
||||
receiver: Option<Id>,
|
||||
call: Option<Id>,
|
||||
style: MessageStyle,
|
||||
#[serde(default)]
|
||||
operation: Option<OperationUse>,
|
||||
#[serde(default)]
|
||||
disabled_operation: Option<OperationUse>,
|
||||
expression: String,
|
||||
second_guard: String,
|
||||
operator: FragmentOperator,
|
||||
@@ -330,6 +341,8 @@ impl State {
|
||||
receiver: None,
|
||||
call: None,
|
||||
style: MessageStyle::CallReply,
|
||||
operation: None,
|
||||
disabled_operation: None,
|
||||
expression: String::new(),
|
||||
second_guard: "else".into(),
|
||||
operator: FragmentOperator::Alternative,
|
||||
@@ -351,6 +364,27 @@ impl State {
|
||||
Event::Name(s) => d.name = s,
|
||||
Event::Expression(s) => d.expression = s,
|
||||
Event::SecondGuard(s) => d.second_guard = s,
|
||||
Event::OperationIntent(enabled) => {
|
||||
if enabled {
|
||||
d.operation = Some(
|
||||
d.operation
|
||||
.clone()
|
||||
.or_else(|| d.disabled_operation.take())
|
||||
.unwrap_or(OperationUse { allocation: None }),
|
||||
);
|
||||
} else {
|
||||
d.disabled_operation =
|
||||
d.operation.take().or_else(|| d.disabled_operation.take());
|
||||
}
|
||||
}
|
||||
Event::Operation(c) => {
|
||||
d.operation = Some(OperationUse {
|
||||
allocation: Some(c.id),
|
||||
})
|
||||
}
|
||||
Event::ClearOperation => {
|
||||
d.operation = Some(OperationUse { allocation: None })
|
||||
}
|
||||
Event::Target(c) => {
|
||||
d.target = Some(c.id);
|
||||
d.bindings.clear();
|
||||
@@ -358,7 +392,9 @@ impl State {
|
||||
Event::Exchange(c) => {
|
||||
if d.exchange != Some(c.id) {
|
||||
d.exchange = Some(c.id);
|
||||
d.reset_values();
|
||||
if !d.has_operation_intent(p) {
|
||||
d.reset_values();
|
||||
}
|
||||
if !d.fixed_endpoints {
|
||||
d.sender = None;
|
||||
d.receiver = None;
|
||||
@@ -394,7 +430,9 @@ impl State {
|
||||
std::mem::swap(&mut d.sender, &mut d.receiver);
|
||||
d.call = None;
|
||||
}
|
||||
d.retain_value_paths(p);
|
||||
if !d.has_operation_intent(p) {
|
||||
d.retain_value_paths(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::Call(c) => {
|
||||
@@ -410,7 +448,7 @@ impl State {
|
||||
..
|
||||
}) = p.elements.get(&c.id).and_then(|e| e.shared())
|
||||
{
|
||||
if d.exchange != *exchange {
|
||||
if d.exchange != *exchange && !d.has_operation_intent(p) {
|
||||
d.reset_values();
|
||||
}
|
||||
d.exchange = *exchange;
|
||||
@@ -426,18 +464,24 @@ impl State {
|
||||
d.values.insert(id, s);
|
||||
}
|
||||
Event::ValueFocus(key)
|
||||
if d.exchange
|
||||
.and_then(|id| p.relations.get(&id))
|
||||
.and_then(|r| crate::data_values::resolve(p, r, &d.kind(), &key))
|
||||
if d.signature(p)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|r| {
|
||||
crate::data_values::resolve(p, &r, &d.value_kind(), &key)
|
||||
})
|
||||
.is_some() =>
|
||||
{
|
||||
d.value_focus = Some(key);
|
||||
}
|
||||
Event::ValueBrowse(key) => {
|
||||
if key.as_ref().is_none_or(|key| {
|
||||
d.exchange
|
||||
.and_then(|id| p.relations.get(&id))
|
||||
.and_then(|r| crate::data_values::resolve(p, r, &d.kind(), key))
|
||||
d.signature(p)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|r| {
|
||||
crate::data_values::resolve(p, &r, &d.value_kind(), key)
|
||||
})
|
||||
.is_some()
|
||||
}) {
|
||||
d.value_browse = key;
|
||||
@@ -767,6 +811,33 @@ fn calls(p: &Project, d: &Draft) -> Vec<Choice> {
|
||||
.collect()
|
||||
}
|
||||
impl Draft {
|
||||
fn has_operation_intent(&self, p: &Project) -> bool {
|
||||
self.operation.is_some()
|
||||
|| self
|
||||
.call
|
||||
.and_then(|id| p.elements.get(&id))
|
||||
.is_some_and(|e| {
|
||||
matches!(
|
||||
e.shared(),
|
||||
Some(SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange {
|
||||
operation: Some(_),
|
||||
..
|
||||
},
|
||||
..
|
||||
})
|
||||
)
|
||||
})
|
||||
}
|
||||
fn proposed_message(&self, p: &Project) -> oa_core::Element {
|
||||
let mut message = self.append(p, "Operation preview", self.message_body());
|
||||
// This is a transient resolver input; no generated identity reaches a command.
|
||||
message.id = Id::nil();
|
||||
message
|
||||
}
|
||||
fn signature(&self, p: &Project) -> Result<Option<Relation>, String> {
|
||||
crate::data_values::message_signature(p, &self.proposed_message(p))
|
||||
}
|
||||
fn reset_values(&mut self) {
|
||||
self.values.clear();
|
||||
self.value_focus = None;
|
||||
@@ -804,8 +875,8 @@ impl Draft {
|
||||
exchange: &Relation,
|
||||
) -> Result<Vec<crate::data_values::FieldPathChoice>, String> {
|
||||
match &self.value_browse {
|
||||
Some(key) => crate::data_values::children(p, exchange, &self.kind(), key),
|
||||
None => Ok(crate::data_values::roots(p, exchange, &self.kind())),
|
||||
Some(key) => crate::data_values::children(p, exchange, &self.value_kind(), key),
|
||||
None => Ok(crate::data_values::roots(p, exchange, &self.value_kind())),
|
||||
}
|
||||
}
|
||||
fn authored_values(&self) -> Vec<(&crate::data_values::FieldPathKey, &str)> {
|
||||
@@ -815,6 +886,9 @@ impl Draft {
|
||||
.map(|(key, value)| (key, value.as_str()))
|
||||
.collect()
|
||||
}
|
||||
fn value_kind(&self) -> MessageKind {
|
||||
crate::data_values::operation_value_phase(&self.kind(), self.operation.as_ref())
|
||||
}
|
||||
fn kind(&self) -> MessageKind {
|
||||
match self.style {
|
||||
MessageStyle::Asynchronous => MessageKind::Asynchronous,
|
||||
@@ -841,6 +915,14 @@ impl Draft {
|
||||
self.receiver
|
||||
},
|
||||
message_kind: self.kind(),
|
||||
operation: if matches!(
|
||||
self.style,
|
||||
MessageStyle::Call | MessageStyle::CallReply | MessageStyle::Asynchronous
|
||||
) {
|
||||
self.operation.clone()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
reply_to: self.call,
|
||||
values: self
|
||||
.values
|
||||
@@ -974,6 +1056,7 @@ impl Draft {
|
||||
sender: self.receiver,
|
||||
receiver: self.sender,
|
||||
message_kind: MessageKind::Reply,
|
||||
operation: None,
|
||||
reply_to: Some(call_id),
|
||||
values: vec![],
|
||||
},
|
||||
@@ -1136,7 +1219,7 @@ fn value_panel<'a>(p: &Project, d: &'a Draft, exchange: &Relation) -> Element<'a
|
||||
use crate::data_values;
|
||||
let mut out = column![text("Message values").size(14)].spacing(7);
|
||||
if let Some(browse) = &d.value_browse {
|
||||
let title = data_values::resolve(p, exchange, &d.kind(), browse)
|
||||
let title = data_values::resolve(p, exchange, &d.value_kind(), browse)
|
||||
.map(|choice| format!("Fields inside {}", choice.label))
|
||||
.unwrap_or_else(|| "This record path is no longer available in this message.".into());
|
||||
out = out.push(text(title).size(11)).push(
|
||||
@@ -1182,7 +1265,7 @@ fn value_panel<'a>(p: &Project, d: &'a Draft, exchange: &Relation) -> Element<'a
|
||||
match d
|
||||
.value_focus
|
||||
.as_ref()
|
||||
.map(|key| data_values::resolve(p, exchange, &d.kind(), key))
|
||||
.map(|key| data_values::resolve(p, exchange, &d.value_kind(), key))
|
||||
{
|
||||
Some(Some(focus)) => {
|
||||
let key = focus.key;
|
||||
@@ -1233,7 +1316,7 @@ fn value_panel<'a>(p: &Project, d: &'a Draft, exchange: &Relation) -> Element<'a
|
||||
ValueSection::Authored,
|
||||
));
|
||||
for (key, expression) in &authored[value_page(authored.len(), d.value_authored_page)] {
|
||||
let title = data_values::resolve(p, exchange, &d.kind(), key)
|
||||
let title = data_values::resolve(p, exchange, &d.value_kind(), key)
|
||||
.map(|choice| choice.label)
|
||||
.unwrap_or_else(|| format!("Unavailable field {} · review this value", key.field));
|
||||
out = out.push(
|
||||
@@ -1579,8 +1662,71 @@ pub fn view<'a>(app: &'a App, owner: Id, mut out: Column<'a, Message>) -> Elemen
|
||||
.size(12),
|
||||
);
|
||||
}
|
||||
if let Some(exchange) = d.exchange.and_then(|id| p.relations.get(&id)) {
|
||||
form = form.push(value_panel(p, d, exchange));
|
||||
if p.layer_of(owner) == Some(Layer::Sa)
|
||||
&& viewpoint(p, owner) == ScenarioViewpoint::BoundaryInterface
|
||||
&& matches!(
|
||||
d.style,
|
||||
MessageStyle::Call | MessageStyle::CallReply | MessageStyle::Asynchronous
|
||||
)
|
||||
{
|
||||
form = form.push(
|
||||
iced::widget::checkbox(d.operation.is_some())
|
||||
.label("Invoke a specific interface operation")
|
||||
.on_toggle(|enabled| event(Event::OperationIntent(enabled))),
|
||||
);
|
||||
if let Some(operation) = &d.operation {
|
||||
form = form
|
||||
.push(bounded_picker(
|
||||
crate::data_values::operation_choices(p, &d.proposed_message(p)),
|
||||
d,
|
||||
operation.allocation,
|
||||
"Choose the exact interface operation allocation…",
|
||||
Event::Operation,
|
||||
))
|
||||
.push(
|
||||
button("Keep operation unresolved")
|
||||
.on_press(event(Event::ClearOperation)),
|
||||
);
|
||||
if let Some(id) = operation.allocation {
|
||||
form = form.push(label(crate::data_values::operation_identity(p, id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(description) =
|
||||
crate::data_values::operation_label(p, &d.proposed_message(p))
|
||||
{
|
||||
form = form.push(label(description));
|
||||
}
|
||||
match d.signature(p) {
|
||||
Ok(Some(exchange)) => form = form.push(value_panel(p, d, &exchange)),
|
||||
Err(problem) => {
|
||||
form = form.push(label(problem));
|
||||
for (key, expression) in d.authored_values() {
|
||||
form = form
|
||||
.push(
|
||||
text(format!(
|
||||
"Retained value · {} / {}",
|
||||
key.path
|
||||
.iter()
|
||||
.map(|id| p.named(*id))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" → "),
|
||||
p.named(key.field)
|
||||
))
|
||||
.size(11),
|
||||
)
|
||||
.push(
|
||||
text_input("Value expression", expression)
|
||||
.on_input({
|
||||
let key = key.clone();
|
||||
move |raw| event(Event::Value(key.clone(), raw))
|
||||
})
|
||||
.padding(8)
|
||||
.size(12),
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
}
|
||||
if d.style == MessageStyle::CallReply {
|
||||
form=form.push(label("Creates the call and reversed, correlated reply together in one undoable operation."));
|
||||
@@ -2072,6 +2218,7 @@ pub(crate) mod direct_tests {
|
||||
f.owner,
|
||||
&format!("Call {index}"),
|
||||
OccurrenceBody::Exchange {
|
||||
operation: None,
|
||||
exchange: Some(f.exchange),
|
||||
sender: Some(f.sender),
|
||||
receiver: Some(f.receiver),
|
||||
@@ -3173,6 +3320,7 @@ mod data_contract_tests {
|
||||
f.scenario,
|
||||
name,
|
||||
OccurrenceBody::Exchange {
|
||||
operation: None,
|
||||
exchange: Some(exchange),
|
||||
sender: Some(lifelines[0]),
|
||||
receiver: Some(lifelines[1]),
|
||||
|
||||
Reference in New Issue
Block a user