Files
ARCADIA-Studio/harness/experiments/delete-native-f7-20260912/focus-review.md
T

6.0 KiB

Canvas focus continuity after accepted edits and history

Native parent observation at immutable f7d84fa: allocation work canvas click, Delete placement, Save, Undo, Save, Redo, Save, Undo, then Shift+Delete produces no dialog. Clicking the same work restores the expected model confirmation. Parent evidence: .local/native-qa/delete-20260912/evidence/03-shift-delete-after-undo-no-dialog.png and 04modelconfirmafterclick.

Isolated worktree: C:/Users/User/Documents/Git/ARCADIA-Studio/.local/delete-undo-focus-20260912. Branch: codex/delete-undo-focus-956atf-20260912, baseline f7d84fa. Reviewed change commit: 7bf57db (six production/test files; no runtime gate run by this worker).

Source diagnosis and change

diagram::focus::Owner::diff previously revoked keyboard focus whenever any route_edit::Context field changed. The context includes document revision, so accepting a placement removal or Undo/Redo revoked the focus that still belonged to the displayed canvas. This is a production defect, unlike the earlier retained-Apply evaluator ordering issue.

Owner now preserves its existing focused and traversing values across a revision change only. It clears pointer history for every changed full context. It still revokes focus on document, diagram or canvas epoch change, disabled input, window loss/resize, a pointer press outside the canvas, and actual focus operations. It never grants focus on a revision change. The inner canvas still receives the new full context, and model/confirmation intent validation is unchanged.

A second necessary cause is passive Save: save_to sets busy, while the old single enabled flag treated busy like a modal owner transfer. The shared keyboard predicate now distinguishes logical owner availability from busy command admission, with the previous command predicate behavior unchanged. Architecture and state callers pass both owner availability and busy suspension to Owner. Save/export suspension retains existing focus but denies keyboard/pointer commands and focus acquisition. Explicit unfocus operations, outside clicks and window loss still revoke the owner. No App save logic was changed. Entering suspension recreates only the wrapped canvas child tree once, retiring transient drags that could otherwise miss a release during I/O. Outer keyboard focus and App-owned diagram presentation remain intact.

refresh_draft restores the selected architecture/state placement from the preserved semantic selection after Undo; no selection-history rewrite is needed.

Actual mounted regression changes

app/diagram_removal_tests.rs adds:

  • OA and SA repeated placement Delete after Undo/Redo, then Shift+Delete without another pointer event or explicit focus operation; exact bundle comparisons.
  • Native allocation work click/hide/Undo/Redo/Undo/Shift+Delete sequence with the expected work confirmation target and unchanged model/source bundle. Real saves are included between history steps; each task is held while the actual busy tree receives F2/Shift+Delete, then completed to a temporary source file.
  • Persisted State placement hide/Undo/Shift+Delete, preserving the architecture placement and correctly targeting the state.
  • A canvas-versus-Inspector test that starts with actual pointer focus, accepts an unrelated rename, traverses Undo/Redo/Undo, checks the real widget operation focus witness, and verifies F2 reaches only the canvas. Repeated F2/Delete cannot reach the diagram from either Inspector or the newly opened inline name.
  • Actual Inspector click or programmatic field focus during a held Save must revoke the retained canvas owner, including after the Save task completes.
  • Passive Save cannot grant focus through either a focus operation or pointer press, and cannot restore focus lost to a window Unfocused event while busy.
  • An actual held diagram drag is released while Save is busy; later motion and release must not commit that old gesture. A fresh drag must work and Undo must restore the exact original source bundle.

Existing stale revision/epoch/document/selection intent, confirmation replay, modal/window/dirty/modified-key, clean Inspector and Tab tests remain unchanged.

Derived viewport assessment

The process/scenario Viewport has analogous revision revocation, but its Scope epoch is camera_epoch, mixing revisions and keyboard ownership boundaries. Passing canvas_epoch as a replacement focus lifetime is unsafe: process/scenario ordinary pan, zoom, viewport sizing and scroll changes also increment that epoch. A separate derived focus-lifetime contract needs a bounded follow-up; this patch does not broaden to those views or weaken their current guards.

Verification boundary

git diff --check passes. This worker has run no Cargo/compiler or native GUI commands and made no Gitea changes. Parent owns executable gates and native recheck. Fresh Astra/high source review found no issue in the revision-only change, then identified a high-priority focus-theft bug in the first busy draft: omitting the suspended canvas from focus operations and outside clicks prevented Inspector acquisition from revoking retained canvas focus. The current change keeps unfocus and outside-click revocation active, denies only acquisition, and adds both actual click and operation regressions. The same critic then identified the swallowed-release gesture risk described above; the child-tree retirement and actual held/fresh drag regression address it. The final Astra/high source re-review reports no further findings and judges the actual gesture regression meaningful. No executable pass is claimed.

Recommended existing root Windows gate filters:

  • app::diagram_removal_tests:: (all affected actual-root tests)
  • app::architecture_authoring::tests:: (inline F2 and ownership dependents)

The new test is app::diagram_removal_tests::mounted_revision_and_history_preserve_the_actual_canvas_or_inspector_focus_owner. The original allocation filter remains app::diagram_removal_tests::mounted_allocation_work_click_then_delete_without_redraw_removes_placement_once.