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:
@@ -0,0 +1,263 @@
|
||||
//! Qualified editors share core's resolver, including incomplete leaf selection.
|
||||
use super::*;
|
||||
use iced::widget::column;
|
||||
use oa_core::topology_context::TopologyIndex;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum Purpose {
|
||||
Precedence,
|
||||
Exchange,
|
||||
CommonWork,
|
||||
}
|
||||
impl std::fmt::Display for Purpose {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Precedence => "Precedence",
|
||||
Self::Exchange => "Exchange assembly",
|
||||
Self::CommonWork => "Common work incidence",
|
||||
})
|
||||
}
|
||||
}
|
||||
fn choice(p: &Project, id: Id) -> crate::app::Choice {
|
||||
crate::app::Choice {
|
||||
id,
|
||||
label: p
|
||||
.elements
|
||||
.get(&id)
|
||||
.map(|e| format!("{} (@{})", e.name, e.alias))
|
||||
.or_else(|| {
|
||||
p.relations
|
||||
.get(&id)
|
||||
.map(|r| format!("{} (@{})", r.name, r.alias))
|
||||
})
|
||||
.unwrap_or_else(|| format!("Unavailable {id}")),
|
||||
}
|
||||
}
|
||||
pub(super) fn property<'a>(
|
||||
draft: &'a Draft,
|
||||
p: &'a Project,
|
||||
value: &'a Value,
|
||||
path: &str,
|
||||
) -> Option<View<'a, Message>> {
|
||||
if !path.contains("/ContextualLink/") {
|
||||
return None;
|
||||
}
|
||||
let relation: Relation = serde_json::from_value(draft.context_value())
|
||||
.ok()
|
||||
.or_else(|| p.relations.get(&draft.original).cloned())?;
|
||||
let RelationBody::ContextualLink { owner, .. } = relation.body else {
|
||||
return None;
|
||||
};
|
||||
if path.ends_with("/source/occurrence") || path.ends_with("/target/occurrence") {
|
||||
let parent = path.strip_suffix("/occurrence")?;
|
||||
let endpoint: OccurrenceEndpoint =
|
||||
serde_json::from_value(draft.context_value().pointer(parent)?.clone()).ok()?;
|
||||
let index = TopologyIndex::new(p);
|
||||
let scope = match index.resolve_scope(owner, &endpoint.references) {
|
||||
Ok(scope) => scope,
|
||||
Err(error) => return Some(text(error.to_string()).size(12).into()),
|
||||
};
|
||||
let choices = index
|
||||
.contributions(scope)
|
||||
.into_iter()
|
||||
.map(|id| choice(p, id))
|
||||
.collect::<Vec<_>>();
|
||||
let selected = (!endpoint.occurrence.is_nil()).then(|| choice(p, endpoint.occurrence));
|
||||
return Some(
|
||||
column![
|
||||
text("Only the leaf changes; the caller and complete reference path remain fixed.")
|
||||
.size(11),
|
||||
pick_list(choices, selected, {
|
||||
let path = path.to_owned();
|
||||
move |choice| Message::FieldShape(path.clone(), json!(choice.id))
|
||||
})
|
||||
.padding(8)
|
||||
.text_size(12)
|
||||
.width(Fill)
|
||||
]
|
||||
.spacing(6)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if path.ends_with("/source") || path.ends_with("/target") {
|
||||
let endpoint: OccurrenceEndpoint = serde_json::from_value(value.clone()).ok()?;
|
||||
let index = TopologyIndex::new(p);
|
||||
let mut out = column![
|
||||
text(crate::behavior::topology::contextual::address_label(
|
||||
p, owner, &endpoint
|
||||
))
|
||||
.size(12)
|
||||
]
|
||||
.spacing(6);
|
||||
match index.resolve_scope(owner, &endpoint.references) {
|
||||
Ok(scope) => {
|
||||
let choices = index
|
||||
.contributions(scope)
|
||||
.into_iter()
|
||||
.map(|id| choice(p, id))
|
||||
.collect::<Vec<_>>();
|
||||
let selected =
|
||||
(!endpoint.occurrence.is_nil()).then(|| choice(p, endpoint.occurrence));
|
||||
out = out.push(
|
||||
pick_list(choices, selected, {
|
||||
let path = path.to_owned();
|
||||
let references = endpoint.references.clone();
|
||||
move |choice| {
|
||||
Message::FieldShape(
|
||||
path.clone(),
|
||||
json!(OccurrenceEndpoint {
|
||||
references: references.clone(),
|
||||
occurrence: choice.id
|
||||
}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.placeholder("Choose a contribution in this context…")
|
||||
.padding(8)
|
||||
.text_size(12)
|
||||
.width(Fill),
|
||||
);
|
||||
if index.resolve(owner, &endpoint).is_ok()
|
||||
&& matches!(
|
||||
p.elements
|
||||
.get(&endpoint.occurrence)
|
||||
.and_then(|e| e.shared()),
|
||||
Some(SharedBody::Occurrence {
|
||||
body: OccurrenceBody::SequenceReference { sequence: Some(_) },
|
||||
..
|
||||
})
|
||||
)
|
||||
{
|
||||
let mut references = endpoint.references.clone();
|
||||
references.push(endpoint.occurrence);
|
||||
out = out.push(button("Enter selected reference").on_press(
|
||||
Message::FieldShape(
|
||||
path.to_owned(),
|
||||
json!(OccurrenceEndpoint {
|
||||
references,
|
||||
occurrence: Id::nil()
|
||||
}),
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
Err(error) => out = out.push(text(error.to_string()).size(12)),
|
||||
}
|
||||
if let Some(reference) = endpoint.references.last() {
|
||||
let mut references = endpoint.references.clone();
|
||||
references.pop();
|
||||
out = out.push(button("Up to caller context").on_press(Message::FieldShape(
|
||||
path.to_owned(),
|
||||
json!(OccurrenceEndpoint {
|
||||
references,
|
||||
occurrence: *reference
|
||||
}),
|
||||
)));
|
||||
}
|
||||
return Some(out.push(text("Entering a reference retains an unfinished endpoint until you choose its contribution. Apply checks the complete caller path.").size(11)).into());
|
||||
}
|
||||
if path.ends_with("/owner") {
|
||||
let mut choices = p
|
||||
.elements
|
||||
.values()
|
||||
.filter(|e| matches!(e.kind(), Kind::Process | Kind::FunctionalChain))
|
||||
.map(|e| choice(p, e.id))
|
||||
.collect::<Vec<_>>();
|
||||
choices.sort_by(|a, b| a.label.cmp(&b.label).then(a.id.cmp(&b.id)));
|
||||
return Some(
|
||||
pick_list(choices, Some(choice(p, owner)), {
|
||||
let path = path.to_owned();
|
||||
move |choice| Message::FieldShape(path.clone(), json!(choice.id))
|
||||
})
|
||||
.padding(8)
|
||||
.text_size(12)
|
||||
.width(Fill)
|
||||
.into(),
|
||||
);
|
||||
}
|
||||
if path.ends_with("/purpose") {
|
||||
let purpose: ContextualLinkPurpose = serde_json::from_value(value.clone()).ok()?;
|
||||
let selected = match purpose {
|
||||
ContextualLinkPurpose::Precedence { .. } => Purpose::Precedence,
|
||||
ContextualLinkPurpose::Exchange { .. } => Purpose::Exchange,
|
||||
ContextualLinkPurpose::CommonWork { .. } => Purpose::CommonWork,
|
||||
};
|
||||
let mut out = column![
|
||||
pick_list(
|
||||
vec![Purpose::Precedence, Purpose::Exchange, Purpose::CommonWork],
|
||||
Some(selected),
|
||||
{
|
||||
let path = path.to_owned();
|
||||
move |purpose| {
|
||||
Message::FieldVariant(
|
||||
path.clone(),
|
||||
match purpose {
|
||||
Purpose::Precedence => json!(ContextualLinkPurpose::Precedence {
|
||||
guard: String::new()
|
||||
}),
|
||||
Purpose::Exchange => json!(ContextualLinkPurpose::Exchange {
|
||||
exchange: Id::nil()
|
||||
}),
|
||||
Purpose::CommonWork => {
|
||||
json!(ContextualLinkPurpose::CommonWork { work: Id::nil() })
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
.padding(8)
|
||||
.text_size(12)
|
||||
.width(Fill)
|
||||
]
|
||||
.spacing(7);
|
||||
match purpose {
|
||||
ContextualLinkPurpose::Precedence { .. } => {
|
||||
let path = format!("{path}/Precedence/guard");
|
||||
let raw = draft
|
||||
.inputs
|
||||
.get(&path)
|
||||
.map(|input| input.raw.as_str())
|
||||
.unwrap_or("");
|
||||
out = out.push(
|
||||
text_input("Precedence guard", raw)
|
||||
.on_input(move |raw| Message::Field(path.clone(), raw))
|
||||
.padding(8)
|
||||
.size(12),
|
||||
);
|
||||
}
|
||||
purpose => {
|
||||
let (selected, choices, leaf) = match purpose {
|
||||
ContextualLinkPurpose::Exchange { exchange } => (
|
||||
exchange,
|
||||
p.relations
|
||||
.values()
|
||||
.filter(|r| {
|
||||
matches!(
|
||||
r.kind(),
|
||||
RelationKind::Interaction | RelationKind::FunctionalExchange
|
||||
) && p.layer_of(r.id) == p.layer_of(owner)
|
||||
})
|
||||
.map(|r| choice(p, r.id))
|
||||
.collect::<Vec<_>>(),
|
||||
"Exchange/exchange",
|
||||
),
|
||||
ContextualLinkPurpose::CommonWork { work } => (
|
||||
work,
|
||||
p.elements
|
||||
.values()
|
||||
.filter(|e| e.kind().is_work() && Some(e.layer()) == p.layer_of(owner))
|
||||
.map(|e| choice(p, e.id))
|
||||
.collect::<Vec<_>>(),
|
||||
"CommonWork/work",
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let path = format!("{path}/{leaf}");
|
||||
out=out.push(pick_list(choices,(!selected.is_nil()).then(||choice(p,selected)),move|choice|Message::FieldShape(path.clone(),json!(choice.id))).placeholder("Choose exact engineering identity…").padding(8).text_size(12).width(Fill)).push(text("Assembly incidence does not imply execution order or merge executions.").size(11));
|
||||
}
|
||||
}
|
||||
return Some(out.into());
|
||||
}
|
||||
None
|
||||
}
|
||||
@@ -51,6 +51,146 @@ pub(super) fn property<'a>(
|
||||
return Some(message_value(draft, project, value, path));
|
||||
}
|
||||
let leaf = path.rsplit('/').next()?;
|
||||
if leaf == "operation" && path.ends_with("/Occurrence/body/Exchange/operation") {
|
||||
let message = element(draft, project)?;
|
||||
let operation = match message.shared()? {
|
||||
SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange { operation, .. },
|
||||
..
|
||||
} => operation.as_ref(),
|
||||
_ => return None,
|
||||
};
|
||||
let choices = crate::data_values::operation_choices(project, &message);
|
||||
let selected = operation
|
||||
.and_then(|o| o.allocation)
|
||||
.map(|id| crate::app::Choice {
|
||||
id,
|
||||
label: crate::data_values::operation_identity(project, id),
|
||||
});
|
||||
let path = path.to_owned();
|
||||
let mut out = column![
|
||||
text("Exact interface operation allocation").size(12),
|
||||
pick_list(choices, selected, {
|
||||
let path = path.clone();
|
||||
move |choice| Message::FieldShape(path.clone(), json!({"allocation":choice.id}))
|
||||
})
|
||||
.placeholder("Choose an eligible allocation…")
|
||||
.text_size(12)
|
||||
.padding(8)
|
||||
.width(Fill),
|
||||
button("Keep operation unresolved").on_press(Message::FieldShape(
|
||||
path.clone(),
|
||||
json!({"allocation":null})
|
||||
)),
|
||||
button("Use ordinary channel semantics")
|
||||
.on_press(Message::FieldShape(path, Value::Null))
|
||||
]
|
||||
.spacing(7);
|
||||
if let Some(label) = crate::data_values::operation_label(project, &message) {
|
||||
out = out.push(text(label).size(11));
|
||||
}
|
||||
return Some(out.into());
|
||||
}
|
||||
if leaf == "Exchange" && path.contains("/Occurrence/") && value.is_object() {
|
||||
let mut out = column![].spacing(7);
|
||||
if let Some(message) = element(draft, project) {
|
||||
let operation_path = format!("{path}/operation");
|
||||
let body = message.shared();
|
||||
if let Some(SharedBody::Occurrence {
|
||||
body:
|
||||
OccurrenceBody::Exchange {
|
||||
message_kind,
|
||||
operation,
|
||||
..
|
||||
},
|
||||
..
|
||||
}) = body
|
||||
{
|
||||
let scenario = validation::ancestor_of_kind(project, message.id, Kind::Scenario);
|
||||
let boundary = scenario
|
||||
.and_then(|id| project.elements.get(&id))
|
||||
.is_some_and(|e| {
|
||||
matches!(
|
||||
e.shared(),
|
||||
Some(SharedBody::Scenario {
|
||||
viewpoint: ScenarioViewpoint::BoundaryInterface,
|
||||
..
|
||||
})
|
||||
)
|
||||
});
|
||||
if message.layer() == Layer::Sa
|
||||
&& boundary
|
||||
&& matches!(message_kind, MessageKind::Call | MessageKind::Asynchronous)
|
||||
{
|
||||
let enabled = operation.is_some();
|
||||
out = out.push(
|
||||
checkbox(enabled)
|
||||
.label("Invoke a specific interface operation")
|
||||
.on_toggle({
|
||||
let path = operation_path.clone();
|
||||
move |enabled| {
|
||||
Message::FieldShape(
|
||||
path.clone(),
|
||||
if enabled {
|
||||
json!({"allocation":null})
|
||||
} else {
|
||||
Value::Null
|
||||
},
|
||||
)
|
||||
}
|
||||
}),
|
||||
);
|
||||
if let Some(operation) = operation {
|
||||
let mut choices = crate::data_values::operation_choices(project, &message);
|
||||
let selected = operation.allocation.map(|id| crate::app::Choice {
|
||||
id,
|
||||
label: crate::data_values::operation_identity(project, id),
|
||||
});
|
||||
if let Some(selected) = &selected
|
||||
&& !choices.iter().any(|c| c.id == selected.id)
|
||||
{
|
||||
choices.push(selected.clone());
|
||||
}
|
||||
out = out
|
||||
.push(
|
||||
pick_list(choices, selected, {
|
||||
let path = operation_path.clone();
|
||||
move |choice| {
|
||||
Message::FieldShape(
|
||||
path.clone(),
|
||||
json!({"allocation":choice.id}),
|
||||
)
|
||||
}
|
||||
})
|
||||
.placeholder("Choose exact interface operation allocation…")
|
||||
.text_size(12)
|
||||
.padding(8)
|
||||
.width(Fill),
|
||||
)
|
||||
.push(button("Keep operation unresolved").on_press(
|
||||
Message::FieldShape(operation_path, json!({"allocation":null})),
|
||||
));
|
||||
}
|
||||
}
|
||||
if let Some(description) = crate::data_values::operation_label(project, &message) {
|
||||
out = out.push(text(description).size(11));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (key, field) in value.as_object()? {
|
||||
if key != "operation" {
|
||||
out = out.push(super::property(
|
||||
draft,
|
||||
project,
|
||||
field,
|
||||
&format!("{path}/{key}"),
|
||||
&friendly(key),
|
||||
false,
|
||||
));
|
||||
}
|
||||
}
|
||||
return Some(out.into());
|
||||
}
|
||||
if leaf == "direction" && path.contains("/DataField/") {
|
||||
let operation = element(draft, project)
|
||||
.and_then(|e| e.parent)
|
||||
@@ -315,21 +455,30 @@ fn message_value<'a>(
|
||||
let row = edited.pointer(path).unwrap_or(value).clone();
|
||||
let current = serde_json::from_value::<MessageValue>(row.clone()).ok();
|
||||
let owner = element(draft, project);
|
||||
let context = owner
|
||||
let signature = owner.as_ref().and_then(|message| {
|
||||
crate::data_values::message_signature(project, message)
|
||||
.ok()
|
||||
.flatten()
|
||||
});
|
||||
let phase = owner
|
||||
.as_ref()
|
||||
.and_then(Element::shared)
|
||||
.and_then(|body| match body {
|
||||
SharedBody::Occurrence {
|
||||
body:
|
||||
OccurrenceBody::Exchange {
|
||||
exchange: Some(id),
|
||||
message_kind,
|
||||
operation,
|
||||
..
|
||||
},
|
||||
..
|
||||
} => project.relations.get(id).map(|r| (r, message_kind)),
|
||||
} => Some(crate::data_values::operation_value_phase(
|
||||
message_kind,
|
||||
operation.as_ref(),
|
||||
)),
|
||||
_ => None,
|
||||
});
|
||||
let context = signature.as_ref().zip(phase.as_ref());
|
||||
let current_key = current
|
||||
.as_ref()
|
||||
.map(|value| crate::data_values::FieldPathKey {
|
||||
@@ -788,6 +937,7 @@ mod tests {
|
||||
.shared_mut()
|
||||
.unwrap() = SharedBody::Occurrence {
|
||||
body: OccurrenceBody::Exchange {
|
||||
operation: None,
|
||||
exchange: Some(f.exchange),
|
||||
sender: None,
|
||||
receiver: None,
|
||||
|
||||
Reference in New Issue
Block a user