diff --git a/crates/oa-desktop/src/app.rs b/crates/oa-desktop/src/app.rs index 971b05c3..97fca6e7 100644 --- a/crates/oa-desktop/src/app.rs +++ b/crates/oa-desktop/src/app.rs @@ -1383,6 +1383,16 @@ impl App { | Message::SourceSymbolsReady(_) | Message::SourceRenameReady(_) | Message::DiagramUsagesReady(_) + | Message::ScenarioCanvas( + behavior::scenario::CanvasEvent::Scrolled(..) + | behavior::scenario::CanvasEvent::Restored(..) + | behavior::scenario::CanvasEvent::Viewport(..) + ) + | Message::TopologyCanvas( + behavior::topology::CanvasEvent::Scrolled(..) + | behavior::topology::CanvasEvent::Viewport(..) + ) + | Message::StateDiagram(behavior::state_edit::Event::Viewport(..)) ) { return Task::none(); diff --git a/crates/oa-desktop/src/app/context_help_tests.rs b/crates/oa-desktop/src/app/context_help_tests.rs index 9af89280..085247aa 100644 --- a/crates/oa-desktop/src/app/context_help_tests.rs +++ b/crates/oa-desktop/src/app/context_help_tests.rs @@ -272,3 +272,77 @@ fn context_help_rejects_a_reference_picker_open_queued_from_the_inspector() { before ); } + +#[test] +fn context_help_accepts_live_process_viewport_and_rejects_stale_measurements() { + use crate::behavior::topology::CanvasEvent; + let mut project = Project::empty(); + let owner = project + .add_element(Kind::Process, Layer::Oa, "Response process", None) + .unwrap(); + let mut app = App::with_project(project); + send(&mut app, Message::Select(owner)); + send(&mut app, Message::Page(Page::Behavior)); + let context = app.topology_context().unwrap(); + send( + &mut app, + Message::TopologyCanvas(CanvasEvent::Viewport(context, iced::Size::new(640., 480.))), + ); + let initial = app.behavior.topology.camera_boundary(); + let before = app.document.snapshot(); + send(&mut app, Message::Help); + assert!(app.context_help); + assert_eq!(app.guide.topic, Topic::Chains); + + let resized = iced::Size::new(420., 330.); + let context = app.topology_context().unwrap(); + send( + &mut app, + Message::TopologyCanvas(CanvasEvent::Viewport(context, resized)), + ); + let measured = app.behavior.topology.camera_boundary(); + assert!( + measured != initial, + "Help must admit passive viewport measurements" + ); + assert!(app.context_help); + let epoch = app.canvas_epoch; + for field in 0..3 { + let mut stale = app.topology_context().unwrap(); + match field { + 0 => stale.document = Id::new_v4(), + 1 => stale.owner = Id::new_v4(), + _ => stale.revision += 1, + } + send( + &mut app, + Message::TopologyCanvas(CanvasEvent::Viewport(stale, iced::Size::new(999., 777.))), + ); + assert!(app.behavior.topology.camera_boundary() == measured); + assert_eq!(app.canvas_epoch, epoch); + } + + send(&mut app, Message::CloseContextHelp); + assert!(!app.context_help); + assert!(app.behavior.topology.camera_boundary() == measured); + let context = app.topology_context().unwrap(); + let epoch = app.canvas_epoch; + send( + &mut app, + Message::TopologyCanvas(CanvasEvent::Viewport(context, resized)), + ); + assert_eq!( + app.canvas_epoch, epoch, + "The size observed behind Help is already current" + ); + let context = app.topology_context().unwrap(); + send( + &mut app, + Message::TopologyCanvas(CanvasEvent::Viewport(context, iced::Size::new(700., 500.))), + ); + assert!(app.behavior.topology.camera_boundary() != measured); + assert_eq!(app.page, Page::Behavior); + assert_eq!(app.selected, owner); + assert!(Arc::ptr_eq(&before, &app.document.snapshot())); + assert!(!app.document.can_undo()); +} diff --git a/harness/compact-context-help-20260925.md b/harness/compact-context-help-20260925.md index 8f79167b..35e3757b 100644 --- a/harness/compact-context-help-20260925.md +++ b/harness/compact-context-help-20260925.md @@ -17,6 +17,6 @@ Rejected alternatives: ordinary navigation to the Guide page cleared selection a ## Verification status -Implementation and six focused state/admission regression tests are prepared. 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. +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. 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. diff --git a/harness/compact-context-help-source-review-20260925.md b/harness/compact-context-help-source-review-20260925.md new file mode 100644 index 00000000..b9985507 --- /dev/null +++ b/harness/compact-context-help-source-review-20260925.md @@ -0,0 +1,27 @@ +# Compact contextual help: independent source review + +Review date: 2026-09-25. Scope: Gitea issue #39 compact editing chrome and contextual Help, reviewed against base `715036e1` and the issue screenshot. Reviewed source includes `e0812b3932fbb36100516f0981dc9c646eeda56d` plus the subsequent local passive-geometry admission patch and regression test in this change. This is source-review evidence, not native acceptance or executed test evidence. + +Criteria: [editing workspace UX review](../docs/ui-review.md), especially preserving pending edits and selection, stable canvas geometry, accessible disclosure, and retaining actionable feedback. + +## Findings addressed in source + +- The initial Help action used ordinary page navigation, clearing multiselection and selected bends, including before a pending behavior form rejected navigation. Contextual Help now overlays the existing workspace. Close/Escape changes reading state without navigating or applying drafts. +- App-level modal admission originally ran after allocation and reference-picker fast paths. Help now gates user messages at the beginning of `App::update`; allocation and behavior keyboard ownership are also disabled while Help is open. +- Combining live gesture feedback with multiselection controls could leave no width for feedback at the configured minimum window size. Selection controls now keep a compact, fixed-height row; current drawing/allocation feedback uses the existing application footer. +- Footer feedback initially replaced operation status, concealing errors while a promoted edge or drawing tool remained selected. The footer now retains operation status alongside feedback and exposes complete text in its tooltip. +- Conditional modal wrapping could recreate the editor widget tree. Both closed and open states now use the same outer Stack with the workspace as its first child. +- State/Process selection guidance removed from persistent chrome was missing from its target Help articles. The States and Chains articles now include group selection, primary-properties semantics and relevant tool/read-only guidance. +- The new early modal gate initially rejected passive behavior viewport/scroll notifications. It now admits Process/Scenario geometry and State viewport notifications to their existing context-validated handlers, preventing resize observations from being lost while Help is open. + +## Regression coverage added + +[`context_help_tests.rs`](../crates/oa-desktop/src/app/context_help_tests.rs) covers property drafts and multiselection, source drafts, pending state-transition forms, blocked background actions, contextual Help topic dispatch, Close/Escape, allocation input ownership, queued inspector reference-picker admission, and valid/stale Process viewport measurements during Help and after closing. Assertions include accepted-model snapshot identity and absence of history changes where applicable. Passive geometry intentionally validates document/owner/revision; stale pointer epochs alone do not invalidate measurements. + +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. + +## 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. + +No further concrete source blocker was identified after the fixes above. Native checks remain necessary at 1100×700 and a representative normal size: Help discovery and keyboard reading/dismissal, focus and caret return, preservation of canvas camera/selection and form drafts, resize while Help is open, multiselection control visibility, allocation refusal/destination feedback, current operation failures, and stable canvas bounds through selection/gesture changes. Source inspection and App tests do not establish layout quality, keyboard scrolling, pointer behavior or usability.