Add exact Process selection and atomic column-slot group reorder

Preserve the full-desktop-1 captured source. Qualified previews inspect their exact subject read-only; group movement preserves source order and other columns. Native acceptance and strict-lint payload-size corrections follow separately.
This commit is contained in:
2026-09-23 05:39:10 -07:00
parent bed7f825fd
commit 1711bff650
25 changed files with 3993 additions and 145 deletions
+66 -2
View File
@@ -268,6 +268,7 @@ mod direct_scenario_tests {
#[cfg(test)]
mod behavior_lifecycle_tests;
mod process_canvas;
mod process_multiselect;
mod state_diagram;
#[cfg(test)]
mod state_diagram_tests;
@@ -818,6 +819,9 @@ impl App {
.map(|n| n.id);
}
self.draft = Draft::from_project(self.project(), self.selected);
if self.process_preview_primary().is_some() {
self.draft = None;
}
if self.exact_strings
&& let Some(d) = &mut self.draft
{
@@ -1294,6 +1298,7 @@ impl App {
])
}
pub fn update(&mut self, message: Message) -> Task<Message> {
let preview_read_only = self.process_preview_primary().is_some();
let selection_boundary = self.multiselect_boundary();
// Reject delayed gestures before they can retire newer input ownership.
if let Message::DiagramSelection(event)
@@ -1326,6 +1331,52 @@ impl App {
// or allocation ownership before its edit is refused.
return Task::none();
}
if let Message::TopologyCanvas(event) = &message {
use crate::behavior::topology::CanvasEvent;
if !matches!(
event,
CanvasEvent::Camera(..) | CanvasEvent::Scrolled(..) | CanvasEvent::Viewport(..)
) && self.topology_context() != Some(event.context())
{
return Task::none();
}
if matches!(
event,
CanvasEvent::Select(..)
| CanvasEvent::SelectQualified(..)
| CanvasEvent::Remove(..)
| CanvasEvent::Reorder { .. }
| CanvasEvent::Reconnect { .. }
) && self
.multiselect_snapshot()
.is_some_and(|s| s.targets().len() > 1)
{
return Task::none();
}
}
if self.process_order_noop(&message) {
return Task::none();
}
if self.process_preview_primary().is_some()
&& matches!(
&message,
Message::Field(..)
| Message::FieldText(..)
| Message::FieldShape(..)
| Message::FieldAppend(..)
| Message::FieldValuePath(..)
| Message::FieldVariant(..)
| Message::FieldArray(..)
| Message::ElementKind(_)
| Message::RelationKind(_)
| Message::Apply
| Message::Delete
| Message::DiagramRemove(_)
)
{
self.status = "Open the referenced definition to edit its contents.".into();
return Task::none();
}
// Captured Text commands validate their origin before they can
// invalidate that same origin or pass into existing command handlers.
if let Message::TextWorkspace(event) = message {
@@ -1420,6 +1471,9 @@ impl App {
{
self.reconcile_multiselect();
}
if preview_read_only && self.process_preview_primary().is_none() && self.draft.is_none() {
self.refresh_draft();
}
task
}
fn behavior_control(&mut self, intent: behavior::canvas_workspace::Intent) -> Task<Message> {
@@ -2333,6 +2387,9 @@ impl App {
self.route_context()
};
}
if request.process.is_some() {
request.process = self.topology_context();
}
self.model_menu = crate::model_menu::Opened::new(self, request, self.menu_sequence);
}
Message::Menu(crate::model_menu::Event::Close) => {
@@ -2362,7 +2419,10 @@ impl App {
"Apply or revert pending edits before using modeling actions.".into();
return Task::none();
}
if matches!(*action, Message::DiagramSelection(_)) {
if matches!(*action, Message::DiagramSelection(_))
|| (menu.request.process.is_some()
&& matches!(*action, Message::TopologyCanvas(_)))
{
return self.update(*action);
}
self.select(menu.request.object);
@@ -3215,7 +3275,11 @@ impl App {
.multiselect_snapshot()
.is_some_and(|s| s.targets().len() > 1)
{
self.status = "Group model-deletion review is not available yet. Select one element or use Hide selected.".into();
self.status = if self.topology_context().is_some() {
"Group removal needs an aggregate review. Select one contribution to remove it individually."
} else {
"Group model-deletion review is not available yet. Select one element or use Hide selected."
}.into();
return Task::none();
}
if self.selected != self.project().id && self.apply() {