Preserve editor input context through diagram help
@@ -380,6 +380,7 @@ pub enum Message {
|
||||
Guide(crate::guide::Event),
|
||||
ContextHelp(crate::guide::Topic),
|
||||
CloseContextHelp,
|
||||
ContextHelpScroll(crate::guide::ContextScroll),
|
||||
Help,
|
||||
Traceability(crate::views::traceability::Event),
|
||||
ReviewLayerTraces,
|
||||
@@ -1301,6 +1302,11 @@ impl App {
|
||||
iced::time::every(std::time::Duration::from_secs(10)).map(|_| Message::CheckpointTick),
|
||||
window::close_requests().map(|id| Message::Request(Pending::Close(id))),
|
||||
window::resize_events().map(|(_, size)| Message::WindowResized(size)),
|
||||
if self.context_help {
|
||||
iced::event::listen_with(crate::guide::context_scroll_key)
|
||||
} else {
|
||||
Subscription::none()
|
||||
},
|
||||
iced::event::listen_with(|event, _, _| {
|
||||
matches!(
|
||||
event,
|
||||
@@ -1355,6 +1361,13 @@ impl App {
|
||||
])
|
||||
}
|
||||
pub fn update(&mut self, message: Message) -> Task<Message> {
|
||||
if let Message::ContextHelpScroll(scroll) = message {
|
||||
return if self.context_help {
|
||||
crate::guide::context_scroll_task(scroll)
|
||||
} else {
|
||||
Task::none()
|
||||
};
|
||||
}
|
||||
// Help owns user input before any canvas/reference fast path can act.
|
||||
// Passive results may finish; they retain their normal lifetime checks.
|
||||
if self.context_help
|
||||
@@ -2852,6 +2865,7 @@ impl App {
|
||||
Message::CloseContextHelp => {
|
||||
self.context_help = false;
|
||||
}
|
||||
Message::ContextHelpScroll(_) => {}
|
||||
Message::Help => {
|
||||
let topic = match self.page {
|
||||
Page::Diagram => Some(crate::guide::Topic::Nesting),
|
||||
|
||||
@@ -220,7 +220,8 @@ fn context_help_f1_selects_scenario_topic_without_leaving_the_workspace() {
|
||||
#[test]
|
||||
fn context_help_disables_allocation_input_including_early_work_drag_dispatch() {
|
||||
let mut project = Project::empty();
|
||||
project.ensure_diagram(Layer::Oa);
|
||||
let diagram = project.ensure_diagram(Layer::Oa);
|
||||
project.diagrams.get_mut(&diagram).unwrap().projection = ProjectionKind::Allocation;
|
||||
let mut app = App::with_project(project);
|
||||
assert!(app.allocation_input_owner());
|
||||
send(&mut app, Message::Help);
|
||||
@@ -346,3 +347,356 @@ fn context_help_accepts_live_process_viewport_and_rejects_stale_measurements() {
|
||||
assert!(Arc::ptr_eq(&before, &app.document.snapshot()));
|
||||
assert!(!app.document.can_undo());
|
||||
}
|
||||
|
||||
async fn help_key(
|
||||
mounted: &mut crate::behavior::canvas_workspace::tests::Mounted,
|
||||
app: &mut App,
|
||||
event: iced::Event,
|
||||
) -> Vec<Message> {
|
||||
let (mut messages, statuses) = mounted.input_with_status(
|
||||
app,
|
||||
std::slice::from_ref(&event),
|
||||
iced::mouse::Cursor::Unavailable,
|
||||
);
|
||||
super::escape::tests::subscriptions(app, event, statuses[0], &mut messages);
|
||||
for message in messages.iter().cloned() {
|
||||
let task = app.update(message);
|
||||
mounted.perform(app, task).await;
|
||||
}
|
||||
messages
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mounted_context_help_keyboard_scroll_preserves_inspector_focus_and_exact_caret() {
|
||||
use crate::behavior::canvas_workspace::tests::{Mounted, character, key};
|
||||
use iced::keyboard::key::Named;
|
||||
let (mut app, diagram, a, _) = architecture();
|
||||
let element = app.project().diagrams[&diagram].nodes[&a].element;
|
||||
send(&mut app, Message::Select(element));
|
||||
let before = app.document.snapshot();
|
||||
let mut mounted = Mounted::new(&mut app).await;
|
||||
let field = mounted
|
||||
.geometry(&app)
|
||||
.text_inputs
|
||||
.into_iter()
|
||||
.filter(|bounds| bounds.x > app.window_size.width - 320.)
|
||||
.min_by(|a, b| a.y.total_cmp(&b.y))
|
||||
.expect("visible Inspector Name input");
|
||||
mounted.click(&mut app, field.center()).await;
|
||||
help_key(&mut mounted, &mut app, key(Named::Home)).await;
|
||||
help_key(&mut mounted, &mut app, character("DRAFT ")).await;
|
||||
help_key(&mut mounted, &mut app, key(Named::ArrowLeft)).await;
|
||||
assert_eq!(
|
||||
app.draft.as_ref().unwrap().inputs["/name"].raw,
|
||||
"DRAFT Send"
|
||||
);
|
||||
let camera = (app.zoom, app.pan);
|
||||
help_key(&mut mounted, &mut app, key(Named::F1)).await;
|
||||
assert!(app.context_help);
|
||||
let article = iced::advanced::widget::Id::new(crate::guide::ARTICLE_SCROLL_ID);
|
||||
let geometry = mounted.geometry(&app);
|
||||
let background_scrolls = geometry
|
||||
.scrolls
|
||||
.iter()
|
||||
.filter(|(id, _)| **id != article)
|
||||
.map(|(id, (_, offset))| (id.clone(), *offset))
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(geometry.scrolls[&article].1.y, 0.);
|
||||
|
||||
help_key(&mut mounted, &mut app, key(Named::PageDown)).await;
|
||||
let down = mounted.geometry(&app).scrolls[&article].1.y;
|
||||
assert!(down > 0., "PageDown scrolls the mounted Help article");
|
||||
help_key(&mut mounted, &mut app, key(Named::End)).await;
|
||||
let end = mounted.geometry(&app).scrolls[&article].1.y;
|
||||
assert!(end > down);
|
||||
help_key(&mut mounted, &mut app, key(Named::PageDown)).await;
|
||||
assert_eq!(mounted.geometry(&app).scrolls[&article].1.y, end);
|
||||
help_key(&mut mounted, &mut app, key(Named::PageUp)).await;
|
||||
assert!(mounted.geometry(&app).scrolls[&article].1.y < end);
|
||||
help_key(&mut mounted, &mut app, key(Named::Home)).await;
|
||||
assert_eq!(mounted.geometry(&app).scrolls[&article].1.y, 0.);
|
||||
help_key(&mut mounted, &mut app, key(Named::ArrowDown)).await;
|
||||
assert_eq!(mounted.geometry(&app).scrolls[&article].1.y, 40.);
|
||||
help_key(&mut mounted, &mut app, key(Named::ArrowUp)).await;
|
||||
help_key(&mut mounted, &mut app, key(Named::PageUp)).await;
|
||||
let geometry = mounted.geometry(&app);
|
||||
assert_eq!(geometry.scrolls[&article].1.y, 0.);
|
||||
for (id, offset) in background_scrolls {
|
||||
assert_eq!(
|
||||
geometry.scrolls[&id].1, offset,
|
||||
"Help only scrolls its own article"
|
||||
);
|
||||
}
|
||||
help_key(&mut mounted, &mut app, character("IGNORED")).await;
|
||||
let messages = mounted
|
||||
.batch(
|
||||
&mut app,
|
||||
&[
|
||||
iced::Event::InputMethod(iced::advanced::input_method::Event::Preedit(
|
||||
"雨".into(),
|
||||
None,
|
||||
)),
|
||||
iced::Event::InputMethod(iced::advanced::input_method::Event::Commit("雨".into())),
|
||||
],
|
||||
iced::mouse::Cursor::Unavailable,
|
||||
)
|
||||
.await;
|
||||
assert!(!messages.iter().any(|m| matches!(m, Message::Field(..))));
|
||||
assert_eq!(
|
||||
app.draft.as_ref().unwrap().inputs["/name"].raw,
|
||||
"DRAFT Send"
|
||||
);
|
||||
help_key(&mut mounted, &mut app, key(Named::Escape)).await;
|
||||
assert!(!app.context_help);
|
||||
help_key(&mut mounted, &mut app, character("X")).await;
|
||||
assert_eq!(
|
||||
app.draft.as_ref().unwrap().inputs["/name"].raw,
|
||||
"DRAFTX Send",
|
||||
"Return keeps the actual mid-string caret without refocusing a guessed field"
|
||||
);
|
||||
assert_eq!((app.zoom, app.pan), camera);
|
||||
assert!(Arc::ptr_eq(&before, &app.document.snapshot()));
|
||||
let messages = help_key(&mut mounted, &mut app, key(Named::PageDown)).await;
|
||||
assert!(
|
||||
!messages
|
||||
.iter()
|
||||
.any(|m| matches!(m, Message::ContextHelpScroll(_)))
|
||||
);
|
||||
assert!(
|
||||
iced_runtime::task::into_stream(
|
||||
app.update(Message::ContextHelpScroll(crate::guide::ContextScroll::End,))
|
||||
)
|
||||
.is_none(),
|
||||
"A late Help scroll cannot operate after closing"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mounted_context_help_observes_modifier_release_before_plain_canvas_click() {
|
||||
use crate::behavior::canvas_workspace::tests::{Mounted, key};
|
||||
use iced::{
|
||||
keyboard::{Event, Modifiers, key::Named},
|
||||
mouse,
|
||||
};
|
||||
let (mut app, diagram, a, b) = architecture();
|
||||
choose(&mut app, a, multiselect::Choice::Replace);
|
||||
app.window_size = iced::Size::new(2000., 1400.);
|
||||
let mut mounted = Mounted::new(&mut app).await;
|
||||
app.zoom = 1.;
|
||||
app.pan = iced::Vector::ZERO;
|
||||
app.fit_to_view = false;
|
||||
mounted.settle(&mut app).await;
|
||||
mounted
|
||||
.batch(
|
||||
&mut app,
|
||||
&[iced::Event::Keyboard(Event::ModifiersChanged(
|
||||
Modifiers::SHIFT,
|
||||
))],
|
||||
mouse::Cursor::Unavailable,
|
||||
)
|
||||
.await;
|
||||
help_key(&mut mounted, &mut app, key(Named::F1)).await;
|
||||
assert!(app.context_help);
|
||||
mounted
|
||||
.batch(
|
||||
&mut app,
|
||||
&[iced::Event::Keyboard(Event::ModifiersChanged(
|
||||
Modifiers::empty(),
|
||||
))],
|
||||
mouse::Cursor::Unavailable,
|
||||
)
|
||||
.await;
|
||||
help_key(&mut mounted, &mut app, key(Named::Escape)).await;
|
||||
let bounds = mounted
|
||||
.geometry(&app)
|
||||
.bounds(crate::allocation_input::CANVAS_ID);
|
||||
let node = projection::project_view(app.project(), diagram)
|
||||
.nodes
|
||||
.into_iter()
|
||||
.find(|node| node.id == b)
|
||||
.unwrap();
|
||||
let at = iced::Point::new(
|
||||
bounds.x + node.position.x + 40.,
|
||||
bounds.y + node.position.y + 35.,
|
||||
);
|
||||
mounted.click(&mut app, at).await;
|
||||
assert_eq!(
|
||||
app.multiselect_snapshot().unwrap().targets(),
|
||||
&[Target::Placement(b)],
|
||||
"Shift released during Help must not add the next card to the previous selection"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mounted_context_help_retires_held_pan_without_replaying_its_release() {
|
||||
use crate::behavior::canvas_workspace::tests::{Mounted, key};
|
||||
use iced::{keyboard::key::Named, mouse};
|
||||
let (mut app, _, _, _) = architecture();
|
||||
let mut mounted = Mounted::new(&mut app).await;
|
||||
let canvas = mounted
|
||||
.geometry(&app)
|
||||
.bounds(crate::allocation_input::CANVAS_ID);
|
||||
let start = canvas.center();
|
||||
let moved = start + iced::Vector::new(20., 10.);
|
||||
mounted
|
||||
.batch(
|
||||
&mut app,
|
||||
&[
|
||||
iced::Event::Mouse(mouse::Event::CursorMoved { position: start }),
|
||||
iced::Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Middle)),
|
||||
iced::Event::Mouse(mouse::Event::CursorMoved { position: moved }),
|
||||
],
|
||||
mouse::Cursor::Available(moved),
|
||||
)
|
||||
.await;
|
||||
let camera = (app.zoom, app.pan);
|
||||
let before = app.document.snapshot();
|
||||
help_key(&mut mounted, &mut app, key(Named::F1)).await;
|
||||
assert!(app.context_help);
|
||||
mounted
|
||||
.batch(
|
||||
&mut app,
|
||||
&[iced::Event::Mouse(mouse::Event::ButtonReleased(
|
||||
mouse::Button::Middle,
|
||||
))],
|
||||
mouse::Cursor::Unavailable,
|
||||
)
|
||||
.await;
|
||||
help_key(&mut mounted, &mut app, key(Named::Escape)).await;
|
||||
let next = moved + iced::Vector::new(90., 50.);
|
||||
let messages = mounted
|
||||
.batch(
|
||||
&mut app,
|
||||
&[iced::Event::Mouse(mouse::Event::CursorMoved {
|
||||
position: next,
|
||||
})],
|
||||
mouse::Cursor::Available(next),
|
||||
)
|
||||
.await;
|
||||
assert!(!messages.iter().any(|m| matches!(m, Message::Pan(_))));
|
||||
assert_eq!(
|
||||
(app.zoom, app.pan),
|
||||
camera,
|
||||
"Moving without a held button cannot resume the pre-Help pan"
|
||||
);
|
||||
assert!(Arc::ptr_eq(&before, &app.document.snapshot()));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn context_help_barrier_retires_text_selection_drag_without_changing_the_caret() {
|
||||
use iced::advanced::{Layout, layout, widget};
|
||||
use iced::{Element, Event, Rectangle, mouse};
|
||||
type InputState = iced::widget::text_input::State<
|
||||
<iced::Renderer as iced::advanced::text::Renderer>::Paragraph,
|
||||
>;
|
||||
fn input(open: bool) -> Element<'static, Message> {
|
||||
crate::guide::protect_workspace(
|
||||
open,
|
||||
iced::widget::text_input("Name", "A long retained property name")
|
||||
.on_input(|value| Message::Field("/name".into(), value))
|
||||
.width(400)
|
||||
.padding(8)
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
fn dispatch(
|
||||
root: &mut Element<'_, Message>,
|
||||
tree: &mut widget::Tree,
|
||||
node: &layout::Node,
|
||||
renderer: &iced::Renderer,
|
||||
event: Event,
|
||||
cursor: mouse::Cursor,
|
||||
) -> Vec<Message> {
|
||||
let mut messages = Vec::new();
|
||||
root.as_widget_mut().update(
|
||||
tree,
|
||||
&event,
|
||||
Layout::new(node),
|
||||
cursor,
|
||||
renderer,
|
||||
&mut iced::advanced::clipboard::Null,
|
||||
&mut iced::advanced::Shell::new(&mut messages),
|
||||
&Rectangle::new(iced::Point::ORIGIN, iced::Size::new(400., 60.)),
|
||||
);
|
||||
messages
|
||||
}
|
||||
let mounted = crate::behavior::canvas_workspace::tests::Mounted::unshown().await;
|
||||
let renderer = &mounted.renderer;
|
||||
let mut root = input(false);
|
||||
let mut tree = widget::Tree::new(root.as_widget());
|
||||
let node = root.as_widget_mut().layout(
|
||||
&mut tree,
|
||||
renderer,
|
||||
&layout::Limits::new(iced::Size::ZERO, iced::Size::new(400., 60.)),
|
||||
);
|
||||
let start = iced::Point::new(20., 18.);
|
||||
let end = iced::Point::new(110., 18.);
|
||||
for event in [
|
||||
mouse::Event::CursorMoved { position: start },
|
||||
mouse::Event::ButtonPressed(mouse::Button::Left),
|
||||
mouse::Event::CursorMoved { position: end },
|
||||
] {
|
||||
let cursor = if matches!(event, mouse::Event::CursorMoved { position } if position == end) {
|
||||
mouse::Cursor::Available(end)
|
||||
} else {
|
||||
mouse::Cursor::Available(start)
|
||||
};
|
||||
dispatch(
|
||||
&mut root,
|
||||
&mut tree,
|
||||
&node,
|
||||
renderer,
|
||||
Event::Mouse(event),
|
||||
cursor,
|
||||
);
|
||||
}
|
||||
let caret = tree.children[0].state.downcast_ref::<InputState>().cursor();
|
||||
assert!(
|
||||
tree.children[0]
|
||||
.state
|
||||
.downcast_ref::<InputState>()
|
||||
.is_focused()
|
||||
);
|
||||
root = input(true);
|
||||
tree.diff(root.as_widget());
|
||||
let messages = dispatch(
|
||||
&mut root,
|
||||
&mut tree,
|
||||
&node,
|
||||
renderer,
|
||||
Event::Window(iced::window::Event::RedrawRequested(
|
||||
std::time::Instant::now(),
|
||||
)),
|
||||
mouse::Cursor::Unavailable,
|
||||
);
|
||||
assert!(
|
||||
messages.is_empty(),
|
||||
"Gesture retirement discards release actions"
|
||||
);
|
||||
dispatch(
|
||||
&mut root,
|
||||
&mut tree,
|
||||
&node,
|
||||
renderer,
|
||||
Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)),
|
||||
mouse::Cursor::Unavailable,
|
||||
);
|
||||
root = input(false);
|
||||
tree.diff(root.as_widget());
|
||||
let later = iced::Point::new(300., 18.);
|
||||
dispatch(
|
||||
&mut root,
|
||||
&mut tree,
|
||||
&node,
|
||||
renderer,
|
||||
Event::Mouse(mouse::Event::CursorMoved { position: later }),
|
||||
mouse::Cursor::Available(later),
|
||||
);
|
||||
let state = tree.children[0].state.downcast_ref::<InputState>();
|
||||
assert!(state.is_focused());
|
||||
assert_eq!(
|
||||
state.cursor(),
|
||||
caret,
|
||||
"An unheld pointer no longer extends the pre-Help text selection"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1497,7 +1497,9 @@ impl StateCanvas<'_> {
|
||||
format!("{} · {} · right-click to edit", r.name, routing::state_transition_summary(self.project, r))
|
||||
}
|
||||
}).unwrap_or_default(),
|
||||
_ => "Select / move · drag a vertex · drag selected transition endpoints to reconnect · middle-drag to pan".into(),
|
||||
// General gestures are available in Help; keep this stable slot
|
||||
// for the active operation or the item under the pointer.
|
||||
_ => String::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,11 @@ pub enum Event {
|
||||
|
||||
pub(crate) const ARTICLE_SCROLL_ID: &str = "engineering-guide-article";
|
||||
|
||||
mod context_help;
|
||||
pub(crate) use context_help::{
|
||||
ContextScroll, context_scroll_key, context_scroll_task, protect_workspace,
|
||||
};
|
||||
|
||||
impl State {
|
||||
/// Returns whether navigation selected a different article.
|
||||
pub fn update(&mut self, event: Event) -> bool {
|
||||
|
||||
@@ -0,0 +1,282 @@
|
||||
//! Modal reading input and preservation of the mounted editing workspace.
|
||||
use super::ARTICLE_SCROLL_ID;
|
||||
use crate::app::Message;
|
||||
use iced::Element;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(crate) enum ContextScroll {
|
||||
LineUp,
|
||||
LineDown,
|
||||
PageUp,
|
||||
PageDown,
|
||||
Home,
|
||||
End,
|
||||
}
|
||||
|
||||
pub(crate) fn context_scroll_key(
|
||||
event: iced::Event,
|
||||
status: iced::event::Status,
|
||||
_: iced::window::Id,
|
||||
) -> Option<Message> {
|
||||
use iced::keyboard::{Event, Key, key::Named};
|
||||
let iced::Event::Keyboard(Event::KeyPressed { key, modifiers, .. }) = event else {
|
||||
return None;
|
||||
};
|
||||
if status == iced::event::Status::Captured || !modifiers.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let scroll = match key {
|
||||
Key::Named(Named::ArrowUp) => ContextScroll::LineUp,
|
||||
Key::Named(Named::ArrowDown) => ContextScroll::LineDown,
|
||||
Key::Named(Named::PageUp) => ContextScroll::PageUp,
|
||||
Key::Named(Named::PageDown) => ContextScroll::PageDown,
|
||||
Key::Named(Named::Home) => ContextScroll::Home,
|
||||
Key::Named(Named::End) => ContextScroll::End,
|
||||
_ => return None,
|
||||
};
|
||||
Some(Message::ContextHelpScroll(scroll))
|
||||
}
|
||||
|
||||
pub(crate) fn context_scroll_task(scroll: ContextScroll) -> iced::Task<Message> {
|
||||
struct Scroll(ContextScroll);
|
||||
impl<T> iced::advanced::widget::Operation<T> for Scroll {
|
||||
fn traverse(
|
||||
&mut self,
|
||||
visit: &mut dyn FnMut(&mut dyn iced::advanced::widget::Operation<T>),
|
||||
) {
|
||||
visit(self);
|
||||
}
|
||||
fn scrollable(
|
||||
&mut self,
|
||||
id: Option<&iced::advanced::widget::Id>,
|
||||
bounds: iced::Rectangle,
|
||||
content_bounds: iced::Rectangle,
|
||||
_: iced::Vector,
|
||||
state: &mut dyn iced::advanced::widget::operation::Scrollable,
|
||||
) {
|
||||
use iced::advanced::widget::operation::scrollable::{AbsoluteOffset, RelativeOffset};
|
||||
if id != Some(&iced::advanced::widget::Id::new(ARTICLE_SCROLL_ID)) {
|
||||
return;
|
||||
}
|
||||
match self.0 {
|
||||
ContextScroll::Home | ContextScroll::End => state.snap_to(RelativeOffset {
|
||||
x: None,
|
||||
y: Some(if matches!(self.0, ContextScroll::End) {
|
||||
1.
|
||||
} else {
|
||||
0.
|
||||
}),
|
||||
}),
|
||||
scroll => {
|
||||
let y = match scroll {
|
||||
ContextScroll::LineUp => -40.,
|
||||
ContextScroll::LineDown => 40.,
|
||||
ContextScroll::PageUp => -bounds.height * 0.9,
|
||||
ContextScroll::PageDown => bounds.height * 0.9,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// Iced clamps this movement to the actual content bounds.
|
||||
state.scroll_by(AbsoluteOffset { x: 0., y }, bounds, content_bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
iced::advanced::widget::operate(Scroll(scroll))
|
||||
}
|
||||
|
||||
/// Keep the mounted editor and its caret, but prevent modal input from reaching
|
||||
/// widget-internal state before the App message guard can reject edits.
|
||||
pub(crate) fn protect_workspace<'a, R: iced::advanced::renderer::Renderer + 'a>(
|
||||
help_open: bool,
|
||||
content: Element<'a, Message, iced::Theme, R>,
|
||||
) -> Element<'a, Message, iced::Theme, R> {
|
||||
Element::new(WorkspaceInput { help_open, content })
|
||||
}
|
||||
|
||||
struct WorkspaceInput<'a, R> {
|
||||
help_open: bool,
|
||||
content: Element<'a, Message, iced::Theme, R>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct WorkspaceInputState {
|
||||
help_open: bool,
|
||||
}
|
||||
|
||||
impl<R: iced::advanced::renderer::Renderer> iced::advanced::Widget<Message, iced::Theme, R>
|
||||
for WorkspaceInput<'_, R>
|
||||
{
|
||||
fn tag(&self) -> iced::advanced::widget::tree::Tag {
|
||||
iced::advanced::widget::tree::Tag::of::<WorkspaceInputState>()
|
||||
}
|
||||
fn state(&self) -> iced::advanced::widget::tree::State {
|
||||
iced::advanced::widget::tree::State::new(WorkspaceInputState::default())
|
||||
}
|
||||
fn children(&self) -> Vec<iced::advanced::widget::Tree> {
|
||||
vec![iced::advanced::widget::Tree::new(self.content.as_widget())]
|
||||
}
|
||||
fn diff(&self, tree: &mut iced::advanced::widget::Tree) {
|
||||
tree.diff_children(std::slice::from_ref(&self.content));
|
||||
}
|
||||
fn size(&self) -> iced::Size<iced::Length> {
|
||||
self.content.as_widget().size()
|
||||
}
|
||||
fn size_hint(&self) -> iced::Size<iced::Length> {
|
||||
self.content.as_widget().size_hint()
|
||||
}
|
||||
fn layout(
|
||||
&mut self,
|
||||
tree: &mut iced::advanced::widget::Tree,
|
||||
renderer: &R,
|
||||
limits: &iced::advanced::layout::Limits,
|
||||
) -> iced::advanced::layout::Node {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.layout(&mut tree.children[0], renderer, limits)
|
||||
}
|
||||
fn operate(
|
||||
&mut self,
|
||||
tree: &mut iced::advanced::widget::Tree,
|
||||
layout: iced::advanced::Layout<'_>,
|
||||
renderer: &R,
|
||||
operation: &mut dyn iced::advanced::widget::Operation,
|
||||
) {
|
||||
self.content
|
||||
.as_widget_mut()
|
||||
.operate(&mut tree.children[0], layout, renderer, operation);
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
tree: &mut iced::advanced::widget::Tree,
|
||||
event: &iced::Event,
|
||||
layout: iced::advanced::Layout<'_>,
|
||||
cursor: iced::mouse::Cursor,
|
||||
renderer: &R,
|
||||
clipboard: &mut dyn iced::advanced::Clipboard,
|
||||
shell: &mut iced::advanced::Shell<'_, Message>,
|
||||
viewport: &iced::Rectangle,
|
||||
) {
|
||||
let entering = {
|
||||
let state = tree.state.downcast_mut::<WorkspaceInputState>();
|
||||
let entering = self.help_open && !state.help_open;
|
||||
state.help_open = self.help_open;
|
||||
entering
|
||||
};
|
||||
if entering {
|
||||
// Retire held gestures without executing their release actions.
|
||||
// Text controls keep their real caret/focus; drawing tools remain
|
||||
// selected, but an old drag cannot resume when Help is dismissed.
|
||||
for event in [
|
||||
iced::mouse::Event::CursorLeft,
|
||||
iced::mouse::Event::ButtonReleased(iced::mouse::Button::Left),
|
||||
iced::mouse::Event::ButtonReleased(iced::mouse::Button::Middle),
|
||||
iced::mouse::Event::ButtonReleased(iced::mouse::Button::Right),
|
||||
] {
|
||||
let mut discarded = Vec::new();
|
||||
let mut cancellation = iced::advanced::Shell::new(&mut discarded);
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
&iced::Event::Mouse(event),
|
||||
layout,
|
||||
iced::mouse::Cursor::Unavailable,
|
||||
renderer,
|
||||
clipboard,
|
||||
&mut cancellation,
|
||||
viewport,
|
||||
);
|
||||
shell.request_redraw_at(cancellation.redraw_request());
|
||||
if cancellation.is_layout_invalid() {
|
||||
shell.invalidate_layout();
|
||||
}
|
||||
if cancellation.are_widgets_invalid() {
|
||||
shell.invalidate_widgets();
|
||||
}
|
||||
}
|
||||
}
|
||||
if self.help_open
|
||||
&& matches!(
|
||||
event,
|
||||
iced::Event::Keyboard(
|
||||
iced::keyboard::Event::KeyPressed { .. }
|
||||
| iced::keyboard::Event::KeyReleased { .. }
|
||||
) | iced::Event::Mouse(_)
|
||||
| iced::Event::Touch(_)
|
||||
| iced::Event::InputMethod(_)
|
||||
)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// ModifiersChanged and passive window/redraw events still reach the
|
||||
// editor, so releasing Shift during Help cannot stick on return.
|
||||
self.content.as_widget_mut().update(
|
||||
&mut tree.children[0],
|
||||
event,
|
||||
layout,
|
||||
cursor,
|
||||
renderer,
|
||||
clipboard,
|
||||
shell,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
fn draw(
|
||||
&self,
|
||||
tree: &iced::advanced::widget::Tree,
|
||||
renderer: &mut R,
|
||||
theme: &iced::Theme,
|
||||
style: &iced::advanced::renderer::Style,
|
||||
layout: iced::advanced::Layout<'_>,
|
||||
cursor: iced::mouse::Cursor,
|
||||
viewport: &iced::Rectangle,
|
||||
) {
|
||||
self.content.as_widget().draw(
|
||||
&tree.children[0],
|
||||
renderer,
|
||||
theme,
|
||||
style,
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
);
|
||||
}
|
||||
fn mouse_interaction(
|
||||
&self,
|
||||
tree: &iced::advanced::widget::Tree,
|
||||
layout: iced::advanced::Layout<'_>,
|
||||
cursor: iced::mouse::Cursor,
|
||||
viewport: &iced::Rectangle,
|
||||
renderer: &R,
|
||||
) -> iced::mouse::Interaction {
|
||||
if self.help_open {
|
||||
iced::mouse::Interaction::None
|
||||
} else {
|
||||
self.content.as_widget().mouse_interaction(
|
||||
&tree.children[0],
|
||||
layout,
|
||||
cursor,
|
||||
viewport,
|
||||
renderer,
|
||||
)
|
||||
}
|
||||
}
|
||||
fn overlay<'b>(
|
||||
&'b mut self,
|
||||
tree: &'b mut iced::advanced::widget::Tree,
|
||||
layout: iced::advanced::Layout<'b>,
|
||||
renderer: &R,
|
||||
viewport: &iced::Rectangle,
|
||||
translation: iced::Vector,
|
||||
) -> Option<iced::advanced::overlay::Element<'b, Message, iced::Theme, R>> {
|
||||
if self.help_open {
|
||||
None
|
||||
} else {
|
||||
self.content.as_widget_mut().overlay(
|
||||
&mut tree.children[0],
|
||||
layout,
|
||||
renderer,
|
||||
viewport,
|
||||
translation,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -296,6 +296,7 @@ pub fn view(app: &App) -> View<'_, Message> {
|
||||
.padding([4, 16])
|
||||
.style(style::panel);
|
||||
let root = crate::allocation_input::root(app, column![header, task_nav, body, footer]);
|
||||
let root = crate::guide::protect_workspace(app.context_help, root);
|
||||
if app.context_help {
|
||||
return modal_width(
|
||||
root,
|
||||
@@ -495,7 +496,7 @@ fn diagram(app: &App) -> View<'_, Message> {
|
||||
.active_diagram
|
||||
.and_then(|id| app.project().diagrams.get(&id))
|
||||
.map(|d| d.projection);
|
||||
let modes = row![
|
||||
let mut modes = row![
|
||||
label("View content"),
|
||||
pick_list(
|
||||
[
|
||||
@@ -611,6 +612,26 @@ fn diagram(app: &App) -> View<'_, Message> {
|
||||
.center(Fill)
|
||||
.into()
|
||||
};
|
||||
if projection == Some(ProjectionKind::Allocation) {
|
||||
modes = modes.push(tooltip(
|
||||
action(
|
||||
if app.allocation_drag.arrange_next {
|
||||
"Arrange ✓"
|
||||
} else {
|
||||
"Arrange"
|
||||
},
|
||||
Message::WorkDrag(crate::app::allocation_drag::Event::ArrangeNext),
|
||||
),
|
||||
container(text(format!(
|
||||
"Arrange next drag · {}. Move work within its current allocation; Escape cancels.",
|
||||
crate::allocation_input::ARRANGE_SHORTCUT
|
||||
)))
|
||||
.padding(10)
|
||||
.max_width(420)
|
||||
.style(style::card),
|
||||
tooltip::Position::Bottom,
|
||||
));
|
||||
}
|
||||
let mut tools = column![toolbar].spacing(8);
|
||||
if app.active_diagram.is_some() {
|
||||
tools = tools.push(modes);
|
||||
@@ -645,25 +666,7 @@ fn diagram(app: &App) -> View<'_, Message> {
|
||||
.height(26)
|
||||
.width(Fill)
|
||||
.clip(true);
|
||||
tools = tools.push(
|
||||
row![
|
||||
destination_control,
|
||||
action(
|
||||
&format!(
|
||||
"{} · {}",
|
||||
if app.allocation_drag.arrange_next {
|
||||
"Arrange next drag: armed"
|
||||
} else {
|
||||
"Arrange next drag"
|
||||
},
|
||||
crate::allocation_input::ARRANGE_SHORTCUT
|
||||
),
|
||||
Message::WorkDrag(crate::app::allocation_drag::Event::ArrangeNext)
|
||||
)
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(iced::Center),
|
||||
);
|
||||
tools = tools.push(destination_control);
|
||||
}
|
||||
if let Some(diagram) = app.active_diagram {
|
||||
let view = oa_core::projection::project_view(app.project(), diagram);
|
||||
|
||||
@@ -7,7 +7,7 @@ This record does not accept the complete first release or the other diagram defe
|
||||
|
||||
The user's screenshot showed two permanent Traceability explanation paragraphs and a separate generic gesture-hint row. Direct Windows inspection of the released water-pressure example reproduced these. At a 1100×700 client size (1102×732 captured window), the Traceability canvas began around window y=470 and ended around y=692, leaving about 220 pixels for modeling. At the normal 1440×900 client size its top was around y=417. The release was opened from the existing downloaded package; only an in-memory projection switch was made and Undo restored the clean model before closure.
|
||||
|
||||
The candidate moves general instructions into contextual Help, reusing the offline guide. Architecture, State and Process selection bars use a stable compact row; allocation destination and Arrange controls share a row. Traceability retains its displayed-line count and existing actions. Active drawing/assignment feedback appears beside the operation outcome in the persistent status bar, with full text on hover. Operation errors must never be replaced by generic guidance.
|
||||
The candidate moves general instructions into contextual Help, reusing the offline guide. Architecture, State and Process selection bars use a stable compact row. Allocation retains its full-width destination slot; a concise Arrange control sits beside the existing view-content and Help controls. Traceability retains its displayed-line count and existing actions. Active drawing/assignment feedback appears beside the operation outcome in the persistent status bar, with full text on hover. Operation errors must never be replaced by generic guidance.
|
||||
|
||||
Help opens as an overlay and closes with Close or Escape. F1 opens the relevant diagram topic. The workspace, selected group, pending source/property/behavior drafts and camera remain owned by their existing state. Background user commands are rejected before specialized canvas/reference dispatch. Passive results retain their established validity checks. The editor remains the first child of a stable Stack while Help opens and closes.
|
||||
|
||||
@@ -17,6 +17,12 @@ Rejected alternatives: ordinary navigation to the Guide page cleared selection a
|
||||
|
||||
## Verification status
|
||||
|
||||
Implementation and seven focused state/admission regression tests are prepared. The [independent source review](compact-context-help-source-review-20260925.md) records the corrected findings, including passive viewport updates while Help is open. First intermediate `cargo check -p oa-desktop --locked` passed before later overlay and input changes; it is not a check of the final candidate. Current build, focused tests, strict lint, formatting, native comparison and fresh final review remain pending. Baseline captures and raw build logs are local-only until the evidence publication step. No new executable, integrated change, issue closure or release is claimed.
|
||||
Clean source `f83068757af885572f6b3f932d07233dd1b94b7e` passed `cargo build -p oa-desktop --locked` on Windows gnullvm. Its candidate executable SHA-256 is `c1a9b196ec03e86f049fa2aadb2b86bfcaa3f0fbae582a80ee39ee8adf0758e3e`. The [independent source review](compact-context-help-source-review-20260925.md) records the corrected source findings, including passive viewport updates while Help is open.
|
||||
|
||||
Independent native review of that executable recovered approximately 61 pixels in normal Traceability, 114 pixels in minimum Traceability and 58 pixels in minimum Allocation. Help resizes, pointer-scrolls, selects the State topic, and closes through Close/Escape. The review also reproduced two acceptance failures: PageDown cannot scroll Help, and F1/Escape preserves a property or Process-creation draft but loses typing focus/caret continuation. These findings require correction and another native check; model-state unit tests alone do not cover internal widget focus. The idle State-canvas gesture sentence is also removed in the successor; active/pending and hovered-item feedback retain their stable slot.
|
||||
|
||||
The initial focused test run passed nine and failed one: the allocation test fixture used the default Overview projection before asserting allocation ownership. A separate ten-case mounted geometry run passed nine and failed the destination's existing 400-pixel minimum-width check. Sharing its row with the verbose Arrange action reduced that slot to 328 pixels at minimum size. The successor fixes the fixture explicitly and restores the full destination row without weakening its width check. The original failed logs are preserved in the [native/test evidence](experiments/compact-help-20260925/README.md). These diagnostic runs began before the successor edits and are not frozen-source acceptance of those edits.
|
||||
|
||||
The successor adds mounted keyboard/article-scroll, exact-caret, IME isolation, modifier-release and interrupted-pointer checks. Full desktop tests, strict lint, formatting, corrected native workflows and fresh final review remain pending. Evidence is branch-local until its publication step. No new executable publication, integrated change, issue closure or release is claimed.
|
||||
|
||||
Required native checks: minimum and normal windows; Traceability and Allocation space comparison; multi-selection with long names; Help/F1/Close/Escape with property and behavior drafts; editor focus and canvas scroll/camera restoration; active feedback plus operation failure; State and Process controls; subsequent edit/Undo/Redo and source preservation. Linux remains deferred under the recorded user instruction.
|
||||
|
||||
@@ -20,6 +20,14 @@ Criteria: [editing workspace UX review](../docs/ui-review.md), especially preser
|
||||
|
||||
The reviewer did not compile or execute these tests. Suggested focused command: `cargo test -p oa-desktop context_help_tests`. The integration owner records actual execution and native results separately.
|
||||
|
||||
## Follow-up from native review
|
||||
|
||||
Native review of the subsequent candidate found that contextual Help did not respond to PageDown and that Escape reached a focused underlying text input, dropping its typing focus. The follow-up patch adds a Help-only keyboard subscription and article-ID-targeted scrolling (40-pixel arrows, 90% of the actual viewport for PageUp/Down, Home/End), using Iced's bounds-clamped scroll state. A stable workspace input barrier preserves the actual editor caret while suppressing key, pointer, touch and IME input behind Help; modifier changes and passive geometry continue. Opening Help retires held pointer gestures through cancellation releases with all authored actions discarded, while forwarding redraw/layout invalidation. This also prevents a held canvas pan or text selection from resuming after release behind Help.
|
||||
|
||||
New mounted regressions exercise the production App tree and subscriptions for keyboard scrolling, unchanged background scroll positions, precise Inspector caret return, IME exclusion, Shift release, and held-pan retirement. A real text-input widget regression checks selection-drag retirement while preserving its caret. The allocation ownership fixture was corrected to explicitly request Allocation, because `ensure_diagram` creates Overview. These follow-up changes were formatted and source-reviewed here; compilation, execution and native acceptance remain the integration owner's checks.
|
||||
|
||||
A second, read-only critic reviewed the follow-up independently of its implementation. It found and prompted corrections for stale modifiers, IME leakage and interrupted pointer gestures. Its final read found no further blocking source defect in the input barrier, cancellation, passive-event handling or relocated Arrange control. The separate native reviewer supplied the original keyboard/focus failures. Neither source review establishes executed or native acceptance of the successor.
|
||||
|
||||
## Scope and remaining verification
|
||||
|
||||
Production changes reviewed: desktop App messages/admission, allocation input ownership, Escape bridging, behavior keyboard ownership, architecture/State/Process toolbar layout, guide article disclosure and footer feedback; supporting architecture/UI-review documentation. Regression tests and this record are review artifacts. No model schema, persistence format or authored-model transformation is introduced.
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Compact Help native evidence
|
||||
|
||||
Branch-specific evidence for [issue 39](http://217.177.34.108:3000/sorokin.oleg.edu/ARCADIA-Studio/issues/39).
|
||||
The [design record](../../compact-context-help-20260925.md) owns the decision and current acceptance boundary.
|
||||
|
||||
`native/` contains the Windows baseline captures from published source
|
||||
`715036e132677dd2bc2fb37ed316d93dc9987aec`.
|
||||
`candidate-f830/` preserves the independent review of source
|
||||
`f83068757af885572f6b3f932d07233dd1b94b7e`, including its two failed Help workflows,
|
||||
original screenshots, exact executable/runtime hashes and successful build log.
|
||||
These observations must not be relabeled as acceptance of a successor executable.
|
||||
|
||||
The initial test compilation began at that commit; successor work was edited
|
||||
while it ran. `tests.log` (nine passed, one failed fixture precondition) and
|
||||
`geometry-tests.log` (nine passed, one failed destination-width invariant) are
|
||||
diagnostic evidence from that cycle, not frozen-source acceptance. The geometry
|
||||
run reused its test executable without recompiling. The first command's
|
||||
`allocation_drag_full_tree_tests` filter matched no tests; the second used the
|
||||
actual `full_tree` filter plus the affected mounted State/Process checks.
|
||||
|
||||
The review used a copy of `examples/release1/project.arcadia` from the published
|
||||
Windows package. All 37 fixture files matched the baseline before the trial and
|
||||
again after closing without saving. `baseline-proof.json` records those bytes.
|
||||
The executable is a local development build, not a published release artifact.
|
||||
|
||||
Reproduce the build using the documented Windows gnullvm toolchain and
|
||||
`cargo build -p oa-desktop --locked`. Place its matching `libunwind.dll` beside the
|
||||
executable, launch it, and open an isolated copy of the release1 example. Perform
|
||||
the workflows and use the window sizes in [the native report](candidate-f830/review.md).
|
||||
The baseline captures establish layout comparison; the report records actual
|
||||
interaction results and explicit unverified workflows.
|
||||
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 193 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 108 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 107 KiB |
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"baseline_binary": "2c03bd6cc3f1110c219e4f57ab94874a121590f83779239a93289b7170664399",
|
||||
"baseline_source": "715036e132677dd2bc2fb37ed316d93dc9987aec",
|
||||
"model_files": {
|
||||
"evidence/repair-check.md": "f4db17bdca4ecbb1bf0caef01b2da5f1f7f798ab3152b648ce6dd8696a6a68e5",
|
||||
"expected-diagnostics.json": "3700393d76b940cfc076bf52861bd7b1f43370f8eabce3594531bd0bf1fc851e",
|
||||
"model.json": "ef0ef4006c3e09d5db6e1b621256b3f7d6642f3a7b09cc34607cfbcca830c727",
|
||||
"model.rs": "ddc2d92b0d7699a4cadfbb35ae379f0a68d17f4f5d7df82b4b1afcf8d5425fbe",
|
||||
"oa/activities/activity_oa_restore_work.arcadia": "21cbd7176af8fad68453306f2371ff03e127a994b8796cdf044567332de9686f",
|
||||
"oa/behavior/process_oa_assessment_procedure.arcadia": "23cabc213c264a16c8595414db6acb09248c1cef9cd699f1b0316e5bb82c062a",
|
||||
"oa/behavior/process_oa_field_procedure.arcadia": "9c52086759b7344b4809ceb8d372f65647b16fc24153a03b887faa378c37fe96",
|
||||
"oa/behavior/process_oa_response.arcadia": "675798bd19e6b40d03e7076b0e59b110cdaaab7b6d16c47c3ac80fa960072e84",
|
||||
"oa/behavior/scenario_oa_conversation.arcadia": "e217e5394a0771dc16206ed9437a231da27905d2f19be64b3f48281c0a32fb6b",
|
||||
"oa/behavior/scenario_oa_full_scenario.arcadia": "c5a285f2d7c61bd2a136107f267fbf9cddb1cba3f63ff36a6f53f7ab98f33df5",
|
||||
"oa/behavior/state_machine_oa_monitor.arcadia": "e7349e1e5336282eed3f5a103129db33d3ae4ed500788ee7129e8fc193ac57f6",
|
||||
"oa/capabilities.arcadia": "26a3d9b615a3251ff010e0b47127f2d16bc2dcd45ed95967208b0076d725843a",
|
||||
"oa/model.arcadia": "069a5eaaf525089935cc9d73ae7b916866ed78c6534705d75739650ecdea7ada",
|
||||
"oa/participants.arcadia": "591a75a9ecad280302be4d4ae0171ad616377ed11ead5e233848ca2dc0a71819",
|
||||
"oa/types.arcadia": "f02c391d584674c879f662a418abba3a220f1fee88471ca49322ac9873de628d",
|
||||
"project.arcadia": "b2674e6bf77cfe0f6f0ba79c81d2e96d92f39c02ca2c6266d80e91723bfb9fda",
|
||||
"README.md": "113cfb1cf38a98b51bff60442d86a35d636a3d80ef206bece7d53d0aae67f2d5",
|
||||
"sa/behavior/process_sa_assessment_procedure.arcadia": "5c414fbdaabf9f9e402b0441fcacf1cacf4dbbc6c822162f028de35abf59be2d",
|
||||
"sa/behavior/process_sa_field_procedure.arcadia": "ece846fdf93857e71a9ab0efcbdf64a5c41c71180f62afa3f79077fffb53b8f2",
|
||||
"sa/behavior/process_sa_response.arcadia": "af7fdda280a917cf626f6e077162d481f9aaad280f8e81330c07d7fe384b1c52",
|
||||
"sa/behavior/scenario_sa_conversation.arcadia": "01339699ec3ee409418d9bb232542f2bcd9419f1e67b183e77700c56bb9f71d2",
|
||||
"sa/behavior/scenario_sa_full_scenario.arcadia": "5dce6b00a3a441162e8dee3e23d9ca5ab7bed4a24de669b1fba03664a3086dda",
|
||||
"sa/behavior/state_machine_sa_monitor.arcadia": "17dbcfa5bbfd10fc8e23a325fc950ce853c219b4aa89c74f96278cbb3f34acc8",
|
||||
"sa/capabilities.arcadia": "81706a0ca9d7db2aa0350888b9c00d853885185db2e6b90557d3d51a11c3d645",
|
||||
"sa/functions/function_sa_audit_thresholds.arcadia": "27f798f78f36e72b38edab3725af2feb0eaa010fd52a60b5c6b6bef57ad3b686",
|
||||
"sa/functions/function_sa_restore_work.arcadia": "357c6acb80f86b4e8d8f5c98f9f79d140f85fbe09f3406b41b7ac6eba464ec8c",
|
||||
"sa/model.arcadia": "805b90262e27090ae7da81a51bcefa49c97fb0b3d3220b4a72323b4c4351c395",
|
||||
"sa/participants.arcadia": "ca144d6a8f45fe720b5d18c7b59c7671444baa8c37e5ce9117bcba34b3a6aa84",
|
||||
"sa/types.arcadia": "540ddbc2bcd0ccf45298e3ea5660637341aff63ff65fd87ac2f26469d1adc064",
|
||||
"traceability.arcadia": "474bd28d955ff7af7722f6e9a23220d816879e3f46202e79a233544b9c593116",
|
||||
"transition-before.json": "2253e6434f741687e22327c37d520ed15c043bc36025498650bc2366bb6a874d",
|
||||
"transition-request.json": "43b9a22e5f95ad3bc0ebd2478ead0e14406854abc989ae94ded9367b013d812a",
|
||||
"views/diagram_oa_allocation_view.arcadia": "c45d7467839219d894804c1ef2d22ff4d4a5a84a6a273c526bafd4c1facde624",
|
||||
"views/diagram_sa_allocation_view.arcadia": "352a3ca745517592b5c9b1239b5770f62f0466a329a91fd03457ef2b53c06ed6",
|
||||
"views/diagram_sa_capability_view.arcadia": "8f18fcccbcddcf3c1ea0ffe727b5b23b77fc148c5da4fde953abe681e4c20cd2",
|
||||
"views/diagram_sa_service_view.arcadia": "4c72cb7593d9c4be8d2600b0656a7a3a6ce84c45bb2f3e58a4ee5ae3bf91913c",
|
||||
"views/diagram_sa_state_view.arcadia": "dd1bea81ac7dc549a53f9b190be6f13052e00ede345bd180444cc31edb892230"
|
||||
},
|
||||
"matching_files": 37,
|
||||
"baseline_captures": {
|
||||
"baseline-allocation-compact.png": "d92a0cca62a038dcf0894d92b37a64028342c2365e3fb8880ed597222123d96f",
|
||||
"baseline-trace-compact.png": "b608a3d75612d8c06aacc094a7aebd12a0798ce30f2126c00bc3ad61a9103217",
|
||||
"baseline-trace-normal.png": "6dfb6c595af4247aab3600da0e0a25ba4b11151436e9fc4a406f1036053f71d0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"runtime": "F45604A12E8BCD839FD86F667C1BE1CC7C4F9DA2B3C69FF381AC708F0E1AE7D4",
|
||||
"source": "f83068757af885572f6b3f932d07233dd1b94b7e",
|
||||
"buildExit": 0,
|
||||
"binary": "C1A9B196EC03E86F049FA2AAD2B86BFCAA3F0FBAE582A80EE39EE8ADF0758E3E"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
Compiling oa-desktop v0.2.0 (C:\ar-compact-01a0d717\crates\oa-desktop)
|
||||
Finished `dev` profile [unoptimized] target(s) in 1m 46s
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
running 10 tests
|
||||
test app::allocation_drag::tests::full_tree::actual_app_tree_dirty_description_retains_focus_and_middle_caret_for_next_key ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_app_tree_dirty_name_retains_focus_and_middle_caret_for_next_key ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_app_tree_exact_incoming_drag_keeps_destination_visible_and_canvas_stable ... FAILED
|
||||
test app::allocation_drag::tests::full_tree::actual_keyboard_arrange_shortcut_consumes_once_escape_cancels_and_dirty_owner_refuses ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_rendered_canvas_pixels_do_not_change_when_toolbar_feedback_text_changes ... ok
|
||||
test app::allocation_drag::tests::full_tree::actual_toolbar_hover_inspects_last_full_name_without_authorizing_an_outside_drop ... ok
|
||||
test app::allocation_drag::tests::full_tree::control_focus::dirty_selected_pin_outer_ring_keeps_real_name_focus_and_caret ... ok
|
||||
test app::process_canvas::tests::direct_process_app_save_details_resize_and_passive_geometry_keep_drafts_and_camera ... ok
|
||||
test app::state_diagram_tests::picker_escape::mounted_state_missing_picker_escape_preserves_exact_document_then_reopens_and_places ... ok
|
||||
test app::state_multiselect_focus_tests::mounted_state_selection_and_missing_picker_never_resize_or_refit_the_canvas ... ok
|
||||
|
||||
failures:
|
||||
|
||||
---- app::allocation_drag::tests::full_tree::actual_app_tree_exact_incoming_drag_keeps_destination_visible_and_canvas_stable stdout ----
|
||||
|
||||
thread 'app::allocation_drag::tests::full_tree::actual_app_tree_exact_incoming_drag_keeps_destination_visible_and_canvas_stable' (880) panicked at crates\oa-desktop\src\app\allocation_drag_full_tree_tests.rs:308:5:
|
||||
Rectangle { x: 274.0, y: 242.59999, width: 328.21484, height: 26.0 }
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
|
||||
failures:
|
||||
app::allocation_drag::tests::full_tree::actual_app_tree_exact_incoming_drag_keeps_destination_visible_and_canvas_stable
|
||||
|
||||
test result: FAILED. 9 passed; 1 failed; 0 ignored; 0 measured; 1532 filtered out; finished in 26.81s
|
||||
@@ -0,0 +1,27 @@
|
||||
# Independent native review: compact contextual Help
|
||||
|
||||
Reviewed candidate source `f83068757af885572f6b3f932d07233dd1b94b7e`, executable SHA-256 `C1A9B196EC03E86F049FA2AAD2B86BFCAA3F0FBAE582A80EE39EE8ADF0758E3E`, runtime SHA-256 `F45604A12E8BCD839FD86F667C1BE1CC7C4F9DA2B3C69FF381AC708F0E1AE7D4` (build.json). Review used Windows native Computer Use with the copied `model/project.arcadia`, normal 1440x900 and configured minimum 1100x700 client sizes (captured windows 1442x932 and 1102x732). Original examples and unrelated windows were not edited. This review belongs to this candidate, not a subsequently rebuilt executable.
|
||||
|
||||
## Findings requiring correction
|
||||
|
||||
1. **Help has no working keyboard reading route for overflow.** Open Architecture Help with F1. Press PageDown; the article does not move. Tab followed by PageDown does not move it. Clicking the article then PageDown also does not move it. Pointer wheel scrolling does work. Moved gesture instructions fall below the fold, so keyboard users cannot reach all the guidance. Evidence: `03-help-normal.png`, `04-help-keyboard-no-scroll.png`, `06-help-pointer-scroll.png`. Integration owner independently confirmed missing keyboard handling and is preparing a fix.
|
||||
2. **Help dismissal loses draft input focus/caret continuation.** In minimum Traceability, click inspector Name, press Home, type `DRAFT `, press F1, Escape, then type `X` without clicking. The retained field remains `DRAFT 1...`, rather than becoming `DRAFT X1...`. Repeat with Process W -> empty canvas -> pending contribution name `Review draft` -> F1 -> Escape -> type `X`; text remains `Review draft`. Draft text is retained and no model mutation occurs, but typing cannot resume at the previous caret. Evidence: `08-property-draft.png`, `09-property-focus-lost.png`, `14-process-pending.png`, `15-process-pending-focus-lost.png`.
|
||||
|
||||
## Additional observation
|
||||
|
||||
State still draws a permanent generic instruction at `behavior/state_canvas.rs:1500`: "Select / move ...". At the minimum width it is truncated near "transition endpoin..." (`11-state-minimum.png`). Move this idle instruction to Help while preserving active tool/selection warnings. This is separate from the removed toolbar hint.
|
||||
|
||||
## Verified workflows and layout
|
||||
|
||||
- Opened only the isolated copied water-pressure source project. Initial launch helper timed out waiting for a targetable window; a fresh window enumeration found the normal application. A file-dialog accessibility set-value operation also timed out; re-observation showed no filename change, and ordinary pointer focus/text entry succeeded. These are recorded as tooling delays, not application defects.
|
||||
- Switched OA Allocation to Traceability through View content. All trace actions remain visible. At normal size canvas top moved from baseline approximately y=417 to y=356; at minimum size from approximately y=470 to y=356. The same baseline fixture and panel state were used; this recovers approximately 61 and 114 vertical pixels respectively. Evidence: `02-trace-normal.png`, `07-trace-minimum.png`, baseline files under `../native/`.
|
||||
- Minimum Allocation canvas starts around y=356 versus baseline y=414, recovering approximately 58 pixels. Destination and Arrange controls remain visible (`10-allocation-minimum.png`). Arrange arms visibly and Escape removes its armed button state. The historical operation-status text remains in the footer after cancellation; no claim of full status-lifecycle coverage is made.
|
||||
- F1 and visible Help both open the overlay. Close and Escape dismiss it. Pointer article scrolling works. Resized the native window from normal to minimum while Help was open; header/Close and article remain reachable (`05-help-minimum-resized.png`). The underlying view remains selected after closing, and its camera updates to the smaller viewport.
|
||||
- Property draft text survives Help, and Revert restores accepted name. Undo restored the temporary Traceability projection change and the application showed all changes saved.
|
||||
- State and Process F1 select the correct States and Chains articles. Their compact selection rows and Help control are visible at minimum size (`11-state-minimum.png`, `13-process-minimum.png`). Created a State region, then exercised Undo/Redo/Undo; object and check counts returned to the fixture baseline (94 objects, 62 links, one check). Evidence: `12-state-redo.png`.
|
||||
- Process W -> blank canvas opens a pending contribution form. Named draft survives Help. Disabled Create, source chooser explanation and Cancel remain visible at minimum size. Cancel returned to the existing Process without creating an object.
|
||||
- Closed the reviewed application through Alt+F4 after restoring the clean model. No save was performed.
|
||||
|
||||
## Remaining native coverage
|
||||
|
||||
Do not infer multiselection, long-name group-control layout, source-editor caret preservation, operation-failure coexistence with active guidance, or full Scenario coverage from these observations. The supported Computer Use input API has no modifier-click or key-hold action; source inspection found Shift-click selection but no ordinary-diagram marquee implementation. Native group verification therefore remains a gap. Keyboard reading and property/behavior caret continuation must be rechecked on the successor candidate, together with any affected input behavior. Screenshots alone do not establish these interactions.
|
||||
@@ -0,0 +1,35 @@
|
||||
Compiling oa-server v0.2.0 (C:\ar-compact-01a0d717\crates\oa-server)
|
||||
Compiling iced_widget v0.14.2 (C:\ar-compact-01a0d717\crates\vendor\iced_widget)
|
||||
Compiling oa-core v0.2.0 (C:\ar-compact-01a0d717\crates\oa-core)
|
||||
Compiling iced v0.14.0
|
||||
Compiling oa-desktop v0.2.0 (C:\ar-compact-01a0d717\crates\oa-desktop)
|
||||
Finished `test` profile [unoptimized] target(s) in 20m 57s
|
||||
Running unittests src\main.rs (C:\Users\User\Documents\Git\ARCADIA-Studio\.local\target-gnullvm\debug\deps\arcadia_studio-c5b42126ba36cd38.exe)
|
||||
|
||||
running 10 tests
|
||||
test app::context_help_tests::context_help_accepts_live_process_viewport_and_rejects_stale_measurements ... ok
|
||||
test app::context_help_tests::context_help_disables_allocation_input_including_early_work_drag_dispatch ... FAILED
|
||||
test app::context_help_tests::context_help_escape_preserves_pending_behavior_form_and_blocks_submit ... ok
|
||||
test app::context_help_tests::context_help_f1_selects_scenario_topic_without_leaving_the_workspace ... ok
|
||||
test app::context_help_tests::context_help_preserves_group_property_draft_camera_and_model ... ok
|
||||
test app::context_help_tests::context_help_rejects_a_reference_picker_open_queued_from_the_inspector ... ok
|
||||
test app::context_help_tests::context_help_retains_source_draft_and_rejects_background_source_input ... ok
|
||||
test guide::tests::filtering_and_related_navigation_keep_selection_visible ... ok
|
||||
test guide::tests::guide_topic_navigation_resets_only_article_and_same_topic_search_keeps_scroll ... ok
|
||||
test guide::tests::search_finds_concepts_in_walkthroughs_and_api_reference ... ok
|
||||
|
||||
failures:
|
||||
|
||||
---- app::context_help_tests::context_help_disables_allocation_input_including_early_work_drag_dispatch stdout ----
|
||||
|
||||
thread 'app::context_help_tests::context_help_disables_allocation_input_including_early_work_drag_dispatch' (3680) panicked at crates\oa-desktop\src\app\context_help_tests.rs:225:5:
|
||||
assertion failed: app.allocation_input_owner()
|
||||
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
|
||||
|
||||
failures:
|
||||
app::context_help_tests::context_help_disables_allocation_input_including_early_work_drag_dispatch
|
||||
|
||||
test result: FAILED. 9 passed; 1 failed; 0 ignored; 0 measured; 1532 filtered out; finished in 0.42s
|
||||
|
||||
error: test failed, to rerun pass `-p oa-desktop --bin arcadia-studio`
|
||||
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 152 KiB |