10 KiB
Issue 15: Delete-key design investigation (2026-09-12)
Read-only source investigation for parent Codex-956atf. Source identified by parent: d9b3f0d on codex/behavior-authoring-20260910. No source, Git, compiler, native-app, or Gitea mutations performed. This note is a proposal, not verified runtime behavior. Frozen parent test remains authoritative.
Findings
crates/oa-desktop/src/diagram.rs:632-716: keyboard ownership is a private mouse-setkeyboard_focusboolean. Delete only emits RemoveBend when an editable selected bend exists; no element removal path exists. F2 and context-menu shortcut use the same boolean.diagram.rs:565: context reset clears gesture state but not keyboard_focus. No Unfocused match exists in this file. Clean editable text focus reached through Tab or programmatic focus may therefore coexist with canvas keyboard ownership. This is a source-based risk requiring a mounted test, not a demonstrated runtime defect. Adding a Delete arm alone is insufficient.model_menu.rs:395-470: placed element offersHide from this diagram-> HideSelected; semantic item offersDelete from model…-> Delete. Relation items have no hide operation. A route bend offers Remove bend with Delete hint.app.rs:2913: HideSelected deletes selected_node placement.app.rs:2781: Delete runs apply() before opening confirmation; ConfirmDelete uses mutable app.selected and a boolean pending flag.views.rs:347: existing confirmation explains owned elements/incident relationships/occurrences/placements, cross-layer counterpart preservation, and Undo; it directs the user to Hide for diagram-only removal. Reuse these semantics and wording.model_menu.rs:42-100,1960-1990 in app.rs: menu snapshot captures document, revision, epoch, page, layer, diagram and request object; Invoke validates token/context, rejects dirty state, then establishes semantic and placement selection before dispatch. This is the closest reusable authorization/lifetime pattern.behavior/state_menu.rs:140-188: state diagrams already have placement-only Hide using explicit Command::Delete(node.id), and confirmed semantic Delete. Do not route these through architecture active_diagram by accident.behavior/canvas_workspace.rs:84: process/scenario input owner requires clean drafts and no modal/menu/reference input. Its Viewport implements actual Iced Focusable ownership, checks captured events, resets focus at boundaries, and prevents unfocused keyboard events reaching children (viewport.rs:142-180,312). Its root operation registers current ownership before touching focus (focus.rs). This is a usable precedent; avoid a new global unscoped Delete subscription.behavior/state_canvas.rs:782: state canvas has a local focused boolean for arrow navigation and Escape, no Delete. Process direct canvas has Escape cancellation but no Delete. ScenarioDelete participantis a modeled destruction-message tool, NOT an editor delete command; do not conflate these meanings.
Interaction choices
Criteria: preserve typed text; predictable diagram/model meaning; useful OA/SA workflows; low repeated-operation friction; discoverability; existing command semantics; complete Undo.
A. Plain Delete hides a selected explicit placement; Shift+Delete opens model-delete confirmation. For selected semantic relation with no placement/hide support, plain Delete opens model-delete confirmation. Bend Delete retains first priority. This is fastest for repeated diagram cleanup and preserves existing labels. Costs: node/edge distinction must be explicit in menus, and Shift+Delete is not self-discovering. Do not silently invent hidden-edge persistence. Add exact shortcut hints to existing menu entries and brief contextual status/help. This is the recommended minimal implementation if parent acceptance centers on architecture/model item deletion.
B. Plain Delete always opens the existing model-delete confirmation, augmented with Hide from this diagram when an exact placement exists. This avoids modifier discovery and ambiguous removal scope, exposes both options in one place, and works for semantic-only items. Cost: every diagram cleanup takes another click; default destructive model deletion may surprise users unless Hide is the initial safe option. Prefer this if the user intends Delete itself to offer both scopes; it is a coherent alternative, not a second simultaneous flow.
C. Plain Delete always deletes semantic model objects after confirmation. Reject as sole behavior: existing canvas placement semantics deserve direct access and the user explicitly mentions diagram removal.
Do not call issue15 complete merely because helper dispatch tests pass. Include architecture OA and SA and explorer selection at minimum. Clarify/integrate process/scenario/state support within the parent’s ongoing milestone: occurrence deletion is a model command, not hiding a diagram placement. Avoid introducing an apparently universal shortcut that silently does nothing on these workspaces. If supporting them now, bind Delete to the exact selected occurrence/link/fragment/transition with confirmed semantic deletion and owner-context validation; do not delete its referenced reusable actor/function instead.
Exact implementation seams
- Add a small explicit deletion intent carrying selected semantic ID, optional placement ID and a current owner/lifetime snapshot. Reuse model_menu Request/Opened validation where feasible, but do not fake opening a menu or require mutable global selection as the command’s identity. Validate actual selected projected object and placement-to-element mapping. Empty canvas/diagram background, project root, hidden/stale selection, different owner and invalid bend must not remove arbitrary model objects.
- Introduce actual canvas focus ownership for architecture/state (reuse existing widget focus pattern or equivalent scoped wrapper); ordinary text Tab/programmatic focus must unfocus canvas. Disable and retire ownership on modal/menu/reference editor, dirty properties/source/behavior form, page/layer/diagram/document/owner/revision change, window unfocus and active gesture/tool changes. Reacquire only through explicit live canvas focus. Do not use cursor-over-canvas as keyboard ownership.
- Emit deletion intent only from focused selection mode; suppress during connection/creation/reconnect/drag. Delete valid bend remains prior to element removal. Unsupported modifier combinations do nothing. For architecture placement removal use validated explicit node ID through ordinary execute(Command::Delete(node)); for model deletion retain confirmation. Do not invoke apply() from a destructive keyboard shortcut on dirty drafts.
- App boundary revalidates clean and modal state, context, selected object/placement and owner membership. Make confirmation snapshot-backed rather than relying solely on mutable selected: at minimum ID/document/revision/epoch, plus page/owner scope. Confirmation text uses captured target. Cancel/Escape changes no model. Delayed ConfirmDelete after cancel/new owner/revision cannot target new selection. Preserve menu commands’ existing behavior through shared deletion helper if modifying them.
- Update menu shortcut hints to match chosen semantics: architecture/state Hide
Delete, semantic DeleteShift+Delete; relation Delete can showDeleteas well where no diagram-only operation exists. Explorer Delete should open semantic confirmation for the focused tree row, guarded against explorer search/text focus. Keep the existing Actions menu usable by pointer. - Preserve ordinary execute() history, source projection, refresh and recovery. Hide clears placement/bend selection without deleting reusable elements. Confirmed model deletion refreshes selection and surviving diagram. Undo/Redo restores model+source together, not only a canvas rendering.
Meaningful full App-tree verification
Reuse behavior/canvas_workspace/tests.rs::Mounted: real App::view, production renderer, Iced UserInterface, persistent widget cache, ordered event batches, App::update and task/widget-operation draining. It deliberately never seeds private gesture/focus state. Extend geometry IDs only where needed to locate real controls.
- Start from clean OA and SA fixture. Click an actual placed node, Delete: assert exact placement removed, semantic element and other-diagram placement remain; incident projection adjusts correctly; one undo unit. Undo restores exact placement/routes/source, Redo repeats. Compare parsed canonical source/model, not only counts.
- Shift+Delete on same node: capture actual rendered confirmation target and buttons; Cancel/Escape leaves project/source/history unchanged. Confirm removes intended semantic closure (including owned content and incident relations) while counterpart survives; Undo/Redo round-trip full normalized source/model.
- Route bend selected by real pointer: Delete removes only the bend, keeps route/relation/endpoints. Invalid/stale bend must not fall through to whole-element removal.
- CLEAN text defense: click canvas, then Tab or actual focus operation into an unchanged Inspector text field; press Home/Delete (and Shift+Delete). Verify text behavior and no deletion message/modal/model mutation. Repeat click focus, architecture rename, source editor, source find/search, process/scenario form, reference entry. Dirty-field testing alone cannot expose stale canvas focus because editable=false masks it.
- Window Unfocused; page/layer/diagram/document/owner change; same widget Tree reused across change; menu opening/closing; modal opening; dirty draft and active connection/gesture: Delete produces no stale mutation. Reenter live canvas and verify shortcut works again (avoid permanently disabling focus as a false fix).
- Capture emitted deletion intent without applying it, change revision/owner/selection, then dispatch: reject. Repeat captured confirmation after Cancel and after allowed async load/refresh. This tests boundary safety separately from focus.
- Explorer row Delete resolves focused row rather than old canvas selection; search field Delete edits search. State placement Hide resolves state diagram, not architecture active_diagram. Process/scenario deletion targets occurrence/link identity, not referenced semantic reusable element.
- Native QA after frozen baseline: realistic OA and SA select/delete/Undo, text Tab/delete, Shift+Delete Cancel/Confirm, menu discoverability and focus indication. Mounted tests prove event wiring/lifetimes; native workflow validates usable feedback and navigation. No native/compiler validation was run by this investigation.