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
@@ -30,6 +30,9 @@ pub enum CanvasEvent {
Scrolled(Context, scenario::ScrollArea, iced::Vector),
Choose(Context, CanvasTool),
Select(Context, Id),
SelectQualified(Context, OccurrenceEndpoint),
ExpandQualified(Context, OccurrenceEndpoint),
OpenQualified(Context, OccurrenceEndpoint),
AppendExchange(Context),
Begin {
context: Context,
@@ -57,6 +60,7 @@ pub enum CanvasEvent {
Open(Context, Id),
Back(Context),
Zoom(Context, Option<f32>),
Focus(Context),
Viewport(Context, Size),
}
impl CanvasEvent {
@@ -65,11 +69,15 @@ impl CanvasEvent {
Self::Scrolled(c, ..)
| Self::Choose(c, ..)
| Self::Select(c, ..)
| Self::SelectQualified(c, ..)
| Self::ExpandQualified(c, ..)
| Self::OpenQualified(c, ..)
| Self::Expand(c, ..)
| Self::Open(c, ..)
| Self::Back(c)
| Self::AppendExchange(c)
| Self::Zoom(c, ..)
| Self::Focus(c)
| Self::Viewport(c, ..) => *c,
Self::Begin { context, .. }
| Self::Connect { context, .. }
@@ -79,6 +87,53 @@ impl CanvasEvent {
}
}
impl State {
pub(crate) fn select_context(&mut self, address: oa_core::topology_context::ContextAddress) {
self.selected_context = Some(address);
}
pub(crate) fn clear_context(&mut self) {
self.selected_context = None;
}
pub(super) fn qualified_selection(
&self,
p: &Project,
owner: Id,
) -> Option<&oa_core::topology_context::ContextAddress> {
self.selected_context.as_ref().filter(|address| {
address.owner == owner
&& oa_core::topology_context::TopologyIndex::new(p)
.resolve(owner, &address.endpoint)
.is_ok()
})
}
pub(crate) fn expand_context(
&mut self,
p: &Project,
owner: Id,
endpoint: OccurrenceEndpoint,
) -> bool {
if oa_core::topology_context::TopologyIndex::new(p)
.resolve(owner, &endpoint)
.is_err()
|| !matches!(
p.elements
.get(&endpoint.occurrence)
.and_then(|e| e.shared()),
Some(SharedBody::Occurrence {
body: OccurrenceBody::SequenceReference { sequence: Some(_) },
..
})
)
{
return false;
}
if let Some(root) = endpoint.references.first() {
self.expanded.insert(*root);
}
if !self.contextual_expanded.remove(&endpoint) {
self.contextual_expanded.insert(endpoint);
}
true
}
pub(crate) fn enter(&mut self, document: Id, owner: Id) {
if self.identity != Some((document, owner)) {
self.identity = Some((document, owner));
@@ -90,6 +145,7 @@ impl State {
self.scroll_offsets = [iced::Vector::ZERO; 2];
}
}
#[cfg(test)]
pub(crate) fn scroll_offset(&self) -> iced::Vector {
self.scroll_offsets[0] + self.scroll_offsets[1]
}
@@ -133,11 +189,18 @@ impl State {
true
}
pub(crate) fn remember(&mut self, owner: Id, occurrence: Id) {
self.return_to.push((owner, occurrence));
self.return_to
.push((owner, occurrence, self.selected_context.clone()));
}
pub(crate) fn back(&mut self, p: &Project) -> Option<(Id, Id)> {
while let Some((owner, occurrence)) = self.return_to.pop() {
while let Some((owner, occurrence, address)) = self.return_to.pop() {
if is_contribution(p, owner, occurrence) {
self.selected_context = address.filter(|address| {
address.owner == owner
&& oa_core::topology_context::TopologyIndex::new(p)
.resolve(owner, &address.endpoint)
.is_ok()
});
return Some((owner, occurrence));
}
}
@@ -145,6 +208,11 @@ impl State {
}
pub(crate) fn created_selection(&self, command: &Command) -> Option<Id> {
let owner = self.pending_owner()?;
if let Command::CreateRelation(r) | Command::EditRelation(_, r) = command
&& matches!(&r.body, RelationBody::ContextualLink { owner: caller, .. } if *caller == owner)
{
return Some(r.id);
}
let Command::Batch(commands) = command else {
return None;
};
@@ -355,6 +423,20 @@ impl Graph<'_> {
.find(|n| n.rect.contains(point))
.map(|n| n.id)
}
fn qualified_at(&self, point: Point) -> Option<OccurrenceEndpoint> {
self.nodes.iter().find_map(|node| {
node.preview.iter().enumerate().find_map(|(i, (id, _))| {
let rect = Rectangle::new(
Point::new(
node.rect.x + 12. + (i % 2) as f32 * 125.,
node.rect.y + 118. + (i / 2) as f32 * 44.,
),
Size::new(113., 32.),
);
rect.contains(point).then(|| id.clone())
})
})
}
fn edge_at(&self, point: Point) -> Option<Id> {
self.edges
.iter()
@@ -366,6 +448,19 @@ impl Graph<'_> {
})
.map(|e| e.id)
}
fn contextual_edge_at(&self, point: Point) -> Option<Id> {
self.edges
.iter()
.filter(|edge| edge.layer == EdgeLayer::Contextual)
.find(|edge| {
edge.label.contains(point)
|| edge.points.windows(2).any(|segment| {
crate::diagram::distance_to_segment(point, segment[0], segment[1])
< 8. / self.scale
})
})
.map(|edge| edge.id)
}
fn handle(&self, id: Id) -> Option<Rectangle> {
let n = self.nodes.iter().find(|n| n.id == id)?;
Some(Rectangle::new(
@@ -374,6 +469,9 @@ impl Graph<'_> {
))
}
fn endpoint_at(&self, point: Point) -> Option<(Id, End)> {
if self.project.relations.get(&self.selected)?.kind() != RelationKind::SequenceLink {
return None;
}
let e = self.edges.iter().find(|e| e.id == self.selected)?;
[(e.points[0], End::Source), (*e.points.last()?, End::Target)]
.into_iter()
@@ -497,13 +595,17 @@ impl Graph<'_> {
});
}
CanvasTool::Select => {
if let Some((link, end)) = self.endpoint_at(point) {
if let Some(endpoint) = self.qualified_at(point) {
return publish(CanvasEvent::SelectQualified(context, endpoint));
} else if let Some((link, end)) = self.endpoint_at(point) {
state.press = Some(Press::Reconnect {
link,
end,
start,
moved: false,
});
} else if let Some(link) = self.contextual_edge_at(point) {
return publish(CanvasEvent::Select(context, link));
} else if self
.handle(self.selected)
.is_some_and(|r| r.contains(point))
@@ -728,13 +830,17 @@ impl State {
owner: Id,
id: Id,
) -> Option<scrollable::AbsoluteOffset> {
let graph = Graph::expanded(p, owner, id, &self.expanded);
let graph =
Graph::expanded_qualified(p, owner, id, &self.expanded, &self.contextual_expanded);
let scale = self.scale(graph.size, 500.);
let rect = graph
.nodes
.iter()
.find(|n| n.id == id)
.map(|n| n.rect)
.map(|n| {
self.qualified_selection(p, owner)
.map_or(n.rect, |a| n.endpoint_rect(&a.endpoint))
})
.or_else(|| graph.edges.iter().find(|e| e.id == id).map(|e| e.label))?;
Some(scrollable::AbsoluteOffset {
x: (rect.x * scale - 12.).max(0.),