Files
ARCADIA-Studio/harness/experiments/canvas-editing-followup-20260912/state-focus-review.md
T

8.3 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.

Persisted-state structural correction after root gate

Root's Windows gate at integrated 24bb24839b14353c1a2b668f3bac34235d39f7a7 reported 22 pass / 1 fail with all 531 inputs unchanged. Remaining failure: mounted_state_delete_uses_state_placement_and_preserves_architecture_placement had no delete_target after its real Save/Undo/Shift+Delete sequence. Other new revision, passive-save, Inspector and gesture tests passed in that scoped gate. Receipt: root .local/allocation-union-20260912/focus-24bb248.result.json.

Source cause is separate from Context focus retention: state_view conditionally pushed the unplaced-element picker immediately before the canvas Owner. The installed iced_widget-0.14.2/src/column.rs:207 delegates to Tree::diff_children. Installed iced_core-0.14.0/src/widget/tree.rs reconciles children positionally with zip, appending or truncating, and replaces a Tree on widget-tag mismatch. When Hide adds the picker, the former Owner slot becomes a PickList and a new unfocused Owner is appended. Undo truncates that Owner and replaces PickList with yet another new Owner. StateCanvas has no private focused flag; state viewport resizing does not change canvas_epoch. Loosening Context guards would therefore not fix the cause.

The corrected view has a permanent body Column with exactly two children: an optional-controls Column, and the canvas Owner. Picker insertion/removal is confined to the first child's subtree. The body uses zero spacing when that slot is empty and eight pixels when present, preserving the previous visible layout without a blank spacer. No new focus is assigned.

The original failing test now checks the unplaced set's empty/one/empty transition, unchanged document/diagram/epoch ownership after Hide, and actual mounted focus immediately after Hide, Save and Undo. Redraws after Hide/Undo exercise the real viewport transition. The final Shift+Delete target assertion and source preservation remain unchanged. These additional checks are unrun; root owns the next gate. Source review was requested from root because the independent critic tool currently rejects follow-up with a thread-limit error. Root completed source review and authorized the scoped correction, committed as e24054b on the original isolated branch. Runtime verification remains with root; no compiler/native action was performed by this worker.