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
+151 -54
View File
@@ -1,3 +1,4 @@
pub(crate) mod traceability;
use crate::{
app::{App, Choice, FileOperation, Message, Page, Pending},
diagram::DiagramCanvas,
@@ -192,9 +193,11 @@ pub fn view(app: &App) -> View<'_, Message> {
]
.spacing(3),
)
.push(crate::explorer_view::sidebar(app))
.push(creation)
.push(action("Project properties", Message::SelectProject).width(Fill));
.push(crate::explorer_view::sidebar(app));
if app.page != Page::Diagram {
nav = nav.push(creation);
}
nav = nav.push(action("Project properties", Message::SelectProject).width(Fill));
let sidebar = container(if app.page == Page::Text {
source_files(app)
} else {
@@ -224,12 +227,33 @@ pub fn view(app: &App) -> View<'_, Message> {
Page::Validation => checks(app),
Page::Guide => crate::guide::view(&app.guide),
};
let process_layout = app.topology_context().is_some();
let focus_process = process_layout && app.behavior.topology.focused;
let mut body = if app.page == Page::Home {
row![container(main).width(Fill).height(Fill)].height(Fill)
} else if process_layout {
// Keep main at the same widget-tree path across Focus toggles. The
// collapsed slot has no controls, so it cannot paint over or hit the canvas.
let process_sidebar: View<'_, Message> = if focus_process {
Space::new().width(0).into()
} else {
sidebar.into()
};
// Row::push would discard the zero-width slot and shift main again.
widget::Row::from_vec(vec![
container(process_sidebar)
.width(if focus_process { 0 } else { 260 })
.height(Fill)
.clip(true)
.into(),
container(main).width(Fill).height(Fill).into(),
])
.width(Fill)
.height(Fill)
} else {
row![sidebar, container(main).width(Fill).height(Fill)].height(Fill)
};
if !matches!(app.page, Page::Home | Page::Text | Page::Guide) {
if !focus_process && !matches!(app.page, Page::Home | Page::Text | Page::Guide) {
body = body.push(inspector(app));
}
let footer = container(
@@ -259,6 +283,13 @@ pub fn view(app: &App) -> View<'_, Message> {
.padding([4, 16])
.style(style::panel);
let root: View<'_, Message> = column![header, task_nav, body, footer].into();
if app.architecture.as_ref().is_some_and(|s| s.modal()) {
return modal_width(
root,
crate::app::architecture_authoring::dialog(app),
(app.window_size.width - 50.).clamp(500., 820.),
);
}
if let Some(workflow) = &app.workflow {
if matches!(workflow, crate::workflow::Workflow::Semantic(_)) {
return modal_width(
@@ -345,23 +376,17 @@ fn modal_width<'a>(
.into()
}
fn diagram(app: &App) -> View<'_, Message> {
let diagrams = app
let mut diagrams = app
.project()
.diagrams
.values()
.filter(|d| d.layer == app.layer || d.projection == ProjectionKind::Traceability)
.map(|d| Choice {
id: d.id,
label: short(
&d.name,
if app.window_size.width < 1250. {
26
} else {
48
},
),
label: d.name.clone(),
})
.collect::<Vec<_>>();
diagrams.sort_by(|a, b| a.label.cmp(&b.label).then(a.id.cmp(&b.id)));
let selected = diagrams
.iter()
.find(|c| Some(c.id) == app.active_diagram)
@@ -427,16 +452,26 @@ fn diagram(app: &App) -> View<'_, Message> {
.filter(|(edge, _)| *edge == app.selected)
.map(|(_, i)| i),
editable: !app.draft_dirty() && !app.source_dirty(),
placing: app.architecture.as_ref().is_some_and(|s| s.saved.placing),
connection: app.connection.as_ref(),
};
widget::sensor(widget::stack![
let mut scene = widget::stack![
widget::canvas(program).width(Fill).height(Fill),
widget::canvas(crate::diagram::PortLabels(program))
.width(Fill)
.height(Fill),
])
.on_show(Message::CanvasResized)
.on_resize(Message::CanvasResized)
];
if let Some(editor) = crate::app::architecture_authoring::inline_editor(app) {
scene = scene.push(editor);
}
row![
widget::sensor(scene)
.on_show(Message::CanvasResized)
.on_resize(Message::CanvasResized),
crate::app::architecture_authoring::palette(app)
]
.height(Fill)
.width(Fill)
.into()
} else {
container(
@@ -459,20 +494,10 @@ fn diagram(app: &App) -> View<'_, Message> {
.center(Fill)
.into()
};
let choose = |tool: Option<crate::connection_tool::Tool>| {
let active = app.connection.as_ref().map(|s| s.tool) == tool;
button(text(tool.map_or("Select / move", |t| t.label())).size(12))
.padding([8, 10])
.on_press(Message::Connection(crate::connection_edit::Event::Choose(
tool,
)))
.style(if active { button::primary } else { style::nav })
};
let mut palette = row![choose(None)].spacing(4);
for tool in crate::connection_tool::Tool::for_layer(app.layer) {
palette = palette.push(choose(Some(*tool)));
let mut tools = column![toolbar].spacing(8);
if app.active_diagram.is_some() {
tools = tools.push(modes);
}
let mut tools = column![toolbar, modes, palette].spacing(8);
if let Some(diagram) = app.active_diagram {
let view = oa_core::projection::project_view(app.project(), diagram);
let edge = view.edges.iter().find(|edge| edge.id == app.selected);
@@ -726,12 +751,22 @@ fn inspector(app: &App) -> View<'_, Message> {
);
}
if let Some(r) = p.relations.get(&app.selected) {
body = body.push(text(r.kind().to_string()).size(17)).push(
pick_list(RelationKind::ALL, Some(r.kind()), Message::RelationKind)
body = body.push(text(r.kind().to_string()).size(17));
if r.kind() != RelationKind::ContextualLink {
body = body.push(
pick_list(
RelationKind::ALL
.into_iter()
.filter(|kind| *kind != RelationKind::ContextualLink)
.collect::<Vec<_>>(),
Some(r.kind()),
Message::RelationKind,
)
.text_size(12)
.padding(8)
.width(Fill),
);
);
}
}
body = body.push(
row![
@@ -771,27 +806,81 @@ fn data_catalog(app: &App) -> View<'_, Message> {
fn traces(app: &App) -> View<'_, Message> {
let p = app.project();
let mut out = column![
use traceability::{Event as E, Filter};
let (traces, artifacts) = app.traceability.matching(p);
let mut tabs = row![].spacing(6);
for (filter, label) in [
(
Filter::All,
format!("All ({})", p.traces.len() + p.artifact_links.len()),
),
(Filter::Traces, format!("Layer traces ({})", p.traces.len())),
(
Filter::Artifacts,
format!("Development artifacts ({})", p.artifact_links.len()),
),
] {
tabs = tabs.push(
button(text(label).size(12))
.style(if app.traceability.filter == filter {
button::primary
} else {
style::nav
})
.on_press(Message::Traceability(E::Filter(filter))),
);
}
let header = column![
heading(
"Traceability",
"Connect operational needs, system responsibilities and development evidence."
),
row![
button(text("Create counterpart / review prose…").size(12))
.padding(9)
.on_press(Message::StartTransition),
button(
text(
if p.layer_of(app.selected).unwrap_or(app.layer) == Layer::Oa {
"Create System counterpart…"
} else {
"Create Operational counterpart…"
}
)
.size(12)
)
.padding(9)
.on_press(Message::StartTransition),
action("+ Manual trace", Message::AddTrace),
action(
"Review semantics…",
"Review counterpart changes…",
Message::StartSemanticReview(app.selected)
),
action("+ Artifact", Message::AddArtifact)
]
.spacing(8)
.wrap()
.wrap(),
tabs.wrap(),
row![
text_input(
"Search names, aliases, owners or artifact URI…",
&app.traceability.query
)
.on_input(|raw| Message::Traceability(E::Search(raw)))
.padding(8)
.size(12)
.width(Fill),
button("Clear search").on_press(Message::Traceability(E::Search(String::new())))
]
.spacing(6),
label(format!(
"{} matching traces · {} matching artifacts",
traces.len(),
artifacts.len()
))
]
.spacing(16);
for t in p.traces.values() {
.spacing(10)
.padding(24);
let mut out = column![].spacing(16);
for id in &traces {
let t = &p.traces[id];
let current = match (&t.baseline, p.properties(t.oa), p.properties(t.sa)) {
(Some(b), Some(oa), Some(sa)) if b.oa == oa && b.sa == sa => "Reviewed",
(Some(_), _, _) => "Review needed",
@@ -810,7 +899,10 @@ fn traces(app: &App) -> View<'_, Message> {
row![
label(format!("{:?} · Prose: {current}", t.kind)),
Space::new().width(Fill),
action("Review semantics…", Message::StartSemanticReview(t.id)),
action(
"Review counterpart changes…",
Message::StartSemanticReview(t.id)
),
action("Edit link", Message::Select(t.id))
],
text(crate::semantic_review::summary(p, t))
@@ -824,17 +916,12 @@ fn traces(app: &App) -> View<'_, Message> {
.style(style::card),
);
}
out = out.push(text("Development artifacts").size(18));
for a in p.artifact_links.values() {
let owner = p
.diagrams
.values()
.find_map(|d| {
d.nodes
.get(&a.owner)
.map(|n| format!("Placement of {} · {}", p.named(n.element), d.name))
})
.unwrap_or_else(|| p.named(a.owner).to_owned());
if !artifacts.is_empty() {
out = out.push(text("Development artifacts").size(18));
}
for id in &artifacts {
let a = &p.artifact_links[id];
let owner = traceability::owner_label(p, a.owner);
out = out.push(
container(
column![
@@ -860,8 +947,18 @@ fn traces(app: &App) -> View<'_, Message> {
}
if p.traces.is_empty() && p.artifact_links.is_empty() {
out=out.push(text("Select a model element, then connect it to the other perspective or attach a code, CAD, schematic or document reference.").size(14).color(style::MUTED));
} else if traces.is_empty() && artifacts.is_empty() {
out = out
.push(text("No matching records. Clear the search or choose another filter.").size(13));
}
scrollable(out.padding(24)).into()
column![
header,
scrollable(out.padding(iced::Padding::from(24).right(42)))
.id("traceability-list")
.height(Fill)
]
.height(Fill)
.into()
}
fn source_files(app: &App) -> View<'_, Message> {